diff options
Diffstat (limited to '')
195 files changed, 33803 insertions, 11800 deletions
diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index c8de0bb5bed9..d2ca710ac3fa 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -15,66 +15,26 @@ config SND_SOC_INTEL_SST_TOPLEVEL if SND_SOC_INTEL_SST_TOPLEVEL -config SND_SST_IPC - tristate - # This option controls the IPC core for HiFi2 platforms - -config SND_SST_IPC_PCI - tristate - select SND_SST_IPC - # This option controls the PCI-based IPC for HiFi2 platforms - # (Medfield, Merrifield). - -config SND_SST_IPC_ACPI - tristate - select SND_SST_IPC - # This option controls the ACPI-based IPC for HiFi2 platforms - # (Baytrail, Cherrytrail) - -config SND_SOC_INTEL_SST_ACPI - tristate - # This option controls ACPI-based probing on - # Haswell/Broadwell/Baytrail legacy and will be set - # when these platforms are enabled - config SND_SOC_INTEL_SST tristate -config SND_SOC_INTEL_SST_FIRMWARE - tristate +config SND_SOC_INTEL_CATPT + tristate "Haswell and Broadwell" + depends on ACPI || COMPILE_TEST + depends on DMADEVICES && SND_DMA_SGBUF select DW_DMAC_CORE - # This option controls firmware download on - # Haswell/Broadwell/Baytrail legacy and will be set - # when these platforms are enabled - -config SND_SOC_INTEL_HASWELL - tristate "Haswell/Broadwell Platforms" - depends on SND_DMA_SGBUF - depends on DMADEVICES && ACPI - select SND_SOC_INTEL_SST - select SND_SOC_INTEL_SST_ACPI - select SND_SOC_INTEL_SST_FIRMWARE - select SND_SOC_ACPI_INTEL_MATCH + select SND_SOC_ACPI if ACPI + select WANT_DEV_COREDUMP + select SND_INTEL_DSP_CONFIG help - If you have a Intel Haswell or Broadwell platform connected to - an I2S codec, then enable this option by saying Y or m. This is - typically used for Chromebooks. This is a recommended option. - This option is mutually exclusive with the SOF support on - Broadwell. If you want to enable SOF on Broadwell, you need to - deselect this option first. + Enable support for Intel(R) Haswell and Broadwell platforms + with I2S codec present. This is a recommended option. + Say Y or m if you have such device. + If unsure, say N. -config SND_SOC_INTEL_BAYTRAIL - tristate "Baytrail (legacy) Platforms" - depends on DMADEVICES && ACPI && SND_SST_ATOM_HIFI2_PLATFORM=n && SND_SOC_SOF_BAYTRAIL=n - select SND_SOC_INTEL_SST - select SND_SOC_INTEL_SST_ACPI - select SND_SOC_INTEL_SST_FIRMWARE - select SND_SOC_ACPI_INTEL_MATCH - help - If you have a Intel Baytrail platform connected to an I2S codec, - then enable this option by saying Y or m. This was typically used - for Baytrail Chromebooks but this option is now deprecated and is - not recommended, use SND_SST_ATOM_HIFI2_PLATFORM instead. +config SND_SOC_INTEL_HASWELL + tristate + select SND_SOC_INTEL_CATPT config SND_SST_ATOM_HIFI2_PLATFORM tristate @@ -83,7 +43,6 @@ config SND_SST_ATOM_HIFI2_PLATFORM config SND_SST_ATOM_HIFI2_PLATFORM_PCI tristate "PCI HiFi2 (Merrifield) Platforms" depends on X86 && PCI - select SND_SST_IPC_PCI select SND_SST_ATOM_HIFI2_PLATFORM help If you have a Intel Merrifield/Edison platform, then @@ -96,9 +55,9 @@ config SND_SST_ATOM_HIFI2_PLATFORM_ACPI tristate "ACPI HiFi2 (Baytrail, Cherrytrail) Platforms" default ACPI depends on X86 && ACPI && PCI - select SND_SST_IPC_ACPI select SND_SST_ATOM_HIFI2_PLATFORM select SND_SOC_ACPI_INTEL_MATCH + select SND_INTEL_DSP_CONFIG select IOSF_MBI help If you have a Intel Baytrail or Cherrytrail platform with an I2S @@ -209,12 +168,8 @@ config SND_SOC_INTEL_SKYLAKE_SSP_CLK config SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC bool "HDAudio codec support" help - This option broke audio on Linus' Skylake laptop in December 2018 - and the race conditions during the probe were not fixed since. - This option is DEPRECATED, all HDaudio codec support needs - to be handled by the SOF driver. - Distributions should not enable this option and there are no known - users of this capability. + If you have Intel Skylake or Kabylake with HDAudio codec + and DMIC present then enable this option by saying Y. config SND_SOC_INTEL_SKYLAKE_COMMON tristate @@ -244,6 +199,35 @@ config SND_SOC_ACPI_INTEL_MATCH endif ## SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL +config SND_SOC_INTEL_KEEMBAY + tristate "Keembay Platforms" + depends on ARCH_KEEMBAY || COMPILE_TEST + depends on COMMON_CLK + select SND_DMAENGINE_PCM + select SND_SOC_GENERIC_DMAENGINE_PCM + help + If you have a Intel Keembay platform then enable this option + by saying Y or m. + +config SND_SOC_INTEL_AVS + tristate "Intel AVS driver" + depends on X86 || COMPILE_TEST + depends on PCI + depends on COMMON_CLK + select SND_SOC_ACPI if ACPI + select SND_SOC_TOPOLOGY + select SND_SOC_HDA + select SND_HDA_EXT_CORE + select SND_HDA_DSP_LOADER + select SND_INTEL_DSP_CONFIG + select WANT_DEV_COREDUMP + help + Enable support for Intel(R) cAVS 1.5 platforms with DSP + capabilities. This includes Skylake, Kabylake, Amberlake and + Apollolake. + +# Machine board drivers +source "sound/soc/intel/avs/boards/Kconfig" # ASoC codec drivers source "sound/soc/intel/boards/Kconfig" diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index 8160520fd74c..d44b2652c707 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -1,12 +1,13 @@ -# SPDX-License-Identifier: GPL-2.0 +# SPDX-License-Identifier: GPL-2.0-only # Core support obj-$(CONFIG_SND_SOC) += common/ # Platform Support -obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += haswell/ -obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += baytrail/ obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += atom/ -obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += skylake/ +obj-$(CONFIG_SND_SOC_INTEL_CATPT) += catpt/ +obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE_COMMON) += skylake/ +obj-$(CONFIG_SND_SOC_INTEL_KEEMBAY) += keembay/ +obj-$(CONFIG_SND_SOC_INTEL_AVS) += avs/ # Machine support obj-$(CONFIG_SND_SOC) += boards/ diff --git a/sound/soc/intel/atom/Makefile b/sound/soc/intel/atom/Makefile index 1dc60471b399..c66f03f5d8d6 100644 --- a/sound/soc/intel/atom/Makefile +++ b/sound/soc/intel/atom/Makefile @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0 +# SPDX-License-Identifier: GPL-2.0-only snd-soc-sst-atom-hifi2-platform-objs := sst-mfld-platform-pcm.o \ sst-mfld-platform-compress.o \ sst-atom-controls.o @@ -6,4 +6,4 @@ snd-soc-sst-atom-hifi2-platform-objs := sst-mfld-platform-pcm.o \ obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += snd-soc-sst-atom-hifi2-platform.o # DSP driver -obj-$(CONFIG_SND_SST_IPC) += sst/ +obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += sst/ diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c index baef461a99f1..fd59b35a62ba 100644 --- a/sound/soc/intel/atom/sst-atom-controls.c +++ b/sound/soc/intel/atom/sst-atom-controls.c @@ -50,6 +50,8 @@ static int sst_fill_and_send_cmd_unlocked(struct sst_data *drv, { int ret = 0; + WARN_ON(!mutex_is_locked(&drv->lock)); + ret = sst_fill_byte_control(drv, ipc_msg, block, task_id, pipe_id, len, cmd_data); if (ret < 0) @@ -59,8 +61,13 @@ static int sst_fill_and_send_cmd_unlocked(struct sst_data *drv, /** * sst_fill_and_send_cmd - generate the IPC message and send it to the FW - * @ipc_msg: type of IPC (CMD, SET_PARAMS, GET_PARAMS) - * @cmd_data: the IPC payload + * @drv: sst_data + * @ipc_msg: type of IPC (CMD, SET_PARAMS, GET_PARAMS) + * @block: block index + * @task_id: task index + * @pipe_id: pipe index + * @cmd_data: the IPC payload + * @len: length of data to be sent */ static int sst_fill_and_send_cmd(struct sst_data *drv, u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, @@ -76,7 +83,7 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, return ret; } -/** +/* * tx map value is a bitfield where each bit represents a FW channel * * 3 2 1 0 # 0 = codec0, 1 = codec1 @@ -88,7 +95,7 @@ static u8 sst_ssp_tx_map[SST_MAX_TDM_SLOTS] = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default rx map */ }; -/** +/* * rx map value is a bitfield where each bit represents a slot * * 76543210 # 0 = slot 0, 1 = slot 1 @@ -99,7 +106,7 @@ static u8 sst_ssp_rx_map[SST_MAX_TDM_SLOTS] = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default tx map */ }; -/** +/* * NOTE: this is invoked with lock held */ static int sst_send_slot_map(struct sst_data *drv) @@ -143,7 +150,8 @@ static int sst_slot_enum_info(struct snd_kcontrol *kcontrol, /** * sst_slot_get - get the status of the interleaver/deinterleaver control - * + * @kcontrol: control pointer + * @ucontrol: User data * Searches the map where the control status is stored, and gets the * channel/slot which is currently set for this enumerated control. Since it is * an enumerated control, there is only one possible value. @@ -195,7 +203,8 @@ static int sst_check_and_send_slot_map(struct sst_data *drv, struct snd_kcontrol /** * sst_slot_put - set the status of interleaver/deinterleaver control - * + * @kcontrol: control pointer + * @ucontrol: User data * (de)interleaver controls are defined in opposite sense to be user-friendly * * Instead of the enum value being the value written to the register, it is the @@ -278,7 +287,9 @@ static int sst_send_algo_cmd(struct sst_data *drv, /** * sst_find_and_send_pipe_algo - send all the algo parameters for a pipe - * + * @drv: sst_data + * @pipe: string identifier + * @ids: list of algorithms * The algos which are in each pipeline are sent to the firmware one by one * * Called with lock held @@ -288,7 +299,7 @@ static int sst_find_and_send_pipe_algo(struct sst_data *drv, { int ret = 0; struct sst_algo_control *bc; - struct sst_module *algo = NULL; + struct sst_module *algo; dev_dbg(&drv->pdev->dev, "Enter: widget=%s\n", pipe); @@ -377,11 +388,15 @@ static int sst_gain_ctl_info(struct snd_kcontrol *kcontrol, /** * sst_send_gain_cmd - send the gain algorithm IPC to the FW - * @gv: the stored value of gain (also contains rampduration) - * @mute: flag that indicates whether this was called from the - * digital_mute callback or directly. If called from the - * digital_mute callback, module will be muted/unmuted based on this - * flag. The flag is always 0 if called directly. + * @drv: sst_data + * @gv:the stored value of gain (also contains rampduration) + * @task_id: task index + * @loc_id: location/position index + * @module_id: module index + * @mute: flag that indicates whether this was called from the + * digital_mute callback or directly. If called from the + * digital_mute callback, module will be muted/unmuted based on this + * flag. The flag is always 0 if called directly. * * Called with sst_data.lock held * @@ -542,9 +557,12 @@ static const uint swm_mixer_input_ids[SST_SWM_INPUT_COUNT] = { /** * fill_swm_input - fill in the SWM input ids given the register + * @cmpnt: ASoC component + * @swm_input: array of swm_input_ids + * @reg: the register value is a bit-field inicated which mixer inputs are ON. * - * The register value is a bit-field inicated which mixer inputs are ON. Use the - * lookup table to get the input-id and fill it in the structure. + * Use the lookup table to get the input-id and fill it in the + * structure. */ static int fill_swm_input(struct snd_soc_component *cmpnt, struct swm_input_ids *swm_input, unsigned int reg) @@ -575,7 +593,7 @@ static int fill_swm_input(struct snd_soc_component *cmpnt, } -/** +/* * called with lock held */ static int sst_set_pipe_gain(struct sst_ids *ids, @@ -584,7 +602,7 @@ static int sst_set_pipe_gain(struct sst_ids *ids, int ret = 0; struct sst_gain_mixer_control *mc; struct sst_gain_value *gv; - struct sst_module *gain = NULL; + struct sst_module *gain; list_for_each_entry(gain, &ids->gain_list, node) { struct snd_kcontrol *kctl = gain->kctl; @@ -705,7 +723,7 @@ SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm2_controls); SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_sprot_l0_controls); SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l1_controls); SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l2_controls); -SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_voip_controls); +SST_SBA_DECLARE_MIX_CONTROLS(__maybe_unused sst_mix_voip_controls); SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls); SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_controls); SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_modem_controls); @@ -809,14 +827,14 @@ static int sst_get_ssp_mode(struct snd_soc_dai *dai, unsigned int fmt) { int format; - format = (fmt & SND_SOC_DAIFMT_MASTER_MASK); + format = (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK); dev_dbg(dai->dev, "Enter:%s, format=%x\n", __func__, format); switch (format) { - case SND_SOC_DAIFMT_CBS_CFS: - return SSP_MODE_MASTER; - case SND_SOC_DAIFMT_CBM_CFM: - return SSP_MODE_SLAVE; + case SND_SOC_DAIFMT_BP_FP: + return SSP_MODE_PROVIDER; + case SND_SOC_DAIFMT_BC_FC: + return SSP_MODE_CONSUMER; default: dev_err(dai->dev, "Invalid ssp protocol: %d\n", format); } @@ -879,7 +897,7 @@ int sst_fill_ssp_config(struct snd_soc_dai *dai, unsigned int fmt) return 0; } -/** +/* * sst_ssp_config - contains SSP configuration for media UC * this can be overwritten by set_dai_xxx APIs */ @@ -887,7 +905,7 @@ static const struct sst_ssp_config sst_ssp_configs = { .ssp_id = SSP_CODEC, .bits_per_slot = 24, .slots = 4, - .ssp_mode = SSP_MODE_MASTER, + .ssp_mode = SSP_MODE_PROVIDER, .pcm_mode = SSP_PCM_MODE_NETWORK, .duplex = SSP_DUPLEX, .ssp_protocol = SSP_MODE_PCM, @@ -966,7 +984,9 @@ static int sst_set_be_modules(struct snd_soc_dapm_widget *w, dev_dbg(c->dev, "Enter: widget=%s\n", w->name); if (SND_SOC_DAPM_EVENT_ON(event)) { + mutex_lock(&drv->lock); ret = sst_send_slot_map(drv); + mutex_unlock(&drv->lock); if (ret) return ret; ret = sst_send_pipe_module_params(w, k); @@ -1296,6 +1316,9 @@ static bool is_sst_dapm_widget(struct snd_soc_dapm_widget *w) /** * sst_send_pipe_gains - send gains for the front-end DAIs + * @dai: front-end dai + * @stream: direction + * @mute: boolean indicating mute status * * The gains in the pipes connected to the front-ends are muted/unmuted * automatically via the digital_mute() DAPM callback. This function sends the @@ -1305,7 +1328,7 @@ int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) { struct sst_data *drv = snd_soc_dai_get_drvdata(dai); struct snd_soc_dapm_widget *w; - struct snd_soc_dapm_path *p = NULL; + struct snd_soc_dapm_path *p; dev_dbg(dai->dev, "enter, dai-name=%s dir=%d\n", dai->name, stream); @@ -1333,7 +1356,7 @@ int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) dai->capture_widget->name); w = dai->capture_widget; snd_soc_dapm_widget_for_each_source_path(w, p) { - if (p->connected && !p->connected(w, p->sink)) + if (p->connected && !p->connected(w, p->source)) continue; if (p->connect && p->source->power && @@ -1353,7 +1376,9 @@ int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) /** * sst_fill_module_list - populate the list of modules/gains for a pipe - * + * @kctl: kcontrol pointer + * @w: dapm widget + * @type: widget type * * Fills the widget pointer in the kcontrol private data, and also fills the * kcontrol pointer in the widget private data. @@ -1367,7 +1392,7 @@ int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) static int sst_fill_module_list(struct snd_kcontrol *kctl, struct snd_soc_dapm_widget *w, int type) { - struct sst_module *module = NULL; + struct sst_module *module; struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); struct sst_ids *ids = w->priv; int ret = 0; @@ -1399,7 +1424,8 @@ static int sst_fill_module_list(struct snd_kcontrol *kctl, /** * sst_fill_widget_module_info - fill list of gains/algos for the pipe - * @widget: pipe modelled as a DAPM widget + * @w: pipe modeled as a DAPM widget + * @component: ASoC component * * Fill the list of gains/algos for the widget by looking at all the card * controls and comparing the name of the widget with the first part of control @@ -1459,6 +1485,8 @@ static int sst_fill_widget_module_info(struct snd_soc_dapm_widget *w, /** * sst_fill_linked_widgets - fill the parent pointer for the linked widget + * @component: ASoC component + * @ids: sst_ids array */ static void sst_fill_linked_widgets(struct snd_soc_component *component, struct sst_ids *ids) @@ -1476,6 +1504,7 @@ static void sst_fill_linked_widgets(struct snd_soc_component *component, /** * sst_map_modules_to_pipe - fill algo/gains list for all pipes + * @component: ASoC component */ static int sst_map_modules_to_pipe(struct snd_soc_component *component) { diff --git a/sound/soc/intel/atom/sst-atom-controls.h b/sound/soc/intel/atom/sst-atom-controls.h index 5356e954a732..23bf37544a8d 100644 --- a/sound/soc/intel/atom/sst-atom-controls.h +++ b/sound/soc/intel/atom/sst-atom-controls.h @@ -410,7 +410,7 @@ struct sst_cmd_set_gain_dual { struct sst_cmd_set_params { struct sst_destination_id dst; u16 command_id; - char params[0]; + char params[]; } __packed; @@ -439,8 +439,8 @@ struct sst_cmd_tone_stop { } __packed; enum sst_ssp_mode { - SSP_MODE_MASTER = 0, - SSP_MODE_SLAVE = 1, + SSP_MODE_PROVIDER = 0, + SSP_MODE_CONSUMER = 1, }; enum sst_ssp_pcm_mode { diff --git a/sound/soc/intel/atom/sst-mfld-dsp.h b/sound/soc/intel/atom/sst-mfld-dsp.h index 5795f98e04d4..c8f0816edb53 100644 --- a/sound/soc/intel/atom/sst-mfld-dsp.h +++ b/sound/soc/intel/atom/sst-mfld-dsp.h @@ -256,7 +256,7 @@ struct snd_sst_tstamp { u32 channel_peak[8]; } __packed; -/* Stream type params struture for Alloc stream */ +/* Stream type params structure for Alloc stream */ struct snd_sst_str_type { u8 codec_type; /* Codec type */ u8 str_type; /* 1 = voice 2 = music */ @@ -358,7 +358,7 @@ struct snd_wma_params { u8 reserved; /* reserved */ } __packed; -/* Codec params struture */ +/* Codec params structure */ union snd_sst_codec_params { struct snd_pcm_params pcm_params; struct snd_mp3_params mp3_params; @@ -427,7 +427,7 @@ struct snd_sst_drop_response { struct snd_sst_async_msg { u32 msg_id; /* Async msg id */ - u32 payload[0]; + u32 payload[]; }; struct snd_sst_async_err_msg { @@ -514,7 +514,7 @@ struct snd_sst_bytes_v2 { u8 pipe_id; u8 rsvd; u16 len; - char bytes[0]; + char bytes[]; }; #define MAX_VTSV_FILES 2 diff --git a/sound/soc/intel/atom/sst-mfld-platform-compress.c b/sound/soc/intel/atom/sst-mfld-platform-compress.c index 4a7a9426a3b9..89c9c5ad6b21 100644 --- a/sound/soc/intel/atom/sst-mfld-platform-compress.c +++ b/sound/soc/intel/atom/sst-mfld-platform-compress.c @@ -39,10 +39,10 @@ static void sst_drain_notify(void *arg) snd_compr_drain_notify(cstream); } -static int sst_platform_compr_open(struct snd_compr_stream *cstream) +static int sst_platform_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) { - - int ret_val = 0; + int ret_val; struct snd_compr_runtime *runtime = cstream->runtime; struct sst_runtime_stream *stream; @@ -72,7 +72,8 @@ out_ops: return ret_val; } -static int sst_platform_compr_free(struct snd_compr_stream *cstream) +static int sst_platform_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream) { struct sst_runtime_stream *stream; int ret_val = 0, str_id; @@ -91,15 +92,14 @@ static int sst_platform_compr_free(struct snd_compr_stream *cstream) return 0; } -static int sst_platform_compr_set_params(struct snd_compr_stream *cstream, - struct snd_compr_params *params) +static int sst_platform_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_params *params) { struct sst_runtime_stream *stream; int retval; struct snd_sst_params str_params; struct sst_compress_cb cb; - struct snd_soc_pcm_runtime *rtd = cstream->private_data; - struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct sst_data *ctx = snd_soc_component_get_drvdata(component); stream = cstream->runtime->private_data; @@ -166,7 +166,8 @@ static int sst_platform_compr_set_params(struct snd_compr_stream *cstream, return 0; } -static int sst_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd) +static int sst_platform_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int cmd) { struct sst_runtime_stream *stream = cstream->runtime->private_data; @@ -199,8 +200,9 @@ static int sst_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd) return -EINVAL; } -static int sst_platform_compr_pointer(struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp) +static int sst_platform_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) { struct sst_runtime_stream *stream; @@ -212,8 +214,9 @@ static int sst_platform_compr_pointer(struct snd_compr_stream *cstream, return 0; } -static int sst_platform_compr_ack(struct snd_compr_stream *cstream, - size_t bytes) +static int sst_platform_compr_ack(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + size_t bytes) { struct sst_runtime_stream *stream; @@ -224,8 +227,9 @@ static int sst_platform_compr_ack(struct snd_compr_stream *cstream, return 0; } -static int sst_platform_compr_get_caps(struct snd_compr_stream *cstream, - struct snd_compr_caps *caps) +static int sst_platform_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) { struct sst_runtime_stream *stream = cstream->runtime->private_data; @@ -233,8 +237,9 @@ static int sst_platform_compr_get_caps(struct snd_compr_stream *cstream, return stream->compr_ops->get_caps(caps); } -static int sst_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, - struct snd_compr_codec_caps *codec) +static int sst_platform_compr_get_codec_caps(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) { struct sst_runtime_stream *stream = cstream->runtime->private_data; @@ -242,8 +247,9 @@ static int sst_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, return stream->compr_ops->get_codec_caps(codec); } -static int sst_platform_compr_set_metadata(struct snd_compr_stream *cstream, - struct snd_compr_metadata *metadata) +static int sst_platform_compr_set_metadata(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_metadata *metadata) { struct sst_runtime_stream *stream = cstream->runtime->private_data; @@ -251,7 +257,7 @@ static int sst_platform_compr_set_metadata(struct snd_compr_stream *cstream, return stream->compr_ops->set_metadata(sst->dev, stream->id, metadata); } -const struct snd_compr_ops sst_platform_compr_ops = { +const struct snd_compress_ops sst_platform_compress_ops = { .open = sst_platform_compr_open, .free = sst_platform_compr_free, diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c index 340bd2be39a7..c75616a5fd0a 100644 --- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c +++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c @@ -127,7 +127,7 @@ static void sst_fill_alloc_params(struct snd_pcm_substream *substream, snd_pcm_uframes_t period_size; ssize_t periodbytes; ssize_t buffer_bytes = snd_pcm_lib_buffer_bytes(substream); - u32 buffer_addr = virt_to_phys(substream->dma_buffer.area); + u32 buffer_addr = substream->runtime->dma_addr; channels = substream->runtime->channels; period_size = substream->runtime->period_size; @@ -233,7 +233,6 @@ static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, /* set codec params and inform SST driver the same */ sst_fill_pcm_params(substream, ¶m); sst_fill_alloc_params(substream, &alloc_params); - substream->runtime->dma_area = substream->dma_buffer.area; str_params.sparams = param; str_params.aparams = alloc_params; str_params.codec = SST_CODEC_TYPE_PCM; @@ -274,7 +273,7 @@ static int sst_platform_init_stream(struct snd_pcm_substream *substream) { struct sst_runtime_stream *stream = substream->runtime->private_data; - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); int ret_val; dev_dbg(rtd->dev, "setting buffer ptr param\n"); @@ -331,7 +330,18 @@ static int sst_media_open(struct snd_pcm_substream *substream, ret_val = power_up_sst(stream); if (ret_val < 0) - return ret_val; + goto out_power_up; + + /* + * Make sure the period to be multiple of 1ms to align the + * design of firmware. Apply same rule to buffer size to make + * sure alsa could always find a value for period size + * regardless the buffer size given by user space. + */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 48); + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 48); /* Make sure, that the period size is always even */ snd_pcm_hw_constraint_step(substream->runtime, 0, @@ -340,8 +350,9 @@ static int sst_media_open(struct snd_pcm_substream *substream, return snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); out_ops: - kfree(stream); mutex_unlock(&sst_lock); +out_power_up: + kfree(stream); return ret_val; } @@ -365,7 +376,7 @@ static int sst_media_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sst_runtime_stream *stream; - int ret_val = 0, str_id; + int ret_val, str_id; stream = substream->runtime->private_data; str_id = stream->stream_info.str_id; @@ -384,7 +395,7 @@ static int sst_media_prepare(struct snd_pcm_substream *substream, if (ret_val) return ret_val; substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER; - return ret_val; + return 0; } static int sst_enable_ssp(struct snd_pcm_substream *substream, @@ -392,7 +403,7 @@ static int sst_enable_ssp(struct snd_pcm_substream *substream, { int ret = 0; - if (!dai->active) { + if (!snd_soc_dai_active(dai)) { ret = sst_handle_vb_timer(dai, true); sst_fill_ssp_defaults(dai); } @@ -405,7 +416,7 @@ static int sst_be_hw_params(struct snd_pcm_substream *substream, { int ret = 0; - if (dai->active == 1) + if (snd_soc_dai_active(dai) == 1) ret = send_ssp_cmd(dai, dai->name, 1); return ret; } @@ -414,7 +425,7 @@ static int sst_set_format(struct snd_soc_dai *dai, unsigned int fmt) { int ret = 0; - if (!dai->active) + if (!snd_soc_dai_active(dai)) return 0; ret = sst_fill_ssp_config(dai, fmt); @@ -429,7 +440,7 @@ static int sst_platform_set_ssp_slot(struct snd_soc_dai *dai, int slots, int slot_width) { int ret = 0; - if (!dai->active) + if (!snd_soc_dai_active(dai)) return ret; ret = sst_fill_ssp_slot(dai, tx_mask, rx_mask, slots, slot_width); @@ -442,7 +453,7 @@ static int sst_platform_set_ssp_slot(struct snd_soc_dai *dai, static void sst_disable_ssp(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - if (!dai->active) { + if (!snd_soc_dai_active(dai)) { send_ssp_cmd(dai, dai->name, 0); sst_handle_vb_timer(dai, false); } @@ -475,15 +486,15 @@ static struct snd_soc_dai_driver sst_platform_dai[] = { .stream_name = "Headset Playback", .channels_min = SST_STEREO, .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .capture = { .stream_name = "Headset Capture", .channels_min = 1, .channels_max = 2, - .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, }, { @@ -493,8 +504,8 @@ static struct snd_soc_dai_driver sst_platform_dai[] = { .stream_name = "Deepbuffer Playback", .channels_min = SST_STEREO, .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, }, { @@ -582,7 +593,7 @@ static int sst_soc_trigger(struct snd_soc_component *component, int ret_val = 0, str_id; struct sst_runtime_stream *stream; int status; - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); dev_dbg(rtd->dev, "%s called\n", __func__); if (substream->pcm->internal) @@ -630,7 +641,7 @@ static snd_pcm_uframes_t sst_soc_pointer(struct snd_soc_component *component, struct sst_runtime_stream *stream; int ret_val, status; struct pcm_stream_info *str_info; - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); stream = substream->runtime->private_data; status = sst_get_stream_status(stream); @@ -642,22 +653,32 @@ static snd_pcm_uframes_t sst_soc_pointer(struct snd_soc_component *component, dev_err(rtd->dev, "sst: error code = %d\n", ret_val); return ret_val; } - substream->runtime->delay = str_info->pcm_delay; return str_info->buffer_ptr; } +static snd_pcm_sframes_t sst_soc_delay(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream = substream->runtime->private_data; + struct pcm_stream_info *str_info = &stream->stream_info; + + if (sst_get_stream_status(stream) == SST_PLATFORM_INIT) + return 0; + + return str_info->pcm_delay; +} + static int sst_soc_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0); struct snd_pcm *pcm = rtd->pcm; if (dai->driver->playback.channels_min || dai->driver->capture.channels_min) { - snd_pcm_set_managed_buffer_all(pcm, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_DMA), - SST_MIN_BUFFER, SST_MAX_BUFFER); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + pcm->card->dev, + SST_MIN_BUFFER, SST_MAX_BUFFER); } return 0; } @@ -684,7 +705,8 @@ static const struct snd_soc_component_driver sst_soc_platform_drv = { .open = sst_soc_open, .trigger = sst_soc_trigger, .pointer = sst_soc_pointer, - .compr_ops = &sst_platform_compr_ops, + .delay = sst_soc_delay, + .compress_ops = &sst_platform_compress_ops, .pcm_construct = sst_soc_pcm_new, }; @@ -741,9 +763,9 @@ static int sst_soc_prepare(struct device *dev) /* set the SSPs to idle */ for_each_card_rtds(drv->soc_card, rtd) { - struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0); - if (dai->active) { + if (snd_soc_dai_active(dai)) { send_ssp_cmd(dai, dai->name, 0); sst_handle_vb_timer(dai, false); } @@ -762,9 +784,9 @@ static void sst_soc_complete(struct device *dev) /* restart SSPs */ for_each_card_rtds(drv->soc_card, rtd) { - struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0); - if (dai->active) { + if (snd_soc_dai_active(dai)) { sst_handle_vb_timer(dai, true); send_ssp_cmd(dai, dai->name, 1); } diff --git a/sound/soc/intel/atom/sst-mfld-platform.h b/sound/soc/intel/atom/sst-mfld-platform.h index fe4749cfa4f5..8b5777d3229a 100644 --- a/sound/soc/intel/atom/sst-mfld-platform.h +++ b/sound/soc/intel/atom/sst-mfld-platform.h @@ -17,7 +17,7 @@ #include "sst-atom-controls.h" extern struct sst_device *sst; -extern const struct snd_compr_ops sst_platform_compr_ops; +extern const struct snd_compress_ops sst_platform_compress_ops; #define DRV_NAME "sst" @@ -173,6 +173,6 @@ struct sst_data { struct snd_soc_card *soc_card; struct sst_cmd_sba_hw_set_ssp ssp_cmd; }; -int sst_register_dsp(struct sst_device *sst); -int sst_unregister_dsp(struct sst_device *sst); +int sst_register_dsp(struct sst_device *dev); +int sst_unregister_dsp(struct sst_device *dev); #endif diff --git a/sound/soc/intel/atom/sst/Makefile b/sound/soc/intel/atom/sst/Makefile index 795d1cf8f386..5761d30a5f9d 100644 --- a/sound/soc/intel/atom/sst/Makefile +++ b/sound/soc/intel/atom/sst/Makefile @@ -1,8 +1,8 @@ -# SPDX-License-Identifier: GPL-2.0 +# SPDX-License-Identifier: GPL-2.0-only snd-intel-sst-core-objs := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o snd-intel-sst-pci-objs += sst_pci.o snd-intel-sst-acpi-objs += sst_acpi.o -obj-$(CONFIG_SND_SST_IPC) += snd-intel-sst-core.o -obj-$(CONFIG_SND_SST_IPC_PCI) += snd-intel-sst-pci.o -obj-$(CONFIG_SND_SST_IPC_ACPI) += snd-intel-sst-acpi.o +obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += snd-intel-sst-core.o +obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_PCI) += snd-intel-sst-pci.o +obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI) += snd-intel-sst-acpi.o diff --git a/sound/soc/intel/atom/sst/sst.c b/sound/soc/intel/atom/sst/sst.c index 68bcec5241f7..a0d29510d2bc 100644 --- a/sound/soc/intel/atom/sst/sst.c +++ b/sound/soc/intel/atom/sst/sst.c @@ -26,7 +26,6 @@ #include <asm/platform_sst_audio.h> #include "../sst-mfld-platform.h" #include "sst.h" -#include "../../common/sst-dsp.h" MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); @@ -49,7 +48,7 @@ static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) union ipc_header_mrfld header; union sst_imr_reg_mrfld imr; struct ipc_post *msg = NULL; - unsigned int size = 0; + unsigned int size; struct intel_sst_drv *drv = (struct intel_sst_drv *) context; irqreturn_t retval = IRQ_HANDLED; @@ -115,7 +114,7 @@ static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) { struct intel_sst_drv *drv = (struct intel_sst_drv *) context; - struct ipc_post *__msg, *msg = NULL; + struct ipc_post *__msg, *msg; unsigned long irq_flags; spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); @@ -187,7 +186,7 @@ int sst_driver_ops(struct intel_sst_drv *sst) "SST Driver capabilities missing for dev_id: %x", sst->dev_id); return -EINVAL; - }; + } } void sst_process_pending_msg(struct work_struct *work) @@ -243,11 +242,11 @@ static ssize_t firmware_version_show(struct device *dev, if (ctx->fw_version.type == 0 && ctx->fw_version.major == 0 && ctx->fw_version.minor == 0 && ctx->fw_version.build == 0) - return sprintf(buf, "FW not yet loaded\n"); + return sysfs_emit(buf, "FW not yet loaded\n"); else - return sprintf(buf, "v%02x.%02x.%02x.%02x\n", - ctx->fw_version.type, ctx->fw_version.major, - ctx->fw_version.minor, ctx->fw_version.build); + return sysfs_emit(buf, "v%02x.%02x.%02x.%02x\n", + ctx->fw_version.type, ctx->fw_version.major, + ctx->fw_version.minor, ctx->fw_version.build); } @@ -325,8 +324,7 @@ int sst_context_init(struct intel_sst_drv *ctx) ret = -ENOMEM; goto do_free_mem; } - pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY, - PM_QOS_DEFAULT_VALUE); + cpu_latency_qos_add_request(ctx->qos, PM_QOS_DEFAULT_VALUE); dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name); ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name, @@ -362,16 +360,14 @@ void sst_context_cleanup(struct intel_sst_drv *ctx) sst_unregister(ctx->dev); sst_set_fw_state_locked(ctx, SST_SHUTDOWN); sysfs_remove_group(&ctx->dev->kobj, &sst_fw_version_attr_group); - flush_scheduled_work(); destroy_workqueue(ctx->post_msg_wq); - pm_qos_remove_request(ctx->qos); + cpu_latency_qos_remove_request(ctx->qos); kfree(ctx->fw_sg_list.src); kfree(ctx->fw_sg_list.dst); ctx->fw_sg_list.list_len = 0; kfree(ctx->fw_in_mem); ctx->fw_in_mem = NULL; sst_memcpy_free_resources(ctx); - ctx = NULL; } EXPORT_SYMBOL_GPL(sst_context_cleanup); @@ -425,7 +421,7 @@ static int intel_sst_suspend(struct device *dev) { struct intel_sst_drv *ctx = dev_get_drvdata(dev); struct sst_fw_save *fw_save; - int i, ret = 0; + int i, ret; /* check first if we are already in SW reset */ if (ctx->sst_state == SST_RESET) diff --git a/sound/soc/intel/atom/sst/sst.h b/sound/soc/intel/atom/sst/sst.h index 50441cf6f77d..4d37d39fd8f4 100644 --- a/sound/soc/intel/atom/sst/sst.h +++ b/sound/soc/intel/atom/sst/sst.h @@ -34,6 +34,13 @@ #define MRFLD_FW_FEATURE_BASE_OFFSET 0x4 #define MRFLD_FW_BSS_RESET_BIT 0 +/* SST Shim register map */ +#define SST_CSR 0x00 +#define SST_ISRX 0x18 +#define SST_IMRX 0x28 +#define SST_IPCX 0x38 /* IPC IA -> SST */ +#define SST_IPCD 0x40 /* IPC SST -> IA */ + extern const struct dev_pm_ops intel_sst_pm; enum sst_states { SST_FW_LOADING = 1, @@ -428,34 +435,34 @@ struct intel_sst_ops { }; int sst_realloc_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); -int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int id); -int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int id); -int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int id); -int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); +int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); +int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); +int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); -int sst_send_byte_stream_mrfld(struct intel_sst_drv *ctx, - struct snd_sst_bytes_v2 *sbytes); +int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct snd_sst_bytes_v2 *bytes); int sst_set_stream_param(int str_id, struct snd_sst_params *str_param); int sst_set_metadata(int str_id, char *params); -int sst_get_stream(struct intel_sst_drv *sst_drv_ctx, +int sst_get_stream(struct intel_sst_drv *ctx, struct snd_sst_params *str_param); int sst_get_stream_allocated(struct intel_sst_drv *ctx, struct snd_sst_params *str_param, struct snd_sst_lib_download **lib_dnld); int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, int str_id, bool partial_drain); -int sst_post_message_mrfld(struct intel_sst_drv *ctx, - struct ipc_post *msg, bool sync); -void sst_process_reply_mrfld(struct intel_sst_drv *ctx, struct ipc_post *msg); -int sst_start_mrfld(struct intel_sst_drv *ctx); -int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *ctx); -void intel_sst_clear_intr_mrfld(struct intel_sst_drv *ctx); - -int sst_load_fw(struct intel_sst_drv *ctx); +int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *ipc_msg, bool sync); +void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, struct ipc_post *msg); +int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx); +int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx); +void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx); + +int sst_load_fw(struct intel_sst_drv *sst_drv_ctx); int sst_load_library(struct snd_sst_lib_download *lib, u8 ops); void sst_post_download_mrfld(struct intel_sst_drv *ctx); int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx); -void sst_memcpy_free_resources(struct intel_sst_drv *ctx); +void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx); int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block); @@ -490,7 +497,7 @@ int sst_prepare_and_post_msg(struct intel_sst_drv *sst, bool large, bool fill_dsp, bool sync, bool response); void sst_process_pending_msg(struct work_struct *work); -int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx); +int sst_assign_pvt_id(struct intel_sst_drv *drv); int sst_validate_strid(struct intel_sst_drv *sst_drv_ctx, int str_id); struct stream_info *get_stream_info(struct intel_sst_drv *sst_drv_ctx, int str_id); diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index f3cfe83b9ac6..3be64430c256 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -21,6 +21,7 @@ #include <linux/acpi.h> #include <asm/platform_sst_audio.h> #include <sound/core.h> +#include <sound/intel-dsp-config.h> #include <sound/soc.h> #include <sound/compress_driver.h> #include <acpi/acbuffer.h> @@ -31,7 +32,6 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> #include "../sst-mfld-platform.h" -#include "../../common/sst-dsp.h" #include "../../common/soc-intel-quirks.h" #include "sst.h" @@ -247,6 +247,13 @@ static int sst_acpi_probe(struct platform_device *pdev) id = acpi_match_device(dev->driver->acpi_match_table, dev); if (!id) return -ENODEV; + + ret = snd_intel_acpi_dsp_driver_probe(dev, id->id); + if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SST) { + dev_dbg(dev, "SST ACPI driver not selected, aborting probe\n"); + return -ENODEV; + } + dev_dbg(dev, "for %s\n", id->id); mach = (struct snd_soc_acpi_mach *)id->driver_data; @@ -321,7 +328,7 @@ static int sst_acpi_probe(struct platform_device *pdev) } /** -* intel_sst_remove - remove function +* sst_acpi_remove - remove function * * @pdev: platform device structure * diff --git a/sound/soc/intel/atom/sst/sst_drv_interface.c b/sound/soc/intel/atom/sst/sst_drv_interface.c index 762495385d5c..dc31c2c8f54c 100644 --- a/sound/soc/intel/atom/sst/sst_drv_interface.c +++ b/sound/soc/intel/atom/sst/sst_drv_interface.c @@ -24,9 +24,6 @@ #include <asm/platform_sst_audio.h> #include "../sst-mfld-platform.h" #include "sst.h" -#include "../../common/sst-dsp.h" - - #define NUM_CODEC 2 #define MIN_FRAGMENT 2 @@ -139,11 +136,10 @@ static int sst_power_control(struct device *dev, bool state) int usage_count = 0; if (state) { - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); usage_count = GET_USAGE_COUNT(dev); dev_dbg(ctx->dev, "Enable: pm usage count: %d\n", usage_count); if (ret < 0) { - pm_runtime_put_sync(dev); dev_err(ctx->dev, "Runtime get failed with err: %d\n", ret); return ret; } @@ -196,11 +192,9 @@ static int sst_cdev_open(struct device *dev, struct stream_info *stream; struct intel_sst_drv *ctx = dev_get_drvdata(dev); - retval = pm_runtime_get_sync(ctx->dev); - if (retval < 0) { - pm_runtime_put_sync(ctx->dev); + retval = pm_runtime_resume_and_get(ctx->dev); + if (retval < 0) return retval; - } str_id = sst_get_stream(ctx, str_params); if (str_id > 0) { @@ -648,11 +642,9 @@ static int sst_send_byte_stream(struct device *dev, if (NULL == bytes) return -EINVAL; - ret_val = pm_runtime_get_sync(ctx->dev); - if (ret_val < 0) { - pm_runtime_put_sync(ctx->dev); + ret_val = pm_runtime_resume_and_get(ctx->dev); + if (ret_val < 0) return ret_val; - } ret_val = sst_send_byte_stream_mrfld(ctx, bytes); sst_pm_runtime_put(ctx); diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c index c2851a829a64..4e039c7173d8 100644 --- a/sound/soc/intel/atom/sst/sst_ipc.c +++ b/sound/soc/intel/atom/sst/sst_ipc.c @@ -24,12 +24,11 @@ #include <asm/platform_sst_audio.h> #include "../sst-mfld-platform.h" #include "sst.h" -#include "../../common/sst-dsp.h" struct sst_block *sst_create_block(struct intel_sst_drv *ctx, u32 msg_id, u32 drv_id) { - struct sst_block *msg = NULL; + struct sst_block *msg; dev_dbg(ctx->dev, "Enter\n"); msg = kzalloc(sizeof(*msg), GFP_KERNEL); @@ -64,7 +63,7 @@ struct sst_block *sst_create_block(struct intel_sst_drv *ctx, int sst_wake_up_block(struct intel_sst_drv *ctx, int result, u32 drv_id, u32 ipc, void *data, u32 size) { - struct sst_block *block = NULL; + struct sst_block *block; dev_dbg(ctx->dev, "Enter\n"); @@ -92,7 +91,7 @@ int sst_wake_up_block(struct intel_sst_drv *ctx, int result, int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed) { - struct sst_block *block = NULL, *__block; + struct sst_block *block, *__block; dev_dbg(ctx->dev, "Enter\n"); spin_lock_bh(&ctx->block_lock); @@ -129,7 +128,7 @@ int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx, while (header.p.header_high.part.busy) { if (loop_count > 25) { dev_err(sst_drv_ctx->dev, - "sst: Busy wait failed, cant send this msg\n"); + "sst: Busy wait failed, can't send this msg\n"); retval = -EBUSY; goto out; } @@ -342,7 +341,7 @@ void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, } /* FW sent short error response for an IPC */ - if (msg_high.part.result && drv_id && !msg_high.part.large) { + if (msg_high.part.result && !msg_high.part.large) { /* 32-bit FW error code in msg_low */ dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low); sst_wake_up_block(sst_drv_ctx, msg_high.part.result, diff --git a/sound/soc/intel/atom/sst/sst_loader.c b/sound/soc/intel/atom/sst/sst_loader.c index ce11c36848c4..eea889001c24 100644 --- a/sound/soc/intel/atom/sst/sst_loader.c +++ b/sound/soc/intel/atom/sst/sst_loader.c @@ -29,7 +29,6 @@ #include <asm/platform_sst_audio.h> #include "../sst-mfld-platform.h" #include "sst.h" -#include "../../common/sst-dsp.h" void memcpy32_toio(void __iomem *dst, const void *src, int count) { @@ -49,6 +48,7 @@ void memcpy32_fromio(void *dst, const void __iomem *src, int count) /** * intel_sst_reset_dsp_mrfld - Resetting SST DSP + * @sst_drv_ctx: intel_sst_drv context pointer * * This resets DSP in case of MRFLD platfroms */ @@ -76,7 +76,8 @@ int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx) } /** - * sst_start_merrifield - Start the SST DSP processor + * sst_start_mrfld - Start the SST DSP processor + * @sst_drv_ctx: intel_sst_drv context pointer * * This starts the DSP in MERRIFIELD platfroms */ @@ -274,12 +275,10 @@ void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx) struct sst_memcpy_list *listnode, *tmplistnode; /* Free the list */ - if (!list_empty(&sst_drv_ctx->memcpy_list)) { - list_for_each_entry_safe(listnode, tmplistnode, - &sst_drv_ctx->memcpy_list, memcpylist) { - list_del(&listnode->memcpylist); - kfree(listnode); - } + list_for_each_entry_safe(listnode, tmplistnode, + &sst_drv_ctx->memcpy_list, memcpylist) { + list_del(&listnode->memcpylist); + kfree(listnode); } } @@ -387,6 +386,8 @@ void sst_post_download_mrfld(struct intel_sst_drv *ctx) /** * sst_load_fw - function to load FW into DSP + * @sst_drv_ctx: intel_sst_drv context pointer + * * Transfers the FW to DSP using dma/memcpy */ int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) @@ -396,8 +397,7 @@ int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n"); - if (sst_drv_ctx->sst_state != SST_RESET || - sst_drv_ctx->sst_state == SST_SHUTDOWN) + if (sst_drv_ctx->sst_state != SST_RESET) return -EAGAIN; if (!sst_drv_ctx->fw_in_mem) { @@ -412,7 +412,7 @@ int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) return -ENOMEM; /* Prevent C-states beyond C6 */ - pm_qos_update_request(sst_drv_ctx->qos, 0); + cpu_latency_qos_update_request(sst_drv_ctx->qos, 0); sst_drv_ctx->sst_state = SST_FW_LOADING; @@ -442,7 +442,7 @@ int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) restore: /* Re-enable Deeper C-states beyond C6 */ - pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE); + cpu_latency_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE); sst_free_block(sst_drv_ctx, block); dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n"); diff --git a/sound/soc/intel/atom/sst/sst_pci.c b/sound/soc/intel/atom/sst/sst_pci.c index d952719bc098..5862fe968083 100644 --- a/sound/soc/intel/atom/sst/sst_pci.c +++ b/sound/soc/intel/atom/sst/sst_pci.c @@ -99,7 +99,7 @@ static int sst_platform_get_resources(struct intel_sst_drv *ctx) dev_dbg(ctx->dev, "DRAM Ptr %p\n", ctx->dram); do_release_regions: pci_release_regions(pci); - return 0; + return ret; } /* diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c index 13db2854db3e..e6a5c18a7018 100644 --- a/sound/soc/intel/atom/sst/sst_pvt.c +++ b/sound/soc/intel/atom/sst/sst_pvt.c @@ -26,7 +26,6 @@ #include <asm/platform_sst_audio.h> #include "../sst-mfld-platform.h" #include "sst.h" -#include "../../common/sst-dsp.h" int sst_shim_write(void __iomem *addr, int offset, int value) { @@ -188,7 +187,7 @@ int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, u32 msg_id, u32 drv_id) { - int retval = 0; + int retval; retval = sst_create_ipc_msg(arg, large); if (retval) @@ -198,7 +197,7 @@ int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, kfree(*arg); return -ENOMEM; } - return retval; + return 0; } /* @@ -223,9 +222,9 @@ int sst_prepare_and_post_msg(struct intel_sst_drv *sst, size_t mbox_data_len, const void *mbox_data, void **data, bool large, bool fill_dsp, bool sync, bool response) { + struct sst_block *block = NULL; struct ipc_post *msg = NULL; struct ipc_dsp_hdr dsp_hdr; - struct sst_block *block; int ret = 0, pvt_id; pvt_id = sst_assign_pvt_id(sst); diff --git a/sound/soc/intel/atom/sst/sst_stream.c b/sound/soc/intel/atom/sst/sst_stream.c index ea09f4170201..ea1ef8a61fa6 100644 --- a/sound/soc/intel/atom/sst/sst_stream.c +++ b/sound/soc/intel/atom/sst/sst_stream.c @@ -23,7 +23,6 @@ #include <asm/platform_sst_audio.h> #include "../sst-mfld-platform.h" #include "sst.h" -#include "../../common/sst-dsp.h" int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params) { @@ -92,8 +91,8 @@ int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params) /** * sst_realloc_stream - Send msg for (re-)allocating a stream using the - * @sst_drv_ctx intel_sst_drv context pointer - * @str_id: stream ID + * @sst_drv_ctx: intel_sst_drv context pointer + * @str_id: stream ID * * Send a msg for (re-)allocating a stream using the parameters previously * passed to sst_alloc_stream_mrfld() for the same stream ID. @@ -142,12 +141,13 @@ out: } /** -* sst_start_stream - Send msg for a starting stream -* @str_id: stream ID -* -* This function is called by any function which wants to start -* a stream. -*/ + * sst_start_stream - Send msg for a starting stream + * @sst_drv_ctx: intel_sst_drv context pointer + * @str_id: stream ID + * + * This function is called by any function which wants to start + * a stream. + */ int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) { int retval = 0; @@ -234,7 +234,8 @@ out: /** * sst_pause_stream - Send msg for a pausing stream - * @str_id: stream ID + * @sst_drv_ctx: intel_sst_drv context pointer + * @str_id: stream ID * * This function is called by any function which wants to pause * an already running stream. @@ -278,7 +279,8 @@ int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) /** * sst_resume_stream - Send msg for resuming stream - * @str_id: stream ID + * @sst_drv_ctx: intel_sst_drv context pointer + * @str_id: stream ID * * This function is called by any function which wants to resume * an already paused stream. @@ -345,7 +347,8 @@ int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) /** * sst_drop_stream - Send msg for stopping stream - * @str_id: stream ID + * @sst_drv_ctx: intel_sst_drv context pointer + * @str_id: stream ID * * This function is called by any function which wants to stop * a stream. @@ -377,12 +380,14 @@ int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) } /** -* sst_drain_stream - Send msg for draining stream -* @str_id: stream ID -* -* This function is called by any function which wants to drain -* a stream. -*/ + * sst_drain_stream - Send msg for draining stream + * @sst_drv_ctx: intel_sst_drv context pointer + * @str_id: stream ID + * @partial_drain: boolean indicating if a gapless transition is taking place + * + * This function is called by any function which wants to drain + * a stream. + */ int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, int str_id, bool partial_drain) { @@ -415,7 +420,8 @@ int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, /** * sst_free_stream - Frees a stream - * @str_id: stream ID + * @sst_drv_ctx: intel_sst_drv context pointer + * @str_id: stream ID * * This function is called by any function which wants to free * a stream. diff --git a/sound/soc/intel/avs/Makefile b/sound/soc/intel/avs/Makefile new file mode 100644 index 000000000000..919212825f21 --- /dev/null +++ b/sound/soc/intel/avs/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only + +snd-soc-avs-objs := dsp.o ipc.o messages.o utils.o core.o loader.o \ + topology.o path.o pcm.o board_selection.o +snd-soc-avs-objs += cldma.o +snd-soc-avs-objs += skl.o apl.o + +snd-soc-avs-objs += trace.o +# tell define_trace.h where to find the trace header +CFLAGS_trace.o := -I$(src) + +obj-$(CONFIG_SND_SOC_INTEL_AVS) += snd-soc-avs.o + +# Machine support +obj-$(CONFIG_SND_SOC) += boards/ diff --git a/sound/soc/intel/avs/apl.c b/sound/soc/intel/avs/apl.c new file mode 100644 index 000000000000..b8e2b23c9f64 --- /dev/null +++ b/sound/soc/intel/avs/apl.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/devcoredump.h> +#include <linux/slab.h> +#include "avs.h" +#include "messages.h" +#include "path.h" +#include "topology.h" + +static int apl_enable_logs(struct avs_dev *adev, enum avs_log_enable enable, u32 aging_period, + u32 fifo_full_period, unsigned long resource_mask, u32 *priorities) +{ + struct apl_log_state_info *info; + u32 size, num_cores = adev->hw_cfg.dsp_cores; + int ret, i; + + if (fls_long(resource_mask) > num_cores) + return -EINVAL; + size = struct_size(info, logs_core, num_cores); + info = kzalloc(size, GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->aging_timer_period = aging_period; + info->fifo_full_timer_period = fifo_full_period; + info->core_mask = resource_mask; + if (enable) + for_each_set_bit(i, &resource_mask, num_cores) { + info->logs_core[i].enable = enable; + info->logs_core[i].min_priority = *priorities++; + } + else + for_each_set_bit(i, &resource_mask, num_cores) + info->logs_core[i].enable = enable; + + ret = avs_ipc_set_enable_logs(adev, (u8 *)info, size); + kfree(info); + if (ret) + return AVS_IPC_RET(ret); + + return 0; +} + +static int apl_log_buffer_status(struct avs_dev *adev, union avs_notify_msg *msg) +{ + struct apl_log_buffer_layout layout; + unsigned long flags; + void __iomem *addr, *buf; + + addr = avs_log_buffer_addr(adev, msg->log.core); + if (!addr) + return -ENXIO; + + memcpy_fromio(&layout, addr, sizeof(layout)); + + spin_lock_irqsave(&adev->dbg.trace_lock, flags); + if (!kfifo_initialized(&adev->dbg.trace_fifo)) + /* consume the logs regardless of consumer presence */ + goto update_read_ptr; + + buf = apl_log_payload_addr(addr); + + if (layout.read_ptr > layout.write_ptr) { + __kfifo_fromio_locked(&adev->dbg.trace_fifo, buf + layout.read_ptr, + apl_log_payload_size(adev) - layout.read_ptr, + &adev->dbg.fifo_lock); + layout.read_ptr = 0; + } + __kfifo_fromio_locked(&adev->dbg.trace_fifo, buf + layout.read_ptr, + layout.write_ptr - layout.read_ptr, &adev->dbg.fifo_lock); + + wake_up(&adev->dbg.trace_waitq); + +update_read_ptr: + spin_unlock_irqrestore(&adev->dbg.trace_lock, flags); + writel(layout.write_ptr, addr); + return 0; +} + +static int apl_wait_log_entry(struct avs_dev *adev, u32 core, struct apl_log_buffer_layout *layout) +{ + unsigned long timeout; + void __iomem *addr; + + addr = avs_log_buffer_addr(adev, core); + if (!addr) + return -ENXIO; + + timeout = jiffies + msecs_to_jiffies(10); + + do { + memcpy_fromio(layout, addr, sizeof(*layout)); + if (layout->read_ptr != layout->write_ptr) + return 0; + usleep_range(500, 1000); + } while (!time_after(jiffies, timeout)); + + return -ETIMEDOUT; +} + +/* reads log header and tests its type */ +#define apl_is_entry_stackdump(addr) ((readl(addr) >> 30) & 0x1) + +static int apl_coredump(struct avs_dev *adev, union avs_notify_msg *msg) +{ + struct apl_log_buffer_layout layout; + void __iomem *addr, *buf; + size_t dump_size; + u16 offset = 0; + u8 *dump, *pos; + + dump_size = AVS_FW_REGS_SIZE + msg->ext.coredump.stack_dump_size; + dump = vzalloc(dump_size); + if (!dump) + return -ENOMEM; + + memcpy_fromio(dump, avs_sram_addr(adev, AVS_FW_REGS_WINDOW), AVS_FW_REGS_SIZE); + + if (!msg->ext.coredump.stack_dump_size) + goto exit; + + /* Dump the registers even if an external error prevents gathering the stack. */ + addr = avs_log_buffer_addr(adev, msg->ext.coredump.core_id); + if (!addr) + goto exit; + + buf = apl_log_payload_addr(addr); + memcpy_fromio(&layout, addr, sizeof(layout)); + if (!apl_is_entry_stackdump(buf + layout.read_ptr)) { + /* + * DSP awaits the remaining logs to be + * gathered before dumping stack + */ + msg->log.core = msg->ext.coredump.core_id; + avs_dsp_op(adev, log_buffer_status, msg); + } + + pos = dump + AVS_FW_REGS_SIZE; + /* gather the stack */ + do { + u32 count; + + if (apl_wait_log_entry(adev, msg->ext.coredump.core_id, &layout)) + break; + + if (layout.read_ptr > layout.write_ptr) { + count = apl_log_payload_size(adev) - layout.read_ptr; + memcpy_fromio(pos + offset, buf + layout.read_ptr, count); + layout.read_ptr = 0; + offset += count; + } + count = layout.write_ptr - layout.read_ptr; + memcpy_fromio(pos + offset, buf + layout.read_ptr, count); + offset += count; + + /* update read pointer */ + writel(layout.write_ptr, addr); + } while (offset < msg->ext.coredump.stack_dump_size); + +exit: + dev_coredumpv(adev->dev, dump, dump_size, GFP_KERNEL); + + return 0; +} + +static bool apl_lp_streaming(struct avs_dev *adev) +{ + struct avs_path *path; + + /* Any gateway without buffer allocated in LP area disqualifies D0IX. */ + list_for_each_entry(path, &adev->path_list, node) { + struct avs_path_pipeline *ppl; + + list_for_each_entry(ppl, &path->ppl_list, node) { + struct avs_path_module *mod; + + list_for_each_entry(mod, &ppl->mod_list, node) { + struct avs_tplg_modcfg_ext *cfg; + + cfg = mod->template->cfg_ext; + + /* only copiers have gateway attributes */ + if (!guid_equal(&cfg->type, &AVS_COPIER_MOD_UUID)) + continue; + /* non-gateway copiers do not prevent PG */ + if (cfg->copier.dma_type == INVALID_OBJECT_ID) + continue; + + if (!mod->gtw_attrs.lp_buffer_alloc) + return false; + } + } + } + + return true; +} + +static bool apl_d0ix_toggle(struct avs_dev *adev, struct avs_ipc_msg *tx, bool wake) +{ + /* wake in all cases */ + if (wake) + return true; + + /* + * If no pipelines are running, allow for d0ix schedule. + * If all gateways have lp=1, allow for d0ix schedule. + * If any gateway with lp=0 is allocated, abort scheduling d0ix. + * + * Note: for cAVS 1.5+ and 1.8, D0IX is LP-firmware transition, + * not the power-gating mechanism known from cAVS 2.0. + */ + return apl_lp_streaming(adev); +} + +static int apl_set_d0ix(struct avs_dev *adev, bool enable) +{ + bool streaming = false; + int ret; + + if (enable) + /* Either idle or all gateways with lp=1. */ + streaming = !list_empty(&adev->path_list); + + ret = avs_ipc_set_d0ix(adev, enable, streaming); + return AVS_IPC_RET(ret); +} + +const struct avs_dsp_ops apl_dsp_ops = { + .power = avs_dsp_core_power, + .reset = avs_dsp_core_reset, + .stall = avs_dsp_core_stall, + .irq_handler = avs_dsp_irq_handler, + .irq_thread = avs_dsp_irq_thread, + .int_control = avs_dsp_interrupt_control, + .load_basefw = avs_hda_load_basefw, + .load_lib = avs_hda_load_library, + .transfer_mods = avs_hda_transfer_modules, + .enable_logs = apl_enable_logs, + .log_buffer_offset = skl_log_buffer_offset, + .log_buffer_status = apl_log_buffer_status, + .coredump = apl_coredump, + .d0ix_toggle = apl_d0ix_toggle, + .set_d0ix = apl_set_d0ix, +}; diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h new file mode 100644 index 000000000000..92e37722d280 --- /dev/null +++ b/sound/soc/intel/avs/avs.h @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation. All rights reserved. + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_H +#define __SOUND_SOC_INTEL_AVS_H + +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/kfifo.h> +#include <sound/hda_codec.h> +#include <sound/hda_register.h> +#include <sound/soc-component.h> +#include "messages.h" +#include "registers.h" + +struct avs_dev; +struct avs_tplg; +struct avs_tplg_library; +struct avs_soc_component; +struct avs_ipc_msg; + +/* + * struct avs_dsp_ops - Platform-specific DSP operations + * + * @power: Power on or off DSP cores + * @reset: Enter or exit reset state on DSP cores + * @stall: Stall or run DSP cores + * @irq_handler: Top half of IPC servicing + * @irq_thread: Bottom half of IPC servicing + * @int_control: Enable or disable IPC interrupts + */ +struct avs_dsp_ops { + int (* const power)(struct avs_dev *, u32, bool); + int (* const reset)(struct avs_dev *, u32, bool); + int (* const stall)(struct avs_dev *, u32, bool); + irqreturn_t (* const irq_handler)(int, void *); + irqreturn_t (* const irq_thread)(int, void *); + void (* const int_control)(struct avs_dev *, bool); + int (* const load_basefw)(struct avs_dev *, struct firmware *); + int (* const load_lib)(struct avs_dev *, struct firmware *, u32); + int (* const transfer_mods)(struct avs_dev *, bool, struct avs_module_entry *, u32); + int (* const enable_logs)(struct avs_dev *, enum avs_log_enable, u32, u32, unsigned long, + u32 *); + int (* const log_buffer_offset)(struct avs_dev *, u32); + int (* const log_buffer_status)(struct avs_dev *, union avs_notify_msg *); + int (* const coredump)(struct avs_dev *, union avs_notify_msg *); + bool (* const d0ix_toggle)(struct avs_dev *, struct avs_ipc_msg *, bool); + int (* const set_d0ix)(struct avs_dev *, bool); +}; + +#define avs_dsp_op(adev, op, ...) \ + ((adev)->spec->dsp_ops->op(adev, ## __VA_ARGS__)) + +extern const struct avs_dsp_ops skl_dsp_ops; +extern const struct avs_dsp_ops apl_dsp_ops; + +#define AVS_PLATATTR_CLDMA BIT_ULL(0) +#define AVS_PLATATTR_IMR BIT_ULL(1) + +#define avs_platattr_test(adev, attr) \ + ((adev)->spec->attributes & AVS_PLATATTR_##attr) + +/* Platform specific descriptor */ +struct avs_spec { + const char *name; + + const struct avs_dsp_ops *const dsp_ops; + struct avs_fw_version min_fw_version; /* anything below is rejected */ + + const u32 core_init_mask; /* used during DSP boot */ + const u64 attributes; /* bitmask of AVS_PLATATTR_* */ + const u32 sram_base_offset; + const u32 sram_window_size; + const u32 rom_status; +}; + +struct avs_fw_entry { + char *name; + const struct firmware *fw; + + struct list_head node; +}; + +struct avs_debug { + struct kfifo trace_fifo; + spinlock_t fifo_lock; /* serialize I/O for trace_fifo */ + spinlock_t trace_lock; /* serialize debug window I/O between each LOG_BUFFER_STATUS */ + wait_queue_head_t trace_waitq; + u32 aging_timer_period; + u32 fifo_full_timer_period; + u32 logged_resources; /* context dependent: core or library */ +}; + +/* + * struct avs_dev - Intel HD-Audio driver data + * + * @dev: PCI device + * @dsp_ba: DSP bar address + * @spec: platform-specific descriptor + * @fw_cfg: Firmware configuration, obtained through FW_CONFIG message + * @hw_cfg: Hardware configuration, obtained through HW_CONFIG message + * @mods_info: Available module-types, obtained through MODULES_INFO message + * @mod_idas: Module instance ID pool, one per module-type + * @modres_mutex: For synchronizing any @mods_info updates + * @ppl_ida: Pipeline instance ID pool + * @fw_list: List of libraries loaded, including base firmware + */ +struct avs_dev { + struct hda_bus base; + struct device *dev; + + void __iomem *dsp_ba; + const struct avs_spec *spec; + struct avs_ipc *ipc; + + struct avs_fw_cfg fw_cfg; + struct avs_hw_cfg hw_cfg; + struct avs_mods_info *mods_info; + struct ida **mod_idas; + struct mutex modres_mutex; + struct ida ppl_ida; + struct list_head fw_list; + int *core_refs; /* reference count per core */ + char **lib_names; + + struct completion fw_ready; + struct work_struct probe_work; + + struct nhlt_acpi_table *nhlt; + struct list_head comp_list; + struct mutex comp_list_mutex; + struct list_head path_list; + spinlock_t path_list_lock; + struct mutex path_mutex; + + struct avs_debug dbg; +}; + +/* from hda_bus to avs_dev */ +#define hda_to_avs(hda) container_of(hda, struct avs_dev, base) +/* from hdac_bus to avs_dev */ +#define hdac_to_avs(hdac) hda_to_avs(to_hda_bus(hdac)) +/* from device to avs_dev */ +#define to_avs_dev(dev) \ +({ \ + struct hdac_bus *__bus = dev_get_drvdata(dev); \ + hdac_to_avs(__bus); \ +}) + +int avs_dsp_core_power(struct avs_dev *adev, u32 core_mask, bool power); +int avs_dsp_core_reset(struct avs_dev *adev, u32 core_mask, bool reset); +int avs_dsp_core_stall(struct avs_dev *adev, u32 core_mask, bool stall); +int avs_dsp_core_enable(struct avs_dev *adev, u32 core_mask); +int avs_dsp_core_disable(struct avs_dev *adev, u32 core_mask); + +/* Inter Process Communication */ + +struct avs_ipc_msg { + union { + u64 header; + union avs_global_msg glb; + union avs_reply_msg rsp; + }; + void *data; + size_t size; +}; + +/* + * struct avs_ipc - DSP IPC context + * + * @dev: PCI device + * @rx: Reply message cache + * @default_timeout_ms: default message timeout in MS + * @ready: whether firmware is ready and communication is open + * @rx_completed: whether RX for previously sent TX has been received + * @rx_lock: for serializing manipulation of rx_* fields + * @msg_lock: for synchronizing request handling + * @done_completion: DONE-part of IPC i.e. ROM and ACKs from FW + * @busy_completion: BUSY-part of IPC i.e. receiving responses from FW + */ +struct avs_ipc { + struct device *dev; + + struct avs_ipc_msg rx; + u32 default_timeout_ms; + bool ready; + atomic_t recovering; + + bool rx_completed; + spinlock_t rx_lock; + struct mutex msg_mutex; + struct completion done_completion; + struct completion busy_completion; + + struct work_struct recovery_work; + struct delayed_work d0ix_work; + atomic_t d0ix_disable_depth; + bool in_d0ix; +}; + +#define AVS_EIPC EREMOTEIO +/* + * IPC handlers may return positive value (firmware error code) what denotes + * successful HOST <-> DSP communication yet failure to process specific request. + * + * Below macro converts returned value to linux kernel error code. + * All IPC callers MUST use it as soon as firmware error code is consumed. + */ +#define AVS_IPC_RET(ret) \ + (((ret) <= 0) ? (ret) : -AVS_EIPC) + +static inline void avs_ipc_err(struct avs_dev *adev, struct avs_ipc_msg *tx, + const char *name, int error) +{ + /* + * If IPC channel is blocked e.g.: due to ongoing recovery, + * -EPERM error code is expected and thus it's not an actual error. + */ + if (error == -EPERM) + dev_dbg(adev->dev, "%s 0x%08x 0x%08x failed: %d\n", name, + tx->glb.primary, tx->glb.ext.val, error); + else + dev_err(adev->dev, "%s 0x%08x 0x%08x failed: %d\n", name, + tx->glb.primary, tx->glb.ext.val, error); +} + +irqreturn_t avs_dsp_irq_handler(int irq, void *dev_id); +irqreturn_t avs_dsp_irq_thread(int irq, void *dev_id); +void avs_dsp_process_response(struct avs_dev *adev, u64 header); +int avs_dsp_send_msg_timeout(struct avs_dev *adev, + struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout); +int avs_dsp_send_msg(struct avs_dev *adev, + struct avs_ipc_msg *request, struct avs_ipc_msg *reply); +/* Two variants below are for messages that control DSP power states. */ +int avs_dsp_send_pm_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, bool wake_d0i0); +int avs_dsp_send_pm_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, bool wake_d0i0); +int avs_dsp_send_rom_msg_timeout(struct avs_dev *adev, + struct avs_ipc_msg *request, int timeout); +int avs_dsp_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request); +void avs_dsp_interrupt_control(struct avs_dev *adev, bool enable); +int avs_ipc_init(struct avs_ipc *ipc, struct device *dev); +void avs_ipc_block(struct avs_ipc *ipc); + +int avs_dsp_disable_d0ix(struct avs_dev *adev); +int avs_dsp_enable_d0ix(struct avs_dev *adev); + +int skl_log_buffer_offset(struct avs_dev *adev, u32 core); + +/* Firmware resources management */ + +int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_module_entry *entry); +int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_module_entry *entry); +int avs_get_module_id(struct avs_dev *adev, const guid_t *uuid); +bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id); + +int avs_module_info_init(struct avs_dev *adev, bool purge); +void avs_module_info_free(struct avs_dev *adev); +int avs_module_id_alloc(struct avs_dev *adev, u16 module_id); +void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id); +int avs_request_firmware(struct avs_dev *adev, const struct firmware **fw_p, const char *name); +void avs_release_last_firmware(struct avs_dev *adev); +void avs_release_firmwares(struct avs_dev *adev); + +int avs_dsp_init_module(struct avs_dev *adev, u16 module_id, u8 ppl_instance_id, + u8 core_id, u8 domain, void *param, u32 param_size, + u16 *instance_id); +void avs_dsp_delete_module(struct avs_dev *adev, u16 module_id, u16 instance_id, + u8 ppl_instance_id, u8 core_id); +int avs_dsp_create_pipeline(struct avs_dev *adev, u16 req_size, u8 priority, + bool lp, u16 attributes, u8 *instance_id); +int avs_dsp_delete_pipeline(struct avs_dev *adev, u8 instance_id); + +/* Firmware loading */ + +void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable); +void avs_hda_power_gating_enable(struct avs_dev *adev, bool enable); +void avs_hda_l1sen_enable(struct avs_dev *adev, bool enable); + +int avs_dsp_load_libraries(struct avs_dev *adev, struct avs_tplg_library *libs, u32 num_libs); +int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge); +int avs_dsp_first_boot_firmware(struct avs_dev *adev); + +int avs_cldma_load_basefw(struct avs_dev *adev, struct firmware *fw); +int avs_cldma_load_library(struct avs_dev *adev, struct firmware *lib, u32 id); +int avs_cldma_transfer_modules(struct avs_dev *adev, bool load, + struct avs_module_entry *mods, u32 num_mods); +int avs_hda_load_basefw(struct avs_dev *adev, struct firmware *fw); +int avs_hda_load_library(struct avs_dev *adev, struct firmware *lib, u32 id); +int avs_hda_transfer_modules(struct avs_dev *adev, bool load, + struct avs_module_entry *mods, u32 num_mods); + +/* Soc component members */ + +struct avs_soc_component { + struct snd_soc_component base; + struct avs_tplg *tplg; + + struct list_head node; +}; + +#define to_avs_soc_component(comp) \ + container_of(comp, struct avs_soc_component, base) + +extern const struct snd_soc_dai_ops avs_dai_fe_ops; + +int avs_dmic_platform_register(struct avs_dev *adev, const char *name); +int avs_i2s_platform_register(struct avs_dev *adev, const char *name, unsigned long port_mask, + unsigned long *tdms); +int avs_hda_platform_register(struct avs_dev *adev, const char *name); + +int avs_register_all_boards(struct avs_dev *adev); +void avs_unregister_all_boards(struct avs_dev *adev); + +/* Firmware tracing helpers */ + +unsigned int __kfifo_fromio_locked(struct kfifo *fifo, const void __iomem *src, unsigned int len, + spinlock_t *lock); + +#define avs_log_buffer_size(adev) \ + ((adev)->fw_cfg.trace_log_bytes / (adev)->hw_cfg.dsp_cores) + +#define avs_log_buffer_addr(adev, core) \ +({ \ + s32 __offset = avs_dsp_op(adev, log_buffer_offset, core); \ + (__offset < 0) ? NULL : \ + (avs_sram_addr(adev, AVS_DEBUG_WINDOW) + __offset); \ +}) + +struct apl_log_buffer_layout { + u32 read_ptr; + u32 write_ptr; + u8 buffer[]; +} __packed; + +#define apl_log_payload_size(adev) \ + (avs_log_buffer_size(adev) - sizeof(struct apl_log_buffer_layout)) + +#define apl_log_payload_addr(addr) \ + (addr + sizeof(struct apl_log_buffer_layout)) + +#endif /* __SOUND_SOC_INTEL_AVS_H */ diff --git a/sound/soc/intel/avs/board_selection.c b/sound/soc/intel/avs/board_selection.c new file mode 100644 index 000000000000..87f9c18be238 --- /dev/null +++ b/sound/soc/intel/avs/board_selection.c @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/dmi.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <sound/hda_codec.h> +#include <sound/hda_register.h> +#include <sound/intel-nhlt.h> +#include <sound/soc-acpi.h> +#include <sound/soc-component.h> +#include "avs.h" + +static bool i2s_test; +module_param(i2s_test, bool, 0444); +MODULE_PARM_DESC(i2s_test, "Probe I2S test-board and skip all other I2S boards"); + +static const struct dmi_system_id kbl_dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Skylake Y LPDDR3 RVP3"), + }, + }, + {} +}; + +static const struct dmi_system_id kblr_dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Kabylake R DDR4 RVP"), + }, + }, + {} +}; + +static struct snd_soc_acpi_mach *dmi_match_quirk(void *arg) +{ + struct snd_soc_acpi_mach *mach = arg; + const struct dmi_system_id *dmi_id; + struct dmi_system_id *dmi_table; + + if (mach->quirk_data == NULL) + return mach; + + dmi_table = (struct dmi_system_id *)mach->quirk_data; + + dmi_id = dmi_first_match(dmi_table); + if (!dmi_id) + return NULL; + + return mach; +} + +#define AVS_SSP(x) (BIT(x)) +#define AVS_SSP_RANGE(a, b) (GENMASK(b, a)) + +/* supported I2S board codec configurations */ +static struct snd_soc_acpi_mach avs_skl_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt286", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "rt286-tplg.bin", + }, + { + .id = "10508825", + .drv_name = "avs_nau8825", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "nau8825-tplg.bin", + }, + { + .id = "INT343B", + .drv_name = "avs_ssm4567", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "ssm4567-tplg.bin", + }, + { + .id = "MX98357A", + .drv_name = "avs_max98357a", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "max98357a-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_kbl_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt286", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .quirk_data = &kbl_dmi_table, + .machine_quirk = dmi_match_quirk, + .tplg_filename = "rt286-tplg.bin", + }, + { + .id = "INT343A", + .drv_name = "avs_rt298", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .quirk_data = &kblr_dmi_table, + .machine_quirk = dmi_match_quirk, + .tplg_filename = "rt298-tplg.bin", + }, + { + .id = "MX98373", + .drv_name = "avs_max98373", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "max98373-tplg.bin", + }, + { + .id = "DLGS7219", + .drv_name = "avs_da7219", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "da7219-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_apl_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt298", + .mach_params = { + .i2s_link_mask = AVS_SSP(5), + }, + .tplg_filename = "rt298-tplg.bin", + }, + { + .id = "INT34C3", + .drv_name = "avs_tdf8532", + .mach_params = { + .i2s_link_mask = AVS_SSP_RANGE(0, 5), + }, + .pdata = (unsigned long[]){ 0, 0, 0x14, 0, 0, 0 }, /* SSP2 TDMs */ + .tplg_filename = "tdf8532-tplg.bin", + }, + { + .id = "MX98357A", + .drv_name = "avs_max98357a", + .mach_params = { + .i2s_link_mask = AVS_SSP(5), + }, + .tplg_filename = "max98357a-tplg.bin", + }, + { + .id = "DLGS7219", + .drv_name = "avs_da7219", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "da7219-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_gml_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt298", + .mach_params = { + .i2s_link_mask = AVS_SSP(2), + }, + .tplg_filename = "rt298-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_test_i2s_machines[] = { + { + .drv_name = "avs_i2s_test", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "i2s-test-tplg.bin", + }, + { + .drv_name = "avs_i2s_test", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "i2s-test-tplg.bin", + }, + { + .drv_name = "avs_i2s_test", + .mach_params = { + .i2s_link_mask = AVS_SSP(2), + }, + .tplg_filename = "i2s-test-tplg.bin", + }, + { + .drv_name = "avs_i2s_test", + .mach_params = { + .i2s_link_mask = AVS_SSP(3), + }, + .tplg_filename = "i2s-test-tplg.bin", + }, + { + .drv_name = "avs_i2s_test", + .mach_params = { + .i2s_link_mask = AVS_SSP(4), + }, + .tplg_filename = "i2s-test-tplg.bin", + }, + { + .drv_name = "avs_i2s_test", + .mach_params = { + .i2s_link_mask = AVS_SSP(5), + }, + .tplg_filename = "i2s-test-tplg.bin", + }, + /* no NULL terminator, as we depend on ARRAY SIZE due to .id == NULL */ +}; + +struct avs_acpi_boards { + int id; + struct snd_soc_acpi_mach *machs; +}; + +#define AVS_MACH_ENTRY(_id, _mach) \ + { .id = (_id), .machs = (_mach), } + +/* supported I2S boards per platform */ +static const struct avs_acpi_boards i2s_boards[] = { + AVS_MACH_ENTRY(0x9d70, avs_skl_i2s_machines), /* SKL */ + AVS_MACH_ENTRY(0x9d71, avs_kbl_i2s_machines), /* KBL */ + AVS_MACH_ENTRY(0x5a98, avs_apl_i2s_machines), /* APL */ + AVS_MACH_ENTRY(0x3198, avs_gml_i2s_machines), /* GML */ + {}, +}; + +static const struct avs_acpi_boards *avs_get_i2s_boards(struct avs_dev *adev) +{ + int id, i; + + id = adev->base.pci->device; + for (i = 0; i < ARRAY_SIZE(i2s_boards); i++) + if (i2s_boards[i].id == id) + return &i2s_boards[i]; + return NULL; +} + +/* platform devices owned by AVS audio are removed with this hook */ +static void board_pdev_unregister(void *data) +{ + platform_device_unregister(data); +} + +static int avs_register_dmic_board(struct avs_dev *adev) +{ + struct platform_device *codec, *board; + struct snd_soc_acpi_mach mach = {{0}}; + int ret; + + if (!adev->nhlt || + !intel_nhlt_has_endpoint_type(adev->nhlt, NHLT_LINK_DMIC)) { + dev_dbg(adev->dev, "no DMIC endpoints present\n"); + return 0; + } + + codec = platform_device_register_simple("dmic-codec", PLATFORM_DEVID_NONE, NULL, 0); + if (IS_ERR(codec)) { + dev_err(adev->dev, "dmic codec register failed\n"); + return PTR_ERR(codec); + } + + ret = devm_add_action(adev->dev, board_pdev_unregister, codec); + if (ret < 0) { + platform_device_unregister(codec); + return ret; + } + + ret = avs_dmic_platform_register(adev, "dmic-platform"); + if (ret < 0) + return ret; + + mach.tplg_filename = "dmic-tplg.bin"; + mach.mach_params.platform = "dmic-platform"; + + board = platform_device_register_data(NULL, "avs_dmic", PLATFORM_DEVID_NONE, + (const void *)&mach, sizeof(mach)); + if (IS_ERR(board)) { + dev_err(adev->dev, "dmic board register failed\n"); + return PTR_ERR(board); + } + + ret = devm_add_action(adev->dev, board_pdev_unregister, board); + if (ret < 0) { + platform_device_unregister(board); + return ret; + } + + return 0; +} + +static int avs_register_i2s_board(struct avs_dev *adev, struct snd_soc_acpi_mach *mach) +{ + struct platform_device *board; + int num_ssps; + char *name; + int ret; + + num_ssps = adev->hw_cfg.i2s_caps.ctrl_count; + if (fls(mach->mach_params.i2s_link_mask) > num_ssps) { + dev_err(adev->dev, "Platform supports %d SSPs but board %s requires SSP%ld\n", + num_ssps, mach->drv_name, + (unsigned long)__fls(mach->mach_params.i2s_link_mask)); + return -ENODEV; + } + + name = devm_kasprintf(adev->dev, GFP_KERNEL, "%s.%d-platform", mach->drv_name, + mach->mach_params.i2s_link_mask); + if (!name) + return -ENOMEM; + + ret = avs_i2s_platform_register(adev, name, mach->mach_params.i2s_link_mask, mach->pdata); + if (ret < 0) + return ret; + + mach->mach_params.platform = name; + + board = platform_device_register_data(NULL, mach->drv_name, mach->mach_params.i2s_link_mask, + (const void *)mach, sizeof(*mach)); + if (IS_ERR(board)) { + dev_err(adev->dev, "ssp board register failed\n"); + return PTR_ERR(board); + } + + ret = devm_add_action(adev->dev, board_pdev_unregister, board); + if (ret < 0) { + platform_device_unregister(board); + return ret; + } + + return 0; +} + +static int avs_register_i2s_boards(struct avs_dev *adev) +{ + const struct avs_acpi_boards *boards; + struct snd_soc_acpi_mach *mach; + int ret; + + if (!adev->nhlt || !intel_nhlt_has_endpoint_type(adev->nhlt, NHLT_LINK_SSP)) { + dev_dbg(adev->dev, "no I2S endpoints present\n"); + return 0; + } + + if (i2s_test) { + int i, num_ssps; + + num_ssps = adev->hw_cfg.i2s_caps.ctrl_count; + /* constrain just in case FW says there can be more SSPs than possible */ + num_ssps = min_t(int, ARRAY_SIZE(avs_test_i2s_machines), num_ssps); + + mach = avs_test_i2s_machines; + + for (i = 0; i < num_ssps; i++) { + ret = avs_register_i2s_board(adev, &mach[i]); + if (ret < 0) + dev_warn(adev->dev, "register i2s %s failed: %d\n", mach->drv_name, + ret); + } + return 0; + } + + boards = avs_get_i2s_boards(adev); + if (!boards) { + dev_dbg(adev->dev, "no I2S endpoints supported\n"); + return 0; + } + + for (mach = boards->machs; mach->id[0]; mach++) { + if (!acpi_dev_present(mach->id, NULL, -1)) + continue; + + if (mach->machine_quirk) + if (!mach->machine_quirk(mach)) + continue; + + ret = avs_register_i2s_board(adev, mach); + if (ret < 0) + dev_warn(adev->dev, "register i2s %s failed: %d\n", mach->drv_name, ret); + } + + return 0; +} + +static int avs_register_hda_board(struct avs_dev *adev, struct hda_codec *codec) +{ + struct snd_soc_acpi_mach mach = {{0}}; + struct platform_device *board; + struct hdac_device *hdev = &codec->core; + char *pname; + int ret, id; + + pname = devm_kasprintf(adev->dev, GFP_KERNEL, "%s-platform", dev_name(&hdev->dev)); + if (!pname) + return -ENOMEM; + + ret = avs_hda_platform_register(adev, pname); + if (ret < 0) + return ret; + + mach.pdata = codec; + mach.mach_params.platform = pname; + mach.tplg_filename = devm_kasprintf(adev->dev, GFP_KERNEL, "hda-%08x-tplg.bin", + hdev->vendor_id); + if (!mach.tplg_filename) + return -ENOMEM; + + id = adev->base.core.idx * HDA_MAX_CODECS + hdev->addr; + board = platform_device_register_data(NULL, "avs_hdaudio", id, (const void *)&mach, + sizeof(mach)); + if (IS_ERR(board)) { + dev_err(adev->dev, "hda board register failed\n"); + return PTR_ERR(board); + } + + ret = devm_add_action(adev->dev, board_pdev_unregister, board); + if (ret < 0) { + platform_device_unregister(board); + return ret; + } + + return 0; +} + +static int avs_register_hda_boards(struct avs_dev *adev) +{ + struct hdac_bus *bus = &adev->base.core; + struct hdac_device *hdev; + int ret; + + if (!bus->num_codecs) { + dev_dbg(adev->dev, "no HDA endpoints present\n"); + return 0; + } + + list_for_each_entry(hdev, &bus->codec_list, list) { + struct hda_codec *codec; + + codec = dev_to_hda_codec(&hdev->dev); + + ret = avs_register_hda_board(adev, codec); + if (ret < 0) + dev_warn(adev->dev, "register hda-%08x failed: %d\n", + codec->core.vendor_id, ret); + } + + return 0; +} + +int avs_register_all_boards(struct avs_dev *adev) +{ + int ret; + + ret = avs_register_dmic_board(adev); + if (ret < 0) + dev_warn(adev->dev, "enumerate DMIC endpoints failed: %d\n", + ret); + + ret = avs_register_i2s_boards(adev); + if (ret < 0) + dev_warn(adev->dev, "enumerate I2S endpoints failed: %d\n", + ret); + + ret = avs_register_hda_boards(adev); + if (ret < 0) + dev_warn(adev->dev, "enumerate HDA endpoints failed: %d\n", + ret); + + return 0; +} + +void avs_unregister_all_boards(struct avs_dev *adev) +{ + snd_soc_unregister_component(adev->dev); +} diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig new file mode 100644 index 000000000000..4d68e3ef992b --- /dev/null +++ b/sound/soc/intel/avs/boards/Kconfig @@ -0,0 +1,121 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "Intel AVS Machine drivers" + depends on SND_SOC_INTEL_AVS + +comment "Available DSP configurations" + +config SND_SOC_INTEL_AVS_MACH_DA7219 + tristate "da7219 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_DA7219 + help + This adds support for AVS with DA7219 I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_DMIC + tristate "DMIC generic board" + select SND_SOC_DMIC + help + This adds support for AVS with Digital Mic array configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_HDAUDIO + tristate "HD-Audio generic board" + select SND_SOC_HDA + help + This adds support for AVS with HDAudio codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_I2S_TEST + tristate "I2S test board" + help + This adds support for I2S test-board which can be used to verify + transfer over I2S interface with SSP loopback scenarios. + +config SND_SOC_INTEL_AVS_MACH_MAX98357A + tristate "max98357A I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_MAX98357A + help + This adds support for AVS with MAX98357A I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_MAX98373 + tristate "max98373 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_MAX98373 + help + This adds support for AVS with MAX98373 I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_NAU8825 + tristate "nau8825 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_NAU8825 + help + This adds support for ASoC machine driver with NAU8825 I2S audio codec. + It is meant to be used with AVS driver. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT274 + tristate "rt274 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT274 + help + This adds support for ASoC machine driver with RT274 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT286 + tristate "rt286 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT286 + help + This adds support for ASoC machine driver with RT286 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT298 + tristate "rt298 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT298 + help + This adds support for ASoC machine driver with RT298 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT5682 + tristate "rt5682 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT5682_I2C + help + This adds support for ASoC machine driver with RT5682 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_SSM4567 + tristate "ssm4567 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_SSM4567 + help + This adds support for ASoC machine driver with SSM4567 I2S audio codec. + It is meant to be used with AVS driver. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +endmenu diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile new file mode 100644 index 000000000000..bc75376d58c2 --- /dev/null +++ b/sound/soc/intel/avs/boards/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only + +snd-soc-avs-da7219-objs := da7219.o +snd-soc-avs-dmic-objs := dmic.o +snd-soc-avs-hdaudio-objs := hdaudio.o +snd-soc-avs-i2s-test-objs := i2s_test.o +snd-soc-avs-max98357a-objs := max98357a.o +snd-soc-avs-max98373-objs := max98373.o +snd-soc-avs-nau8825-objs := nau8825.o +snd-soc-avs-rt274-objs := rt274.o +snd-soc-avs-rt286-objs := rt286.o +snd-soc-avs-rt298-objs := rt298.o +snd-soc-avs-rt5682-objs := rt5682.o +snd-soc-avs-ssm4567-objs := ssm4567.o + +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DA7219) += snd-soc-avs-da7219.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_I2S_TEST) += snd-soc-avs-i2s-test.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98357A) += snd-soc-avs-max98357a.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98373) += snd-soc-avs-max98373.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_NAU8825) += snd-soc-avs-nau8825.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT298) += snd-soc-avs-rt298.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5682) += snd-soc-avs-rt5682.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSM4567) += snd-soc-avs-ssm4567.o diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c new file mode 100644 index 000000000000..02ae542ad779 --- /dev/null +++ b/sound/soc/intel/avs/boards/da7219.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <uapi/linux/input-event-codes.h> +#include "../../../codecs/da7219.h" +#include "../../../codecs/da7219-aad.h" + +#define DA7219_DAI_NAME "da7219-hifi" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret = 0; + + codec_dai = snd_soc_card_get_codec_dai(card, DA7219_DAI_NAME); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found. Unable to set/unset codec pll\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_MCLK, 0, 0); + if (ret) + dev_err(card->dev, "failed to stop PLL: %d\n", ret); + } else if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL_SRM, + 0, DA7219_PLL_FREQ_OUT_98304); + if (ret) + dev_err(card->dev, "failed to start PLL: %d\n", ret); + } + + return ret; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + {"MIC", NULL, "Headset Mic"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static int avs_da7219_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_card *card = runtime->card; + struct snd_soc_jack *jack; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + int clk_freq; + int ret; + + jack = snd_soc_card_get_drvdata(card); + clk_freq = 19200000; + + ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, clk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3 | SND_JACK_LINEOUT, jack); + if (ret) { + dev_err(card->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + da7219_aad_jack_det(component, jack); + + return 0; +} + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-DLGS7219:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, DA7219_DAI_NAME); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_da7219_codec_init; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 2; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "Capture"); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_card_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +static int avs_da7219_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + card->name = "avs_da7219"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_da7219_driver = { + .probe = avs_da7219_probe, + .driver = { + .name = "avs_da7219", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_da7219_driver); + +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_da7219"); diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c new file mode 100644 index 000000000000..90a921638572 --- /dev/null +++ b/sound/soc/intel/avs/boards/dmic.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/device.h> +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> + +SND_SOC_DAILINK_DEF(dmic_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin"))); +SND_SOC_DAILINK_DEF(dmic_wov_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC WoV Pin"))); +SND_SOC_DAILINK_DEF(dmic_codec, DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); +/* Name overridden on probe */ +SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM(""))); + +static struct snd_soc_dai_link card_dai_links[] = { + /* Back ends */ + { + .name = "DMIC", + .id = 0, + .dpcm_capture = 1, + .nonatomic = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform), + }, + { + .name = "DMIC WoV", + .id = 1, + .dpcm_capture = 1, + .nonatomic = 1, + .no_pcm = 1, + .ignore_suspend = 1, + SND_SOC_DAILINK_REG(dmic_wov_pin, dmic_codec, platform), + }, +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route card_routes[] = { + {"DMic", NULL, "SoC DMIC"}, + {"DMIC Rx", NULL, "Capture"}, + {"DMIC WoV Rx", NULL, "Capture"}, +}; + +static int avs_dmic_probe(struct platform_device *pdev) +{ + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ret; + + mach = dev_get_platdata(dev); + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = "avs_dmic"; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = card_dai_links; + card->num_links = ARRAY_SIZE(card_dai_links); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_routes; + card->num_dapm_routes = ARRAY_SIZE(card_routes); + card->fully_routed = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, mach->mach_params.platform); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_dmic_driver = { + .probe = avs_dmic_probe, + .driver = { + .name = "avs_dmic", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_dmic_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_dmic"); diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c new file mode 100644 index 000000000000..073663ba140d --- /dev/null +++ b/sound/soc/intel/avs/boards/hdaudio.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/platform_device.h> +#include <sound/hda_codec.h> +#include <sound/hda_i915.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/hda.h" + +static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, + const char *platform_name, struct snd_soc_dai_link **links) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + struct hda_pcm *pcm; + const char *cname = dev_name(&codec->core.dev); + int i; + + dl = devm_kcalloc(dev, pcm_count, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + pcm = list_first_entry(&codec->pcm_list_head, struct hda_pcm, list); + + for (i = 0; i < pcm_count; i++, pcm = list_next_entry(pcm, list)) { + dl[i].name = devm_kasprintf(dev, GFP_KERNEL, "%s link%d", cname, i); + if (!dl[i].name) + return -ENOMEM; + + dl[i].id = i; + dl[i].nonatomic = 1; + dl[i].no_pcm = 1; + dl[i].dpcm_playback = 1; + dl[i].dpcm_capture = 1; + dl[i].platforms = platform; + dl[i].num_platforms = 1; + dl[i].ignore_pmdown_time = 1; + + dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + if (!dl[i].codecs || !dl[i].cpus) + return -ENOMEM; + + dl[i].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "%s-cpu%d", cname, i); + if (!dl[i].cpus->dai_name) + return -ENOMEM; + + dl[i].codecs->name = devm_kstrdup(dev, cname, GFP_KERNEL); + dl[i].codecs->dai_name = pcm->name; + dl[i].num_codecs = 1; + dl[i].num_cpus = 1; + } + + *links = dl; + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, struct hda_codec *codec, int pcm_count, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + struct hda_pcm *pcm; + const char *cname = dev_name(&codec->core.dev); + int i, n = 0; + + /* at max twice the number of pcms */ + dr = devm_kcalloc(dev, pcm_count * 2, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + pcm = list_first_entry(&codec->pcm_list_head, struct hda_pcm, list); + + for (i = 0; i < pcm_count; i++, pcm = list_next_entry(pcm, list)) { + struct hda_pcm_stream *stream; + int dir; + + dir = SNDRV_PCM_STREAM_PLAYBACK; + stream = &pcm->stream[dir]; + if (!stream->substreams) + goto capture_routes; + + dr[n].sink = devm_kasprintf(dev, GFP_KERNEL, "%s %s", pcm->name, + snd_pcm_direction_name(dir)); + dr[n].source = devm_kasprintf(dev, GFP_KERNEL, "%s-cpu%d Tx", cname, i); + if (!dr[n].sink || !dr[n].source) + return -ENOMEM; + n++; + +capture_routes: + dir = SNDRV_PCM_STREAM_CAPTURE; + stream = &pcm->stream[dir]; + if (!stream->substreams) + continue; + + dr[n].sink = devm_kasprintf(dev, GFP_KERNEL, "%s-cpu%d Rx", cname, i); + dr[n].source = devm_kasprintf(dev, GFP_KERNEL, "%s %s", pcm->name, + snd_pcm_direction_name(dir)); + if (!dr[n].sink || !dr[n].source) + return -ENOMEM; + n++; + } + + *routes = dr; + *num_routes = n; + return 0; +} + +/* Should be aligned with SectionPCM's name from topology */ +#define FEDAI_NAME_PREFIX "HDMI" + +static struct snd_pcm * +avs_card_hdmi_pcm_at(struct snd_soc_card *card, int hdmi_idx) +{ + struct snd_soc_pcm_runtime *rtd; + int dir = SNDRV_PCM_STREAM_PLAYBACK; + + for_each_card_rtds(card, rtd) { + struct snd_pcm *spcm; + int ret, n; + + spcm = rtd->pcm ? rtd->pcm->streams[dir].pcm : NULL; + if (!spcm || !strstr(spcm->id, FEDAI_NAME_PREFIX)) + continue; + + ret = sscanf(spcm->id, FEDAI_NAME_PREFIX "%d", &n); + if (ret != 1) + continue; + if (n == hdmi_idx) + return rtd->pcm; + } + + return NULL; +} + +static int avs_card_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev); + struct hda_codec *codec = mach->pdata; + struct hda_pcm *hpcm; + /* Topology pcm indexing is 1-based */ + int i = 1; + + list_for_each_entry(hpcm, &codec->pcm_list_head, list) { + struct snd_pcm *spcm; + + spcm = avs_card_hdmi_pcm_at(card, i); + if (spcm) { + hpcm->pcm = spcm; + hpcm->device = spcm->device; + dev_info(card->dev, "%s: mapping HDMI converter %d to PCM %d (%p)\n", + __func__, i, hpcm->device, spcm); + } else { + hpcm->pcm = NULL; + hpcm->device = SNDRV_PCM_INVALID_DEVICE; + dev_warn(card->dev, "%s: no PCM in topology for HDMI converter %d\n", + __func__, i); + } + i++; + } + + return hda_codec_probe_complete(codec); +} + +static int avs_probing_link_init(struct snd_soc_pcm_runtime *rtm) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_acpi_mach *mach; + struct snd_soc_dai_link *links = NULL; + struct snd_soc_card *card = rtm->card; + struct hda_codec *codec; + struct hda_pcm *pcm; + int ret, n, pcm_count = 0; + + mach = dev_get_platdata(card->dev); + codec = mach->pdata; + + if (list_empty(&codec->pcm_list_head)) + return -EINVAL; + list_for_each_entry(pcm, &codec->pcm_list_head, list) + pcm_count++; + + ret = avs_create_dai_links(card->dev, codec, pcm_count, mach->mach_params.platform, &links); + if (ret < 0) { + dev_err(card->dev, "create links failed: %d\n", ret); + return ret; + } + + for (n = 0; n < pcm_count; n++) { + ret = snd_soc_add_pcm_runtime(card, &links[n]); + if (ret < 0) { + dev_err(card->dev, "add links failed: %d\n", ret); + return ret; + } + } + + ret = avs_create_dapm_routes(card->dev, codec, pcm_count, &routes, &n); + if (ret < 0) { + dev_err(card->dev, "create routes failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, routes, n); + if (ret < 0) { + dev_err(card->dev, "add routes failed: %d\n", ret); + return ret; + } + + return 0; +} + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link probing_link = { + .name = "probing-LINK", + .id = -1, + .nonatomic = 1, + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .cpus = dummy, + .num_cpus = ARRAY_SIZE(dummy), + .init = avs_probing_link_init, +}; + +static int avs_hdaudio_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *binder; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + struct hda_codec *codec; + + mach = dev_get_platdata(dev); + codec = mach->pdata; + + /* codec may be unloaded before card's probe() fires */ + if (!device_is_registered(&codec->core.dev)) + return -ENODEV; + + binder = devm_kmemdup(dev, &probing_link, sizeof(probing_link), GFP_KERNEL); + if (!binder) + return -ENOMEM; + + binder->platforms = devm_kzalloc(dev, sizeof(*binder->platforms), GFP_KERNEL); + binder->codecs = devm_kzalloc(dev, sizeof(*binder->codecs), GFP_KERNEL); + if (!binder->platforms || !binder->codecs) + return -ENOMEM; + + binder->codecs->name = devm_kstrdup(dev, dev_name(&codec->core.dev), GFP_KERNEL); + if (!binder->codecs->name) + return -ENOMEM; + + binder->platforms->name = mach->mach_params.platform; + binder->num_platforms = 1; + binder->codecs->dai_name = "codec-probing-DAI"; + binder->num_codecs = 1; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = binder->codecs->name; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = binder; + card->num_links = 1; + card->fully_routed = true; + if (hda_codec_is_display(codec)) + card->late_probe = avs_card_late_probe; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_hdaudio_driver = { + .probe = avs_hdaudio_probe, + .driver = { + .name = "avs_hdaudio", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_hdaudio_driver) + +MODULE_DESCRIPTION("Intel HD-Audio machine driver"); +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_hdaudio"); diff --git a/sound/soc/intel/avs/boards/i2s_test.c b/sound/soc/intel/avs/boards/i2s_test.c new file mode 100644 index 000000000000..8f0fd87bc866 --- /dev/null +++ b/sound/soc/intel/avs/boards/i2s_test.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "snd-soc-dummy"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "snd-soc-dummy-dai"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_dr = 2; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + dr[0].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%dpb", ssp_port); + dr[0].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[0].sink || !dr[0].source) + return -ENOMEM; + + dr[1].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[1].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%dcp", ssp_port); + if (!dr[1].sink || !dr[1].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_create_dapm_widgets(struct device *dev, int ssp_port, + struct snd_soc_dapm_widget **widgets, int *num_widgets) +{ + struct snd_soc_dapm_widget *dw; + const int num_dw = 2; + + dw = devm_kcalloc(dev, num_dw, sizeof(*dw), GFP_KERNEL); + if (!dw) + return -ENOMEM; + + dw[0].id = snd_soc_dapm_hp; + dw[0].reg = SND_SOC_NOPM; + dw[0].name = devm_kasprintf(dev, GFP_KERNEL, "ssp%dpb", ssp_port); + if (!dw[0].name) + return -ENOMEM; + + dw[1].id = snd_soc_dapm_mic; + dw[1].reg = SND_SOC_NOPM; + dw[1].name = devm_kasprintf(dev, GFP_KERNEL, "ssp%dcp", ssp_port); + if (!dw[1].name) + return -ENOMEM; + + *widgets = dw; + *num_widgets = num_dw; + + return 0; +} + +static int avs_i2s_test_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_widget *widgets; + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, num_widgets; + int ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = devm_kasprintf(dev, GFP_KERNEL, "ssp%d-loopback", ssp_port); + if (!card->name) + return -ENOMEM; + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d\n", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d\n", ret); + return ret; + } + + ret = avs_create_dapm_widgets(dev, ssp_port, &widgets, &num_widgets); + if (ret) { + dev_err(dev, "Failed to create dapm widgets: %d\n", ret); + return ret; + } + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->dapm_widgets = widgets; + card->num_dapm_widgets = num_widgets; + card->fully_routed = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_i2s_test_driver = { + .probe = avs_i2s_test_probe, + .driver = { + .name = "avs_i2s_test", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_i2s_test_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_i2s_test"); diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c new file mode 100644 index 000000000000..921f42caf7e0 --- /dev/null +++ b/sound/soc/intel/avs/boards/max98357a.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Spk", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Spk", NULL, "Speaker" }, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "MX98357A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "HiFi"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 1; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "HiFi Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_max98357a_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = "avs_max98357a"; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_max98357a_driver = { + .probe = avs_max98357a_probe, + .driver = { + .name = "avs_max98357a", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_max98357a_driver) + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_max98357a"); diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c new file mode 100644 index 000000000000..0fa8f5606385 --- /dev/null +++ b/sound/soc/intel/avs/boards/max98373.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> + +#define MAX98373_DEV0_NAME "i2c-MX98373:00" +#define MAX98373_DEV1_NAME "i2c-MX98373:01" +#define MAX98373_CODEC_NAME "max98373-aif1" + +static struct snd_soc_codec_conf card_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF(MAX98373_DEV0_NAME), + .name_prefix = "Right", + }, + { + .dlc = COMP_CODEC_CONF(MAX98373_DEV1_NAME), + .name_prefix = "Left", + }, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Left Spk", NULL, "Left BE_OUT" }, + { "Right Spk", NULL, "Right BE_OUT" }, +}; + +static int +avs_max98373_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int avs_max98373_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + int ret, i; + + for_each_rtd_codec_dais(runtime, i, codec_dai) { + if (!strcmp(codec_dai->component->name, MAX98373_DEV0_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16); + if (ret < 0) { + dev_err(runtime->dev, "DEV0 TDM slot err:%d\n", ret); + return ret; + } + } + if (!strcmp(codec_dai->component->name, MAX98373_DEV1_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16); + if (ret < 0) { + dev_err(runtime->dev, "DEV1 TDM slot err:%d\n", ret); + return ret; + } + } + } + + return 0; +} + +static const struct snd_soc_ops avs_max98373_ops = { + .hw_params = avs_max98373_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs) * 2, GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs[0].name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_DEV0_NAME); + dl->codecs[0].dai_name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_CODEC_NAME); + dl->codecs[1].name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_DEV1_NAME); + dl->codecs[1].dai_name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_CODEC_NAME); + if (!dl->cpus->dai_name || !dl->codecs[0].name || !dl->codecs[0].dai_name || + !dl->codecs[1].name || !dl->codecs[1].dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 2; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->be_hw_params_fixup = avs_max98373_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + dl->ignore_pmdown_time = 1; + dl->ops = &avs_max98373_ops; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 2; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Left HiFi Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Right HiFi Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_max98373_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = "avs_max98373"; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->codec_conf = card_codec_conf; + card->num_configs = ARRAY_SIZE(card_codec_conf); + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_max98373_driver = { + .probe = avs_max98373_probe, + .driver = { + .name = "avs_max98373", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_max98373_driver) + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_max98373"); diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c new file mode 100644 index 000000000000..f76909e9f990 --- /dev/null +++ b/sound/soc/intel/avs/boards/nau8825.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/nau8825.h" + +#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" + +static int +avs_nau8825_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EINVAL; + } + + if (!SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return ret; + } + } + + return 0; +} + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, avs_nau8825_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + { "MIC", NULL, "Headset Mic" }, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_nau8825_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + /* + * 4 buttons here map to the google Reference headset. + * The use of these buttons can be decided by the user space. + */ + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, + jack, pins, num_pins); + if (ret) + return ret; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + //snd_soc_component_set_jack(component, jack, NULL); + // TODO: Fix nau8825 codec to use .set_jack, like everyone else + nau8825_enable_jack_detect(component, jack); + + return 0; +} + +static int +avs_nau8825_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_nau8825_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtm = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtm, 0); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set FS clock %d\n", ret); + break; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, runtime->rate, runtime->rate * 256); + if (ret < 0) + dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, runtime->rate, runtime->rate * 256); + if (ret < 0) + dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); + break; + } + + return ret; +} + + +static const struct snd_soc_ops avs_nau8825_ops = { + .trigger = avs_nau8825_trigger, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10508825:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, SKL_NUVOTON_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_nau8825_codec_init; + dl->be_hw_params_fixup = avs_nau8825_be_fixup; + dl->ops = &avs_nau8825_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 2; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "Capture"); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_card_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EINVAL; + } + + if (codec_dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK] && + codec_dai->playback_widget->active) + snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, 0, SND_SOC_CLOCK_IN); + + return avs_card_set_jack(card, jack); +} + +static int avs_nau8825_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + card->name = "avs_nau8825"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_nau8825_driver = { + .probe = avs_nau8825_probe, + .driver = { + .name = "avs_nau8825", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_nau8825_driver) + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_nau8825"); diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c new file mode 100644 index 000000000000..afef5a3ca60b --- /dev/null +++ b/sound/soc/intel/avs/boards/rt274.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt274.h" + +#define AVS_RT274_FREQ_OUT 24000000 +#define AVS_RT274_BE_FIXUP_RATE 48000 +#define RT274_CODEC_DAI "rt274-aif1" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), +}; + +static int +avs_rt274_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, RT274_CODEC_DAI); + if (!codec_dai) + return -EINVAL; + + /* Codec needs clock for Jack detection and button press */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT274_SCLK_S_PLL2, AVS_RT274_FREQ_OUT, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "set codec sysclk failed: %d\n", ret); + return ret; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + int ratio = 100; + + snd_soc_dai_set_bclk_ratio(codec_dai, ratio); + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT274_PLL2_S_BCLK, + AVS_RT274_BE_FIXUP_RATE * ratio, AVS_RT274_FREQ_OUT); + if (ret) { + dev_err(codec_dai->dev, "failed to enable PLL2: %d\n", ret); + return ret; + } + } + + return 0; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, avs_rt274_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC", NULL, "Mic Jack"}, + + {"Headphone Jack", NULL, "Platform Clock"}, + {"MIC", NULL, "Platform Clock"}, +}; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt274_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET, jack, pins, num_pins); + if (ret) + return ret; + + snd_soc_component_set_jack(component, jack, NULL); + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { + dev_err(card->dev, "can't set codec pcm format %d\n", ret); + return ret; + } + + card->dapm.idle_bias_off = true; + + return 0; +} + +static int avs_rt274_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = AVS_RT274_BE_FIXUP_RATE; + channels->min = channels->max = 2; + + /* set SSPN to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT34C2:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "rt274-aif1"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_rt274_codec_init; + dl->be_hw_params_fixup = avs_rt274_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 2; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Capture"); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_card_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +static int avs_rt274_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + card->name = "avs_rt274"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_rt274_driver = { + .probe = avs_rt274_probe, + .driver = { + .name = "avs_rt274", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_rt274_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_rt274"); diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c new file mode 100644 index 000000000000..e51d4e181274 --- /dev/null +++ b/sound/soc/intel/avs/boards/rt286.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt286.h" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC1", NULL, "Mic Jack"}, + + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, +}; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt286_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0, jack, + pins, num_pins); + if (ret) + return ret; + + snd_soc_component_set_jack(component, jack, NULL); + + return 0; +} + +static int avs_rt286_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int +avs_rt286_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = substream->private_data; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(runtime->dev, "Set codec sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt286_ops = { + .hw_params = avs_rt286_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "rt286-aif1"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_rt286_codec_init; + dl->be_hw_params_fixup = avs_rt286_be_fixup; + dl->ops = &avs_rt286_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 2; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Capture"); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_card_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +static int avs_rt286_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + card->name = "avs_rt286"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_rt286_driver = { + .probe = avs_rt286_probe, + .driver = { + .name = "avs_rt286", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_rt286_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_rt286"); diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c new file mode 100644 index 000000000000..b28d36872dcb --- /dev/null +++ b/sound/soc/intel/avs/boards/rt298.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt298.h" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC1", NULL, "Mic Jack"}, + + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, +}; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt298_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0, jack, + pins, num_pins); + if (ret) + return ret; + + snd_soc_component_set_jack(component, jack, NULL); + + return 0; +} + +static int avs_rt298_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int +avs_rt298_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT298_SCLK_S_PLL, 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "Set codec sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt298_ops = { + .hw_params = avs_rt298_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "rt298-aif1"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_rt298_codec_init; + dl->be_hw_params_fixup = avs_rt298_be_fixup; + dl->ops = &avs_rt298_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 2; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Capture"); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_card_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +static int avs_rt298_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + card->name = "avs_rt298"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_rt298_driver = { + .probe = avs_rt298_probe, + .driver = { + .name = "avs_rt298", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_rt298_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_rt298"); diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c new file mode 100644 index 000000000000..01f9b9f0c12b --- /dev/null +++ b/sound/soc/intel/avs/boards/rt5682.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/clk.h> +#include <linux/dmi.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rt5682.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../common/soc-intel-quirks.h" +#include "../../../codecs/rt5682.h" + +#define AVS_RT5682_SSP_CODEC(quirk) ((quirk) & GENMASK(2, 0)) +#define AVS_RT5682_SSP_CODEC_MASK (GENMASK(2, 0)) +#define AVS_RT5682_MCLK_EN BIT(3) +#define AVS_RT5682_MCLK_24MHZ BIT(4) + +/* Default: MCLK on, MCLK 19.2M, SSP0 */ +static unsigned long avs_rt5682_quirk = AVS_RT5682_MCLK_EN | AVS_RT5682_SSP_CODEC(0); + +static int avs_rt5682_quirk_cb(const struct dmi_system_id *id) +{ + avs_rt5682_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id avs_rt5682_quirk_table[] = { + { + .callback = avs_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "WhiskeyLake Client"), + }, + .driver_data = (void *)(AVS_RT5682_MCLK_EN | + AVS_RT5682_MCLK_24MHZ | + AVS_RT5682_SSP_CODEC(1)), + }, + { + .callback = avs_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Ice Lake Client"), + }, + .driver_data = (void *)(AVS_RT5682_MCLK_EN | + AVS_RT5682_SSP_CODEC(0)), + }, + {} +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* other jacks */ + { "IN1P", NULL, "Headset Mic" }, +}; + +static int avs_rt5682_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int ret; + + jack = snd_soc_card_get_drvdata(card); + + /* Need to enable ASRC function for 24MHz mclk rate */ + if ((avs_rt5682_quirk & AVS_RT5682_MCLK_EN) && + (avs_rt5682_quirk & AVS_RT5682_MCLK_24MHZ)) { + rt5682_sel_asrc_clk_src(component, RT5682_DA_STEREO1_FILTER | + RT5682_AD_STEREO1_FILTER, RT5682_CLK_SEL_I2S1_ASRC); + } + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, jack); + if (ret) { + dev_err(card->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + if (ret) { + dev_err(card->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return 0; +}; + +static int +avs_rt5682_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + int clk_id, clk_freq; + int pll_out, ret; + + if (avs_rt5682_quirk & AVS_RT5682_MCLK_EN) { + clk_id = RT5682_PLL1_S_MCLK; + if (avs_rt5682_quirk & AVS_RT5682_MCLK_24MHZ) + clk_freq = 24000000; + else + clk_freq = 19200000; + } else { + clk_id = RT5682_PLL1_S_BCLK1; + clk_freq = params_rate(params) * 50; + } + + pll_out = params_rate(params) * 512; + + ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out); + if (ret < 0) + dev_err(runtime->dev, "snd_soc_dai_set_pll err = %d\n", ret); + + /* Configure sysclk for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(runtime->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + /* slot_width should equal or large than data length, set them be the same */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0, 0x0, 2, params_width(params)); + if (ret < 0) { + dev_err(runtime->dev, "set TDM slot err:%d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops avs_rt5682_ops = { + .hw_params = avs_rt5682_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10EC5682:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "rt5682-aif1"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->init = avs_rt5682_codec_init; + dl->ops = &avs_rt5682_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 2; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Capture"); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_card_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +static int avs_rt5682_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + if (pdev->id_entry && pdev->id_entry->driver_data) + avs_rt5682_quirk = (unsigned long)pdev->id_entry->driver_data; + + dmi_check_system(avs_rt5682_quirk_table); + dev_dbg(dev, "avs_rt5682_quirk = %lx\n", avs_rt5682_quirk); + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + card->name = "avs_rt5682"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_rt5682_driver = { + .probe = avs_rt5682_probe, + .driver = { + .name = "avs_rt5682", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_rt5682_driver) + +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_rt5682"); diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c new file mode 100644 index 000000000000..9f84c8ab3447 --- /dev/null +++ b/sound/soc/intel/avs/boards/ssm4567.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/nau8825.h" + +#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" +#define SKL_SSM_CODEC_DAI "ssm4567-hifi" + +static struct snd_soc_codec_conf card_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF("i2c-INT343B:00"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("i2c-INT343B:01"), + .name_prefix = "Right", + }, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Speaker"), + SOC_DAPM_PIN_SWITCH("Right Speaker"), +}; + +static int +platform_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EINVAL; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_MCLK, 24000000, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(card->dev, "set sysclk err = %d\n", ret); + } else { + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(card->dev, "set sysclk err = %d\n", ret); + } + + return ret; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Left Speaker", NULL), + SND_SOC_DAPM_SPK("Right Speaker", NULL), + SND_SOC_DAPM_SPK("DP1", NULL), + SND_SOC_DAPM_SPK("DP2", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + {"Left Speaker", NULL, "Left OUT"}, + {"Right Speaker", NULL, "Right OUT"}, +}; + +static int avs_ssm4567_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + + /* Slot 1 for left */ + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(runtime, 0), 0x01, 0x01, 2, 48); + if (ret < 0) + return ret; + + /* Slot 2 for right */ + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(runtime, 1), 0x02, 0x02, 2, 48); + if (ret < 0) + return ret; + + return 0; +} + +static int +avs_ssm4567_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs) * 2, GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs[0].name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343B:00"); + dl->codecs[0].dai_name = devm_kasprintf(dev, GFP_KERNEL, "ssm4567-hifi"); + dl->codecs[1].name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343B:01"); + dl->codecs[1].dai_name = devm_kasprintf(dev, GFP_KERNEL, "ssm4567-hifi"); + if (!dl->cpus->dai_name || !dl->codecs[0].name || !dl->codecs[0].dai_name || + !dl->codecs[1].name || !dl->codecs[1].dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 2; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_ssm4567_codec_init; + dl->be_hw_params_fixup = avs_ssm4567_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + dl->ignore_pmdown_time = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_create_dapm_routes(struct device *dev, int ssp_port, + struct snd_soc_dapm_route **routes, int *num_routes) +{ + struct snd_soc_dapm_route *dr; + const int num_base = ARRAY_SIZE(card_base_routes); + const int num_dr = num_base + 4; + int idx; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + memcpy(dr, card_base_routes, num_base * sizeof(*dr)); + + idx = num_base; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Left Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Right Playback"); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "Left Capture Sense"); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + idx++; + dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "Right Capture Sense"); + if (!dr[idx].sink || !dr[idx].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_ssm4567_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_route *routes; + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + const char *pname; + int num_routes, ssp_port, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + ssp_port = __ffs(mach->mach_params.i2s_link_mask); + + ret = avs_create_dai_link(dev, pname, ssp_port, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d", ret); + return ret; + } + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = "avs_ssm4567-adi"; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->codec_conf = card_codec_conf; + card->num_configs = ARRAY_SIZE(card_codec_conf); + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->fully_routed = true; + card->disable_route_checks = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_ssm4567_driver = { + .probe = avs_ssm4567_probe, + .driver = { + .name = "avs_ssm4567", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_ssm4567_driver) + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_ssm4567"); diff --git a/sound/soc/intel/avs/cldma.c b/sound/soc/intel/avs/cldma.c new file mode 100644 index 000000000000..d7a9390b5e48 --- /dev/null +++ b/sound/soc/intel/avs/cldma.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/pci.h> +#include <sound/hda_register.h> +#include <sound/hdaudio_ext.h> +#include "cldma.h" +#include "registers.h" + +/* Stream Registers */ +#define AZX_CL_SD_BASE 0x80 +#define AZX_SD_CTL_STRM_MASK GENMASK(23, 20) +#define AZX_SD_CTL_STRM(s) (((s)->stream_tag << 20) & AZX_SD_CTL_STRM_MASK) +#define AZX_SD_BDLPL_BDLPLBA_MASK GENMASK(31, 7) +#define AZX_SD_BDLPL_BDLPLBA(lb) ((lb) & AZX_SD_BDLPL_BDLPLBA_MASK) + +/* Software Position Based FIFO Capability Registers */ +#define AZX_CL_SPBFCS 0x20 +#define AZX_REG_CL_SPBFCTL (AZX_CL_SPBFCS + 0x4) +#define AZX_REG_CL_SD_SPIB (AZX_CL_SPBFCS + 0x8) + +#define AVS_CL_OP_INTERVAL_US 3 +#define AVS_CL_OP_TIMEOUT_US 300 +#define AVS_CL_IOC_TIMEOUT_MS 300 +#define AVS_CL_STREAM_INDEX 0 + +struct hda_cldma { + struct device *dev; + struct hdac_bus *bus; + void __iomem *dsp_ba; + + unsigned int buffer_size; + unsigned int num_periods; + unsigned int stream_tag; + void __iomem *sd_addr; + + struct snd_dma_buffer dmab_data; + struct snd_dma_buffer dmab_bdl; + struct delayed_work memcpy_work; + struct completion completion; + + /* runtime */ + void *position; + unsigned int remaining; + unsigned int sd_status; +}; + +static void cldma_memcpy_work(struct work_struct *work); + +struct hda_cldma code_loader = { + .stream_tag = AVS_CL_STREAM_INDEX + 1, + .memcpy_work = __DELAYED_WORK_INITIALIZER(code_loader.memcpy_work, cldma_memcpy_work, 0), + .completion = COMPLETION_INITIALIZER(code_loader.completion), +}; + +void hda_cldma_fill(struct hda_cldma *cl) +{ + unsigned int size, offset; + + if (cl->remaining > cl->buffer_size) + size = cl->buffer_size; + else + size = cl->remaining; + + offset = snd_hdac_stream_readl(cl, CL_SD_SPIB); + if (offset + size > cl->buffer_size) { + unsigned int ss; + + ss = cl->buffer_size - offset; + memcpy(cl->dmab_data.area + offset, cl->position, ss); + offset = 0; + size -= ss; + cl->position += ss; + cl->remaining -= ss; + } + + memcpy(cl->dmab_data.area + offset, cl->position, size); + cl->position += size; + cl->remaining -= size; + + snd_hdac_stream_writel(cl, CL_SD_SPIB, offset + size); +} + +static void cldma_memcpy_work(struct work_struct *work) +{ + struct hda_cldma *cl = container_of(work, struct hda_cldma, memcpy_work.work); + int ret; + + ret = hda_cldma_start(cl); + if (ret < 0) { + dev_err(cl->dev, "cldma set RUN failed: %d\n", ret); + return; + } + + while (true) { + ret = wait_for_completion_timeout(&cl->completion, + msecs_to_jiffies(AVS_CL_IOC_TIMEOUT_MS)); + if (!ret) { + dev_err(cl->dev, "cldma IOC timeout\n"); + break; + } + + if (!(cl->sd_status & SD_INT_COMPLETE)) { + dev_err(cl->dev, "cldma transfer error, SD status: 0x%08x\n", + cl->sd_status); + break; + } + + if (!cl->remaining) + break; + + reinit_completion(&cl->completion); + hda_cldma_fill(cl); + /* enable CLDMA interrupt */ + snd_hdac_adsp_updatel(cl, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_CLDMA, + AVS_ADSP_ADSPIC_CLDMA); + } +} + +void hda_cldma_transfer(struct hda_cldma *cl, unsigned long start_delay) +{ + if (!cl->remaining) + return; + + reinit_completion(&cl->completion); + /* fill buffer with the first chunk before scheduling run */ + hda_cldma_fill(cl); + + schedule_delayed_work(&cl->memcpy_work, start_delay); +} + +int hda_cldma_start(struct hda_cldma *cl) +{ + unsigned int reg; + + /* enable interrupts */ + snd_hdac_adsp_updatel(cl, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_CLDMA, + AVS_ADSP_ADSPIC_CLDMA); + snd_hdac_stream_updateb(cl, SD_CTL, SD_INT_MASK | SD_CTL_DMA_START, + SD_INT_MASK | SD_CTL_DMA_START); + + /* await DMA engine start */ + return snd_hdac_stream_readb_poll(cl, SD_CTL, reg, reg & SD_CTL_DMA_START, + AVS_CL_OP_INTERVAL_US, AVS_CL_OP_TIMEOUT_US); +} + +int hda_cldma_stop(struct hda_cldma *cl) +{ + unsigned int reg; + int ret; + + /* disable interrupts */ + snd_hdac_adsp_updatel(cl, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_CLDMA, 0); + snd_hdac_stream_updateb(cl, SD_CTL, SD_INT_MASK | SD_CTL_DMA_START, 0); + + /* await DMA engine stop */ + ret = snd_hdac_stream_readb_poll(cl, SD_CTL, reg, !(reg & SD_CTL_DMA_START), + AVS_CL_OP_INTERVAL_US, AVS_CL_OP_TIMEOUT_US); + cancel_delayed_work_sync(&cl->memcpy_work); + + return ret; +} + +int hda_cldma_reset(struct hda_cldma *cl) +{ + unsigned int reg; + int ret; + + ret = hda_cldma_stop(cl); + if (ret < 0) { + dev_err(cl->dev, "cldma stop failed: %d\n", ret); + return ret; + } + + snd_hdac_stream_updateb(cl, SD_CTL, SD_CTL_STREAM_RESET, SD_CTL_STREAM_RESET); + ret = snd_hdac_stream_readb_poll(cl, SD_CTL, reg, (reg & SD_CTL_STREAM_RESET), + AVS_CL_OP_INTERVAL_US, AVS_CL_OP_TIMEOUT_US); + if (ret < 0) { + dev_err(cl->dev, "cldma set SRST failed: %d\n", ret); + return ret; + } + + snd_hdac_stream_updateb(cl, SD_CTL, SD_CTL_STREAM_RESET, 0); + ret = snd_hdac_stream_readb_poll(cl, SD_CTL, reg, !(reg & SD_CTL_STREAM_RESET), + AVS_CL_OP_INTERVAL_US, AVS_CL_OP_TIMEOUT_US); + if (ret < 0) { + dev_err(cl->dev, "cldma unset SRST failed: %d\n", ret); + return ret; + } + + return 0; +} + +void hda_cldma_set_data(struct hda_cldma *cl, void *data, unsigned int size) +{ + /* setup runtime */ + cl->position = data; + cl->remaining = size; +} + +static void cldma_setup_bdle(struct hda_cldma *cl, u32 bdle_size) +{ + struct snd_dma_buffer *dmab = &cl->dmab_data; + __le32 *bdl = (__le32 *)cl->dmab_bdl.area; + int remaining = cl->buffer_size; + int offset = 0; + + cl->num_periods = 0; + + while (remaining > 0) { + phys_addr_t addr; + int chunk; + + addr = snd_sgbuf_get_addr(dmab, offset); + bdl[0] = cpu_to_le32(lower_32_bits(addr)); + bdl[1] = cpu_to_le32(upper_32_bits(addr)); + chunk = snd_sgbuf_get_chunk_size(dmab, offset, bdle_size); + bdl[2] = cpu_to_le32(chunk); + + remaining -= chunk; + /* set IOC only for the last entry */ + bdl[3] = (remaining > 0) ? 0 : cpu_to_le32(0x01); + + bdl += 4; + offset += chunk; + cl->num_periods++; + } +} + +void hda_cldma_setup(struct hda_cldma *cl) +{ + dma_addr_t bdl_addr = cl->dmab_bdl.addr; + + cldma_setup_bdle(cl, cl->buffer_size / 2); + + snd_hdac_stream_writel(cl, SD_BDLPL, AZX_SD_BDLPL_BDLPLBA(lower_32_bits(bdl_addr))); + snd_hdac_stream_writel(cl, SD_BDLPU, upper_32_bits(bdl_addr)); + + snd_hdac_stream_writel(cl, SD_CBL, cl->buffer_size); + snd_hdac_stream_writeb(cl, SD_LVI, cl->num_periods - 1); + + snd_hdac_stream_updatel(cl, SD_CTL, AZX_SD_CTL_STRM_MASK, AZX_SD_CTL_STRM(cl)); + /* enable spib */ + snd_hdac_stream_writel(cl, CL_SPBFCTL, 1); +} + +static irqreturn_t cldma_irq_handler(int irq, void *dev_id) +{ + struct hda_cldma *cl = dev_id; + u32 adspis; + + adspis = snd_hdac_adsp_readl(cl, AVS_ADSP_REG_ADSPIS); + if (adspis == UINT_MAX) + return IRQ_NONE; + if (!(adspis & AVS_ADSP_ADSPIS_CLDMA)) + return IRQ_NONE; + + cl->sd_status = snd_hdac_stream_readb(cl, SD_STS); + dev_warn(cl->dev, "%s sd_status: 0x%08x\n", __func__, cl->sd_status); + + /* disable CLDMA interrupt */ + snd_hdac_adsp_updatel(cl, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_CLDMA, 0); + + complete(&cl->completion); + + return IRQ_HANDLED; +} + +int hda_cldma_init(struct hda_cldma *cl, struct hdac_bus *bus, void __iomem *dsp_ba, + unsigned int buffer_size) +{ + struct pci_dev *pci = to_pci_dev(bus->dev); + int ret; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, bus->dev, buffer_size, &cl->dmab_data); + if (ret < 0) + return ret; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, bus->dev, BDL_SIZE, &cl->dmab_bdl); + if (ret < 0) + goto alloc_err; + + cl->dev = bus->dev; + cl->bus = bus; + cl->dsp_ba = dsp_ba; + cl->buffer_size = buffer_size; + cl->sd_addr = dsp_ba + AZX_CL_SD_BASE; + + ret = pci_request_irq(pci, 0, cldma_irq_handler, NULL, cl, "CLDMA"); + if (ret < 0) { + dev_err(cl->dev, "Failed to request CLDMA IRQ handler: %d\n", ret); + goto req_err; + } + + return 0; + +req_err: + snd_dma_free_pages(&cl->dmab_bdl); +alloc_err: + snd_dma_free_pages(&cl->dmab_data); + + return ret; +} + +void hda_cldma_free(struct hda_cldma *cl) +{ + struct pci_dev *pci = to_pci_dev(cl->dev); + + pci_free_irq(pci, 0, cl); + snd_dma_free_pages(&cl->dmab_data); + snd_dma_free_pages(&cl->dmab_bdl); +} diff --git a/sound/soc/intel/avs/cldma.h b/sound/soc/intel/avs/cldma.h new file mode 100644 index 000000000000..754fcf9ee585 --- /dev/null +++ b/sound/soc/intel/avs/cldma.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation. All rights reserved. + * + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_CLDMA_H +#define __SOUND_SOC_INTEL_AVS_CLDMA_H + +#define AVS_CL_DEFAULT_BUFFER_SIZE (32 * PAGE_SIZE) + +struct hda_cldma; +extern struct hda_cldma code_loader; + +void hda_cldma_fill(struct hda_cldma *cl); +void hda_cldma_transfer(struct hda_cldma *cl, unsigned long start_delay); + +int hda_cldma_start(struct hda_cldma *cl); +int hda_cldma_stop(struct hda_cldma *cl); +int hda_cldma_reset(struct hda_cldma *cl); + +void hda_cldma_set_data(struct hda_cldma *cl, void *data, unsigned int size); +void hda_cldma_setup(struct hda_cldma *cl); +int hda_cldma_init(struct hda_cldma *cl, struct hdac_bus *bus, void __iomem *dsp_ba, + unsigned int buffer_size); +void hda_cldma_free(struct hda_cldma *cl); + +#endif diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c new file mode 100644 index 000000000000..bb0719c58ca4 --- /dev/null +++ b/sound/soc/intel/avs/core.c @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// +// Special thanks to: +// Krzysztof Hejmowski <krzysztof.hejmowski@intel.com> +// Michal Sienkiewicz <michal.sienkiewicz@intel.com> +// Filip Proborszcz +// +// for sharing Intel AudioDSP expertise and helping shape the very +// foundation of this driver +// + +#include <linux/module.h> +#include <linux/pci.h> +#include <sound/hda_codec.h> +#include <sound/hda_i915.h> +#include <sound/hda_register.h> +#include <sound/hdaudio.h> +#include <sound/hdaudio_ext.h> +#include <sound/intel-dsp-config.h> +#include <sound/intel-nhlt.h> +#include "../../codecs/hda.h" +#include "avs.h" +#include "cldma.h" + +static void +avs_hda_update_config_dword(struct hdac_bus *bus, u32 reg, u32 mask, u32 value) +{ + struct pci_dev *pci = to_pci_dev(bus->dev); + u32 data; + + pci_read_config_dword(pci, reg, &data); + data &= ~mask; + data |= (value & mask); + pci_write_config_dword(pci, reg, data); +} + +void avs_hda_power_gating_enable(struct avs_dev *adev, bool enable) +{ + u32 value; + + value = enable ? 0 : AZX_PGCTL_LSRMD_MASK; + avs_hda_update_config_dword(&adev->base.core, AZX_PCIREG_PGCTL, + AZX_PGCTL_LSRMD_MASK, value); +} + +static void avs_hdac_clock_gating_enable(struct hdac_bus *bus, bool enable) +{ + u32 value; + + value = enable ? AZX_CGCTL_MISCBDCGE_MASK : 0; + avs_hda_update_config_dword(bus, AZX_PCIREG_CGCTL, AZX_CGCTL_MISCBDCGE_MASK, value); +} + +void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable) +{ + avs_hdac_clock_gating_enable(&adev->base.core, enable); +} + +void avs_hda_l1sen_enable(struct avs_dev *adev, bool enable) +{ + u32 value; + + value = enable ? AZX_VS_EM2_L1SEN : 0; + snd_hdac_chip_updatel(&adev->base.core, VS_EM2, AZX_VS_EM2_L1SEN, value); +} + +static int avs_hdac_bus_init_streams(struct hdac_bus *bus) +{ + unsigned int cp_streams, pb_streams; + unsigned int gcap; + + gcap = snd_hdac_chip_readw(bus, GCAP); + cp_streams = (gcap >> 8) & 0x0F; + pb_streams = (gcap >> 12) & 0x0F; + bus->num_streams = cp_streams + pb_streams; + + snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); + snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); + + return snd_hdac_bus_alloc_stream_pages(bus); +} + +static bool avs_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) +{ + struct hdac_ext_link *hlink; + bool ret; + + avs_hdac_clock_gating_enable(bus, false); + ret = snd_hdac_bus_init_chip(bus, full_reset); + + /* Reset stream-to-link mapping */ + list_for_each_entry(hlink, &bus->hlink_list, list) + writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV); + + avs_hdac_clock_gating_enable(bus, true); + + /* Set DUM bit to address incorrect position reporting for capture + * streams. In order to do so, CTRL needs to be out of reset state + */ + snd_hdac_chip_updatel(bus, VS_EM2, AZX_VS_EM2_DUM, AZX_VS_EM2_DUM); + + return ret; +} + +static int probe_codec(struct hdac_bus *bus, int addr) +{ + struct hda_codec *codec; + unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) | + (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; + unsigned int res = -1; + int ret; + + mutex_lock(&bus->cmd_mutex); + snd_hdac_bus_send_cmd(bus, cmd); + snd_hdac_bus_get_response(bus, addr, &res); + mutex_unlock(&bus->cmd_mutex); + if (res == -1) + return -EIO; + + dev_dbg(bus->dev, "codec #%d probed OK: 0x%x\n", addr, res); + + codec = snd_hda_codec_device_init(to_hda_bus(bus), addr, "hdaudioB%dD%d", bus->idx, addr); + if (IS_ERR(codec)) { + dev_err(bus->dev, "init codec failed: %ld\n", PTR_ERR(codec)); + return PTR_ERR(codec); + } + /* + * Allow avs_core suspend by forcing suspended state on all + * of its codec child devices. Component interested in + * dealing with hda codecs directly takes pm responsibilities + */ + pm_runtime_set_suspended(hda_codec_dev(codec)); + + /* configure effectively creates new ASoC component */ + ret = snd_hda_codec_configure(codec); + if (ret < 0) { + dev_err(bus->dev, "failed to config codec %d\n", ret); + return ret; + } + + return 0; +} + +static void avs_hdac_bus_probe_codecs(struct hdac_bus *bus) +{ + int c; + + /* First try to probe all given codec slots */ + for (c = 0; c < HDA_MAX_CODECS; c++) { + if (!(bus->codec_mask & BIT(c))) + continue; + + if (!probe_codec(bus, c)) + /* success, continue probing */ + continue; + + /* + * Some BIOSen give you wrong codec addresses + * that don't exist + */ + dev_warn(bus->dev, "Codec #%d probe error; disabling it...\n", c); + bus->codec_mask &= ~BIT(c); + /* + * More badly, accessing to a non-existing + * codec often screws up the controller bus, + * and disturbs the further communications. + * Thus if an error occurs during probing, + * better to reset the controller bus to get + * back to the sanity state. + */ + snd_hdac_bus_stop_chip(bus); + avs_hdac_bus_init_chip(bus, true); + } +} + +static void avs_hda_probe_work(struct work_struct *work) +{ + struct avs_dev *adev = container_of(work, struct avs_dev, probe_work); + struct hdac_bus *bus = &adev->base.core; + struct hdac_ext_link *hlink; + int ret; + + pm_runtime_set_active(bus->dev); /* clear runtime_error flag */ + + ret = snd_hdac_i915_init(bus); + if (ret < 0) + dev_info(bus->dev, "i915 init unsuccessful: %d\n", ret); + + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true); + avs_hdac_bus_init_chip(bus, true); + avs_hdac_bus_probe_codecs(bus); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + + /* with all codecs probed, links can be powered down */ + list_for_each_entry(hlink, &bus->hlink_list, list) + snd_hdac_ext_bus_link_put(bus, hlink); + + snd_hdac_ext_bus_ppcap_enable(bus, true); + snd_hdac_ext_bus_ppcap_int_enable(bus, true); + + ret = avs_dsp_first_boot_firmware(adev); + if (ret < 0) + return; + + adev->nhlt = intel_nhlt_init(adev->dev); + if (!adev->nhlt) + dev_info(bus->dev, "platform has no NHLT\n"); + + avs_register_all_boards(adev); + + /* configure PM */ + pm_runtime_set_autosuspend_delay(bus->dev, 2000); + pm_runtime_use_autosuspend(bus->dev); + pm_runtime_mark_last_busy(bus->dev); + pm_runtime_put_autosuspend(bus->dev); + pm_runtime_allow(bus->dev); +} + +static void hdac_stream_update_pos(struct hdac_stream *stream, u64 buffer_size) +{ + u64 prev_pos, pos, num_bytes; + + div64_u64_rem(stream->curr_pos, buffer_size, &prev_pos); + pos = snd_hdac_stream_get_pos_posbuf(stream); + + if (pos < prev_pos) + num_bytes = (buffer_size - prev_pos) + pos; + else + num_bytes = pos - prev_pos; + + stream->curr_pos += num_bytes; +} + +/* called from IRQ */ +static void hdac_update_stream(struct hdac_bus *bus, struct hdac_stream *stream) +{ + if (stream->substream) { + snd_pcm_period_elapsed(stream->substream); + } else if (stream->cstream) { + u64 buffer_size = stream->cstream->runtime->buffer_size; + + hdac_stream_update_pos(stream, buffer_size); + snd_compr_fragment_elapsed(stream->cstream); + } +} + +static irqreturn_t hdac_bus_irq_handler(int irq, void *context) +{ + struct hdac_bus *bus = context; + u32 mask, int_enable; + u32 status; + int ret = IRQ_NONE; + + if (!pm_runtime_active(bus->dev)) + return ret; + + spin_lock(&bus->reg_lock); + + status = snd_hdac_chip_readl(bus, INTSTS); + if (status == 0 || status == UINT_MAX) { + spin_unlock(&bus->reg_lock); + return ret; + } + + /* clear rirb int */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { + if (status & RIRB_INT_RESPONSE) + snd_hdac_bus_update_rirb(bus); + snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK); + } + + mask = (0x1 << bus->num_streams) - 1; + + status = snd_hdac_chip_readl(bus, INTSTS); + status &= mask; + if (status) { + /* Disable stream interrupts; Re-enable in bottom half */ + int_enable = snd_hdac_chip_readl(bus, INTCTL); + snd_hdac_chip_writel(bus, INTCTL, (int_enable & (~mask))); + ret = IRQ_WAKE_THREAD; + } else { + ret = IRQ_HANDLED; + } + + spin_unlock(&bus->reg_lock); + return ret; +} + +static irqreturn_t hdac_bus_irq_thread(int irq, void *context) +{ + struct hdac_bus *bus = context; + u32 status; + u32 int_enable; + u32 mask; + unsigned long flags; + + status = snd_hdac_chip_readl(bus, INTSTS); + + snd_hdac_bus_handle_stream_irq(bus, status, hdac_update_stream); + + /* Re-enable stream interrupts */ + mask = (0x1 << bus->num_streams) - 1; + spin_lock_irqsave(&bus->reg_lock, flags); + int_enable = snd_hdac_chip_readl(bus, INTCTL); + snd_hdac_chip_writel(bus, INTCTL, (int_enable | mask)); + spin_unlock_irqrestore(&bus->reg_lock, flags); + + return IRQ_HANDLED; +} + +static int avs_hdac_acquire_irq(struct avs_dev *adev) +{ + struct hdac_bus *bus = &adev->base.core; + struct pci_dev *pci = to_pci_dev(bus->dev); + int ret; + + /* request one and check that we only got one interrupt */ + ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY); + if (ret != 1) { + dev_err(adev->dev, "Failed to allocate IRQ vector: %d\n", ret); + return ret; + } + + ret = pci_request_irq(pci, 0, hdac_bus_irq_handler, hdac_bus_irq_thread, bus, + KBUILD_MODNAME); + if (ret < 0) { + dev_err(adev->dev, "Failed to request stream IRQ handler: %d\n", ret); + goto free_vector; + } + + ret = pci_request_irq(pci, 0, avs_dsp_irq_handler, avs_dsp_irq_thread, adev, + KBUILD_MODNAME); + if (ret < 0) { + dev_err(adev->dev, "Failed to request IPC IRQ handler: %d\n", ret); + goto free_stream_irq; + } + + return 0; + +free_stream_irq: + pci_free_irq(pci, 0, bus); +free_vector: + pci_free_irq_vectors(pci); + return ret; +} + +static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct pci_device_id *id) +{ + struct hda_bus *bus = &adev->base; + struct avs_ipc *ipc; + struct device *dev = &pci->dev; + int ret; + + ret = snd_hdac_ext_bus_init(&bus->core, dev, NULL, &soc_hda_ext_bus_ops); + if (ret < 0) + return ret; + + bus->core.use_posbuf = 1; + bus->core.bdl_pos_adj = 0; + bus->core.sync_write = 1; + bus->pci = pci; + bus->mixer_assigned = -1; + mutex_init(&bus->prepare_mutex); + + ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return -ENOMEM; + ret = avs_ipc_init(ipc, dev); + if (ret < 0) + return ret; + + adev->dev = dev; + adev->spec = (const struct avs_spec *)id->driver_data; + adev->ipc = ipc; + adev->hw_cfg.dsp_cores = hweight_long(AVS_MAIN_CORE_MASK); + INIT_WORK(&adev->probe_work, avs_hda_probe_work); + INIT_LIST_HEAD(&adev->comp_list); + INIT_LIST_HEAD(&adev->path_list); + INIT_LIST_HEAD(&adev->fw_list); + init_completion(&adev->fw_ready); + spin_lock_init(&adev->path_list_lock); + mutex_init(&adev->modres_mutex); + mutex_init(&adev->comp_list_mutex); + mutex_init(&adev->path_mutex); + + return 0; +} + +static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + struct hdac_bus *bus; + struct avs_dev *adev; + struct device *dev = &pci->dev; + int ret; + + ret = snd_intel_dsp_driver_probe(pci); + if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_AVS) + return -ENODEV; + + ret = pcim_enable_device(pci); + if (ret < 0) + return ret; + + adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL); + if (!adev) + return -ENOMEM; + ret = avs_bus_init(adev, pci, id); + if (ret < 0) { + dev_err(dev, "failed to init avs bus: %d\n", ret); + return ret; + } + + ret = pci_request_regions(pci, "AVS HDAudio"); + if (ret < 0) + return ret; + + bus = &adev->base.core; + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (!bus->remap_addr) { + dev_err(bus->dev, "ioremap error\n"); + ret = -ENXIO; + goto err_remap_bar0; + } + + adev->dsp_ba = pci_ioremap_bar(pci, 4); + if (!adev->dsp_ba) { + dev_err(bus->dev, "ioremap error\n"); + ret = -ENXIO; + goto err_remap_bar4; + } + + snd_hdac_bus_parse_capabilities(bus); + if (bus->mlcap) + snd_hdac_ext_bus_get_ml_capabilities(bus); + + if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + dma_set_max_seg_size(dev, UINT_MAX); + + ret = avs_hdac_bus_init_streams(bus); + if (ret < 0) { + dev_err(dev, "failed to init streams: %d\n", ret); + goto err_init_streams; + } + + ret = avs_hdac_acquire_irq(adev); + if (ret < 0) { + dev_err(bus->dev, "failed to acquire irq: %d\n", ret); + goto err_acquire_irq; + } + + pci_set_master(pci); + pci_set_drvdata(pci, bus); + device_disable_async_suspend(dev); + + schedule_work(&adev->probe_work); + + return 0; + +err_acquire_irq: + snd_hdac_bus_free_stream_pages(bus); + snd_hdac_ext_stream_free_all(bus); +err_init_streams: + iounmap(adev->dsp_ba); +err_remap_bar4: + iounmap(bus->remap_addr); +err_remap_bar0: + pci_release_regions(pci); + return ret; +} + +static void avs_pci_remove(struct pci_dev *pci) +{ + struct hdac_device *hdev, *save; + struct hdac_bus *bus = pci_get_drvdata(pci); + struct avs_dev *adev = hdac_to_avs(bus); + + cancel_work_sync(&adev->probe_work); + avs_ipc_block(adev->ipc); + + avs_unregister_all_boards(adev); + + if (adev->nhlt) + intel_nhlt_free(adev->nhlt); + + if (avs_platattr_test(adev, CLDMA)) + hda_cldma_free(&code_loader); + + snd_hdac_stop_streams_and_chip(bus); + avs_dsp_op(adev, int_control, false); + snd_hdac_ext_bus_ppcap_int_enable(bus, false); + + /* it is safe to remove all codecs from the system now */ + list_for_each_entry_safe(hdev, save, &bus->codec_list, list) + snd_hda_codec_unregister(hdac_to_hda_codec(hdev)); + + snd_hdac_bus_free_stream_pages(bus); + snd_hdac_ext_stream_free_all(bus); + /* reverse ml_capabilities */ + snd_hdac_link_free_all(bus); + snd_hdac_ext_bus_exit(bus); + + avs_dsp_core_disable(adev, GENMASK(adev->hw_cfg.dsp_cores - 1, 0)); + snd_hdac_ext_bus_ppcap_enable(bus, false); + + /* snd_hdac_stop_streams_and_chip does that already? */ + snd_hdac_bus_stop_chip(bus); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + if (bus->audio_component) + snd_hdac_i915_exit(bus); + + avs_module_info_free(adev); + pci_free_irq(pci, 0, adev); + pci_free_irq(pci, 0, bus); + pci_free_irq_vectors(pci); + iounmap(bus->remap_addr); + iounmap(adev->dsp_ba); + pci_release_regions(pci); + + /* Firmware is not needed anymore */ + avs_release_firmwares(adev); + + /* pm_runtime_forbid() can rpm_resume() which we do not want */ + pm_runtime_disable(&pci->dev); + pm_runtime_forbid(&pci->dev); + pm_runtime_enable(&pci->dev); + pm_runtime_get_noresume(&pci->dev); +} + +static int __maybe_unused avs_suspend_common(struct avs_dev *adev) +{ + struct hdac_bus *bus = &adev->base.core; + int ret; + + flush_work(&adev->probe_work); + + snd_hdac_ext_bus_link_power_down_all(bus); + + ret = avs_ipc_set_dx(adev, AVS_MAIN_CORE_MASK, false); + /* + * pm_runtime is blocked on DSP failure but system-wide suspend is not. + * Do not block entire system from suspending if that's the case. + */ + if (ret && ret != -EPERM) { + dev_err(adev->dev, "set dx failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + + avs_ipc_block(adev->ipc); + avs_dsp_op(adev, int_control, false); + snd_hdac_ext_bus_ppcap_int_enable(bus, false); + + ret = avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + if (ret < 0) { + dev_err(adev->dev, "core_mask %ld disable failed: %d\n", AVS_MAIN_CORE_MASK, ret); + return ret; + } + + snd_hdac_ext_bus_ppcap_enable(bus, false); + /* disable LP SRAM retention */ + avs_hda_power_gating_enable(adev, false); + snd_hdac_bus_stop_chip(bus); + /* disable CG when putting controller to reset */ + avs_hdac_clock_gating_enable(bus, false); + snd_hdac_bus_enter_link_reset(bus); + avs_hdac_clock_gating_enable(bus, true); + + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + + return 0; +} + +static int __maybe_unused avs_resume_common(struct avs_dev *adev, bool purge) +{ + struct hdac_bus *bus = &adev->base.core; + struct hdac_ext_link *hlink; + int ret; + + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true); + avs_hdac_bus_init_chip(bus, true); + + snd_hdac_ext_bus_ppcap_enable(bus, true); + snd_hdac_ext_bus_ppcap_int_enable(bus, true); + + ret = avs_dsp_boot_firmware(adev, purge); + if (ret < 0) { + dev_err(adev->dev, "firmware boot failed: %d\n", ret); + return ret; + } + + /* turn off the links that were off before suspend */ + list_for_each_entry(hlink, &bus->hlink_list, list) { + if (!hlink->ref_count) + snd_hdac_ext_bus_link_power_down(hlink); + } + + /* check dma status and clean up CORB/RIRB buffers */ + if (!bus->cmd_dma_state) + snd_hdac_bus_stop_cmd_io(bus); + + return 0; +} + +static int __maybe_unused avs_suspend(struct device *dev) +{ + return avs_suspend_common(to_avs_dev(dev)); +} + +static int __maybe_unused avs_resume(struct device *dev) +{ + return avs_resume_common(to_avs_dev(dev), true); +} + +static int __maybe_unused avs_runtime_suspend(struct device *dev) +{ + return avs_suspend_common(to_avs_dev(dev)); +} + +static int __maybe_unused avs_runtime_resume(struct device *dev) +{ + return avs_resume_common(to_avs_dev(dev), true); +} + +static const struct dev_pm_ops avs_dev_pm = { + SET_SYSTEM_SLEEP_PM_OPS(avs_suspend, avs_resume) + SET_RUNTIME_PM_OPS(avs_runtime_suspend, avs_runtime_resume, NULL) +}; + +static const struct avs_spec skl_desc = { + .name = "skl", + .min_fw_version = { + .major = 9, + .minor = 21, + .hotfix = 0, + .build = 4732, + }, + .dsp_ops = &skl_dsp_ops, + .core_init_mask = 1, + .attributes = AVS_PLATATTR_CLDMA, + .sram_base_offset = SKL_ADSP_SRAM_BASE_OFFSET, + .sram_window_size = SKL_ADSP_SRAM_WINDOW_SIZE, + .rom_status = SKL_ADSP_SRAM_BASE_OFFSET, +}; + +static const struct avs_spec apl_desc = { + .name = "apl", + .min_fw_version = { + .major = 9, + .minor = 22, + .hotfix = 1, + .build = 4323, + }, + .dsp_ops = &apl_dsp_ops, + .core_init_mask = 3, + .attributes = AVS_PLATATTR_IMR, + .sram_base_offset = APL_ADSP_SRAM_BASE_OFFSET, + .sram_window_size = APL_ADSP_SRAM_WINDOW_SIZE, + .rom_status = APL_ADSP_SRAM_BASE_OFFSET, +}; + +static const struct pci_device_id avs_ids[] = { + { PCI_VDEVICE(INTEL, 0x9d70), (unsigned long)&skl_desc }, /* SKL */ + { PCI_VDEVICE(INTEL, 0x9d71), (unsigned long)&skl_desc }, /* KBL */ + { PCI_VDEVICE(INTEL, 0x5a98), (unsigned long)&apl_desc }, /* APL */ + { PCI_VDEVICE(INTEL, 0x3198), (unsigned long)&apl_desc }, /* GML */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, avs_ids); + +static struct pci_driver avs_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = avs_ids, + .probe = avs_pci_probe, + .remove = avs_pci_remove, + .driver = { + .pm = &avs_dev_pm, + }, +}; +module_pci_driver(avs_pci_driver); + +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_AUTHOR("Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>"); +MODULE_DESCRIPTION("Intel cAVS sound driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/dsp.c b/sound/soc/intel/avs/dsp.c new file mode 100644 index 000000000000..b881100d3e02 --- /dev/null +++ b/sound/soc/intel/avs/dsp.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "registers.h" +#include "trace.h" + +#define AVS_ADSPCS_INTERVAL_US 500 +#define AVS_ADSPCS_TIMEOUT_US 50000 +#define AVS_ADSPCS_DELAY_US 1000 + +int avs_dsp_core_power(struct avs_dev *adev, u32 core_mask, bool power) +{ + u32 value, mask, reg; + int ret; + + value = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPCS); + trace_avs_dsp_core_op(value, core_mask, "power", power); + + mask = AVS_ADSPCS_SPA_MASK(core_mask); + value = power ? mask : 0; + + snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPCS, mask, value); + /* Delay the polling to avoid false positives. */ + usleep_range(AVS_ADSPCS_DELAY_US, 2 * AVS_ADSPCS_DELAY_US); + + mask = AVS_ADSPCS_CPA_MASK(core_mask); + value = power ? mask : 0; + + ret = snd_hdac_adsp_readl_poll(adev, AVS_ADSP_REG_ADSPCS, + reg, (reg & mask) == value, + AVS_ADSPCS_INTERVAL_US, + AVS_ADSPCS_TIMEOUT_US); + if (ret) + dev_err(adev->dev, "core_mask %d power %s failed: %d\n", + core_mask, power ? "on" : "off", ret); + + return ret; +} + +int avs_dsp_core_reset(struct avs_dev *adev, u32 core_mask, bool reset) +{ + u32 value, mask, reg; + int ret; + + value = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPCS); + trace_avs_dsp_core_op(value, core_mask, "reset", reset); + + mask = AVS_ADSPCS_CRST_MASK(core_mask); + value = reset ? mask : 0; + + snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPCS, mask, value); + + ret = snd_hdac_adsp_readl_poll(adev, AVS_ADSP_REG_ADSPCS, + reg, (reg & mask) == value, + AVS_ADSPCS_INTERVAL_US, + AVS_ADSPCS_TIMEOUT_US); + if (ret) + dev_err(adev->dev, "core_mask %d %s reset failed: %d\n", + core_mask, reset ? "enter" : "exit", ret); + + return ret; +} + +int avs_dsp_core_stall(struct avs_dev *adev, u32 core_mask, bool stall) +{ + u32 value, mask, reg; + int ret; + + value = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPCS); + trace_avs_dsp_core_op(value, core_mask, "stall", stall); + + mask = AVS_ADSPCS_CSTALL_MASK(core_mask); + value = stall ? mask : 0; + + snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPCS, mask, value); + + ret = snd_hdac_adsp_readl_poll(adev, AVS_ADSP_REG_ADSPCS, + reg, (reg & mask) == value, + AVS_ADSPCS_INTERVAL_US, + AVS_ADSPCS_TIMEOUT_US); + if (ret) { + dev_err(adev->dev, "core_mask %d %sstall failed: %d\n", + core_mask, stall ? "" : "un", ret); + return ret; + } + + /* Give HW time to propagate the change. */ + usleep_range(AVS_ADSPCS_DELAY_US, 2 * AVS_ADSPCS_DELAY_US); + return 0; +} + +int avs_dsp_core_enable(struct avs_dev *adev, u32 core_mask) +{ + int ret; + + ret = avs_dsp_op(adev, power, core_mask, true); + if (ret) + return ret; + + ret = avs_dsp_op(adev, reset, core_mask, false); + if (ret) + return ret; + + return avs_dsp_op(adev, stall, core_mask, false); +} + +int avs_dsp_core_disable(struct avs_dev *adev, u32 core_mask) +{ + /* No error checks to allow for complete DSP shutdown. */ + avs_dsp_op(adev, stall, core_mask, true); + avs_dsp_op(adev, reset, core_mask, true); + + return avs_dsp_op(adev, power, core_mask, false); +} + +static int avs_dsp_enable(struct avs_dev *adev, u32 core_mask) +{ + u32 mask; + int ret; + + ret = avs_dsp_core_enable(adev, core_mask); + if (ret < 0) + return ret; + + mask = core_mask & ~AVS_MAIN_CORE_MASK; + if (!mask) + /* + * without main core, fw is dead anyway + * so setting D0 for it is futile. + */ + return 0; + + ret = avs_ipc_set_dx(adev, mask, true); + return AVS_IPC_RET(ret); +} + +static int avs_dsp_disable(struct avs_dev *adev, u32 core_mask) +{ + int ret; + + ret = avs_ipc_set_dx(adev, core_mask, false); + if (ret) + return AVS_IPC_RET(ret); + + return avs_dsp_core_disable(adev, core_mask); +} + +static int avs_dsp_get_core(struct avs_dev *adev, u32 core_id) +{ + u32 mask; + int ret; + + mask = BIT_MASK(core_id); + if (mask == AVS_MAIN_CORE_MASK) + /* nothing to do for main core */ + return 0; + if (core_id >= adev->hw_cfg.dsp_cores) { + ret = -EINVAL; + goto err; + } + + adev->core_refs[core_id]++; + if (adev->core_refs[core_id] == 1) { + /* + * No cores other than main-core can be running for DSP + * to achieve d0ix. Conscious SET_D0IX IPC failure is permitted, + * simply d0ix power state will no longer be attempted. + */ + ret = avs_dsp_disable_d0ix(adev); + if (ret && ret != -AVS_EIPC) + goto err_disable_d0ix; + + ret = avs_dsp_enable(adev, mask); + if (ret) + goto err_enable_dsp; + } + + return 0; + +err_enable_dsp: + avs_dsp_enable_d0ix(adev); +err_disable_d0ix: + adev->core_refs[core_id]--; +err: + dev_err(adev->dev, "get core %d failed: %d\n", core_id, ret); + return ret; +} + +static int avs_dsp_put_core(struct avs_dev *adev, u32 core_id) +{ + u32 mask; + int ret; + + mask = BIT_MASK(core_id); + if (mask == AVS_MAIN_CORE_MASK) + /* nothing to do for main core */ + return 0; + if (core_id >= adev->hw_cfg.dsp_cores) { + ret = -EINVAL; + goto err; + } + + adev->core_refs[core_id]--; + if (!adev->core_refs[core_id]) { + ret = avs_dsp_disable(adev, mask); + if (ret) + goto err; + + /* Match disable_d0ix in avs_dsp_get_core(). */ + avs_dsp_enable_d0ix(adev); + } + + return 0; +err: + dev_err(adev->dev, "put core %d failed: %d\n", core_id, ret); + return ret; +} + +int avs_dsp_init_module(struct avs_dev *adev, u16 module_id, u8 ppl_instance_id, + u8 core_id, u8 domain, void *param, u32 param_size, + u16 *instance_id) +{ + struct avs_module_entry mentry; + bool was_loaded = false; + int ret, id; + + id = avs_module_id_alloc(adev, module_id); + if (id < 0) + return id; + + ret = avs_get_module_id_entry(adev, module_id, &mentry); + if (ret) + goto err_mod_entry; + + ret = avs_dsp_get_core(adev, core_id); + if (ret) + goto err_mod_entry; + + /* Load code into memory if this is the first instance. */ + if (!id && !avs_module_entry_is_loaded(&mentry)) { + ret = avs_dsp_op(adev, transfer_mods, true, &mentry, 1); + if (ret) { + dev_err(adev->dev, "load modules failed: %d\n", ret); + goto err_mod_entry; + } + was_loaded = true; + } + + ret = avs_ipc_init_instance(adev, module_id, id, ppl_instance_id, + core_id, domain, param, param_size); + if (ret) { + ret = AVS_IPC_RET(ret); + goto err_ipc; + } + + *instance_id = id; + return 0; + +err_ipc: + if (was_loaded) + avs_dsp_op(adev, transfer_mods, false, &mentry, 1); + avs_dsp_put_core(adev, core_id); +err_mod_entry: + avs_module_id_free(adev, module_id, id); + return ret; +} + +void avs_dsp_delete_module(struct avs_dev *adev, u16 module_id, u16 instance_id, + u8 ppl_instance_id, u8 core_id) +{ + struct avs_module_entry mentry; + int ret; + + /* Modules not owned by any pipeline need to be freed explicitly. */ + if (ppl_instance_id == INVALID_PIPELINE_ID) + avs_ipc_delete_instance(adev, module_id, instance_id); + + avs_module_id_free(adev, module_id, instance_id); + + ret = avs_get_module_id_entry(adev, module_id, &mentry); + /* Unload occupied memory if this was the last instance. */ + if (!ret && mentry.type.load_type == AVS_MODULE_LOAD_TYPE_LOADABLE) { + if (avs_is_module_ida_empty(adev, module_id)) { + ret = avs_dsp_op(adev, transfer_mods, false, &mentry, 1); + if (ret) + dev_err(adev->dev, "unload modules failed: %d\n", ret); + } + } + + avs_dsp_put_core(adev, core_id); +} + +int avs_dsp_create_pipeline(struct avs_dev *adev, u16 req_size, u8 priority, + bool lp, u16 attributes, u8 *instance_id) +{ + struct avs_fw_cfg *fw_cfg = &adev->fw_cfg; + int ret, id; + + id = ida_alloc_max(&adev->ppl_ida, fw_cfg->max_ppl_count - 1, GFP_KERNEL); + if (id < 0) + return id; + + ret = avs_ipc_create_pipeline(adev, req_size, priority, id, lp, attributes); + if (ret) { + ida_free(&adev->ppl_ida, id); + return AVS_IPC_RET(ret); + } + + *instance_id = id; + return 0; +} + +int avs_dsp_delete_pipeline(struct avs_dev *adev, u8 instance_id) +{ + int ret; + + ret = avs_ipc_delete_pipeline(adev, instance_id); + if (ret) + ret = AVS_IPC_RET(ret); + + ida_free(&adev->ppl_ida, instance_id); + return ret; +} diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c new file mode 100644 index 000000000000..020d85c7520d --- /dev/null +++ b/sound/soc/intel/avs/ipc.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/slab.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "messages.h" +#include "registers.h" +#include "trace.h" + +#define AVS_IPC_TIMEOUT_MS 300 +#define AVS_D0IX_DELAY_MS 300 + +static int +avs_dsp_set_d0ix(struct avs_dev *adev, bool enable) +{ + struct avs_ipc *ipc = adev->ipc; + int ret; + + /* Is transition required? */ + if (ipc->in_d0ix == enable) + return 0; + + ret = avs_dsp_op(adev, set_d0ix, enable); + if (ret) { + /* Prevent further d0ix attempts on conscious IPC failure. */ + if (ret == -AVS_EIPC) + atomic_inc(&ipc->d0ix_disable_depth); + + ipc->in_d0ix = false; + return ret; + } + + ipc->in_d0ix = enable; + return 0; +} + +static void avs_dsp_schedule_d0ix(struct avs_dev *adev, struct avs_ipc_msg *tx) +{ + if (atomic_read(&adev->ipc->d0ix_disable_depth)) + return; + + mod_delayed_work(system_power_efficient_wq, &adev->ipc->d0ix_work, + msecs_to_jiffies(AVS_D0IX_DELAY_MS)); +} + +static void avs_dsp_d0ix_work(struct work_struct *work) +{ + struct avs_ipc *ipc = container_of(work, struct avs_ipc, d0ix_work.work); + + avs_dsp_set_d0ix(to_avs_dev(ipc->dev), true); +} + +static int avs_dsp_wake_d0i0(struct avs_dev *adev, struct avs_ipc_msg *tx) +{ + struct avs_ipc *ipc = adev->ipc; + + if (!atomic_read(&ipc->d0ix_disable_depth)) { + cancel_delayed_work_sync(&ipc->d0ix_work); + return avs_dsp_set_d0ix(adev, false); + } + + return 0; +} + +int avs_dsp_disable_d0ix(struct avs_dev *adev) +{ + struct avs_ipc *ipc = adev->ipc; + + /* Prevent PG only on the first disable. */ + if (atomic_add_return(1, &ipc->d0ix_disable_depth) == 1) { + cancel_delayed_work_sync(&ipc->d0ix_work); + return avs_dsp_set_d0ix(adev, false); + } + + return 0; +} + +int avs_dsp_enable_d0ix(struct avs_dev *adev) +{ + struct avs_ipc *ipc = adev->ipc; + + if (atomic_dec_and_test(&ipc->d0ix_disable_depth)) + queue_delayed_work(system_power_efficient_wq, &ipc->d0ix_work, + msecs_to_jiffies(AVS_D0IX_DELAY_MS)); + return 0; +} + +static void avs_dsp_recovery(struct avs_dev *adev) +{ + struct avs_soc_component *acomp; + unsigned int core_mask; + int ret; + + mutex_lock(&adev->comp_list_mutex); + /* disconnect all running streams */ + list_for_each_entry(acomp, &adev->comp_list, node) { + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_card *card; + + card = acomp->base.card; + if (!card) + continue; + + for_each_card_rtds(card, rtd) { + struct snd_pcm *pcm; + int dir; + + pcm = rtd->pcm; + if (!pcm || rtd->dai_link->no_pcm) + continue; + + for_each_pcm_streams(dir) { + struct snd_pcm_substream *substream; + + substream = pcm->streams[dir].substream; + if (!substream || !substream->runtime) + continue; + + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + } + } + } + mutex_unlock(&adev->comp_list_mutex); + + /* forcibly shutdown all cores */ + core_mask = GENMASK(adev->hw_cfg.dsp_cores - 1, 0); + avs_dsp_core_disable(adev, core_mask); + + /* attempt dsp reboot */ + ret = avs_dsp_boot_firmware(adev, true); + if (ret < 0) + dev_err(adev->dev, "dsp reboot failed: %d\n", ret); + + pm_runtime_mark_last_busy(adev->dev); + pm_runtime_enable(adev->dev); + pm_request_autosuspend(adev->dev); + + atomic_set(&adev->ipc->recovering, 0); +} + +static void avs_dsp_recovery_work(struct work_struct *work) +{ + struct avs_ipc *ipc = container_of(work, struct avs_ipc, recovery_work); + + avs_dsp_recovery(to_avs_dev(ipc->dev)); +} + +static void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg *msg) +{ + struct avs_ipc *ipc = adev->ipc; + + /* Account for the double-exception case. */ + ipc->ready = false; + + if (!atomic_add_unless(&ipc->recovering, 1, 1)) { + dev_err(adev->dev, "dsp recovery is already in progress\n"); + return; + } + + dev_crit(adev->dev, "communication severed, rebooting dsp..\n"); + + cancel_delayed_work_sync(&ipc->d0ix_work); + ipc->in_d0ix = false; + /* Re-enabled on recovery completion. */ + pm_runtime_disable(adev->dev); + + /* Process received notification. */ + avs_dsp_op(adev, coredump, msg); + + schedule_work(&ipc->recovery_work); +} + +static void avs_dsp_receive_rx(struct avs_dev *adev, u64 header) +{ + struct avs_ipc *ipc = adev->ipc; + union avs_reply_msg msg = AVS_MSG(header); + u64 reg; + + reg = readq(avs_sram_addr(adev, AVS_FW_REGS_WINDOW)); + trace_avs_ipc_reply_msg(header, reg); + + ipc->rx.header = header; + /* Abort copying payload if request processing was unsuccessful. */ + if (!msg.status) { + /* update size in case of LARGE_CONFIG_GET */ + if (msg.msg_target == AVS_MOD_MSG && + msg.global_msg_type == AVS_MOD_LARGE_CONFIG_GET) + ipc->rx.size = msg.ext.large_config.data_off_size; + + memcpy_fromio(ipc->rx.data, avs_uplink_addr(adev), ipc->rx.size); + trace_avs_msg_payload(ipc->rx.data, ipc->rx.size); + } +} + +static void avs_dsp_process_notification(struct avs_dev *adev, u64 header) +{ + struct avs_notify_mod_data mod_data; + union avs_notify_msg msg = AVS_MSG(header); + size_t data_size = 0; + void *data = NULL; + u64 reg; + + reg = readq(avs_sram_addr(adev, AVS_FW_REGS_WINDOW)); + trace_avs_ipc_notify_msg(header, reg); + + /* Ignore spurious notifications until handshake is established. */ + if (!adev->ipc->ready && msg.notify_msg_type != AVS_NOTIFY_FW_READY) { + dev_dbg(adev->dev, "FW not ready, skip notification: 0x%08x\n", msg.primary); + return; + } + + /* Calculate notification payload size. */ + switch (msg.notify_msg_type) { + case AVS_NOTIFY_FW_READY: + break; + + case AVS_NOTIFY_PHRASE_DETECTED: + data_size = sizeof(struct avs_notify_voice_data); + break; + + case AVS_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct avs_notify_res_data); + break; + + case AVS_NOTIFY_LOG_BUFFER_STATUS: + case AVS_NOTIFY_EXCEPTION_CAUGHT: + break; + + case AVS_NOTIFY_MODULE_EVENT: + /* To know the total payload size, header needs to be read first. */ + memcpy_fromio(&mod_data, avs_uplink_addr(adev), sizeof(mod_data)); + data_size = sizeof(mod_data) + mod_data.data_size; + break; + + default: + dev_info(adev->dev, "unknown notification: 0x%08x\n", msg.primary); + break; + } + + if (data_size) { + data = kmalloc(data_size, GFP_KERNEL); + if (!data) + return; + + memcpy_fromio(data, avs_uplink_addr(adev), data_size); + trace_avs_msg_payload(data, data_size); + } + + /* Perform notification-specific operations. */ + switch (msg.notify_msg_type) { + case AVS_NOTIFY_FW_READY: + dev_dbg(adev->dev, "FW READY 0x%08x\n", msg.primary); + adev->ipc->ready = true; + complete(&adev->fw_ready); + break; + + case AVS_NOTIFY_LOG_BUFFER_STATUS: + avs_dsp_op(adev, log_buffer_status, &msg); + break; + + case AVS_NOTIFY_EXCEPTION_CAUGHT: + avs_dsp_exception_caught(adev, &msg); + break; + + default: + break; + } + + kfree(data); +} + +void avs_dsp_process_response(struct avs_dev *adev, u64 header) +{ + struct avs_ipc *ipc = adev->ipc; + + /* + * Response may either be solicited - a reply for a request that has + * been sent beforehand - or unsolicited (notification). + */ + if (avs_msg_is_reply(header)) { + /* Response processing is invoked from IRQ thread. */ + spin_lock_irq(&ipc->rx_lock); + avs_dsp_receive_rx(adev, header); + ipc->rx_completed = true; + spin_unlock_irq(&ipc->rx_lock); + } else { + avs_dsp_process_notification(adev, header); + } + + complete(&ipc->busy_completion); +} + +irqreturn_t avs_dsp_irq_handler(int irq, void *dev_id) +{ + struct avs_dev *adev = dev_id; + struct avs_ipc *ipc = adev->ipc; + u32 adspis, hipc_rsp, hipc_ack; + irqreturn_t ret = IRQ_NONE; + + adspis = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPIS); + if (adspis == UINT_MAX || !(adspis & AVS_ADSP_ADSPIS_IPC)) + return ret; + + hipc_ack = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCIE); + hipc_rsp = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT); + + /* DSP acked host's request */ + if (hipc_ack & SKL_ADSP_HIPCIE_DONE) { + /* + * As an extra precaution, mask done interrupt. Code executed + * due to complete() found below does not assume any masking. + */ + snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, + AVS_ADSP_HIPCCTL_DONE, 0); + + complete(&ipc->done_completion); + + /* tell DSP it has our attention */ + snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCIE, + SKL_ADSP_HIPCIE_DONE, + SKL_ADSP_HIPCIE_DONE); + /* unmask done interrupt */ + snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, + AVS_ADSP_HIPCCTL_DONE, + AVS_ADSP_HIPCCTL_DONE); + ret = IRQ_HANDLED; + } + + /* DSP sent new response to process */ + if (hipc_rsp & SKL_ADSP_HIPCT_BUSY) { + /* mask busy interrupt */ + snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, + AVS_ADSP_HIPCCTL_BUSY, 0); + + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +irqreturn_t avs_dsp_irq_thread(int irq, void *dev_id) +{ + struct avs_dev *adev = dev_id; + union avs_reply_msg msg; + u32 hipct, hipcte; + + hipct = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT); + hipcte = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCTE); + + /* ensure DSP sent new response to process */ + if (!(hipct & SKL_ADSP_HIPCT_BUSY)) + return IRQ_NONE; + + msg.primary = hipct; + msg.ext.val = hipcte; + avs_dsp_process_response(adev, msg.val); + + /* tell DSP we accepted its message */ + snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCT, + SKL_ADSP_HIPCT_BUSY, SKL_ADSP_HIPCT_BUSY); + /* unmask busy interrupt */ + snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, + AVS_ADSP_HIPCCTL_BUSY, AVS_ADSP_HIPCCTL_BUSY); + + return IRQ_HANDLED; +} + +static bool avs_ipc_is_busy(struct avs_ipc *ipc) +{ + struct avs_dev *adev = to_avs_dev(ipc->dev); + u32 hipc_rsp; + + hipc_rsp = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT); + return hipc_rsp & SKL_ADSP_HIPCT_BUSY; +} + +static int avs_ipc_wait_busy_completion(struct avs_ipc *ipc, int timeout) +{ + u32 repeats_left = 128; /* to avoid infinite looping */ + int ret; + +again: + ret = wait_for_completion_timeout(&ipc->busy_completion, msecs_to_jiffies(timeout)); + + /* DSP could be unresponsive at this point. */ + if (!ipc->ready) + return -EPERM; + + if (!ret) { + if (!avs_ipc_is_busy(ipc)) + return -ETIMEDOUT; + /* + * Firmware did its job, either notification or reply + * has been received - now wait until it's processed. + */ + wait_for_completion_killable(&ipc->busy_completion); + } + + /* Ongoing notification's bottom-half may cause early wakeup */ + spin_lock(&ipc->rx_lock); + if (!ipc->rx_completed) { + if (repeats_left) { + /* Reply delayed due to notification. */ + repeats_left--; + reinit_completion(&ipc->busy_completion); + spin_unlock(&ipc->rx_lock); + goto again; + } + + spin_unlock(&ipc->rx_lock); + return -ETIMEDOUT; + } + + spin_unlock(&ipc->rx_lock); + return 0; +} + +static void avs_ipc_msg_init(struct avs_ipc *ipc, struct avs_ipc_msg *reply) +{ + lockdep_assert_held(&ipc->rx_lock); + + ipc->rx.header = 0; + ipc->rx.size = reply ? reply->size : 0; + ipc->rx_completed = false; + + reinit_completion(&ipc->done_completion); + reinit_completion(&ipc->busy_completion); +} + +static void avs_dsp_send_tx(struct avs_dev *adev, struct avs_ipc_msg *tx, bool read_fwregs) +{ + u64 reg = ULONG_MAX; + + tx->header |= SKL_ADSP_HIPCI_BUSY; + if (read_fwregs) + reg = readq(avs_sram_addr(adev, AVS_FW_REGS_WINDOW)); + + trace_avs_request(tx, reg); + + if (tx->size) + memcpy_toio(avs_downlink_addr(adev), tx->data, tx->size); + snd_hdac_adsp_writel(adev, SKL_ADSP_REG_HIPCIE, tx->header >> 32); + snd_hdac_adsp_writel(adev, SKL_ADSP_REG_HIPCI, tx->header & UINT_MAX); +} + +static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout) +{ + struct avs_ipc *ipc = adev->ipc; + int ret; + + if (!ipc->ready) + return -EPERM; + + mutex_lock(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); + avs_dsp_send_tx(adev, request, true); + spin_unlock(&ipc->rx_lock); + + ret = avs_ipc_wait_busy_completion(ipc, timeout); + if (ret) { + if (ret == -ETIMEDOUT) { + union avs_notify_msg msg = AVS_NOTIFICATION(EXCEPTION_CAUGHT); + + /* Same treatment as on exception, just stack_dump=0. */ + avs_dsp_exception_caught(adev, &msg); + } + goto exit; + } + + ret = ipc->rx.rsp.status; + if (reply) { + reply->header = ipc->rx.header; + reply->size = ipc->rx.size; + if (reply->data && ipc->rx.size) + memcpy(reply->data, ipc->rx.data, reply->size); + } + +exit: + mutex_unlock(&ipc->msg_mutex); + return ret; +} + +static int avs_dsp_send_msg_sequence(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, bool wake_d0i0, + bool schedule_d0ix) +{ + int ret; + + trace_avs_d0ix("wake", wake_d0i0, request->header); + if (wake_d0i0) { + ret = avs_dsp_wake_d0i0(adev, request); + if (ret) + return ret; + } + + ret = avs_dsp_do_send_msg(adev, request, reply, timeout); + if (ret) + return ret; + + trace_avs_d0ix("schedule", schedule_d0ix, request->header); + if (schedule_d0ix) + avs_dsp_schedule_d0ix(adev, request); + + return 0; +} + +int avs_dsp_send_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout) +{ + bool wake_d0i0 = avs_dsp_op(adev, d0ix_toggle, request, true); + bool schedule_d0ix = avs_dsp_op(adev, d0ix_toggle, request, false); + + return avs_dsp_send_msg_sequence(adev, request, reply, timeout, wake_d0i0, schedule_d0ix); +} + +int avs_dsp_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply) +{ + return avs_dsp_send_msg_timeout(adev, request, reply, adev->ipc->default_timeout_ms); +} + +int avs_dsp_send_pm_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, bool wake_d0i0) +{ + return avs_dsp_send_msg_sequence(adev, request, reply, timeout, wake_d0i0, false); +} + +int avs_dsp_send_pm_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, bool wake_d0i0) +{ + return avs_dsp_send_pm_msg_timeout(adev, request, reply, adev->ipc->default_timeout_ms, + wake_d0i0); +} + +static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request, int timeout) +{ + struct avs_ipc *ipc = adev->ipc; + int ret; + + mutex_lock(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, NULL); + /* + * with hw still stalled, memory windows may not be + * configured properly so avoid accessing SRAM + */ + avs_dsp_send_tx(adev, request, false); + spin_unlock(&ipc->rx_lock); + + /* ROM messages must be sent before main core is unstalled */ + ret = avs_dsp_op(adev, stall, AVS_MAIN_CORE_MASK, false); + if (!ret) { + ret = wait_for_completion_timeout(&ipc->done_completion, msecs_to_jiffies(timeout)); + ret = ret ? 0 : -ETIMEDOUT; + } + + mutex_unlock(&ipc->msg_mutex); + + return ret; +} + +int avs_dsp_send_rom_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, int timeout) +{ + return avs_dsp_do_send_rom_msg(adev, request, timeout); +} + +int avs_dsp_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request) +{ + return avs_dsp_send_rom_msg_timeout(adev, request, adev->ipc->default_timeout_ms); +} + +void avs_dsp_interrupt_control(struct avs_dev *adev, bool enable) +{ + u32 value, mask; + + /* + * No particular bit setting order. All of these are required + * to have a functional SW <-> FW communication. + */ + value = enable ? AVS_ADSP_ADSPIC_IPC : 0; + snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_IPC, value); + + mask = AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY; + value = enable ? mask : 0; + snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, mask, value); +} + +int avs_ipc_init(struct avs_ipc *ipc, struct device *dev) +{ + ipc->rx.data = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!ipc->rx.data) + return -ENOMEM; + + ipc->dev = dev; + ipc->ready = false; + ipc->default_timeout_ms = AVS_IPC_TIMEOUT_MS; + INIT_WORK(&ipc->recovery_work, avs_dsp_recovery_work); + INIT_DELAYED_WORK(&ipc->d0ix_work, avs_dsp_d0ix_work); + init_completion(&ipc->done_completion); + init_completion(&ipc->busy_completion); + spin_lock_init(&ipc->rx_lock); + mutex_init(&ipc->msg_mutex); + + return 0; +} + +void avs_ipc_block(struct avs_ipc *ipc) +{ + ipc->ready = false; + cancel_work_sync(&ipc->recovery_work); + cancel_delayed_work_sync(&ipc->d0ix_work); + ipc->in_d0ix = false; +} diff --git a/sound/soc/intel/avs/loader.c b/sound/soc/intel/avs/loader.c new file mode 100644 index 000000000000..9e3f8ff33a87 --- /dev/null +++ b/sound/soc/intel/avs/loader.c @@ -0,0 +1,692 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <sound/hdaudio.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "cldma.h" +#include "messages.h" +#include "registers.h" +#include "topology.h" + +#define AVS_ROM_STS_MASK 0xFF +#define AVS_ROM_INIT_DONE 0x1 +#define SKL_ROM_BASEFW_ENTERED 0xF +#define APL_ROM_FW_ENTERED 0x5 +#define AVS_ROM_INIT_POLLING_US 5 +#define SKL_ROM_INIT_TIMEOUT_US 1000000 +#define APL_ROM_INIT_TIMEOUT_US 300000 +#define APL_ROM_INIT_RETRIES 3 + +#define AVS_FW_INIT_POLLING_US 500 +#define AVS_FW_INIT_TIMEOUT_MS 3000 +#define AVS_FW_INIT_TIMEOUT_US (AVS_FW_INIT_TIMEOUT_MS * 1000) + +#define AVS_CLDMA_START_DELAY_MS 100 + +#define AVS_ROOT_DIR "intel/avs" +#define AVS_BASEFW_FILENAME "dsp_basefw.bin" +#define AVS_EXT_MANIFEST_MAGIC 0x31454124 +#define SKL_MANIFEST_MAGIC 0x00000006 +#define SKL_ADSPFW_OFFSET 0x284 +#define APL_MANIFEST_MAGIC 0x44504324 +#define APL_ADSPFW_OFFSET 0x2000 + +/* Occasionally, engineering (release candidate) firmware is provided for testing. */ +static bool debug_ignore_fw_version; +module_param_named(ignore_fw_version, debug_ignore_fw_version, bool, 0444); +MODULE_PARM_DESC(ignore_fw_version, "Verify FW version 0=yes (default), 1=no"); + +#define AVS_LIB_NAME_SIZE 8 + +struct avs_fw_manifest { + u32 id; + u32 len; + char name[AVS_LIB_NAME_SIZE]; + u32 preload_page_count; + u32 img_flags; + u32 feature_mask; + struct avs_fw_version version; +} __packed; + +struct avs_fw_ext_manifest { + u32 id; + u32 len; + u16 version_major; + u16 version_minor; + u32 entries; +} __packed; + +static int avs_fw_ext_manifest_strip(struct firmware *fw) +{ + struct avs_fw_ext_manifest *man; + + if (fw->size < sizeof(*man)) + return -EINVAL; + + man = (struct avs_fw_ext_manifest *)fw->data; + if (man->id == AVS_EXT_MANIFEST_MAGIC) { + fw->data += man->len; + fw->size -= man->len; + } + + return 0; +} + +static int avs_fw_manifest_offset(struct firmware *fw) +{ + /* Header type found in first DWORD of fw binary. */ + u32 magic = *(u32 *)fw->data; + + switch (magic) { + case SKL_MANIFEST_MAGIC: + return SKL_ADSPFW_OFFSET; + case APL_MANIFEST_MAGIC: + return APL_ADSPFW_OFFSET; + default: + return -EINVAL; + } +} + +static int avs_fw_manifest_strip_verify(struct avs_dev *adev, struct firmware *fw, + const struct avs_fw_version *min) +{ + struct avs_fw_manifest *man; + int offset, ret; + + ret = avs_fw_ext_manifest_strip(fw); + if (ret) + return ret; + + offset = avs_fw_manifest_offset(fw); + if (offset < 0) + return offset; + + if (fw->size < offset + sizeof(*man)) + return -EINVAL; + if (!min) + return 0; + + man = (struct avs_fw_manifest *)(fw->data + offset); + if (man->version.major != min->major || + man->version.minor != min->minor || + man->version.hotfix != min->hotfix || + man->version.build < min->build) { + dev_warn(adev->dev, "bad FW version %d.%d.%d.%d, expected %d.%d.%d.%d or newer\n", + man->version.major, man->version.minor, + man->version.hotfix, man->version.build, + min->major, min->minor, min->hotfix, min->build); + + if (!debug_ignore_fw_version) + return -EINVAL; + } + + return 0; +} + +int avs_cldma_load_basefw(struct avs_dev *adev, struct firmware *fw) +{ + struct hda_cldma *cl = &code_loader; + unsigned int reg; + int ret; + + ret = avs_dsp_op(adev, power, AVS_MAIN_CORE_MASK, true); + if (ret < 0) + return ret; + + ret = avs_dsp_op(adev, reset, AVS_MAIN_CORE_MASK, false); + if (ret < 0) + return ret; + + ret = hda_cldma_reset(cl); + if (ret < 0) { + dev_err(adev->dev, "cldma reset failed: %d\n", ret); + return ret; + } + hda_cldma_setup(cl); + + ret = avs_dsp_op(adev, stall, AVS_MAIN_CORE_MASK, false); + if (ret < 0) + return ret; + + reinit_completion(&adev->fw_ready); + avs_dsp_op(adev, int_control, true); + + /* await ROM init */ + ret = snd_hdac_adsp_readl_poll(adev, AVS_FW_REG_STATUS(adev), reg, + (reg & AVS_ROM_INIT_DONE) == AVS_ROM_INIT_DONE, + AVS_ROM_INIT_POLLING_US, SKL_ROM_INIT_TIMEOUT_US); + if (ret < 0) { + dev_err(adev->dev, "rom init timeout: %d\n", ret); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + return ret; + } + + hda_cldma_set_data(cl, (void *)fw->data, fw->size); + /* transfer firmware */ + hda_cldma_transfer(cl, 0); + ret = snd_hdac_adsp_readl_poll(adev, AVS_FW_REG_STATUS(adev), reg, + (reg & AVS_ROM_STS_MASK) == SKL_ROM_BASEFW_ENTERED, + AVS_FW_INIT_POLLING_US, AVS_FW_INIT_TIMEOUT_US); + hda_cldma_stop(cl); + if (ret < 0) { + dev_err(adev->dev, "transfer fw failed: %d\n", ret); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + return ret; + } + + return 0; +} + +int avs_cldma_load_library(struct avs_dev *adev, struct firmware *lib, u32 id) +{ + struct hda_cldma *cl = &code_loader; + int ret; + + hda_cldma_set_data(cl, (void *)lib->data, lib->size); + /* transfer modules manifest */ + hda_cldma_transfer(cl, msecs_to_jiffies(AVS_CLDMA_START_DELAY_MS)); + + /* DMA id ignored as there is only ever one code-loader DMA */ + ret = avs_ipc_load_library(adev, 0, id); + hda_cldma_stop(cl); + + if (ret) { + ret = AVS_IPC_RET(ret); + dev_err(adev->dev, "transfer lib %d failed: %d\n", id, ret); + } + + return ret; +} + +static int avs_cldma_load_module(struct avs_dev *adev, struct avs_module_entry *mentry) +{ + struct hda_cldma *cl = &code_loader; + const struct firmware *mod; + char *mod_name; + int ret; + + mod_name = kasprintf(GFP_KERNEL, "%s/%s/dsp_mod_%pUL.bin", AVS_ROOT_DIR, + adev->spec->name, mentry->uuid.b); + if (!mod_name) + return -ENOMEM; + + ret = avs_request_firmware(adev, &mod, mod_name); + kfree(mod_name); + if (ret < 0) + return ret; + + hda_cldma_set_data(cl, (void *)mod->data, mod->size); + hda_cldma_transfer(cl, msecs_to_jiffies(AVS_CLDMA_START_DELAY_MS)); + ret = avs_ipc_load_modules(adev, &mentry->module_id, 1); + hda_cldma_stop(cl); + + if (ret) { + dev_err(adev->dev, "load module %d failed: %d\n", mentry->module_id, ret); + avs_release_last_firmware(adev); + return AVS_IPC_RET(ret); + } + + return 0; +} + +int avs_cldma_transfer_modules(struct avs_dev *adev, bool load, + struct avs_module_entry *mods, u32 num_mods) +{ + u16 *mod_ids; + int ret, i; + + /* Either load to DSP or unload them to free space. */ + if (load) { + for (i = 0; i < num_mods; i++) { + ret = avs_cldma_load_module(adev, &mods[i]); + if (ret) + return ret; + } + + return 0; + } + + mod_ids = kcalloc(num_mods, sizeof(u16), GFP_KERNEL); + if (!mod_ids) + return -ENOMEM; + + for (i = 0; i < num_mods; i++) + mod_ids[i] = mods[i].module_id; + + ret = avs_ipc_unload_modules(adev, mod_ids, num_mods); + kfree(mod_ids); + if (ret) + return AVS_IPC_RET(ret); + + return 0; +} + +static int +avs_hda_init_rom(struct avs_dev *adev, unsigned int dma_id, bool purge) +{ + const struct avs_spec *const spec = adev->spec; + unsigned int corex_mask, reg; + int ret; + + corex_mask = spec->core_init_mask & ~AVS_MAIN_CORE_MASK; + + ret = avs_dsp_op(adev, power, spec->core_init_mask, true); + if (ret < 0) + goto err; + + ret = avs_dsp_op(adev, reset, AVS_MAIN_CORE_MASK, false); + if (ret < 0) + goto err; + + reinit_completion(&adev->fw_ready); + avs_dsp_op(adev, int_control, true); + + /* set boot config */ + ret = avs_ipc_set_boot_config(adev, dma_id, purge); + if (ret) { + ret = AVS_IPC_RET(ret); + goto err; + } + + /* await ROM init */ + ret = snd_hdac_adsp_readq_poll(adev, spec->rom_status, reg, + (reg & 0xF) == AVS_ROM_INIT_DONE || + (reg & 0xF) == APL_ROM_FW_ENTERED, + AVS_ROM_INIT_POLLING_US, APL_ROM_INIT_TIMEOUT_US); + if (ret < 0) { + dev_err(adev->dev, "rom init timeout: %d\n", ret); + goto err; + } + + /* power down non-main cores */ + if (corex_mask) { + ret = avs_dsp_op(adev, power, corex_mask, false); + if (ret < 0) + goto err; + } + + return 0; + +err: + avs_dsp_core_disable(adev, spec->core_init_mask); + return ret; +} + +static int avs_imr_load_basefw(struct avs_dev *adev) +{ + int ret; + + /* DMA id ignored when flashing from IMR as no transfer occurs. */ + ret = avs_hda_init_rom(adev, 0, false); + if (ret < 0) { + dev_err(adev->dev, "rom init failed: %d\n", ret); + return ret; + } + + ret = wait_for_completion_timeout(&adev->fw_ready, + msecs_to_jiffies(AVS_FW_INIT_TIMEOUT_MS)); + if (!ret) { + dev_err(adev->dev, "firmware ready timeout\n"); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + return -ETIMEDOUT; + } + + return 0; +} + +int avs_hda_load_basefw(struct avs_dev *adev, struct firmware *fw) +{ + struct snd_pcm_substream substream; + struct snd_dma_buffer dmab; + struct hdac_ext_stream *estream; + struct hdac_stream *hstream; + struct hdac_bus *bus = &adev->base.core; + unsigned int sdfmt, reg; + int ret, i; + + /* configure hda dma */ + memset(&substream, 0, sizeof(substream)); + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + estream = snd_hdac_ext_stream_assign(bus, &substream, + HDAC_EXT_STREAM_TYPE_HOST); + if (!estream) + return -ENODEV; + hstream = hdac_stream(estream); + + /* code loading performed with default format */ + sdfmt = snd_hdac_calc_stream_format(48000, 1, SNDRV_PCM_FORMAT_S32_LE, 32, 0); + ret = snd_hdac_dsp_prepare(hstream, sdfmt, fw->size, &dmab); + if (ret < 0) + goto release_stream; + + /* enable SPIB for hda stream */ + snd_hdac_ext_stream_spbcap_enable(bus, true, hstream->index); + ret = snd_hdac_ext_stream_set_spib(bus, estream, fw->size); + if (ret) + goto cleanup_resources; + + memcpy(dmab.area, fw->data, fw->size); + + for (i = 0; i < APL_ROM_INIT_RETRIES; i++) { + unsigned int dma_id = hstream->stream_tag - 1; + + ret = avs_hda_init_rom(adev, dma_id, true); + if (!ret) + break; + dev_info(adev->dev, "#%d rom init fail: %d\n", i + 1, ret); + } + if (ret < 0) + goto cleanup_resources; + + /* transfer firmware */ + snd_hdac_dsp_trigger(hstream, true); + ret = snd_hdac_adsp_readl_poll(adev, AVS_FW_REG_STATUS(adev), reg, + (reg & AVS_ROM_STS_MASK) == APL_ROM_FW_ENTERED, + AVS_FW_INIT_POLLING_US, AVS_FW_INIT_TIMEOUT_US); + snd_hdac_dsp_trigger(hstream, false); + if (ret < 0) { + dev_err(adev->dev, "transfer fw failed: %d\n", ret); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + } + +cleanup_resources: + /* disable SPIB for hda stream */ + snd_hdac_ext_stream_spbcap_enable(bus, false, hstream->index); + snd_hdac_ext_stream_set_spib(bus, estream, 0); + + snd_hdac_dsp_cleanup(hstream, &dmab); +release_stream: + snd_hdac_ext_stream_release(estream, HDAC_EXT_STREAM_TYPE_HOST); + + return ret; +} + +int avs_hda_load_library(struct avs_dev *adev, struct firmware *lib, u32 id) +{ + struct snd_pcm_substream substream; + struct snd_dma_buffer dmab; + struct hdac_ext_stream *estream; + struct hdac_stream *stream; + struct hdac_bus *bus = &adev->base.core; + unsigned int sdfmt; + int ret; + + /* configure hda dma */ + memset(&substream, 0, sizeof(substream)); + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + estream = snd_hdac_ext_stream_assign(bus, &substream, + HDAC_EXT_STREAM_TYPE_HOST); + if (!estream) + return -ENODEV; + stream = hdac_stream(estream); + + /* code loading performed with default format */ + sdfmt = snd_hdac_calc_stream_format(48000, 1, SNDRV_PCM_FORMAT_S32_LE, 32, 0); + ret = snd_hdac_dsp_prepare(stream, sdfmt, lib->size, &dmab); + if (ret < 0) + goto release_stream; + + /* enable SPIB for hda stream */ + snd_hdac_ext_stream_spbcap_enable(bus, true, stream->index); + snd_hdac_ext_stream_set_spib(bus, estream, lib->size); + + memcpy(dmab.area, lib->data, lib->size); + + /* transfer firmware */ + snd_hdac_dsp_trigger(stream, true); + ret = avs_ipc_load_library(adev, stream->stream_tag - 1, id); + snd_hdac_dsp_trigger(stream, false); + if (ret) { + dev_err(adev->dev, "transfer lib %d failed: %d\n", id, ret); + ret = AVS_IPC_RET(ret); + } + + /* disable SPIB for hda stream */ + snd_hdac_ext_stream_spbcap_enable(bus, false, stream->index); + snd_hdac_ext_stream_set_spib(bus, estream, 0); + + snd_hdac_dsp_cleanup(stream, &dmab); +release_stream: + snd_hdac_ext_stream_release(estream, HDAC_EXT_STREAM_TYPE_HOST); + + return ret; +} + +int avs_hda_transfer_modules(struct avs_dev *adev, bool load, + struct avs_module_entry *mods, u32 num_mods) +{ + /* + * All platforms without CLDMA are equipped with IMR, + * and thus the module transferring is offloaded to DSP. + */ + return 0; +} + +int avs_dsp_load_libraries(struct avs_dev *adev, struct avs_tplg_library *libs, u32 num_libs) +{ + int start, id, i = 0; + int ret; + + /* Calculate the id to assign for the next lib. */ + for (id = 0; id < adev->fw_cfg.max_libs_count; id++) + if (adev->lib_names[id][0] == '\0') + break; + if (id + num_libs >= adev->fw_cfg.max_libs_count) + return -EINVAL; + + start = id; + while (i < num_libs) { + struct avs_fw_manifest *man; + const struct firmware *fw; + struct firmware stripped_fw; + char *filename; + int j; + + filename = kasprintf(GFP_KERNEL, "%s/%s/%s", AVS_ROOT_DIR, adev->spec->name, + libs[i].name); + if (!filename) + return -ENOMEM; + + /* + * If any call after this one fails, requested firmware is not released with + * avs_release_last_firmware() as failing to load code results in need for reload + * of entire driver module. And then avs_release_firmwares() is in place already. + */ + ret = avs_request_firmware(adev, &fw, filename); + kfree(filename); + if (ret < 0) + return ret; + + stripped_fw = *fw; + ret = avs_fw_manifest_strip_verify(adev, &stripped_fw, NULL); + if (ret) { + dev_err(adev->dev, "invalid library data: %d\n", ret); + return ret; + } + + ret = avs_fw_manifest_offset(&stripped_fw); + if (ret < 0) + return ret; + man = (struct avs_fw_manifest *)(stripped_fw.data + ret); + + /* Don't load anything that's already in DSP memory. */ + for (j = 0; j < id; j++) + if (!strncmp(adev->lib_names[j], man->name, AVS_LIB_NAME_SIZE)) + goto next_lib; + + ret = avs_dsp_op(adev, load_lib, &stripped_fw, id); + if (ret) + return ret; + + strncpy(adev->lib_names[id], man->name, AVS_LIB_NAME_SIZE); + id++; +next_lib: + i++; + } + + return start == id ? 1 : 0; +} + +static int avs_dsp_load_basefw(struct avs_dev *adev) +{ + const struct avs_fw_version *min_req; + const struct avs_spec *const spec = adev->spec; + const struct firmware *fw; + struct firmware stripped_fw; + char *filename; + int ret; + + filename = kasprintf(GFP_KERNEL, "%s/%s/%s", AVS_ROOT_DIR, spec->name, AVS_BASEFW_FILENAME); + if (!filename) + return -ENOMEM; + + ret = avs_request_firmware(adev, &fw, filename); + kfree(filename); + if (ret < 0) { + dev_err(adev->dev, "request firmware failed: %d\n", ret); + return ret; + } + + stripped_fw = *fw; + min_req = &adev->spec->min_fw_version; + + ret = avs_fw_manifest_strip_verify(adev, &stripped_fw, min_req); + if (ret < 0) { + dev_err(adev->dev, "invalid firmware data: %d\n", ret); + goto release_fw; + } + + ret = avs_dsp_op(adev, load_basefw, &stripped_fw); + if (ret < 0) { + dev_err(adev->dev, "basefw load failed: %d\n", ret); + goto release_fw; + } + + ret = wait_for_completion_timeout(&adev->fw_ready, + msecs_to_jiffies(AVS_FW_INIT_TIMEOUT_MS)); + if (!ret) { + dev_err(adev->dev, "firmware ready timeout\n"); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + ret = -ETIMEDOUT; + goto release_fw; + } + + return 0; + +release_fw: + avs_release_last_firmware(adev); + return ret; +} + +int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge) +{ + struct avs_soc_component *acomp; + int ret, i; + + /* Forgo full boot if flash from IMR succeeds. */ + if (!purge && avs_platattr_test(adev, IMR)) { + ret = avs_imr_load_basefw(adev); + if (!ret) + return 0; + + dev_dbg(adev->dev, "firmware flash from imr failed: %d\n", ret); + } + + /* Full boot, clear cached data except for basefw (slot 0). */ + for (i = 1; i < adev->fw_cfg.max_libs_count; i++) + memset(adev->lib_names[i], 0, AVS_LIB_NAME_SIZE); + + avs_hda_clock_gating_enable(adev, false); + avs_hda_l1sen_enable(adev, false); + + ret = avs_dsp_load_basefw(adev); + if (ret) + goto reenable_gating; + + mutex_lock(&adev->comp_list_mutex); + list_for_each_entry(acomp, &adev->comp_list, node) { + struct avs_tplg *tplg = acomp->tplg; + + ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); + if (ret < 0) + break; + } + mutex_unlock(&adev->comp_list_mutex); + +reenable_gating: + avs_hda_l1sen_enable(adev, true); + avs_hda_clock_gating_enable(adev, true); + + if (ret < 0) + return ret; + + /* With all code loaded, refresh module information. */ + ret = avs_module_info_init(adev, true); + if (ret) { + dev_err(adev->dev, "init module info failed: %d\n", ret); + return ret; + } + + return 0; +} + +int avs_dsp_first_boot_firmware(struct avs_dev *adev) +{ + int ret, i; + + if (avs_platattr_test(adev, CLDMA)) { + ret = hda_cldma_init(&code_loader, &adev->base.core, + adev->dsp_ba, AVS_CL_DEFAULT_BUFFER_SIZE); + if (ret < 0) { + dev_err(adev->dev, "cldma init failed: %d\n", ret); + return ret; + } + } + + ret = avs_dsp_boot_firmware(adev, true); + if (ret < 0) { + dev_err(adev->dev, "firmware boot failed: %d\n", ret); + return ret; + } + + ret = avs_ipc_get_hw_config(adev, &adev->hw_cfg); + if (ret) { + dev_err(adev->dev, "get hw cfg failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + + ret = avs_ipc_get_fw_config(adev, &adev->fw_cfg); + if (ret) { + dev_err(adev->dev, "get fw cfg failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + + adev->core_refs = devm_kcalloc(adev->dev, adev->hw_cfg.dsp_cores, + sizeof(*adev->core_refs), GFP_KERNEL); + adev->lib_names = devm_kcalloc(adev->dev, adev->fw_cfg.max_libs_count, + sizeof(*adev->lib_names), GFP_KERNEL); + if (!adev->core_refs || !adev->lib_names) + return -ENOMEM; + + for (i = 0; i < adev->fw_cfg.max_libs_count; i++) { + adev->lib_names[i] = devm_kzalloc(adev->dev, AVS_LIB_NAME_SIZE, GFP_KERNEL); + if (!adev->lib_names[i]) + return -ENOMEM; + } + + /* basefw always occupies slot 0 */ + strcpy(&adev->lib_names[0][0], "BASEFW"); + + ida_init(&adev->ppl_ida); + + return 0; +} diff --git a/sound/soc/intel/avs/messages.c b/sound/soc/intel/avs/messages.c new file mode 100644 index 000000000000..d4bcee1aabcf --- /dev/null +++ b/sound/soc/intel/avs/messages.c @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/slab.h> +#include "avs.h" +#include "messages.h" + +#define AVS_CL_TIMEOUT_MS 5000 + +int avs_ipc_set_boot_config(struct avs_dev *adev, u32 dma_id, u32 purge) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(ROM_CONTROL); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.boot_cfg.rom_ctrl_msg_type = AVS_ROM_SET_BOOT_CONFIG; + msg.boot_cfg.dma_id = dma_id; + msg.boot_cfg.purge_request = purge; + request.header = msg.val; + + ret = avs_dsp_send_rom_msg(adev, &request); + if (ret) + avs_ipc_err(adev, &request, "set boot config", ret); + + return ret; +} + +int avs_ipc_load_modules(struct avs_dev *adev, u16 *mod_ids, u32 num_mod_ids) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(LOAD_MULTIPLE_MODULES); + struct avs_ipc_msg request; + int ret; + + msg.load_multi_mods.mod_cnt = num_mod_ids; + request.header = msg.val; + request.data = mod_ids; + request.size = sizeof(*mod_ids) * num_mod_ids; + + ret = avs_dsp_send_msg_timeout(adev, &request, NULL, AVS_CL_TIMEOUT_MS); + if (ret) + avs_ipc_err(adev, &request, "load multiple modules", ret); + + return ret; +} + +int avs_ipc_unload_modules(struct avs_dev *adev, u16 *mod_ids, u32 num_mod_ids) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(UNLOAD_MULTIPLE_MODULES); + struct avs_ipc_msg request; + int ret; + + msg.load_multi_mods.mod_cnt = num_mod_ids; + request.header = msg.val; + request.data = mod_ids; + request.size = sizeof(*mod_ids) * num_mod_ids; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "unload multiple modules", ret); + + return ret; +} + +int avs_ipc_load_library(struct avs_dev *adev, u32 dma_id, u32 lib_id) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(LOAD_LIBRARY); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.load_lib.dma_id = dma_id; + msg.load_lib.lib_id = lib_id; + request.header = msg.val; + + ret = avs_dsp_send_msg_timeout(adev, &request, NULL, AVS_CL_TIMEOUT_MS); + if (ret) + avs_ipc_err(adev, &request, "load library", ret); + + return ret; +} + +int avs_ipc_create_pipeline(struct avs_dev *adev, u16 req_size, u8 priority, + u8 instance_id, bool lp, u16 attributes) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(CREATE_PIPELINE); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.create_ppl.ppl_mem_size = req_size; + msg.create_ppl.ppl_priority = priority; + msg.create_ppl.instance_id = instance_id; + msg.ext.create_ppl.lp = lp; + msg.ext.create_ppl.attributes = attributes; + request.header = msg.val; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "create pipeline", ret); + + return ret; +} + +int avs_ipc_delete_pipeline(struct avs_dev *adev, u8 instance_id) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(DELETE_PIPELINE); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.ppl.instance_id = instance_id; + request.header = msg.val; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "delete pipeline", ret); + + return ret; +} + +int avs_ipc_set_pipeline_state(struct avs_dev *adev, u8 instance_id, + enum avs_pipeline_state state) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(SET_PIPELINE_STATE); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.set_ppl_state.ppl_id = instance_id; + msg.set_ppl_state.state = state; + request.header = msg.val; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "set pipeline state", ret); + + return ret; +} + +int avs_ipc_get_pipeline_state(struct avs_dev *adev, u8 instance_id, + enum avs_pipeline_state *state) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(GET_PIPELINE_STATE); + struct avs_ipc_msg request = {{0}}; + struct avs_ipc_msg reply = {{0}}; + int ret; + + msg.get_ppl_state.ppl_id = instance_id; + request.header = msg.val; + + ret = avs_dsp_send_msg(adev, &request, &reply); + if (ret) { + avs_ipc_err(adev, &request, "get pipeline state", ret); + return ret; + } + + *state = reply.rsp.ext.get_ppl_state.state; + return ret; +} + +/* + * avs_ipc_init_instance - Initialize module instance + * + * @adev: Driver context + * @module_id: Module-type id + * @instance_id: Unique module instance id + * @ppl_id: Parent pipeline id + * @core_id: DSP core to allocate module on + * @domain: Processing domain (low latency or data processing) + * @param: Module-type specific configuration + * @param_size: Size of @param in bytes + * + * Argument verification, as well as pipeline state checks are done by the + * firmware. + * + * Note: @ppl_id and @core_id are independent of each other as single pipeline + * can be composed of module instances located on different DSP cores. + */ +int avs_ipc_init_instance(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 ppl_id, u8 core_id, u8 domain, + void *param, u32 param_size) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(INIT_INSTANCE); + struct avs_ipc_msg request; + int ret; + + msg.module_id = module_id; + msg.instance_id = instance_id; + /* firmware expects size provided in dwords */ + msg.ext.init_instance.param_block_size = DIV_ROUND_UP(param_size, sizeof(u32)); + msg.ext.init_instance.ppl_instance_id = ppl_id; + msg.ext.init_instance.core_id = core_id; + msg.ext.init_instance.proc_domain = domain; + + request.header = msg.val; + request.data = param; + request.size = param_size; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "init instance", ret); + + return ret; +} + +/* + * avs_ipc_delete_instance - Delete module instance + * + * @adev: Driver context + * @module_id: Module-type id + * @instance_id: Unique module instance id + * + * Argument verification, as well as pipeline state checks are done by the + * firmware. + * + * Note: only standalone modules i.e. without a parent pipeline shall be + * deleted using this IPC message. In all other cases, pipeline owning the + * modules performs cleanup automatically when it is deleted. + */ +int avs_ipc_delete_instance(struct avs_dev *adev, u16 module_id, u8 instance_id) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(DELETE_INSTANCE); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.module_id = module_id; + msg.instance_id = instance_id; + request.header = msg.val; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "delete instance", ret); + + return ret; +} + +/* + * avs_ipc_bind - Bind two module instances + * + * @adev: Driver context + * @module_id: Source module-type id + * @instance_id: Source module instance id + * @dst_module_id: Sink module-type id + * @dst_instance_id: Sink module instance id + * @dst_queue: Sink module pin to bind @src_queue with + * @src_queue: Source module pin to bind @dst_queue with + */ +int avs_ipc_bind(struct avs_dev *adev, u16 module_id, u8 instance_id, + u16 dst_module_id, u8 dst_instance_id, + u8 dst_queue, u8 src_queue) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(BIND); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.module_id = module_id; + msg.instance_id = instance_id; + msg.ext.bind_unbind.dst_module_id = dst_module_id; + msg.ext.bind_unbind.dst_instance_id = dst_instance_id; + msg.ext.bind_unbind.dst_queue = dst_queue; + msg.ext.bind_unbind.src_queue = src_queue; + request.header = msg.val; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "bind modules", ret); + + return ret; +} + +/* + * avs_ipc_unbind - Unbind two module instances + * + * @adev: Driver context + * @module_id: Source module-type id + * @instance_id: Source module instance id + * @dst_module_id: Sink module-type id + * @dst_instance_id: Sink module instance id + * @dst_queue: Sink module pin to unbind @src_queue from + * @src_queue: Source module pin to unbind @dst_queue from + */ +int avs_ipc_unbind(struct avs_dev *adev, u16 module_id, u8 instance_id, + u16 dst_module_id, u8 dst_instance_id, + u8 dst_queue, u8 src_queue) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(UNBIND); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.module_id = module_id; + msg.instance_id = instance_id; + msg.ext.bind_unbind.dst_module_id = dst_module_id; + msg.ext.bind_unbind.dst_instance_id = dst_instance_id; + msg.ext.bind_unbind.dst_queue = dst_queue; + msg.ext.bind_unbind.src_queue = src_queue; + request.header = msg.val; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "unbind modules", ret); + + return ret; +} + +static int __avs_ipc_set_large_config(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 param_id, bool init_block, bool final_block, + u8 *request_data, size_t request_size, size_t off_size) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(LARGE_CONFIG_SET); + struct avs_ipc_msg request; + int ret; + + msg.module_id = module_id; + msg.instance_id = instance_id; + msg.ext.large_config.data_off_size = off_size; + msg.ext.large_config.large_param_id = param_id; + msg.ext.large_config.final_block = final_block; + msg.ext.large_config.init_block = init_block; + + request.header = msg.val; + request.data = request_data; + request.size = request_size; + + ret = avs_dsp_send_msg(adev, &request, NULL); + if (ret) + avs_ipc_err(adev, &request, "large config set", ret); + + return ret; +} + +int avs_ipc_set_large_config(struct avs_dev *adev, u16 module_id, + u8 instance_id, u8 param_id, + u8 *request, size_t request_size) +{ + size_t remaining, tx_size; + bool final; + int ret; + + remaining = request_size; + tx_size = min_t(size_t, AVS_MAILBOX_SIZE, remaining); + final = (tx_size == remaining); + + /* Initial request states total payload size. */ + ret = __avs_ipc_set_large_config(adev, module_id, instance_id, + param_id, 1, final, request, tx_size, + request_size); + if (ret) + return ret; + + remaining -= tx_size; + + /* Loop the rest only when payload exceeds mailbox's size. */ + while (remaining) { + size_t offset; + + offset = request_size - remaining; + tx_size = min_t(size_t, AVS_MAILBOX_SIZE, remaining); + final = (tx_size == remaining); + + ret = __avs_ipc_set_large_config(adev, module_id, instance_id, + param_id, 0, final, + request + offset, tx_size, + offset); + if (ret) + return ret; + + remaining -= tx_size; + } + + return 0; +} + +int avs_ipc_get_large_config(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 param_id, u8 *request_data, size_t request_size, + u8 **reply_data, size_t *reply_size) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(LARGE_CONFIG_GET); + struct avs_ipc_msg request; + struct avs_ipc_msg reply = {{0}}; + void *buf; + int ret; + + reply.data = kzalloc(AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!reply.data) + return -ENOMEM; + + msg.module_id = module_id; + msg.instance_id = instance_id; + msg.ext.large_config.data_off_size = request_size; + msg.ext.large_config.large_param_id = param_id; + /* final_block is always 0 on request. Updated by fw on reply. */ + msg.ext.large_config.final_block = 0; + msg.ext.large_config.init_block = 1; + + request.header = msg.val; + request.data = request_data; + request.size = request_size; + reply.size = AVS_MAILBOX_SIZE; + + ret = avs_dsp_send_msg(adev, &request, &reply); + if (ret) { + avs_ipc_err(adev, &request, "large config get", ret); + kfree(reply.data); + return ret; + } + + buf = krealloc(reply.data, reply.size, GFP_KERNEL); + if (!buf) { + kfree(reply.data); + return -ENOMEM; + } + + *reply_data = buf; + *reply_size = reply.size; + + return 0; +} + +int avs_ipc_set_dx(struct avs_dev *adev, u32 core_mask, bool powerup) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(SET_DX); + struct avs_ipc_msg request; + struct avs_dxstate_info dx; + int ret; + + dx.core_mask = core_mask; + dx.dx_mask = powerup ? core_mask : 0; + request.header = msg.val; + request.data = &dx; + request.size = sizeof(dx); + + ret = avs_dsp_send_pm_msg(adev, &request, NULL, true); + if (ret) + avs_ipc_err(adev, &request, "set dx", ret); + + return ret; +} + +/* + * avs_ipc_set_d0ix - Set power gating policy (entering D0IX substates) + * + * @enable_pg: Whether to enable or disable power gating + * @streaming: Whether a stream is running when transitioning + */ +int avs_ipc_set_d0ix(struct avs_dev *adev, bool enable_pg, bool streaming) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(SET_D0IX); + struct avs_ipc_msg request = {{0}}; + int ret; + + msg.ext.set_d0ix.wake = enable_pg; + msg.ext.set_d0ix.streaming = streaming; + + request.header = msg.val; + + ret = avs_dsp_send_pm_msg(adev, &request, NULL, false); + if (ret) + avs_ipc_err(adev, &request, "set d0ix", ret); + + return ret; +} + +int avs_ipc_get_fw_config(struct avs_dev *adev, struct avs_fw_cfg *cfg) +{ + struct avs_tlv *tlv; + size_t payload_size; + size_t offset = 0; + u8 *payload; + int ret; + + ret = avs_ipc_get_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_FIRMWARE_CONFIG, NULL, 0, + &payload, &payload_size); + if (ret) + return ret; + /* Non-zero payload expected for FIRMWARE_CONFIG. */ + if (!payload_size) + return -EREMOTEIO; + + while (offset < payload_size) { + tlv = (struct avs_tlv *)(payload + offset); + + switch (tlv->type) { + case AVS_FW_CFG_FW_VERSION: + memcpy(&cfg->fw_version, tlv->value, sizeof(cfg->fw_version)); + break; + + case AVS_FW_CFG_MEMORY_RECLAIMED: + cfg->memory_reclaimed = *tlv->value; + break; + + case AVS_FW_CFG_SLOW_CLOCK_FREQ_HZ: + cfg->slow_clock_freq_hz = *tlv->value; + break; + + case AVS_FW_CFG_FAST_CLOCK_FREQ_HZ: + cfg->fast_clock_freq_hz = *tlv->value; + break; + + case AVS_FW_CFG_ALH_SUPPORT_LEVEL: + cfg->alh_support = *tlv->value; + break; + + case AVS_FW_CFG_IPC_DL_MAILBOX_BYTES: + cfg->ipc_dl_mailbox_bytes = *tlv->value; + break; + + case AVS_FW_CFG_IPC_UL_MAILBOX_BYTES: + cfg->ipc_ul_mailbox_bytes = *tlv->value; + break; + + case AVS_FW_CFG_TRACE_LOG_BYTES: + cfg->trace_log_bytes = *tlv->value; + break; + + case AVS_FW_CFG_MAX_PPL_COUNT: + cfg->max_ppl_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_ASTATE_COUNT: + cfg->max_astate_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_MODULE_PIN_COUNT: + cfg->max_module_pin_count = *tlv->value; + break; + + case AVS_FW_CFG_MODULES_COUNT: + cfg->modules_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_MOD_INST_COUNT: + cfg->max_mod_inst_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_LL_TASKS_PER_PRI_COUNT: + cfg->max_ll_tasks_per_pri_count = *tlv->value; + break; + + case AVS_FW_CFG_LL_PRI_COUNT: + cfg->ll_pri_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_DP_TASKS_COUNT: + cfg->max_dp_tasks_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_LIBS_COUNT: + cfg->max_libs_count = *tlv->value; + break; + + case AVS_FW_CFG_XTAL_FREQ_HZ: + cfg->xtal_freq_hz = *tlv->value; + break; + + case AVS_FW_CFG_POWER_GATING_POLICY: + cfg->power_gating_policy = *tlv->value; + break; + + /* Known but not useful to us. */ + case AVS_FW_CFG_DMA_BUFFER_CONFIG: + case AVS_FW_CFG_SCHEDULER_CONFIG: + case AVS_FW_CFG_CLOCKS_CONFIG: + case AVS_FW_CFG_RESERVED: + break; + + default: + dev_info(adev->dev, "Unrecognized fw param: %d\n", tlv->type); + break; + } + + offset += sizeof(*tlv) + tlv->length; + } + + /* No longer needed, free it as it's owned by the get_large_config() caller. */ + kfree(payload); + return ret; +} + +int avs_ipc_get_hw_config(struct avs_dev *adev, struct avs_hw_cfg *cfg) +{ + struct avs_tlv *tlv; + size_t payload_size; + size_t size, offset = 0; + u8 *payload; + int ret; + + ret = avs_ipc_get_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_HARDWARE_CONFIG, NULL, 0, + &payload, &payload_size); + if (ret) + return ret; + /* Non-zero payload expected for HARDWARE_CONFIG. */ + if (!payload_size) + return -EREMOTEIO; + + while (offset < payload_size) { + tlv = (struct avs_tlv *)(payload + offset); + + switch (tlv->type) { + case AVS_HW_CFG_AVS_VER: + cfg->avs_version = *tlv->value; + break; + + case AVS_HW_CFG_DSP_CORES: + cfg->dsp_cores = *tlv->value; + break; + + case AVS_HW_CFG_MEM_PAGE_BYTES: + cfg->mem_page_bytes = *tlv->value; + break; + + case AVS_HW_CFG_TOTAL_PHYS_MEM_PAGES: + cfg->total_phys_mem_pages = *tlv->value; + break; + + case AVS_HW_CFG_I2S_CAPS: + cfg->i2s_caps.i2s_version = tlv->value[0]; + size = tlv->value[1]; + cfg->i2s_caps.ctrl_count = size; + if (!size) + break; + + /* Multiply to get entire array size. */ + size *= sizeof(*cfg->i2s_caps.ctrl_base_addr); + cfg->i2s_caps.ctrl_base_addr = devm_kmemdup(adev->dev, + &tlv->value[2], + size, GFP_KERNEL); + if (!cfg->i2s_caps.ctrl_base_addr) { + ret = -ENOMEM; + goto exit; + } + break; + + case AVS_HW_CFG_GATEWAY_COUNT: + cfg->gateway_count = *tlv->value; + break; + + case AVS_HW_CFG_HP_EBB_COUNT: + cfg->hp_ebb_count = *tlv->value; + break; + + case AVS_HW_CFG_LP_EBB_COUNT: + cfg->lp_ebb_count = *tlv->value; + break; + + case AVS_HW_CFG_EBB_SIZE_BYTES: + cfg->ebb_size_bytes = *tlv->value; + break; + + case AVS_HW_CFG_GPDMA_CAPS: + break; + + default: + dev_info(adev->dev, "Unrecognized hw config: %d\n", tlv->type); + break; + } + + offset += sizeof(*tlv) + tlv->length; + } + +exit: + /* No longer needed, free it as it's owned by the get_large_config() caller. */ + kfree(payload); + return ret; +} + +int avs_ipc_get_modules_info(struct avs_dev *adev, struct avs_mods_info **info) +{ + size_t payload_size; + u8 *payload; + int ret; + + ret = avs_ipc_get_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_MODULES_INFO, NULL, 0, + &payload, &payload_size); + if (ret) + return ret; + /* Non-zero payload expected for MODULES_INFO. */ + if (!payload_size) + return -EREMOTEIO; + + *info = (struct avs_mods_info *)payload; + return 0; +} + +int avs_ipc_set_enable_logs(struct avs_dev *adev, u8 *log_info, size_t size) +{ + int ret; + + ret = avs_ipc_set_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_ENABLE_LOGS, log_info, size); + if (ret) + dev_err(adev->dev, "enable logs failed: %d\n", ret); + + return ret; +} + +int avs_ipc_set_system_time(struct avs_dev *adev) +{ + struct avs_sys_time sys_time; + int ret; + u64 us; + + /* firmware expects UTC time in micro seconds */ + us = ktime_to_us(ktime_get()); + sys_time.val_l = us & UINT_MAX; + sys_time.val_u = us >> 32; + + ret = avs_ipc_set_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_SYSTEM_TIME, (u8 *)&sys_time, sizeof(sys_time)); + if (ret) + dev_err(adev->dev, "set system time failed: %d\n", ret); + + return ret; +} + +int avs_ipc_copier_set_sink_format(struct avs_dev *adev, u16 module_id, + u8 instance_id, u32 sink_id, + const struct avs_audio_format *src_fmt, + const struct avs_audio_format *sink_fmt) +{ + struct avs_copier_sink_format cpr_fmt; + + cpr_fmt.sink_id = sink_id; + /* Firmware expects driver to resend copier's input format. */ + cpr_fmt.src_fmt = *src_fmt; + cpr_fmt.sink_fmt = *sink_fmt; + + return avs_ipc_set_large_config(adev, module_id, instance_id, + AVS_COPIER_SET_SINK_FORMAT, + (u8 *)&cpr_fmt, sizeof(cpr_fmt)); +} diff --git a/sound/soc/intel/avs/messages.h b/sound/soc/intel/avs/messages.h new file mode 100644 index 000000000000..c0f90dba9af8 --- /dev/null +++ b/sound/soc/intel/avs/messages.h @@ -0,0 +1,803 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation. All rights reserved. + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_MSGS_H +#define __SOUND_SOC_INTEL_AVS_MSGS_H + +struct avs_dev; + +#define AVS_MAILBOX_SIZE 4096 + +enum avs_msg_target { + AVS_FW_GEN_MSG = 0, + AVS_MOD_MSG = 1 +}; + +enum avs_msg_direction { + AVS_MSG_REQUEST = 0, + AVS_MSG_REPLY = 1 +}; + +enum avs_global_msg_type { + AVS_GLB_ROM_CONTROL = 1, + AVS_GLB_LOAD_MULTIPLE_MODULES = 15, + AVS_GLB_UNLOAD_MULTIPLE_MODULES = 16, + AVS_GLB_CREATE_PIPELINE = 17, + AVS_GLB_DELETE_PIPELINE = 18, + AVS_GLB_SET_PIPELINE_STATE = 19, + AVS_GLB_GET_PIPELINE_STATE = 20, + AVS_GLB_LOAD_LIBRARY = 24, + AVS_GLB_NOTIFICATION = 27, +}; + +union avs_global_msg { + u64 val; + struct { + union { + u32 primary; + struct { + u32 rsvd:24; + u32 global_msg_type:5; + u32 msg_direction:1; + u32 msg_target:1; + }; + /* set boot config */ + struct { + u32 rom_ctrl_msg_type:9; + u32 dma_id:5; + u32 purge_request:1; + } boot_cfg; + /* module loading */ + struct { + u32 mod_cnt:8; + } load_multi_mods; + /* pipeline management */ + struct { + u32 ppl_mem_size:11; + u32 ppl_priority:5; + u32 instance_id:8; + } create_ppl; + struct { + u32 rsvd:16; + u32 instance_id:8; + } ppl; /* generic ppl request */ + struct { + u32 state:16; + u32 ppl_id:8; + } set_ppl_state; + struct { + u32 ppl_id:8; + } get_ppl_state; + /* library loading */ + struct { + u32 dma_id:5; + u32 rsvd:11; + u32 lib_id:4; + } load_lib; + }; + union { + u32 val; + /* pipeline management */ + struct { + u32 lp:1; /* low power flag */ + u32 rsvd:3; + u32 attributes:16; /* additional scheduling flags */ + } create_ppl; + } ext; + }; +} __packed; + +struct avs_tlv { + u32 type; + u32 length; + u32 value[]; +} __packed; + +enum avs_module_msg_type { + AVS_MOD_INIT_INSTANCE = 0, + AVS_MOD_LARGE_CONFIG_GET = 3, + AVS_MOD_LARGE_CONFIG_SET = 4, + AVS_MOD_BIND = 5, + AVS_MOD_UNBIND = 6, + AVS_MOD_SET_DX = 7, + AVS_MOD_SET_D0IX = 8, + AVS_MOD_DELETE_INSTANCE = 11, +}; + +union avs_module_msg { + u64 val; + struct { + union { + u32 primary; + struct { + u32 module_id:16; + u32 instance_id:8; + u32 module_msg_type:5; + u32 msg_direction:1; + u32 msg_target:1; + }; + }; + union { + u32 val; + struct { + u32 param_block_size:16; + u32 ppl_instance_id:8; + u32 core_id:4; + u32 proc_domain:1; + } init_instance; + struct { + u32 data_off_size:20; + u32 large_param_id:8; + u32 final_block:1; + u32 init_block:1; + } large_config; + struct { + u32 dst_module_id:16; + u32 dst_instance_id:8; + u32 dst_queue:3; + u32 src_queue:3; + } bind_unbind; + struct { + u32 wake:1; + u32 streaming:1; + } set_d0ix; + } ext; + }; +} __packed; + +union avs_reply_msg { + u64 val; + struct { + union { + u32 primary; + struct { + u32 status:24; + u32 global_msg_type:5; + u32 msg_direction:1; + u32 msg_target:1; + }; + }; + union { + u32 val; + /* module loading */ + struct { + u32 err_mod_id:16; + } load_multi_mods; + /* pipeline management */ + struct { + u32 state:5; + } get_ppl_state; + /* module management */ + struct { + u32 data_off_size:20; + u32 large_param_id:8; + u32 final_block:1; + u32 init_block:1; + } large_config; + } ext; + }; +} __packed; + +enum avs_notify_msg_type { + AVS_NOTIFY_PHRASE_DETECTED = 4, + AVS_NOTIFY_RESOURCE_EVENT = 5, + AVS_NOTIFY_LOG_BUFFER_STATUS = 6, + AVS_NOTIFY_FW_READY = 8, + AVS_NOTIFY_EXCEPTION_CAUGHT = 10, + AVS_NOTIFY_MODULE_EVENT = 12, +}; + +union avs_notify_msg { + u64 val; + struct { + union { + u32 primary; + struct { + u32 rsvd:16; + u32 notify_msg_type:8; + u32 global_msg_type:5; + u32 msg_direction:1; + u32 msg_target:1; + }; + struct { + u16 rsvd:12; + u16 core:4; + } log; + }; + union { + u32 val; + struct { + u32 core_id:2; + u32 stack_dump_size:16; + } coredump; + } ext; + }; +} __packed; + +#define AVS_MSG(hdr) { .val = hdr } + +#define AVS_GLOBAL_REQUEST(msg_type) \ +{ \ + .global_msg_type = AVS_GLB_##msg_type, \ + .msg_direction = AVS_MSG_REQUEST, \ + .msg_target = AVS_FW_GEN_MSG, \ +} + +#define AVS_MODULE_REQUEST(msg_type) \ +{ \ + .module_msg_type = AVS_MOD_##msg_type, \ + .msg_direction = AVS_MSG_REQUEST, \ + .msg_target = AVS_MOD_MSG, \ +} + +#define AVS_NOTIFICATION(msg_type) \ +{ \ + .notify_msg_type = AVS_NOTIFY_##msg_type,\ + .global_msg_type = AVS_GLB_NOTIFICATION,\ + .msg_direction = AVS_MSG_REPLY, \ + .msg_target = AVS_FW_GEN_MSG, \ +} + +#define avs_msg_is_reply(hdr) \ +({ \ + union avs_reply_msg __msg = AVS_MSG(hdr); \ + __msg.msg_direction == AVS_MSG_REPLY && \ + __msg.global_msg_type != AVS_GLB_NOTIFICATION; \ +}) + +/* Notification types */ + +struct avs_notify_voice_data { + u16 kpd_score; + u16 reserved; +} __packed; + +struct avs_notify_res_data { + u32 resource_type; + u32 resource_id; + u32 event_type; + u32 reserved; + u32 data[6]; +} __packed; + +struct avs_notify_mod_data { + u32 module_instance_id; + u32 event_id; + u32 data_size; + u32 data[]; +} __packed; + +/* ROM messages */ +enum avs_rom_control_msg_type { + AVS_ROM_SET_BOOT_CONFIG = 0, +}; + +int avs_ipc_set_boot_config(struct avs_dev *adev, u32 dma_id, u32 purge); + +/* Code loading messages */ +int avs_ipc_load_modules(struct avs_dev *adev, u16 *mod_ids, u32 num_mod_ids); +int avs_ipc_unload_modules(struct avs_dev *adev, u16 *mod_ids, u32 num_mod_ids); +int avs_ipc_load_library(struct avs_dev *adev, u32 dma_id, u32 lib_id); + +/* Pipeline management messages */ +enum avs_pipeline_state { + AVS_PPL_STATE_INVALID, + AVS_PPL_STATE_UNINITIALIZED, + AVS_PPL_STATE_RESET, + AVS_PPL_STATE_PAUSED, + AVS_PPL_STATE_RUNNING, +}; + +int avs_ipc_create_pipeline(struct avs_dev *adev, u16 req_size, u8 priority, + u8 instance_id, bool lp, u16 attributes); +int avs_ipc_delete_pipeline(struct avs_dev *adev, u8 instance_id); +int avs_ipc_set_pipeline_state(struct avs_dev *adev, u8 instance_id, + enum avs_pipeline_state state); +int avs_ipc_get_pipeline_state(struct avs_dev *adev, u8 instance_id, + enum avs_pipeline_state *state); + +/* Module management messages */ +int avs_ipc_init_instance(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 ppl_id, u8 core_id, u8 domain, + void *param, u32 param_size); +int avs_ipc_delete_instance(struct avs_dev *adev, u16 module_id, u8 instance_id); +int avs_ipc_bind(struct avs_dev *adev, u16 module_id, u8 instance_id, + u16 dst_module_id, u8 dst_instance_id, + u8 dst_queue, u8 src_queue); +int avs_ipc_unbind(struct avs_dev *adev, u16 module_id, u8 instance_id, + u16 dst_module_id, u8 dst_instance_id, + u8 dst_queue, u8 src_queue); +int avs_ipc_set_large_config(struct avs_dev *adev, u16 module_id, + u8 instance_id, u8 param_id, + u8 *request, size_t request_size); +int avs_ipc_get_large_config(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 param_id, u8 *request_data, size_t request_size, + u8 **reply_data, size_t *reply_size); + +/* DSP cores and domains power management messages */ +struct avs_dxstate_info { + u32 core_mask; /* which cores are subject for power transition */ + u32 dx_mask; /* bit[n]=1 core n goes to D0, bit[n]=0 it goes to D3 */ +} __packed; + +int avs_ipc_set_dx(struct avs_dev *adev, u32 core_mask, bool powerup); +int avs_ipc_set_d0ix(struct avs_dev *adev, bool enable_pg, bool streaming); + +/* Base-firmware runtime parameters */ + +#define AVS_BASEFW_MOD_ID 0 +#define AVS_BASEFW_INST_ID 0 + +enum avs_basefw_runtime_param { + AVS_BASEFW_ENABLE_LOGS = 6, + AVS_BASEFW_FIRMWARE_CONFIG = 7, + AVS_BASEFW_HARDWARE_CONFIG = 8, + AVS_BASEFW_MODULES_INFO = 9, + AVS_BASEFW_LIBRARIES_INFO = 16, + AVS_BASEFW_SYSTEM_TIME = 20, +}; + +enum avs_log_enable { + AVS_LOG_DISABLE = 0, + AVS_LOG_ENABLE = 1 +}; + +enum avs_skl_log_priority { + AVS_SKL_LOG_CRITICAL = 1, + AVS_SKL_LOG_HIGH, + AVS_SKL_LOG_MEDIUM, + AVS_SKL_LOG_LOW, + AVS_SKL_LOG_VERBOSE, +}; + +struct skl_log_state { + u32 enable; + u32 min_priority; +} __packed; + +struct skl_log_state_info { + u32 core_mask; + struct skl_log_state logs_core[]; +} __packed; + +struct apl_log_state_info { + u32 aging_timer_period; + u32 fifo_full_timer_period; + u32 core_mask; + struct skl_log_state logs_core[]; +} __packed; + +int avs_ipc_set_enable_logs(struct avs_dev *adev, u8 *log_info, size_t size); + +struct avs_fw_version { + u16 major; + u16 minor; + u16 hotfix; + u16 build; +}; + +enum avs_fw_cfg_params { + AVS_FW_CFG_FW_VERSION = 0, + AVS_FW_CFG_MEMORY_RECLAIMED, + AVS_FW_CFG_SLOW_CLOCK_FREQ_HZ, + AVS_FW_CFG_FAST_CLOCK_FREQ_HZ, + AVS_FW_CFG_DMA_BUFFER_CONFIG, + AVS_FW_CFG_ALH_SUPPORT_LEVEL, + AVS_FW_CFG_IPC_DL_MAILBOX_BYTES, + AVS_FW_CFG_IPC_UL_MAILBOX_BYTES, + AVS_FW_CFG_TRACE_LOG_BYTES, + AVS_FW_CFG_MAX_PPL_COUNT, + AVS_FW_CFG_MAX_ASTATE_COUNT, + AVS_FW_CFG_MAX_MODULE_PIN_COUNT, + AVS_FW_CFG_MODULES_COUNT, + AVS_FW_CFG_MAX_MOD_INST_COUNT, + AVS_FW_CFG_MAX_LL_TASKS_PER_PRI_COUNT, + AVS_FW_CFG_LL_PRI_COUNT, + AVS_FW_CFG_MAX_DP_TASKS_COUNT, + AVS_FW_CFG_MAX_LIBS_COUNT, + AVS_FW_CFG_SCHEDULER_CONFIG, + AVS_FW_CFG_XTAL_FREQ_HZ, + AVS_FW_CFG_CLOCKS_CONFIG, + AVS_FW_CFG_RESERVED, + AVS_FW_CFG_POWER_GATING_POLICY, + AVS_FW_CFG_ASSERT_MODE, +}; + +struct avs_fw_cfg { + struct avs_fw_version fw_version; + u32 memory_reclaimed; + u32 slow_clock_freq_hz; + u32 fast_clock_freq_hz; + u32 alh_support; + u32 ipc_dl_mailbox_bytes; + u32 ipc_ul_mailbox_bytes; + u32 trace_log_bytes; + u32 max_ppl_count; + u32 max_astate_count; + u32 max_module_pin_count; + u32 modules_count; + u32 max_mod_inst_count; + u32 max_ll_tasks_per_pri_count; + u32 ll_pri_count; + u32 max_dp_tasks_count; + u32 max_libs_count; + u32 xtal_freq_hz; + u32 power_gating_policy; +}; + +int avs_ipc_get_fw_config(struct avs_dev *adev, struct avs_fw_cfg *cfg); + +enum avs_hw_cfg_params { + AVS_HW_CFG_AVS_VER, + AVS_HW_CFG_DSP_CORES, + AVS_HW_CFG_MEM_PAGE_BYTES, + AVS_HW_CFG_TOTAL_PHYS_MEM_PAGES, + AVS_HW_CFG_I2S_CAPS, + AVS_HW_CFG_GPDMA_CAPS, + AVS_HW_CFG_GATEWAY_COUNT, + AVS_HW_CFG_HP_EBB_COUNT, + AVS_HW_CFG_LP_EBB_COUNT, + AVS_HW_CFG_EBB_SIZE_BYTES, +}; + +enum avs_iface_version { + AVS_AVS_VER_1_5 = 0x10005, + AVS_AVS_VER_1_8 = 0x10008, +}; + +enum avs_i2s_version { + AVS_I2S_VER_15_SKYLAKE = 0x00000, + AVS_I2S_VER_15_BROXTON = 0x10000, + AVS_I2S_VER_15_BROXTON_P = 0x20000, + AVS_I2S_VER_18_KBL_CNL = 0x30000, +}; + +struct avs_i2s_caps { + u32 i2s_version; + u32 ctrl_count; + u32 *ctrl_base_addr; +}; + +struct avs_hw_cfg { + u32 avs_version; + u32 dsp_cores; + u32 mem_page_bytes; + u32 total_phys_mem_pages; + struct avs_i2s_caps i2s_caps; + u32 gateway_count; + u32 hp_ebb_count; + u32 lp_ebb_count; + u32 ebb_size_bytes; +}; + +int avs_ipc_get_hw_config(struct avs_dev *adev, struct avs_hw_cfg *cfg); + +#define AVS_MODULE_LOAD_TYPE_BUILTIN 0 +#define AVS_MODULE_LOAD_TYPE_LOADABLE 1 +#define AVS_MODULE_STATE_LOADED BIT(0) + +struct avs_module_type { + u32 load_type:4; + u32 auto_start:1; + u32 domain_ll:1; + u32 domain_dp:1; + u32 lib_code:1; + u32 rsvd:24; +} __packed; + +union avs_segment_flags { + u32 ul; + struct { + u32 contents:1; + u32 alloc:1; + u32 load:1; + u32 readonly:1; + u32 code:1; + u32 data:1; + u32 rsvd_1:2; + u32 type:4; + u32 rsvd_2:4; + u32 length:16; + }; +} __packed; + +struct avs_segment_desc { + union avs_segment_flags flags; + u32 v_base_addr; + u32 file_offset; +} __packed; + +struct avs_module_entry { + u16 module_id; + u16 state_flags; + u8 name[8]; + guid_t uuid; + struct avs_module_type type; + u8 hash[32]; + u32 entry_point; + u16 cfg_offset; + u16 cfg_count; + u32 affinity_mask; + u16 instance_max_count; + u16 instance_bss_size; + struct avs_segment_desc segments[3]; +} __packed; + +struct avs_mods_info { + u32 count; + struct avs_module_entry entries[]; +} __packed; + +static inline bool avs_module_entry_is_loaded(struct avs_module_entry *mentry) +{ + return mentry->type.load_type == AVS_MODULE_LOAD_TYPE_BUILTIN || + mentry->state_flags & AVS_MODULE_STATE_LOADED; +} + +int avs_ipc_get_modules_info(struct avs_dev *adev, struct avs_mods_info **info); + +struct avs_sys_time { + u32 val_l; + u32 val_u; +} __packed; + +int avs_ipc_set_system_time(struct avs_dev *adev); + +/* Module configuration */ + +#define AVS_MIXIN_MOD_UUID \ + GUID_INIT(0x39656EB2, 0x3B71, 0x4049, 0x8D, 0x3F, 0xF9, 0x2C, 0xD5, 0xC4, 0x3C, 0x09) + +#define AVS_MIXOUT_MOD_UUID \ + GUID_INIT(0x3C56505A, 0x24D7, 0x418F, 0xBD, 0xDC, 0xC1, 0xF5, 0xA3, 0xAC, 0x2A, 0xE0) + +#define AVS_COPIER_MOD_UUID \ + GUID_INIT(0x9BA00C83, 0xCA12, 0x4A83, 0x94, 0x3C, 0x1F, 0xA2, 0xE8, 0x2F, 0x9D, 0xDA) + +#define AVS_KPBUFF_MOD_UUID \ + GUID_INIT(0xA8A0CB32, 0x4A77, 0x4DB1, 0x85, 0xC7, 0x53, 0xD7, 0xEE, 0x07, 0xBC, 0xE6) + +#define AVS_MICSEL_MOD_UUID \ + GUID_INIT(0x32FE92C1, 0x1E17, 0x4FC2, 0x97, 0x58, 0xC7, 0xF3, 0x54, 0x2E, 0x98, 0x0A) + +#define AVS_MUX_MOD_UUID \ + GUID_INIT(0x64CE6E35, 0x857A, 0x4878, 0xAC, 0xE8, 0xE2, 0xA2, 0xF4, 0x2e, 0x30, 0x69) + +#define AVS_UPDWMIX_MOD_UUID \ + GUID_INIT(0x42F8060C, 0x832F, 0x4DBF, 0xB2, 0x47, 0x51, 0xE9, 0x61, 0x99, 0x7b, 0x35) + +#define AVS_SRCINTC_MOD_UUID \ + GUID_INIT(0xE61BB28D, 0x149A, 0x4C1F, 0xB7, 0x09, 0x46, 0x82, 0x3E, 0xF5, 0xF5, 0xAE) + +#define AVS_PROBE_MOD_UUID \ + GUID_INIT(0x7CAD0808, 0xAB10, 0xCD23, 0xEF, 0x45, 0x12, 0xAB, 0x34, 0xCD, 0x56, 0xEF) + +#define AVS_AEC_MOD_UUID \ + GUID_INIT(0x46CB87FB, 0xD2C9, 0x4970, 0x96, 0xD2, 0x6D, 0x7E, 0x61, 0x4B, 0xB6, 0x05) + +#define AVS_ASRC_MOD_UUID \ + GUID_INIT(0x66B4402D, 0xB468, 0x42F2, 0x81, 0xA7, 0xB3, 0x71, 0x21, 0x86, 0x3D, 0xD4) + +#define AVS_INTELWOV_MOD_UUID \ + GUID_INIT(0xEC774FA9, 0x28D3, 0x424A, 0x90, 0xE4, 0x69, 0xF9, 0x84, 0xF1, 0xEE, 0xB7) + +/* channel map */ +enum avs_channel_index { + AVS_CHANNEL_LEFT = 0, + AVS_CHANNEL_RIGHT = 1, + AVS_CHANNEL_CENTER = 2, + AVS_CHANNEL_LEFT_SURROUND = 3, + AVS_CHANNEL_CENTER_SURROUND = 3, + AVS_CHANNEL_RIGHT_SURROUND = 4, + AVS_CHANNEL_LFE = 7, + AVS_CHANNEL_INVALID = 0xF, +}; + +enum avs_channel_config { + AVS_CHANNEL_CONFIG_MONO = 0, + AVS_CHANNEL_CONFIG_STEREO = 1, + AVS_CHANNEL_CONFIG_2_1 = 2, + AVS_CHANNEL_CONFIG_3_0 = 3, + AVS_CHANNEL_CONFIG_3_1 = 4, + AVS_CHANNEL_CONFIG_QUATRO = 5, + AVS_CHANNEL_CONFIG_4_0 = 6, + AVS_CHANNEL_CONFIG_5_0 = 7, + AVS_CHANNEL_CONFIG_5_1 = 8, + AVS_CHANNEL_CONFIG_DUAL_MONO = 9, + AVS_CHANNEL_CONFIG_I2S_DUAL_STEREO_0 = 10, + AVS_CHANNEL_CONFIG_I2S_DUAL_STEREO_1 = 11, + AVS_CHANNEL_CONFIG_4_CHANNEL = 12, + AVS_CHANNEL_CONFIG_INVALID +}; + +enum avs_interleaving { + AVS_INTERLEAVING_PER_CHANNEL = 0, + AVS_INTERLEAVING_PER_SAMPLE = 1, +}; + +enum avs_sample_type { + AVS_SAMPLE_TYPE_INT_MSB = 0, + AVS_SAMPLE_TYPE_INT_LSB = 1, + AVS_SAMPLE_TYPE_INT_SIGNED = 2, + AVS_SAMPLE_TYPE_INT_UNSIGNED = 3, + AVS_SAMPLE_TYPE_FLOAT = 4, +}; + +#define AVS_CHANNELS_MAX 8 +#define AVS_ALL_CHANNELS_MASK UINT_MAX + +struct avs_audio_format { + u32 sampling_freq; + u32 bit_depth; + u32 channel_map; + u32 channel_config; + u32 interleaving; + u32 num_channels:8; + u32 valid_bit_depth:8; + u32 sample_type:8; + u32 reserved:8; +} __packed; + +struct avs_modcfg_base { + u32 cpc; + u32 ibs; + u32 obs; + u32 is_pages; + struct avs_audio_format audio_fmt; +} __packed; + +struct avs_pin_format { + u32 pin_index; + u32 iobs; + struct avs_audio_format audio_fmt; +} __packed; + +struct avs_modcfg_ext { + struct avs_modcfg_base base; + u16 num_input_pins; + u16 num_output_pins; + u8 reserved[12]; + /* input pin formats followed by output ones */ + struct avs_pin_format pin_fmts[]; +} __packed; + +enum avs_dma_type { + AVS_DMA_HDA_HOST_OUTPUT = 0, + AVS_DMA_HDA_HOST_INPUT = 1, + AVS_DMA_HDA_LINK_OUTPUT = 8, + AVS_DMA_HDA_LINK_INPUT = 9, + AVS_DMA_DMIC_LINK_INPUT = 11, + AVS_DMA_I2S_LINK_OUTPUT = 12, + AVS_DMA_I2S_LINK_INPUT = 13, +}; + +union avs_virtual_index { + u8 val; + struct { + u8 time_slot:4; + u8 instance:4; + } i2s; + struct { + u8 queue_id:3; + u8 time_slot:2; + u8 instance:3; + } dmic; +} __packed; + +union avs_connector_node_id { + u32 val; + struct { + u32 vindex:8; + u32 dma_type:5; + u32 rsvd:19; + }; +} __packed; + +#define INVALID_PIPELINE_ID 0xFF +#define INVALID_NODE_ID \ + ((union avs_connector_node_id) { UINT_MAX }) + +union avs_gtw_attributes { + u32 val; + struct { + u32 lp_buffer_alloc:1; + u32 rsvd:31; + }; +} __packed; + +struct avs_copier_gtw_cfg { + union avs_connector_node_id node_id; + u32 dma_buffer_size; + u32 config_length; + struct { + union avs_gtw_attributes attrs; + u32 blob[]; + } config; +} __packed; + +struct avs_copier_cfg { + struct avs_modcfg_base base; + struct avs_audio_format out_fmt; + u32 feature_mask; + struct avs_copier_gtw_cfg gtw_cfg; +} __packed; + +struct avs_micsel_cfg { + struct avs_modcfg_base base; + struct avs_audio_format out_fmt; +} __packed; + +struct avs_mux_cfg { + struct avs_modcfg_base base; + struct avs_audio_format ref_fmt; + struct avs_audio_format out_fmt; +} __packed; + +struct avs_updown_mixer_cfg { + struct avs_modcfg_base base; + u32 out_channel_config; + u32 coefficients_select; + s32 coefficients[AVS_CHANNELS_MAX]; + u32 channel_map; +} __packed; + +struct avs_src_cfg { + struct avs_modcfg_base base; + u32 out_freq; +} __packed; + +struct avs_probe_gtw_cfg { + union avs_connector_node_id node_id; + u32 dma_buffer_size; +} __packed; + +struct avs_probe_cfg { + struct avs_modcfg_base base; + struct avs_probe_gtw_cfg gtw_cfg; +} __packed; + +struct avs_aec_cfg { + struct avs_modcfg_base base; + struct avs_audio_format ref_fmt; + struct avs_audio_format out_fmt; + u32 cpc_lp_mode; +} __packed; + +struct avs_asrc_cfg { + struct avs_modcfg_base base; + u32 out_freq; + u32 rsvd0:1; + u32 mode:1; + u32 rsvd2:2; + u32 disable_jitter_buffer:1; + u32 rsvd3:27; +} __packed; + +struct avs_wov_cfg { + struct avs_modcfg_base base; + u32 cpc_lp_mode; +} __packed; + +/* Module runtime parameters */ + +enum avs_copier_runtime_param { + AVS_COPIER_SET_SINK_FORMAT = 2, +}; + +struct avs_copier_sink_format { + u32 sink_id; + struct avs_audio_format src_fmt; + struct avs_audio_format sink_fmt; +} __packed; + +int avs_ipc_copier_set_sink_format(struct avs_dev *adev, u16 module_id, + u8 instance_id, u32 sink_id, + const struct avs_audio_format *src_fmt, + const struct avs_audio_format *sink_fmt); + +#endif /* __SOUND_SOC_INTEL_AVS_MSGS_H */ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c new file mode 100644 index 000000000000..ce157a8d6552 --- /dev/null +++ b/sound/soc/intel/avs/path.c @@ -0,0 +1,1009 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <sound/intel-nhlt.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "avs.h" +#include "path.h" +#include "topology.h" + +/* Must be called with adev->comp_list_mutex held. */ +static struct avs_tplg * +avs_path_find_tplg(struct avs_dev *adev, const char *name) +{ + struct avs_soc_component *acomp; + + list_for_each_entry(acomp, &adev->comp_list, node) + if (!strcmp(acomp->tplg->name, name)) + return acomp->tplg; + return NULL; +} + +static struct avs_path_module * +avs_path_find_module(struct avs_path_pipeline *ppl, u32 template_id) +{ + struct avs_path_module *mod; + + list_for_each_entry(mod, &ppl->mod_list, node) + if (mod->template->id == template_id) + return mod; + return NULL; +} + +static struct avs_path_pipeline * +avs_path_find_pipeline(struct avs_path *path, u32 template_id) +{ + struct avs_path_pipeline *ppl; + + list_for_each_entry(ppl, &path->ppl_list, node) + if (ppl->template->id == template_id) + return ppl; + return NULL; +} + +static struct avs_path * +avs_path_find_path(struct avs_dev *adev, const char *name, u32 template_id) +{ + struct avs_tplg_path_template *pos, *template = NULL; + struct avs_tplg *tplg; + struct avs_path *path; + + tplg = avs_path_find_tplg(adev, name); + if (!tplg) + return NULL; + + list_for_each_entry(pos, &tplg->path_tmpl_list, node) { + if (pos->id == template_id) { + template = pos; + break; + } + } + if (!template) + return NULL; + + spin_lock(&adev->path_list_lock); + /* Only one variant of given path template may be instantiated at a time. */ + list_for_each_entry(path, &adev->path_list, node) { + if (path->template->owner == template) { + spin_unlock(&adev->path_list_lock); + return path; + } + } + + spin_unlock(&adev->path_list_lock); + return NULL; +} + +static bool avs_test_hw_params(struct snd_pcm_hw_params *params, + struct avs_audio_format *fmt) +{ + return (params_rate(params) == fmt->sampling_freq && + params_channels(params) == fmt->num_channels && + params_physical_width(params) == fmt->bit_depth && + params_width(params) == fmt->valid_bit_depth); +} + +static struct avs_tplg_path * +avs_path_find_variant(struct avs_dev *adev, + struct avs_tplg_path_template *template, + struct snd_pcm_hw_params *fe_params, + struct snd_pcm_hw_params *be_params) +{ + struct avs_tplg_path *variant; + + list_for_each_entry(variant, &template->path_list, node) { + dev_dbg(adev->dev, "check FE rate %d chn %d vbd %d bd %d\n", + variant->fe_fmt->sampling_freq, variant->fe_fmt->num_channels, + variant->fe_fmt->valid_bit_depth, variant->fe_fmt->bit_depth); + dev_dbg(adev->dev, "check BE rate %d chn %d vbd %d bd %d\n", + variant->be_fmt->sampling_freq, variant->be_fmt->num_channels, + variant->be_fmt->valid_bit_depth, variant->be_fmt->bit_depth); + + if (variant->fe_fmt && avs_test_hw_params(fe_params, variant->fe_fmt) && + variant->be_fmt && avs_test_hw_params(be_params, variant->be_fmt)) + return variant; + } + + return NULL; +} + +__maybe_unused +static bool avs_dma_type_is_host(u32 dma_type) +{ + return dma_type == AVS_DMA_HDA_HOST_OUTPUT || + dma_type == AVS_DMA_HDA_HOST_INPUT; +} + +__maybe_unused +static bool avs_dma_type_is_link(u32 dma_type) +{ + return !avs_dma_type_is_host(dma_type); +} + +__maybe_unused +static bool avs_dma_type_is_output(u32 dma_type) +{ + return dma_type == AVS_DMA_HDA_HOST_OUTPUT || + dma_type == AVS_DMA_HDA_LINK_OUTPUT || + dma_type == AVS_DMA_I2S_LINK_OUTPUT; +} + +__maybe_unused +static bool avs_dma_type_is_input(u32 dma_type) +{ + return !avs_dma_type_is_output(dma_type); +} + +static int avs_copier_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct nhlt_acpi_table *nhlt = adev->nhlt; + struct avs_tplg_module *t = mod->template; + struct avs_copier_cfg *cfg; + struct nhlt_specific_cfg *ep_blob; + union avs_connector_node_id node_id = {0}; + size_t cfg_size, data_size = 0; + void *data = NULL; + u32 dma_type; + int ret; + + dma_type = t->cfg_ext->copier.dma_type; + node_id.dma_type = dma_type; + + switch (dma_type) { + struct avs_audio_format *fmt; + int direction; + + case AVS_DMA_I2S_LINK_OUTPUT: + case AVS_DMA_I2S_LINK_INPUT: + if (avs_dma_type_is_input(dma_type)) + direction = SNDRV_PCM_STREAM_CAPTURE; + else + direction = SNDRV_PCM_STREAM_PLAYBACK; + + if (t->cfg_ext->copier.blob_fmt) + fmt = t->cfg_ext->copier.blob_fmt; + else if (direction == SNDRV_PCM_STREAM_CAPTURE) + fmt = t->in_fmt; + else + fmt = t->cfg_ext->copier.out_fmt; + + ep_blob = intel_nhlt_get_endpoint_blob(adev->dev, + nhlt, t->cfg_ext->copier.vindex.i2s.instance, + NHLT_LINK_SSP, fmt->valid_bit_depth, fmt->bit_depth, + fmt->num_channels, fmt->sampling_freq, direction, + NHLT_DEVICE_I2S); + if (!ep_blob) { + dev_err(adev->dev, "no I2S ep_blob found\n"); + return -ENOENT; + } + + data = ep_blob->caps; + data_size = ep_blob->size; + /* I2S gateway's vindex is statically assigned in topology */ + node_id.vindex = t->cfg_ext->copier.vindex.val; + + break; + + case AVS_DMA_DMIC_LINK_INPUT: + direction = SNDRV_PCM_STREAM_CAPTURE; + + if (t->cfg_ext->copier.blob_fmt) + fmt = t->cfg_ext->copier.blob_fmt; + else + fmt = t->in_fmt; + + ep_blob = intel_nhlt_get_endpoint_blob(adev->dev, nhlt, 0, + NHLT_LINK_DMIC, fmt->valid_bit_depth, + fmt->bit_depth, fmt->num_channels, + fmt->sampling_freq, direction, NHLT_DEVICE_DMIC); + if (!ep_blob) { + dev_err(adev->dev, "no DMIC ep_blob found\n"); + return -ENOENT; + } + + data = ep_blob->caps; + data_size = ep_blob->size; + /* DMIC gateway's vindex is statically assigned in topology */ + node_id.vindex = t->cfg_ext->copier.vindex.val; + + break; + + case AVS_DMA_HDA_HOST_OUTPUT: + case AVS_DMA_HDA_HOST_INPUT: + /* HOST gateway's vindex is dynamically assigned with DMA id */ + node_id.vindex = mod->owner->owner->dma_id; + break; + + case AVS_DMA_HDA_LINK_OUTPUT: + case AVS_DMA_HDA_LINK_INPUT: + node_id.vindex = t->cfg_ext->copier.vindex.val | + mod->owner->owner->dma_id; + break; + + case INVALID_OBJECT_ID: + default: + node_id = INVALID_NODE_ID; + break; + } + + cfg_size = sizeof(*cfg) + data_size; + /* Every config-BLOB contains gateway attributes. */ + if (data_size) + cfg_size -= sizeof(cfg->gtw_cfg.config.attrs); + + cfg = kzalloc(cfg_size, GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->base.cpc = t->cfg_base->cpc; + cfg->base.ibs = t->cfg_base->ibs; + cfg->base.obs = t->cfg_base->obs; + cfg->base.is_pages = t->cfg_base->is_pages; + cfg->base.audio_fmt = *t->in_fmt; + cfg->out_fmt = *t->cfg_ext->copier.out_fmt; + cfg->feature_mask = t->cfg_ext->copier.feature_mask; + cfg->gtw_cfg.node_id = node_id; + cfg->gtw_cfg.dma_buffer_size = t->cfg_ext->copier.dma_buffer_size; + /* config_length in DWORDs */ + cfg->gtw_cfg.config_length = DIV_ROUND_UP(data_size, 4); + if (data) + memcpy(&cfg->gtw_cfg.config, data, data_size); + + mod->gtw_attrs = cfg->gtw_cfg.config.attrs; + + ret = avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, cfg, cfg_size, + &mod->instance_id); + kfree(cfg); + return ret; +} + +static int avs_updown_mix_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_updown_mixer_cfg cfg; + int i; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.out_channel_config = t->cfg_ext->updown_mix.out_channel_config; + cfg.coefficients_select = t->cfg_ext->updown_mix.coefficients_select; + for (i = 0; i < AVS_CHANNELS_MAX; i++) + cfg.coefficients[i] = t->cfg_ext->updown_mix.coefficients[i]; + cfg.channel_map = t->cfg_ext->updown_mix.channel_map; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_src_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_src_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.out_freq = t->cfg_ext->src.out_freq; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_asrc_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_asrc_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.out_freq = t->cfg_ext->asrc.out_freq; + cfg.mode = t->cfg_ext->asrc.mode; + cfg.disable_jitter_buffer = t->cfg_ext->asrc.disable_jitter_buffer; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_aec_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_aec_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.ref_fmt = *t->cfg_ext->aec.ref_fmt; + cfg.out_fmt = *t->cfg_ext->aec.out_fmt; + cfg.cpc_lp_mode = t->cfg_ext->aec.cpc_lp_mode; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_mux_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_mux_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.ref_fmt = *t->cfg_ext->mux.ref_fmt; + cfg.out_fmt = *t->cfg_ext->mux.out_fmt; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_wov_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_wov_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.cpc_lp_mode = t->cfg_ext->wov.cpc_lp_mode; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_micsel_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_micsel_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.out_fmt = *t->cfg_ext->micsel.out_fmt; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_modbase_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_modcfg_base cfg; + + cfg.cpc = t->cfg_base->cpc; + cfg.ibs = t->cfg_base->ibs; + cfg.obs = t->cfg_base->obs; + cfg.is_pages = t->cfg_base->is_pages; + cfg.audio_fmt = *t->in_fmt; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_modext_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_tplg_modcfg_ext *tcfg = t->cfg_ext; + struct avs_modcfg_ext *cfg; + size_t cfg_size, num_pins; + int ret, i; + + num_pins = tcfg->generic.num_input_pins + tcfg->generic.num_output_pins; + cfg_size = sizeof(*cfg) + sizeof(*cfg->pin_fmts) * num_pins; + + cfg = kzalloc(cfg_size, GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->base.cpc = t->cfg_base->cpc; + cfg->base.ibs = t->cfg_base->ibs; + cfg->base.obs = t->cfg_base->obs; + cfg->base.is_pages = t->cfg_base->is_pages; + cfg->base.audio_fmt = *t->in_fmt; + cfg->num_input_pins = tcfg->generic.num_input_pins; + cfg->num_output_pins = tcfg->generic.num_output_pins; + + /* configure pin formats */ + for (i = 0; i < num_pins; i++) { + struct avs_tplg_pin_format *tpin = &tcfg->generic.pin_fmts[i]; + struct avs_pin_format *pin = &cfg->pin_fmts[i]; + + pin->pin_index = tpin->pin_index; + pin->iobs = tpin->iobs; + pin->audio_fmt = *tpin->fmt; + } + + ret = avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, cfg, cfg_size, + &mod->instance_id); + kfree(cfg); + return ret; +} + +static int avs_probe_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + dev_err(adev->dev, "Probe module can't be instantiated by topology"); + return -EINVAL; +} + +struct avs_module_create { + guid_t *guid; + int (*create)(struct avs_dev *adev, struct avs_path_module *mod); +}; + +static struct avs_module_create avs_module_create[] = { + { &AVS_MIXIN_MOD_UUID, avs_modbase_create }, + { &AVS_MIXOUT_MOD_UUID, avs_modbase_create }, + { &AVS_KPBUFF_MOD_UUID, avs_modbase_create }, + { &AVS_COPIER_MOD_UUID, avs_copier_create }, + { &AVS_MICSEL_MOD_UUID, avs_micsel_create }, + { &AVS_MUX_MOD_UUID, avs_mux_create }, + { &AVS_UPDWMIX_MOD_UUID, avs_updown_mix_create }, + { &AVS_SRCINTC_MOD_UUID, avs_src_create }, + { &AVS_AEC_MOD_UUID, avs_aec_create }, + { &AVS_ASRC_MOD_UUID, avs_asrc_create }, + { &AVS_INTELWOV_MOD_UUID, avs_wov_create }, + { &AVS_PROBE_MOD_UUID, avs_probe_create }, +}; + +static int avs_path_module_type_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + const guid_t *type = &mod->template->cfg_ext->type; + + for (int i = 0; i < ARRAY_SIZE(avs_module_create); i++) + if (guid_equal(type, avs_module_create[i].guid)) + return avs_module_create[i].create(adev, mod); + + return avs_modext_create(adev, mod); +} + +static void avs_path_module_free(struct avs_dev *adev, struct avs_path_module *mod) +{ + kfree(mod); +} + +static struct avs_path_module * +avs_path_module_create(struct avs_dev *adev, + struct avs_path_pipeline *owner, + struct avs_tplg_module *template) +{ + struct avs_path_module *mod; + int module_id, ret; + + module_id = avs_get_module_id(adev, &template->cfg_ext->type); + if (module_id < 0) + return ERR_PTR(module_id); + + mod = kzalloc(sizeof(*mod), GFP_KERNEL); + if (!mod) + return ERR_PTR(-ENOMEM); + + mod->template = template; + mod->module_id = module_id; + mod->owner = owner; + INIT_LIST_HEAD(&mod->node); + + ret = avs_path_module_type_create(adev, mod); + if (ret) { + dev_err(adev->dev, "module-type create failed: %d\n", ret); + kfree(mod); + return ERR_PTR(ret); + } + + return mod; +} + +static int avs_path_binding_arm(struct avs_dev *adev, struct avs_path_binding *binding) +{ + struct avs_path_module *this_mod, *target_mod; + struct avs_path_pipeline *target_ppl; + struct avs_path *target_path; + struct avs_tplg_binding *t; + + t = binding->template; + this_mod = avs_path_find_module(binding->owner, + t->mod_id); + if (!this_mod) { + dev_err(adev->dev, "path mod %d not found\n", t->mod_id); + return -EINVAL; + } + + /* update with target_tplg_name too */ + target_path = avs_path_find_path(adev, t->target_tplg_name, + t->target_path_tmpl_id); + if (!target_path) { + dev_err(adev->dev, "target path %s:%d not found\n", + t->target_tplg_name, t->target_path_tmpl_id); + return -EINVAL; + } + + target_ppl = avs_path_find_pipeline(target_path, + t->target_ppl_id); + if (!target_ppl) { + dev_err(adev->dev, "target ppl %d not found\n", t->target_ppl_id); + return -EINVAL; + } + + target_mod = avs_path_find_module(target_ppl, t->target_mod_id); + if (!target_mod) { + dev_err(adev->dev, "target mod %d not found\n", t->target_mod_id); + return -EINVAL; + } + + if (t->is_sink) { + binding->sink = this_mod; + binding->sink_pin = t->mod_pin; + binding->source = target_mod; + binding->source_pin = t->target_mod_pin; + } else { + binding->sink = target_mod; + binding->sink_pin = t->target_mod_pin; + binding->source = this_mod; + binding->source_pin = t->mod_pin; + } + + return 0; +} + +static void avs_path_binding_free(struct avs_dev *adev, struct avs_path_binding *binding) +{ + kfree(binding); +} + +static struct avs_path_binding *avs_path_binding_create(struct avs_dev *adev, + struct avs_path_pipeline *owner, + struct avs_tplg_binding *t) +{ + struct avs_path_binding *binding; + + binding = kzalloc(sizeof(*binding), GFP_KERNEL); + if (!binding) + return ERR_PTR(-ENOMEM); + + binding->template = t; + binding->owner = owner; + INIT_LIST_HEAD(&binding->node); + + return binding; +} + +static int avs_path_pipeline_arm(struct avs_dev *adev, + struct avs_path_pipeline *ppl) +{ + struct avs_path_module *mod; + + list_for_each_entry(mod, &ppl->mod_list, node) { + struct avs_path_module *source, *sink; + int ret; + + /* + * Only one module (so it's implicitly last) or it is the last + * one, either way we don't have next module to bind it to. + */ + if (mod == list_last_entry(&ppl->mod_list, + struct avs_path_module, node)) + break; + + /* bind current module to next module on list */ + source = mod; + sink = list_next_entry(mod, node); + if (!source || !sink) + return -EINVAL; + + ret = avs_ipc_bind(adev, source->module_id, source->instance_id, + sink->module_id, sink->instance_id, 0, 0); + if (ret) + return AVS_IPC_RET(ret); + } + + return 0; +} + +static void avs_path_pipeline_free(struct avs_dev *adev, + struct avs_path_pipeline *ppl) +{ + struct avs_path_binding *binding, *bsave; + struct avs_path_module *mod, *save; + + list_for_each_entry_safe(binding, bsave, &ppl->binding_list, node) { + list_del(&binding->node); + avs_path_binding_free(adev, binding); + } + + avs_dsp_delete_pipeline(adev, ppl->instance_id); + + /* Unload resources occupied by owned modules */ + list_for_each_entry_safe(mod, save, &ppl->mod_list, node) { + avs_dsp_delete_module(adev, mod->module_id, mod->instance_id, + mod->owner->instance_id, + mod->template->core_id); + avs_path_module_free(adev, mod); + } + + list_del(&ppl->node); + kfree(ppl); +} + +static struct avs_path_pipeline * +avs_path_pipeline_create(struct avs_dev *adev, struct avs_path *owner, + struct avs_tplg_pipeline *template) +{ + struct avs_path_pipeline *ppl; + struct avs_tplg_pplcfg *cfg = template->cfg; + struct avs_tplg_module *tmod; + int ret, i; + + ppl = kzalloc(sizeof(*ppl), GFP_KERNEL); + if (!ppl) + return ERR_PTR(-ENOMEM); + + ppl->template = template; + ppl->owner = owner; + INIT_LIST_HEAD(&ppl->binding_list); + INIT_LIST_HEAD(&ppl->mod_list); + INIT_LIST_HEAD(&ppl->node); + + ret = avs_dsp_create_pipeline(adev, cfg->req_size, cfg->priority, + cfg->lp, cfg->attributes, + &ppl->instance_id); + if (ret) { + dev_err(adev->dev, "error creating pipeline %d\n", ret); + kfree(ppl); + return ERR_PTR(ret); + } + + list_for_each_entry(tmod, &template->mod_list, node) { + struct avs_path_module *mod; + + mod = avs_path_module_create(adev, ppl, tmod); + if (IS_ERR(mod)) { + ret = PTR_ERR(mod); + dev_err(adev->dev, "error creating module %d\n", ret); + goto init_err; + } + + list_add_tail(&mod->node, &ppl->mod_list); + } + + for (i = 0; i < template->num_bindings; i++) { + struct avs_path_binding *binding; + + binding = avs_path_binding_create(adev, ppl, template->bindings[i]); + if (IS_ERR(binding)) { + ret = PTR_ERR(binding); + dev_err(adev->dev, "error creating binding %d\n", ret); + goto init_err; + } + + list_add_tail(&binding->node, &ppl->binding_list); + } + + return ppl; + +init_err: + avs_path_pipeline_free(adev, ppl); + return ERR_PTR(ret); +} + +static int avs_path_init(struct avs_dev *adev, struct avs_path *path, + struct avs_tplg_path *template, u32 dma_id) +{ + struct avs_tplg_pipeline *tppl; + + path->owner = adev; + path->template = template; + path->dma_id = dma_id; + INIT_LIST_HEAD(&path->ppl_list); + INIT_LIST_HEAD(&path->node); + + /* create all the pipelines */ + list_for_each_entry(tppl, &template->ppl_list, node) { + struct avs_path_pipeline *ppl; + + ppl = avs_path_pipeline_create(adev, path, tppl); + if (IS_ERR(ppl)) + return PTR_ERR(ppl); + + list_add_tail(&ppl->node, &path->ppl_list); + } + + spin_lock(&adev->path_list_lock); + list_add_tail(&path->node, &adev->path_list); + spin_unlock(&adev->path_list_lock); + + return 0; +} + +static int avs_path_arm(struct avs_dev *adev, struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_path_binding *binding; + int ret; + + list_for_each_entry(ppl, &path->ppl_list, node) { + /* + * Arm all ppl bindings before binding internal modules + * as it costs no IPCs which isn't true for the latter. + */ + list_for_each_entry(binding, &ppl->binding_list, node) { + ret = avs_path_binding_arm(adev, binding); + if (ret < 0) + return ret; + } + + ret = avs_path_pipeline_arm(adev, ppl); + if (ret < 0) + return ret; + } + + return 0; +} + +static void avs_path_free_unlocked(struct avs_path *path) +{ + struct avs_path_pipeline *ppl, *save; + + spin_lock(&path->owner->path_list_lock); + list_del(&path->node); + spin_unlock(&path->owner->path_list_lock); + + list_for_each_entry_safe(ppl, save, &path->ppl_list, node) + avs_path_pipeline_free(path->owner, ppl); + + kfree(path); +} + +static struct avs_path *avs_path_create_unlocked(struct avs_dev *adev, u32 dma_id, + struct avs_tplg_path *template) +{ + struct avs_path *path; + int ret; + + path = kzalloc(sizeof(*path), GFP_KERNEL); + if (!path) + return ERR_PTR(-ENOMEM); + + ret = avs_path_init(adev, path, template, dma_id); + if (ret < 0) + goto err; + + ret = avs_path_arm(adev, path); + if (ret < 0) + goto err; + + path->state = AVS_PPL_STATE_INVALID; + return path; +err: + avs_path_free_unlocked(path); + return ERR_PTR(ret); +} + +void avs_path_free(struct avs_path *path) +{ + struct avs_dev *adev = path->owner; + + mutex_lock(&adev->path_mutex); + avs_path_free_unlocked(path); + mutex_unlock(&adev->path_mutex); +} + +struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + struct avs_tplg_path_template *template, + struct snd_pcm_hw_params *fe_params, + struct snd_pcm_hw_params *be_params) +{ + struct avs_tplg_path *variant; + struct avs_path *path; + + variant = avs_path_find_variant(adev, template, fe_params, be_params); + if (!variant) { + dev_err(adev->dev, "no matching variant found\n"); + return ERR_PTR(-ENOENT); + } + + /* Serialize path and its components creation. */ + mutex_lock(&adev->path_mutex); + /* Satisfy needs of avs_path_find_tplg(). */ + mutex_lock(&adev->comp_list_mutex); + + path = avs_path_create_unlocked(adev, dma_id, variant); + + mutex_unlock(&adev->comp_list_mutex); + mutex_unlock(&adev->path_mutex); + + return path; +} + +static int avs_path_bind_prepare(struct avs_dev *adev, + struct avs_path_binding *binding) +{ + const struct avs_audio_format *src_fmt, *sink_fmt; + struct avs_tplg_module *tsource = binding->source->template; + struct avs_path_module *source = binding->source; + int ret; + + /* + * only copier modules about to be bound + * to output pin other than 0 need preparation + */ + if (!binding->source_pin) + return 0; + if (!guid_equal(&tsource->cfg_ext->type, &AVS_COPIER_MOD_UUID)) + return 0; + + src_fmt = tsource->in_fmt; + sink_fmt = binding->sink->template->in_fmt; + + ret = avs_ipc_copier_set_sink_format(adev, source->module_id, + source->instance_id, binding->source_pin, + src_fmt, sink_fmt); + if (ret) { + dev_err(adev->dev, "config copier failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + + return 0; +} + +int avs_path_bind(struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + list_for_each_entry(ppl, &path->ppl_list, node) { + struct avs_path_binding *binding; + + list_for_each_entry(binding, &ppl->binding_list, node) { + struct avs_path_module *source, *sink; + + source = binding->source; + sink = binding->sink; + + ret = avs_path_bind_prepare(adev, binding); + if (ret < 0) + return ret; + + ret = avs_ipc_bind(adev, source->module_id, + source->instance_id, sink->module_id, + sink->instance_id, binding->sink_pin, + binding->source_pin); + if (ret) { + dev_err(adev->dev, "bind path failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + } + } + + return 0; +} + +int avs_path_unbind(struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + list_for_each_entry(ppl, &path->ppl_list, node) { + struct avs_path_binding *binding; + + list_for_each_entry(binding, &ppl->binding_list, node) { + struct avs_path_module *source, *sink; + + source = binding->source; + sink = binding->sink; + + ret = avs_ipc_unbind(adev, source->module_id, + source->instance_id, sink->module_id, + sink->instance_id, binding->sink_pin, + binding->source_pin); + if (ret) { + dev_err(adev->dev, "unbind path failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + } + } + + return 0; +} + +int avs_path_reset(struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + if (path->state == AVS_PPL_STATE_RESET) + return 0; + + list_for_each_entry(ppl, &path->ppl_list, node) { + ret = avs_ipc_set_pipeline_state(adev, ppl->instance_id, + AVS_PPL_STATE_RESET); + if (ret) { + dev_err(adev->dev, "reset path failed: %d\n", ret); + path->state = AVS_PPL_STATE_INVALID; + return AVS_IPC_RET(ret); + } + } + + path->state = AVS_PPL_STATE_RESET; + return 0; +} + +int avs_path_pause(struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + if (path->state == AVS_PPL_STATE_PAUSED) + return 0; + + list_for_each_entry_reverse(ppl, &path->ppl_list, node) { + ret = avs_ipc_set_pipeline_state(adev, ppl->instance_id, + AVS_PPL_STATE_PAUSED); + if (ret) { + dev_err(adev->dev, "pause path failed: %d\n", ret); + path->state = AVS_PPL_STATE_INVALID; + return AVS_IPC_RET(ret); + } + } + + path->state = AVS_PPL_STATE_PAUSED; + return 0; +} + +int avs_path_run(struct avs_path *path, int trigger) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + if (path->state == AVS_PPL_STATE_RUNNING && trigger == AVS_TPLG_TRIGGER_AUTO) + return 0; + + list_for_each_entry(ppl, &path->ppl_list, node) { + if (ppl->template->cfg->trigger != trigger) + continue; + + ret = avs_ipc_set_pipeline_state(adev, ppl->instance_id, + AVS_PPL_STATE_RUNNING); + if (ret) { + dev_err(adev->dev, "run path failed: %d\n", ret); + path->state = AVS_PPL_STATE_INVALID; + return AVS_IPC_RET(ret); + } + } + + path->state = AVS_PPL_STATE_RUNNING; + return 0; +} diff --git a/sound/soc/intel/avs/path.h b/sound/soc/intel/avs/path.h new file mode 100644 index 000000000000..197222c5e008 --- /dev/null +++ b/sound/soc/intel/avs/path.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021 Intel Corporation. All rights reserved. + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_PATH_H +#define __SOUND_SOC_INTEL_AVS_PATH_H + +#include <linux/list.h> +#include "avs.h" +#include "topology.h" + +struct avs_path { + u32 dma_id; + struct list_head ppl_list; + u32 state; + + struct avs_tplg_path *template; + struct avs_dev *owner; + /* device path management */ + struct list_head node; +}; + +struct avs_path_pipeline { + u8 instance_id; + struct list_head mod_list; + struct list_head binding_list; + + struct avs_tplg_pipeline *template; + struct avs_path *owner; + /* path pipelines management */ + struct list_head node; +}; + +struct avs_path_module { + u16 module_id; + u16 instance_id; + union avs_gtw_attributes gtw_attrs; + + struct avs_tplg_module *template; + struct avs_path_pipeline *owner; + /* pipeline modules management */ + struct list_head node; +}; + +struct avs_path_binding { + struct avs_path_module *source; + u8 source_pin; + struct avs_path_module *sink; + u8 sink_pin; + + struct avs_tplg_binding *template; + struct avs_path_pipeline *owner; + /* pipeline bindings management */ + struct list_head node; +}; + +void avs_path_free(struct avs_path *path); +struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + struct avs_tplg_path_template *template, + struct snd_pcm_hw_params *fe_params, + struct snd_pcm_hw_params *be_params); +int avs_path_bind(struct avs_path *path); +int avs_path_unbind(struct avs_path *path); +int avs_path_reset(struct avs_path *path); +int avs_path_pause(struct avs_path *path); +int avs_path_run(struct avs_path *path, int trigger); + +#endif diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c new file mode 100644 index 000000000000..8fe5917b1e26 --- /dev/null +++ b/sound/soc/intel/avs/pcm.c @@ -0,0 +1,1180 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <sound/hda_register.h> +#include <sound/hdaudio_ext.h> +#include <sound/pcm_params.h> +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include <sound/soc-component.h> +#include "avs.h" +#include "path.h" +#include "topology.h" + +struct avs_dma_data { + struct avs_tplg_path_template *template; + struct avs_path *path; + /* + * link stream is stored within substream's runtime + * private_data to fulfill the needs of codec BE path + * + * host stream assigned + */ + struct hdac_ext_stream *host_stream; +}; + +static struct avs_tplg_path_template * +avs_dai_find_path_template(struct snd_soc_dai *dai, bool is_fe, int direction) +{ + struct snd_soc_dapm_widget *dw; + struct snd_soc_dapm_path *dp; + enum snd_soc_dapm_direction dir; + + if (direction == SNDRV_PCM_STREAM_CAPTURE) { + dw = dai->capture_widget; + dir = is_fe ? SND_SOC_DAPM_DIR_OUT : SND_SOC_DAPM_DIR_IN; + } else { + dw = dai->playback_widget; + dir = is_fe ? SND_SOC_DAPM_DIR_IN : SND_SOC_DAPM_DIR_OUT; + } + + dp = list_first_entry_or_null(&dw->edges[dir], typeof(*dp), list_node[dir]); + if (!dp) + return NULL; + + /* Get the other widget, with actual path template data */ + dw = (dp->source == dw) ? dp->sink : dp->source; + + return dw->priv; +} + +static int avs_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, bool is_fe) +{ + struct avs_tplg_path_template *template; + struct avs_dma_data *data; + + template = avs_dai_find_path_template(dai, is_fe, substream->stream); + if (!template) { + dev_err(dai->dev, "no %s path for dai %s, invalid tplg?\n", + snd_pcm_stream_str(substream), dai->name); + return -EINVAL; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->template = template; + snd_soc_dai_set_dma_data(dai, substream, data); + + return 0; +} + +static int avs_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *fe_hw_params, + struct snd_pcm_hw_params *be_hw_params, struct snd_soc_dai *dai, + int dma_id) +{ + struct avs_dma_data *data; + struct avs_path *path; + struct avs_dev *adev = to_avs_dev(dai->dev); + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + + dev_dbg(dai->dev, "%s FE hw_params str %p rtd %p", + __func__, substream, substream->runtime); + dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n", + params_rate(fe_hw_params), params_channels(fe_hw_params), + params_width(fe_hw_params), params_physical_width(fe_hw_params)); + + dev_dbg(dai->dev, "%s BE hw_params str %p rtd %p", + __func__, substream, substream->runtime); + dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n", + params_rate(be_hw_params), params_channels(be_hw_params), + params_width(be_hw_params), params_physical_width(be_hw_params)); + + path = avs_path_create(adev, dma_id, data->template, fe_hw_params, be_hw_params); + if (IS_ERR(path)) { + ret = PTR_ERR(path); + dev_err(dai->dev, "create path failed: %d\n", ret); + return ret; + } + + data->path = path; + return 0; +} + +static int avs_dai_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *be_hw_params, struct snd_soc_dai *dai, + int dma_id) +{ + struct snd_pcm_hw_params *fe_hw_params = NULL; + struct snd_soc_pcm_runtime *fe, *be; + struct snd_soc_dpcm *dpcm; + + be = asoc_substream_to_rtd(substream); + for_each_dpcm_fe(be, substream->stream, dpcm) { + fe = dpcm->fe; + fe_hw_params = &fe->dpcm[substream->stream].hw_params; + } + + return avs_dai_hw_params(substream, fe_hw_params, be_hw_params, dai, dma_id); +} + +static int avs_dai_prepare(struct avs_dev *adev, struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (!data->path) + return 0; + + ret = avs_path_reset(data->path); + if (ret < 0) { + dev_err(dai->dev, "reset path failed: %d\n", ret); + return ret; + } + + ret = avs_path_pause(data->path); + if (ret < 0) + dev_err(dai->dev, "pause path failed: %d\n", ret); + return ret; +} + +static int avs_dai_nonhda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + return avs_dai_startup(substream, dai, false); +} + +static void avs_dai_nonhda_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + + data = snd_soc_dai_get_dma_data(dai, substream); + + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(data); +} + +static int avs_dai_nonhda_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (data->path) + return 0; + + /* Actual port-id comes from topology. */ + return avs_dai_be_hw_params(substream, hw_params, dai, 0); +} + +static int avs_dai_nonhda_be_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + data = snd_soc_dai_get_dma_data(dai, substream); + if (data->path) { + avs_path_free(data->path); + data->path = NULL; + } + + return 0; +} + +static int avs_dai_nonhda_be_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + return avs_dai_prepare(to_avs_dev(dai->dev), substream, dai); +} + +static int avs_dai_nonhda_be_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + int ret = 0; + + data = snd_soc_dai_get_dma_data(dai, substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO); + if (ret < 0) + dev_err(dai->dev, "run BE path failed: %d\n", ret); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + ret = avs_path_pause(data->path); + if (ret < 0) + dev_err(dai->dev, "pause BE path failed: %d\n", ret); + + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + ret = avs_path_reset(data->path); + if (ret < 0) + dev_err(dai->dev, "reset BE path failed: %d\n", ret); + } + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct snd_soc_dai_ops avs_dai_nonhda_be_ops = { + .startup = avs_dai_nonhda_be_startup, + .shutdown = avs_dai_nonhda_be_shutdown, + .hw_params = avs_dai_nonhda_be_hw_params, + .hw_free = avs_dai_nonhda_be_hw_free, + .prepare = avs_dai_nonhda_be_prepare, + .trigger = avs_dai_nonhda_be_trigger, +}; + +static int avs_dai_hda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + return avs_dai_startup(substream, dai, false); +} + +static void avs_dai_hda_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + return avs_dai_nonhda_be_shutdown(substream, dai); +} + +static int avs_dai_hda_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + struct hdac_ext_stream *link_stream; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (data->path) + return 0; + + link_stream = substream->runtime->private_data; + + return avs_dai_be_hw_params(substream, hw_params, dai, + hdac_stream(link_stream)->stream_tag - 1); +} + +static int avs_dai_hda_be_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct hdac_ext_stream *link_stream; + struct hdac_ext_link *link; + struct hda_codec *codec; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + data = snd_soc_dai_get_dma_data(dai, substream); + if (!data->path) + return 0; + + link_stream = substream->runtime->private_data; + link_stream->link_prepared = false; + avs_path_free(data->path); + data->path = NULL; + + /* clear link <-> stream mapping */ + codec = dev_to_hda_codec(asoc_rtd_to_codec(rtd, 0)->dev); + link = snd_hdac_ext_bus_link_at(&codec->bus->core, codec->core.addr); + if (!link) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_hdac_ext_link_clear_stream_id(link, hdac_stream(link_stream)->stream_tag); + + return 0; +} + +static int avs_dai_hda_be_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdac_ext_stream *link_stream = runtime->private_data; + struct hdac_ext_link *link; + struct hda_codec *codec; + struct hdac_bus *bus; + unsigned int format_val; + int ret; + + if (link_stream->link_prepared) + return 0; + + codec = dev_to_hda_codec(asoc_rtd_to_codec(rtd, 0)->dev); + bus = &codec->bus->core; + format_val = snd_hdac_calc_stream_format(runtime->rate, runtime->channels, runtime->format, + runtime->sample_bits, 0); + + snd_hdac_ext_stream_decouple(bus, link_stream, true); + snd_hdac_ext_link_stream_reset(link_stream); + snd_hdac_ext_link_stream_setup(link_stream, format_val); + + link = snd_hdac_ext_bus_link_at(bus, codec->core.addr); + if (!link) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_hdac_ext_link_set_stream_id(link, hdac_stream(link_stream)->stream_tag); + + ret = avs_dai_prepare(to_avs_dev(dai->dev), substream, dai); + if (ret) + return ret; + + link_stream->link_prepared = true; + return 0; +} + +static int avs_dai_hda_be_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *link_stream; + struct avs_dma_data *data; + int ret = 0; + + dev_dbg(dai->dev, "entry %s cmd=%d\n", __func__, cmd); + + data = snd_soc_dai_get_dma_data(dai, substream); + link_stream = substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_hdac_ext_link_stream_start(link_stream); + + ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO); + if (ret < 0) + dev_err(dai->dev, "run BE path failed: %d\n", ret); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + ret = avs_path_pause(data->path); + if (ret < 0) + dev_err(dai->dev, "pause BE path failed: %d\n", ret); + + snd_hdac_ext_link_stream_clear(link_stream); + + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + ret = avs_path_reset(data->path); + if (ret < 0) + dev_err(dai->dev, "reset BE path failed: %d\n", ret); + } + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct snd_soc_dai_ops avs_dai_hda_be_ops = { + .startup = avs_dai_hda_be_startup, + .shutdown = avs_dai_hda_be_shutdown, + .hw_params = avs_dai_hda_be_hw_params, + .hw_free = avs_dai_hda_be_hw_free, + .prepare = avs_dai_hda_be_prepare, + .trigger = avs_dai_hda_be_trigger, +}; + +static const unsigned int rates[] = { + 8000, 11025, 12000, 16000, + 22050, 24000, 32000, 44100, + 48000, 64000, 88200, 96000, + 128000, 176400, 192000, +}; + +static const struct snd_pcm_hw_constraint_list hw_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int avs_dai_fe_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct avs_dma_data *data; + struct avs_dev *adev = to_avs_dev(dai->dev); + struct hdac_bus *bus = &adev->base.core; + struct hdac_ext_stream *host_stream; + int ret; + + ret = avs_dai_startup(substream, dai, true); + if (ret) + return ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + + host_stream = snd_hdac_ext_stream_assign(bus, substream, HDAC_EXT_STREAM_TYPE_HOST); + if (!host_stream) { + kfree(data); + return -EBUSY; + } + + data->host_stream = host_stream; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + /* avoid wrap-around with wall-clock */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME, 20, 178000000); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_rates); + snd_pcm_set_sync(substream); + + dev_dbg(dai->dev, "%s fe STARTUP tag %d str %p", + __func__, hdac_stream(host_stream)->stream_tag, substream); + + return 0; +} + +static void avs_dai_fe_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + + data = snd_soc_dai_get_dma_data(dai, substream); + + snd_soc_dai_set_dma_data(dai, substream, NULL); + snd_hdac_ext_stream_release(data->host_stream, HDAC_EXT_STREAM_TYPE_HOST); + kfree(data); +} + +static int avs_dai_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct snd_pcm_hw_params *be_hw_params = NULL; + struct snd_soc_pcm_runtime *fe, *be; + struct snd_soc_dpcm *dpcm; + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (data->path) + return 0; + + host_stream = data->host_stream; + + hdac_stream(host_stream)->bufsize = 0; + hdac_stream(host_stream)->period_bytes = 0; + hdac_stream(host_stream)->format_val = 0; + + fe = asoc_substream_to_rtd(substream); + for_each_dpcm_be(fe, substream->stream, dpcm) { + be = dpcm->be; + be_hw_params = &be->dpcm[substream->stream].hw_params; + } + + ret = avs_dai_hw_params(substream, hw_params, be_hw_params, dai, + hdac_stream(host_stream)->stream_tag - 1); + if (ret) + goto create_err; + + ret = avs_path_bind(data->path); + if (ret < 0) { + dev_err(dai->dev, "bind FE <-> BE failed: %d\n", ret); + goto bind_err; + } + + return 0; + +bind_err: + avs_path_free(data->path); + data->path = NULL; +create_err: + snd_pcm_lib_free_pages(substream); + return ret; +} + +static int avs_dai_fe_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + int ret; + + dev_dbg(dai->dev, "%s fe HW_FREE str %p rtd %p", + __func__, substream, substream->runtime); + + data = snd_soc_dai_get_dma_data(dai, substream); + if (!data->path) + return 0; + + host_stream = data->host_stream; + + ret = avs_path_unbind(data->path); + if (ret < 0) + dev_err(dai->dev, "unbind FE <-> BE failed: %d\n", ret); + + avs_path_free(data->path); + data->path = NULL; + snd_hdac_stream_cleanup(hdac_stream(host_stream)); + hdac_stream(host_stream)->prepared = false; + + ret = snd_pcm_lib_free_pages(substream); + if (ret < 0) + dev_dbg(dai->dev, "Failed to free pages!\n"); + + return ret; +} + +static int avs_dai_fe_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct avs_dma_data *data; + struct avs_dev *adev = to_avs_dev(dai->dev); + struct hdac_ext_stream *host_stream; + struct hdac_bus *bus; + unsigned int format_val; + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + host_stream = data->host_stream; + + if (hdac_stream(host_stream)->prepared) + return 0; + + bus = hdac_stream(host_stream)->bus; + snd_hdac_ext_stream_decouple(bus, data->host_stream, true); + snd_hdac_stream_reset(hdac_stream(host_stream)); + + format_val = snd_hdac_calc_stream_format(runtime->rate, runtime->channels, runtime->format, + runtime->sample_bits, 0); + + ret = snd_hdac_stream_set_params(hdac_stream(host_stream), format_val); + if (ret < 0) + return ret; + + ret = snd_hdac_stream_setup(hdac_stream(host_stream)); + if (ret < 0) + return ret; + + ret = avs_dai_prepare(adev, substream, dai); + if (ret) + return ret; + + hdac_stream(host_stream)->prepared = true; + return 0; +} + +static int avs_dai_fe_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + struct hdac_bus *bus; + unsigned long flags; + int ret = 0; + + data = snd_soc_dai_get_dma_data(dai, substream); + host_stream = data->host_stream; + bus = hdac_stream(host_stream)->bus; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&bus->reg_lock, flags); + snd_hdac_stream_start(hdac_stream(host_stream), true); + spin_unlock_irqrestore(&bus->reg_lock, flags); + + ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO); + if (ret < 0) + dev_err(dai->dev, "run FE path failed: %d\n", ret); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + ret = avs_path_pause(data->path); + if (ret < 0) + dev_err(dai->dev, "pause FE path failed: %d\n", ret); + + spin_lock_irqsave(&bus->reg_lock, flags); + snd_hdac_stream_stop(hdac_stream(host_stream)); + spin_unlock_irqrestore(&bus->reg_lock, flags); + + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + ret = avs_path_reset(data->path); + if (ret < 0) + dev_err(dai->dev, "reset FE path failed: %d\n", ret); + } + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +const struct snd_soc_dai_ops avs_dai_fe_ops = { + .startup = avs_dai_fe_startup, + .shutdown = avs_dai_fe_shutdown, + .hw_params = avs_dai_fe_hw_params, + .hw_free = avs_dai_fe_hw_free, + .prepare = avs_dai_fe_prepare, + .trigger = avs_dai_fe_trigger, +}; + +static ssize_t topology_name_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct snd_soc_component *component = file->private_data; + struct snd_soc_card *card = component->card; + struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev); + char buf[64]; + size_t len; + + len = scnprintf(buf, sizeof(buf), "%s/%s\n", component->driver->topology_name_prefix, + mach->tplg_filename); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations topology_name_fops = { + .open = simple_open, + .read = topology_name_read, + .llseek = default_llseek, +}; + +static int avs_component_load_libraries(struct avs_soc_component *acomp) +{ + struct avs_tplg *tplg = acomp->tplg; + struct avs_dev *adev = to_avs_dev(acomp->base.dev); + int ret; + + if (!tplg->num_libs) + return 0; + + /* Parent device may be asleep and library loading involves IPCs. */ + ret = pm_runtime_resume_and_get(adev->dev); + if (ret < 0) + return ret; + + avs_hda_clock_gating_enable(adev, false); + avs_hda_l1sen_enable(adev, false); + + ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); + + avs_hda_l1sen_enable(adev, true); + avs_hda_clock_gating_enable(adev, true); + + if (!ret) + ret = avs_module_info_init(adev, false); + + pm_runtime_mark_last_busy(adev->dev); + pm_runtime_put_autosuspend(adev->dev); + + return ret; +} + +static int avs_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct snd_soc_acpi_mach *mach; + struct avs_soc_component *acomp; + struct avs_dev *adev; + char *filename; + int ret; + + dev_dbg(card->dev, "probing %s card %s\n", component->name, card->name); + mach = dev_get_platdata(card->dev); + acomp = to_avs_soc_component(component); + adev = to_avs_dev(component->dev); + + acomp->tplg = avs_tplg_new(component); + if (!acomp->tplg) + return -ENOMEM; + + if (!mach->tplg_filename) + goto finalize; + + /* Load specified topology and create debugfs for it. */ + filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, + mach->tplg_filename); + if (!filename) + return -ENOMEM; + + ret = avs_load_topology(component, filename); + kfree(filename); + if (ret < 0) + return ret; + + ret = avs_component_load_libraries(acomp); + if (ret < 0) { + dev_err(card->dev, "libraries loading failed: %d\n", ret); + goto err_load_libs; + } + +finalize: + debugfs_create_file("topology_name", 0444, component->debugfs_root, component, + &topology_name_fops); + + mutex_lock(&adev->comp_list_mutex); + list_add_tail(&acomp->node, &adev->comp_list); + mutex_unlock(&adev->comp_list_mutex); + + return 0; + +err_load_libs: + avs_remove_topology(component); + return ret; +} + +static void avs_component_remove(struct snd_soc_component *component) +{ + struct avs_soc_component *acomp = to_avs_soc_component(component); + struct snd_soc_acpi_mach *mach; + struct avs_dev *adev = to_avs_dev(component->dev); + int ret; + + mach = dev_get_platdata(component->card->dev); + + mutex_lock(&adev->comp_list_mutex); + list_del(&acomp->node); + mutex_unlock(&adev->comp_list_mutex); + + if (mach->tplg_filename) { + ret = avs_remove_topology(component); + if (ret < 0) + dev_err(component->dev, "unload topology failed: %d\n", ret); + } +} + +static int avs_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_pcm_hardware hwparams; + + /* only FE DAI links are handled here */ + if (rtd->dai_link->no_pcm) + return 0; + + hwparams.info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP; + + hwparams.formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE; + hwparams.period_bytes_min = 128; + hwparams.period_bytes_max = AZX_MAX_BUF_SIZE / 2; + hwparams.periods_min = 2; + hwparams.periods_max = AZX_MAX_FRAG; + hwparams.buffer_bytes_max = AZX_MAX_BUF_SIZE; + hwparams.fifo_size = 0; + + return snd_soc_set_runtime_hwparams(substream, &hwparams); +} + +static unsigned int avs_hda_stream_dpib_read(struct hdac_ext_stream *stream) +{ + return readl(hdac_stream(stream)->bus->remap_addr + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * hdac_stream(stream)->index)); +} + +static snd_pcm_uframes_t +avs_component_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + unsigned int pos; + + data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + if (!data->host_stream) + return 0; + + host_stream = data->host_stream; + pos = avs_hda_stream_dpib_read(host_stream); + + if (pos >= hdac_stream(host_stream)->bufsize) + pos = 0; + + return bytes_to_frames(substream->runtime, pos); +} + +static int avs_component_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return snd_pcm_lib_default_mmap(substream, vma); +} + +#define MAX_PREALLOC_SIZE (32 * 1024 * 1024) + +static int avs_component_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_pcm *pcm = rtd->pcm; + + if (dai->driver->playback.channels_min) + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_DEV_SG, component->dev, 0, + MAX_PREALLOC_SIZE); + + if (dai->driver->capture.channels_min) + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV_SG, component->dev, 0, + MAX_PREALLOC_SIZE); + + return 0; +} + +static const struct snd_soc_component_driver avs_component_driver = { + .name = "avs-pcm", + .probe = avs_component_probe, + .remove = avs_component_remove, + .open = avs_component_open, + .pointer = avs_component_pointer, + .mmap = avs_component_mmap, + .pcm_construct = avs_component_construct, + .module_get_upon_open = 1, /* increment refcount when a pcm is opened */ + .topology_name_prefix = "intel/avs", +}; + +static int avs_soc_component_register(struct device *dev, const char *name, + const struct snd_soc_component_driver *drv, + struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) +{ + struct avs_soc_component *acomp; + int ret; + + acomp = devm_kzalloc(dev, sizeof(*acomp), GFP_KERNEL); + if (!acomp) + return -ENOMEM; + + ret = snd_soc_component_initialize(&acomp->base, drv, dev); + if (ret < 0) + return ret; + + /* force name change after ASoC is done with its init */ + acomp->base.name = name; + INIT_LIST_HEAD(&acomp->node); + + return snd_soc_add_component(&acomp->base, cpu_dais, num_cpu_dais); +} + +static struct snd_soc_dai_driver dmic_cpu_dais[] = { +{ + .name = "DMIC Pin", + .ops = &avs_dai_nonhda_be_ops, + .capture = { + .stream_name = "DMIC Rx", + .channels_min = 1, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "DMIC WoV Pin", + .ops = &avs_dai_nonhda_be_ops, + .capture = { + .stream_name = "DMIC WoV Rx", + .channels_min = 1, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +int avs_dmic_platform_register(struct avs_dev *adev, const char *name) +{ + return avs_soc_component_register(adev->dev, name, &avs_component_driver, dmic_cpu_dais, + ARRAY_SIZE(dmic_cpu_dais)); +} + +static const struct snd_soc_dai_driver i2s_dai_template = { + .ops = &avs_dai_nonhda_be_ops, + .playback = { + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000 | + SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000 | + SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, +}; + +int avs_i2s_platform_register(struct avs_dev *adev, const char *name, unsigned long port_mask, + unsigned long *tdms) +{ + struct snd_soc_dai_driver *cpus, *dai; + size_t ssp_count, cpu_count; + int i, j; + + ssp_count = adev->hw_cfg.i2s_caps.ctrl_count; + cpu_count = hweight_long(port_mask); + if (tdms) + for_each_set_bit(i, &port_mask, ssp_count) + cpu_count += hweight_long(tdms[i]); + + cpus = devm_kzalloc(adev->dev, sizeof(*cpus) * cpu_count, GFP_KERNEL); + if (!cpus) + return -ENOMEM; + + dai = cpus; + for_each_set_bit(i, &port_mask, ssp_count) { + memcpy(dai, &i2s_dai_template, sizeof(*dai)); + + dai->name = + devm_kasprintf(adev->dev, GFP_KERNEL, "SSP%d Pin", i); + dai->playback.stream_name = + devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d Tx", i); + dai->capture.stream_name = + devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d Rx", i); + + if (!dai->name || !dai->playback.stream_name || !dai->capture.stream_name) + return -ENOMEM; + dai++; + } + + if (!tdms) + goto plat_register; + + for_each_set_bit(i, &port_mask, ssp_count) { + for_each_set_bit(j, &tdms[i], ssp_count) { + memcpy(dai, &i2s_dai_template, sizeof(*dai)); + + dai->name = + devm_kasprintf(adev->dev, GFP_KERNEL, "SSP%d:%d Pin", i, j); + dai->playback.stream_name = + devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d:%d Tx", i, j); + dai->capture.stream_name = + devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d:%d Rx", i, j); + + if (!dai->name || !dai->playback.stream_name || !dai->capture.stream_name) + return -ENOMEM; + dai++; + } + } + +plat_register: + return avs_soc_component_register(adev->dev, name, &avs_component_driver, cpus, cpu_count); +} + +/* HD-Audio CPU DAI template */ +static const struct snd_soc_dai_driver hda_cpu_dai = { + .ops = &avs_dai_hda_be_ops, + .playback = { + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, +}; + +static void avs_component_hda_unregister_dais(struct snd_soc_component *component) +{ + struct snd_soc_acpi_mach *mach; + struct snd_soc_dai *dai, *save; + struct hda_codec *codec; + char name[32]; + + mach = dev_get_platdata(component->card->dev); + codec = mach->pdata; + sprintf(name, "%s-cpu", dev_name(&codec->core.dev)); + + for_each_component_dais_safe(component, dai, save) { + if (!strstr(dai->driver->name, name)) + continue; + + if (dai->playback_widget) + snd_soc_dapm_free_widget(dai->playback_widget); + if (dai->capture_widget) + snd_soc_dapm_free_widget(dai->capture_widget); + snd_soc_unregister_dai(dai); + } +} + +static int avs_component_hda_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_dai_driver *dais; + struct snd_soc_acpi_mach *mach; + struct hda_codec *codec; + struct hda_pcm *pcm; + const char *cname; + int pcm_count = 0, ret, i; + + mach = dev_get_platdata(component->card->dev); + if (!mach) + return -EINVAL; + + codec = mach->pdata; + if (list_empty(&codec->pcm_list_head)) + return -EINVAL; + list_for_each_entry(pcm, &codec->pcm_list_head, list) + pcm_count++; + + dais = devm_kcalloc(component->dev, pcm_count, sizeof(*dais), + GFP_KERNEL); + if (!dais) + return -ENOMEM; + + cname = dev_name(&codec->core.dev); + dapm = snd_soc_component_get_dapm(component); + pcm = list_first_entry(&codec->pcm_list_head, struct hda_pcm, list); + + for (i = 0; i < pcm_count; i++, pcm = list_next_entry(pcm, list)) { + struct snd_soc_dai *dai; + + memcpy(&dais[i], &hda_cpu_dai, sizeof(*dais)); + dais[i].id = i; + dais[i].name = devm_kasprintf(component->dev, GFP_KERNEL, + "%s-cpu%d", cname, i); + if (!dais[i].name) { + ret = -ENOMEM; + goto exit; + } + + if (pcm->stream[0].substreams) { + dais[i].playback.stream_name = + devm_kasprintf(component->dev, GFP_KERNEL, + "%s-cpu%d Tx", cname, i); + if (!dais[i].playback.stream_name) { + ret = -ENOMEM; + goto exit; + } + } + + if (pcm->stream[1].substreams) { + dais[i].capture.stream_name = + devm_kasprintf(component->dev, GFP_KERNEL, + "%s-cpu%d Rx", cname, i); + if (!dais[i].capture.stream_name) { + ret = -ENOMEM; + goto exit; + } + } + + dai = snd_soc_register_dai(component, &dais[i], false); + if (!dai) { + dev_err(component->dev, "register dai for %s failed\n", + pcm->name); + ret = -EINVAL; + goto exit; + } + + ret = snd_soc_dapm_new_dai_widgets(dapm, dai); + if (ret < 0) { + dev_err(component->dev, "create widgets failed: %d\n", + ret); + goto exit; + } + } + + ret = avs_component_probe(component); +exit: + if (ret) + avs_component_hda_unregister_dais(component); + + return ret; +} + +static void avs_component_hda_remove(struct snd_soc_component *component) +{ + avs_component_hda_unregister_dais(component); + avs_component_remove(component); +} + +static int avs_component_hda_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct hdac_ext_stream *link_stream; + struct hda_codec *codec; + + /* only BE DAI links are handled here */ + if (!rtd->dai_link->no_pcm) + return avs_component_open(component, substream); + + codec = dev_to_hda_codec(asoc_rtd_to_codec(rtd, 0)->dev); + link_stream = snd_hdac_ext_stream_assign(&codec->bus->core, substream, + HDAC_EXT_STREAM_TYPE_LINK); + if (!link_stream) + return -EBUSY; + + substream->runtime->private_data = link_stream; + return 0; +} + +static int avs_component_hda_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct hdac_ext_stream *link_stream; + + /* only BE DAI links are handled here */ + if (!rtd->dai_link->no_pcm) + return 0; + + link_stream = substream->runtime->private_data; + snd_hdac_ext_stream_release(link_stream, HDAC_EXT_STREAM_TYPE_LINK); + substream->runtime->private_data = NULL; + + return 0; +} + +static const struct snd_soc_component_driver avs_hda_component_driver = { + .name = "avs-hda-pcm", + .probe = avs_component_hda_probe, + .remove = avs_component_hda_remove, + .open = avs_component_hda_open, + .close = avs_component_hda_close, + .pointer = avs_component_pointer, + .mmap = avs_component_mmap, + .pcm_construct = avs_component_construct, + /* + * hda platform component's probe() is dependent on + * codec->pcm_list_head, it needs to be initialized after codec + * component. remove_order is here for completeness sake + */ + .probe_order = SND_SOC_COMP_ORDER_LATE, + .remove_order = SND_SOC_COMP_ORDER_EARLY, + .module_get_upon_open = 1, + .topology_name_prefix = "intel/avs", +}; + +int avs_hda_platform_register(struct avs_dev *adev, const char *name) +{ + return avs_soc_component_register(adev->dev, name, + &avs_hda_component_driver, NULL, 0); +} diff --git a/sound/soc/intel/avs/registers.h b/sound/soc/intel/avs/registers.h new file mode 100644 index 000000000000..95be86148cf3 --- /dev/null +++ b/sound/soc/intel/avs/registers.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation. All rights reserved. + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_REGS_H +#define __SOUND_SOC_INTEL_AVS_REGS_H + +#define AZX_PCIREG_PGCTL 0x44 +#define AZX_PCIREG_CGCTL 0x48 +#define AZX_PGCTL_LSRMD_MASK BIT(4) +#define AZX_CGCTL_MISCBDCGE_MASK BIT(6) +#define AZX_VS_EM2_L1SEN BIT(13) +#define AZX_VS_EM2_DUM BIT(23) + +/* Intel HD Audio General DSP Registers */ +#define AVS_ADSP_GEN_BASE 0x0 +#define AVS_ADSP_REG_ADSPCS (AVS_ADSP_GEN_BASE + 0x04) +#define AVS_ADSP_REG_ADSPIC (AVS_ADSP_GEN_BASE + 0x08) +#define AVS_ADSP_REG_ADSPIS (AVS_ADSP_GEN_BASE + 0x0C) + +#define AVS_ADSP_ADSPIC_IPC BIT(0) +#define AVS_ADSP_ADSPIC_CLDMA BIT(1) +#define AVS_ADSP_ADSPIS_IPC BIT(0) +#define AVS_ADSP_ADSPIS_CLDMA BIT(1) + +#define AVS_ADSPCS_CRST_MASK(cm) (cm) +#define AVS_ADSPCS_CSTALL_MASK(cm) ((cm) << 8) +#define AVS_ADSPCS_SPA_MASK(cm) ((cm) << 16) +#define AVS_ADSPCS_CPA_MASK(cm) ((cm) << 24) +#define AVS_MAIN_CORE_MASK BIT(0) + +#define AVS_ADSP_HIPCCTL_BUSY BIT(0) +#define AVS_ADSP_HIPCCTL_DONE BIT(1) + +/* SKL Intel HD Audio Inter-Processor Communication Registers */ +#define SKL_ADSP_IPC_BASE 0x40 +#define SKL_ADSP_REG_HIPCT (SKL_ADSP_IPC_BASE + 0x00) +#define SKL_ADSP_REG_HIPCTE (SKL_ADSP_IPC_BASE + 0x04) +#define SKL_ADSP_REG_HIPCI (SKL_ADSP_IPC_BASE + 0x08) +#define SKL_ADSP_REG_HIPCIE (SKL_ADSP_IPC_BASE + 0x0C) +#define SKL_ADSP_REG_HIPCCTL (SKL_ADSP_IPC_BASE + 0x10) + +#define SKL_ADSP_HIPCI_BUSY BIT(31) +#define SKL_ADSP_HIPCIE_DONE BIT(30) +#define SKL_ADSP_HIPCT_BUSY BIT(31) + +/* Intel HD Audio SRAM windows base addresses */ +#define SKL_ADSP_SRAM_BASE_OFFSET 0x8000 +#define SKL_ADSP_SRAM_WINDOW_SIZE 0x2000 +#define APL_ADSP_SRAM_BASE_OFFSET 0x80000 +#define APL_ADSP_SRAM_WINDOW_SIZE 0x20000 + +/* Constants used when accessing SRAM, space shared with firmware */ +#define AVS_FW_REG_BASE(adev) ((adev)->spec->sram_base_offset) +#define AVS_FW_REG_STATUS(adev) (AVS_FW_REG_BASE(adev) + 0x0) +#define AVS_FW_REG_ERROR_CODE(adev) (AVS_FW_REG_BASE(adev) + 0x4) + +#define AVS_FW_REGS_SIZE PAGE_SIZE +#define AVS_FW_REGS_WINDOW 0 +/* DSP -> HOST communication window */ +#define AVS_UPLINK_WINDOW AVS_FW_REGS_WINDOW +/* HOST -> DSP communication window */ +#define AVS_DOWNLINK_WINDOW 1 +#define AVS_DEBUG_WINDOW 2 + +/* registry I/O helpers */ +#define avs_sram_offset(adev, window_idx) \ + ((adev)->spec->sram_base_offset + \ + (adev)->spec->sram_window_size * (window_idx)) + +#define avs_sram_addr(adev, window_idx) \ + ((adev)->dsp_ba + avs_sram_offset(adev, window_idx)) + +#define avs_uplink_addr(adev) \ + (avs_sram_addr(adev, AVS_UPLINK_WINDOW) + AVS_FW_REGS_SIZE) +#define avs_downlink_addr(adev) \ + avs_sram_addr(adev, AVS_DOWNLINK_WINDOW) + +#endif /* __SOUND_SOC_INTEL_AVS_REGS_H */ diff --git a/sound/soc/intel/avs/skl.c b/sound/soc/intel/avs/skl.c new file mode 100644 index 000000000000..bda5ec7510fe --- /dev/null +++ b/sound/soc/intel/avs/skl.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/devcoredump.h> +#include <linux/slab.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "messages.h" + +static int skl_enable_logs(struct avs_dev *adev, enum avs_log_enable enable, u32 aging_period, + u32 fifo_full_period, unsigned long resource_mask, u32 *priorities) +{ + struct skl_log_state_info *info; + u32 size, num_cores = adev->hw_cfg.dsp_cores; + int ret, i; + + if (fls_long(resource_mask) > num_cores) + return -EINVAL; + size = struct_size(info, logs_core, num_cores); + info = kzalloc(size, GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->core_mask = resource_mask; + if (enable) + for_each_set_bit(i, &resource_mask, GENMASK(num_cores, 0)) { + info->logs_core[i].enable = enable; + info->logs_core[i].min_priority = *priorities++; + } + else + for_each_set_bit(i, &resource_mask, GENMASK(num_cores, 0)) + info->logs_core[i].enable = enable; + + ret = avs_ipc_set_enable_logs(adev, (u8 *)info, size); + kfree(info); + if (ret) + return AVS_IPC_RET(ret); + + return 0; +} + +int skl_log_buffer_offset(struct avs_dev *adev, u32 core) +{ + return core * avs_log_buffer_size(adev); +} + +/* fw DbgLogWp registers */ +#define FW_REGS_DBG_LOG_WP(core) (0x30 + 0x4 * core) + +static int +skl_log_buffer_status(struct avs_dev *adev, union avs_notify_msg *msg) +{ + unsigned long flags; + void __iomem *buf; + u16 size, write, offset; + + spin_lock_irqsave(&adev->dbg.trace_lock, flags); + if (!kfifo_initialized(&adev->dbg.trace_fifo)) { + spin_unlock_irqrestore(&adev->dbg.trace_lock, flags); + return 0; + } + + size = avs_log_buffer_size(adev) / 2; + write = readl(avs_sram_addr(adev, AVS_FW_REGS_WINDOW) + FW_REGS_DBG_LOG_WP(msg->log.core)); + /* determine buffer half */ + offset = (write < size) ? size : 0; + + /* Address is guaranteed to exist in SRAM2. */ + buf = avs_log_buffer_addr(adev, msg->log.core) + offset; + __kfifo_fromio_locked(&adev->dbg.trace_fifo, buf, size, &adev->dbg.fifo_lock); + wake_up(&adev->dbg.trace_waitq); + spin_unlock_irqrestore(&adev->dbg.trace_lock, flags); + + return 0; +} + +static int skl_coredump(struct avs_dev *adev, union avs_notify_msg *msg) +{ + u8 *dump; + + dump = vzalloc(AVS_FW_REGS_SIZE); + if (!dump) + return -ENOMEM; + + memcpy_fromio(dump, avs_sram_addr(adev, AVS_FW_REGS_WINDOW), AVS_FW_REGS_SIZE); + dev_coredumpv(adev->dev, dump, AVS_FW_REGS_SIZE, GFP_KERNEL); + + return 0; +} + +static bool +skl_d0ix_toggle(struct avs_dev *adev, struct avs_ipc_msg *tx, bool wake) +{ + /* unsupported on cAVS 1.5 hw */ + return false; +} + +static int skl_set_d0ix(struct avs_dev *adev, bool enable) +{ + /* unsupported on cAVS 1.5 hw */ + return 0; +} + +const struct avs_dsp_ops skl_dsp_ops = { + .power = avs_dsp_core_power, + .reset = avs_dsp_core_reset, + .stall = avs_dsp_core_stall, + .irq_handler = avs_dsp_irq_handler, + .irq_thread = avs_dsp_irq_thread, + .int_control = avs_dsp_interrupt_control, + .load_basefw = avs_cldma_load_basefw, + .load_lib = avs_cldma_load_library, + .transfer_mods = avs_cldma_transfer_modules, + .enable_logs = skl_enable_logs, + .log_buffer_offset = skl_log_buffer_offset, + .log_buffer_status = skl_log_buffer_status, + .coredump = skl_coredump, + .d0ix_toggle = skl_d0ix_toggle, + .set_d0ix = skl_set_d0ix, +}; diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c new file mode 100644 index 000000000000..8a9f9fc48938 --- /dev/null +++ b/sound/soc/intel/avs/topology.c @@ -0,0 +1,1625 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/uuid.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-topology.h> +#include <uapi/sound/intel/avs/tokens.h> +#include "avs.h" +#include "topology.h" + +/* Get pointer to vendor array at the specified offset. */ +#define avs_tplg_vendor_array_at(array, offset) \ + ((struct snd_soc_tplg_vendor_array *)((u8 *)array + offset)) + +/* Get pointer to vendor array that is next in line. */ +#define avs_tplg_vendor_array_next(array) \ + (avs_tplg_vendor_array_at(array, le32_to_cpu((array)->size))) + +/* + * Scan provided block of tuples for the specified token. If found, + * @offset is updated with position at which first matching token is + * located. + * + * Returns 0 on success, -ENOENT if not found and error code otherwise. + */ +static int +avs_tplg_vendor_array_lookup(struct snd_soc_tplg_vendor_array *tuples, + u32 block_size, u32 token, u32 *offset) +{ + u32 pos = 0; + + while (block_size > 0) { + struct snd_soc_tplg_vendor_value_elem *tuple; + u32 tuples_size = le32_to_cpu(tuples->size); + + if (tuples_size > block_size) + return -EINVAL; + + tuple = tuples->value; + if (le32_to_cpu(tuple->token) == token) { + *offset = pos; + return 0; + } + + block_size -= tuples_size; + pos += tuples_size; + tuples = avs_tplg_vendor_array_next(tuples); + } + + return -ENOENT; +} + +/* + * See avs_tplg_vendor_array_lookup() for description. + * + * Behaves exactly like avs_tplg_vendor_lookup() but starts from the + * next vendor array in line. Useful when searching for the finish line + * of an arbitrary entry in a list of entries where each is composed of + * several vendor tuples and a specific token marks the beginning of + * a new entry block. + */ +static int +avs_tplg_vendor_array_lookup_next(struct snd_soc_tplg_vendor_array *tuples, + u32 block_size, u32 token, u32 *offset) +{ + u32 tuples_size = le32_to_cpu(tuples->size); + int ret; + + if (tuples_size > block_size) + return -EINVAL; + + tuples = avs_tplg_vendor_array_next(tuples); + block_size -= tuples_size; + + ret = avs_tplg_vendor_array_lookup(tuples, block_size, token, offset); + if (!ret) + *offset += tuples_size; + return ret; +} + +/* + * Scan provided block of tuples for the specified token which marks + * the border of an entry block. Behavior is similar to + * avs_tplg_vendor_array_lookup() except 0 is also returned if no + * matching token has been found. In such case, returned @size is + * assigned to @block_size as the entire block belongs to the current + * entry. + * + * Returns 0 on success, error code otherwise. + */ +static int +avs_tplg_vendor_entry_size(struct snd_soc_tplg_vendor_array *tuples, + u32 block_size, u32 entry_id_token, u32 *size) +{ + int ret; + + ret = avs_tplg_vendor_array_lookup_next(tuples, block_size, entry_id_token, size); + if (ret == -ENOENT) { + *size = block_size; + ret = 0; + } + + return ret; +} + +/* + * Vendor tuple parsing descriptor. + * + * @token: vendor specific token that identifies tuple + * @type: tuple type, one of SND_SOC_TPLG_TUPLE_TYPE_XXX + * @offset: offset of a struct's field to initialize + * @parse: parsing function, extracts and assigns value to object's field + */ +struct avs_tplg_token_parser { + enum avs_tplg_token token; + u32 type; + u32 offset; + int (*parse)(struct snd_soc_component *comp, void *elem, void *object, u32 offset); +}; + +static int +avs_parse_uuid_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_uuid_elem *tuple = elem; + guid_t *val = (guid_t *)((u8 *)object + offset); + + guid_copy((guid_t *)val, (const guid_t *)&tuple->uuid); + + return 0; +} + +static int +avs_parse_bool_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + bool *val = (bool *)((u8 *)object + offset); + + *val = le32_to_cpu(tuple->value); + + return 0; +} + +static int +avs_parse_byte_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + u8 *val = ((u8 *)object + offset); + + *val = le32_to_cpu(tuple->value); + + return 0; +} + +static int +avs_parse_short_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + u16 *val = (u16 *)((u8 *)object + offset); + + *val = le32_to_cpu(tuple->value); + + return 0; +} + +static int +avs_parse_word_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = le32_to_cpu(tuple->value); + + return 0; +} + +static int +avs_parse_string_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_string_elem *tuple = elem; + char *val = (char *)((u8 *)object + offset); + + snprintf(val, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s", tuple->string); + + return 0; +} + +static int avs_parse_uuid_tokens(struct snd_soc_component *comp, void *object, + const struct avs_tplg_token_parser *parsers, int count, + struct snd_soc_tplg_vendor_array *tuples) +{ + struct snd_soc_tplg_vendor_uuid_elem *tuple; + int ret, i, j; + + /* Parse element by element. */ + for (i = 0; i < le32_to_cpu(tuples->num_elems); i++) { + tuple = &tuples->uuid[i]; + + for (j = 0; j < count; j++) { + /* Ignore non-UUID tokens. */ + if (parsers[j].type != SND_SOC_TPLG_TUPLE_TYPE_UUID || + parsers[j].token != le32_to_cpu(tuple->token)) + continue; + + ret = parsers[j].parse(comp, tuple, object, parsers[j].offset); + if (ret) + return ret; + } + } + + return 0; +} + +static int avs_parse_string_tokens(struct snd_soc_component *comp, void *object, + const struct avs_tplg_token_parser *parsers, int count, + struct snd_soc_tplg_vendor_array *tuples) +{ + struct snd_soc_tplg_vendor_string_elem *tuple; + int ret, i, j; + + /* Parse element by element. */ + for (i = 0; i < le32_to_cpu(tuples->num_elems); i++) { + tuple = &tuples->string[i]; + + for (j = 0; j < count; j++) { + /* Ignore non-string tokens. */ + if (parsers[j].type != SND_SOC_TPLG_TUPLE_TYPE_STRING || + parsers[j].token != le32_to_cpu(tuple->token)) + continue; + + ret = parsers[j].parse(comp, tuple, object, parsers[j].offset); + if (ret) + return ret; + } + } + + return 0; +} + +static int avs_parse_word_tokens(struct snd_soc_component *comp, void *object, + const struct avs_tplg_token_parser *parsers, int count, + struct snd_soc_tplg_vendor_array *tuples) +{ + struct snd_soc_tplg_vendor_value_elem *tuple; + int ret, i, j; + + /* Parse element by element. */ + for (i = 0; i < le32_to_cpu(tuples->num_elems); i++) { + tuple = &tuples->value[i]; + + for (j = 0; j < count; j++) { + /* Ignore non-integer tokens. */ + if (!(parsers[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD || + parsers[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT || + parsers[j].type == SND_SOC_TPLG_TUPLE_TYPE_BYTE || + parsers[j].type == SND_SOC_TPLG_TUPLE_TYPE_BOOL)) + continue; + + if (parsers[j].token != le32_to_cpu(tuple->token)) + continue; + + ret = parsers[j].parse(comp, tuple, object, parsers[j].offset); + if (ret) + return ret; + } + } + + return 0; +} + +static int avs_parse_tokens(struct snd_soc_component *comp, void *object, + const struct avs_tplg_token_parser *parsers, size_t count, + struct snd_soc_tplg_vendor_array *tuples, int priv_size) +{ + int array_size, ret; + + while (priv_size > 0) { + array_size = le32_to_cpu(tuples->size); + + if (array_size <= 0) { + dev_err(comp->dev, "invalid array size 0x%x\n", array_size); + return -EINVAL; + } + + /* Make sure there is enough data before parsing. */ + priv_size -= array_size; + if (priv_size < 0) { + dev_err(comp->dev, "invalid array size 0x%x\n", array_size); + return -EINVAL; + } + + switch (le32_to_cpu(tuples->type)) { + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + ret = avs_parse_uuid_tokens(comp, object, parsers, count, tuples); + break; + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + ret = avs_parse_string_tokens(comp, object, parsers, count, tuples); + break; + case SND_SOC_TPLG_TUPLE_TYPE_BOOL: + case SND_SOC_TPLG_TUPLE_TYPE_BYTE: + case SND_SOC_TPLG_TUPLE_TYPE_SHORT: + case SND_SOC_TPLG_TUPLE_TYPE_WORD: + ret = avs_parse_word_tokens(comp, object, parsers, count, tuples); + break; + default: + dev_err(comp->dev, "unknown token type %d\n", tuples->type); + ret = -EINVAL; + } + + if (ret) { + dev_err(comp->dev, "parsing %zu tokens of %d type failed: %d\n", + count, tuples->type, ret); + return ret; + } + + tuples = avs_tplg_vendor_array_next(tuples); + } + + return 0; +} + +#define AVS_DEFINE_PTR_PARSER(name, type, member) \ +static int \ +avs_parse_##name##_ptr(struct snd_soc_component *comp, void *elem, void *object, u32 offset) \ +{ \ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; \ + struct avs_soc_component *acomp = to_avs_soc_component(comp); \ + type **val = (type **)(object + offset); \ + u32 idx; \ + \ + idx = le32_to_cpu(tuple->value); \ + if (idx >= acomp->tplg->num_##member) \ + return -EINVAL; \ + \ + *val = &acomp->tplg->member[idx]; \ + \ + return 0; \ +} + +AVS_DEFINE_PTR_PARSER(audio_format, struct avs_audio_format, fmts); +AVS_DEFINE_PTR_PARSER(modcfg_base, struct avs_tplg_modcfg_base, modcfgs_base); +AVS_DEFINE_PTR_PARSER(modcfg_ext, struct avs_tplg_modcfg_ext, modcfgs_ext); +AVS_DEFINE_PTR_PARSER(pplcfg, struct avs_tplg_pplcfg, pplcfgs); +AVS_DEFINE_PTR_PARSER(binding, struct avs_tplg_binding, bindings); + +static int +parse_audio_format_bitfield(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *velem = elem; + struct avs_audio_format *audio_format = object; + + switch (offset) { + case AVS_TKN_AFMT_NUM_CHANNELS_U32: + audio_format->num_channels = le32_to_cpu(velem->value); + break; + case AVS_TKN_AFMT_VALID_BIT_DEPTH_U32: + audio_format->valid_bit_depth = le32_to_cpu(velem->value); + break; + case AVS_TKN_AFMT_SAMPLE_TYPE_U32: + audio_format->sample_type = le32_to_cpu(velem->value); + break; + } + + return 0; +} + +static int parse_link_formatted_string(struct snd_soc_component *comp, void *elem, + void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_string_elem *tuple = elem; + struct snd_soc_acpi_mach *mach = dev_get_platdata(comp->card->dev); + char *val = (char *)((u8 *)object + offset); + + /* + * Dynamic naming - string formats, e.g.: ssp%d - supported only for + * topologies describing single device e.g.: an I2S codec on SSP0. + */ + if (hweight_long(mach->mach_params.i2s_link_mask) != 1) + return avs_parse_string_token(comp, elem, object, offset); + + snprintf(val, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, tuple->string, + __ffs(mach->mach_params.i2s_link_mask)); + + return 0; +} + +static int +parse_dictionary_header(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + void **dict, u32 *num_entries, size_t entry_size, + u32 num_entries_token) +{ + struct snd_soc_tplg_vendor_value_elem *tuple; + + /* Dictionary header consists of single tuple - entry count. */ + tuple = tuples->value; + if (le32_to_cpu(tuple->token) != num_entries_token) { + dev_err(comp->dev, "invalid dictionary header, expected: %d\n", + num_entries_token); + return -EINVAL; + } + + *num_entries = le32_to_cpu(tuple->value); + *dict = devm_kcalloc(comp->card->dev, *num_entries, entry_size, GFP_KERNEL); + if (!*dict) + return -ENOMEM; + + return 0; +} + +static int +parse_dictionary_entries(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size, + void *dict, u32 num_entries, size_t entry_size, + u32 entry_id_token, + const struct avs_tplg_token_parser *parsers, size_t num_parsers) +{ + void *pos = dict; + int i; + + for (i = 0; i < num_entries; i++) { + u32 esize; + int ret; + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + entry_id_token, &esize); + if (ret) + return ret; + + ret = avs_parse_tokens(comp, pos, parsers, num_parsers, tuples, esize); + if (ret < 0) { + dev_err(comp->dev, "parse entry: %d of type: %d failed: %d\n", + i, entry_id_token, ret); + return ret; + } + + pos += entry_size; + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } + + return 0; +} + +static int parse_dictionary(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size, + void **dict, u32 *num_entries, size_t entry_size, + u32 num_entries_token, u32 entry_id_token, + const struct avs_tplg_token_parser *parsers, size_t num_parsers) +{ + int ret; + + ret = parse_dictionary_header(comp, tuples, dict, num_entries, + entry_size, num_entries_token); + if (ret) + return ret; + + block_size -= le32_to_cpu(tuples->size); + /* With header parsed, move on to parsing entries. */ + tuples = avs_tplg_vendor_array_next(tuples); + + return parse_dictionary_entries(comp, tuples, block_size, *dict, + *num_entries, entry_size, + entry_id_token, parsers, num_parsers); +} + +static const struct avs_tplg_token_parser library_parsers[] = { + { + .token = AVS_TKN_LIBRARY_NAME_STRING, + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .offset = offsetof(struct avs_tplg_library, name), + .parse = avs_parse_string_token, + }, +}; + +static int avs_tplg_parse_libraries(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->libs, + &tplg->num_libs, sizeof(*tplg->libs), + AVS_TKN_MANIFEST_NUM_LIBRARIES_U32, + AVS_TKN_LIBRARY_ID_U32, + library_parsers, ARRAY_SIZE(library_parsers)); +} + +static const struct avs_tplg_token_parser audio_format_parsers[] = { + { + .token = AVS_TKN_AFMT_SAMPLE_RATE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, sampling_freq), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_BIT_DEPTH_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, bit_depth), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_CHANNEL_MAP_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, channel_map), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_CHANNEL_CFG_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, channel_config), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_INTERLEAVING_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, interleaving), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_NUM_CHANNELS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = AVS_TKN_AFMT_NUM_CHANNELS_U32, + .parse = parse_audio_format_bitfield, + }, + { + .token = AVS_TKN_AFMT_VALID_BIT_DEPTH_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = AVS_TKN_AFMT_VALID_BIT_DEPTH_U32, + .parse = parse_audio_format_bitfield, + }, + { + .token = AVS_TKN_AFMT_SAMPLE_TYPE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = AVS_TKN_AFMT_SAMPLE_TYPE_U32, + .parse = parse_audio_format_bitfield, + }, +}; + +static int avs_tplg_parse_audio_formats(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->fmts, + &tplg->num_fmts, sizeof(*tplg->fmts), + AVS_TKN_MANIFEST_NUM_AFMTS_U32, + AVS_TKN_AFMT_ID_U32, + audio_format_parsers, ARRAY_SIZE(audio_format_parsers)); +} + +static const struct avs_tplg_token_parser modcfg_base_parsers[] = { + { + .token = AVS_TKN_MODCFG_BASE_CPC_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_base, cpc), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_BASE_IBS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_base, ibs), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_BASE_OBS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_base, obs), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_BASE_PAGES_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_base, is_pages), + .parse = avs_parse_word_token, + }, +}; + +static int avs_tplg_parse_modcfgs_base(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->modcfgs_base, + &tplg->num_modcfgs_base, sizeof(*tplg->modcfgs_base), + AVS_TKN_MANIFEST_NUM_MODCFGS_BASE_U32, + AVS_TKN_MODCFG_BASE_ID_U32, + modcfg_base_parsers, ARRAY_SIZE(modcfg_base_parsers)); +} + +static const struct avs_tplg_token_parser modcfg_ext_parsers[] = { + { + .token = AVS_TKN_MODCFG_EXT_TYPE_UUID, + .type = SND_SOC_TPLG_TUPLE_TYPE_UUID, + .offset = offsetof(struct avs_tplg_modcfg_ext, type), + .parse = avs_parse_uuid_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_CPR_FEATURE_MASK_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.feature_mask), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_VINDEX_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.vindex), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_DMA_TYPE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.dma_type), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_DMABUFF_SIZE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.dma_buffer_size), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_BLOB_FMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.blob_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_MICSEL_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, micsel.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_INTELWOV_CPC_LP_MODE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, wov.cpc_lp_mode), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_SRC_OUT_FREQ_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, src.out_freq), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_MUX_REF_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, mux.ref_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_MUX_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, mux.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_AEC_REF_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, aec.ref_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_AEC_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, aec.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_AEC_CPC_LP_MODE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, aec.cpc_lp_mode), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_ASRC_OUT_FREQ_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, asrc.out_freq), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_ASRC_MODE_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_modcfg_ext, asrc.mode), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MODCFG_ASRC_DISABLE_JITTER_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_modcfg_ext, asrc.disable_jitter_buffer), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_OUT_CHAN_CFG_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.out_channel_config), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_SELECT_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients_select), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_0_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[0]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_1_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[1]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_2_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[2]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_3_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[3]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_4_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[4]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_5_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[5]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_6_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[6]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_7_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[7]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_CHAN_MAP_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.channel_map), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_EXT_NUM_INPUT_PINS_U16, + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .offset = offsetof(struct avs_tplg_modcfg_ext, generic.num_input_pins), + .parse = avs_parse_short_token, + }, + { + .token = AVS_TKN_MODCFG_EXT_NUM_OUTPUT_PINS_U16, + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .offset = offsetof(struct avs_tplg_modcfg_ext, generic.num_output_pins), + .parse = avs_parse_short_token, + }, +}; + +static const struct avs_tplg_token_parser pin_format_parsers[] = { + { + .token = AVS_TKN_PIN_FMT_INDEX_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pin_format, pin_index), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_PIN_FMT_IOBS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pin_format, iobs), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_PIN_FMT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pin_format, fmt), + .parse = avs_parse_audio_format_ptr, + }, +}; + +static void +assign_copier_gtw_instance(struct snd_soc_component *comp, struct avs_tplg_modcfg_ext *cfg) +{ + struct snd_soc_acpi_mach *mach; + + if (!guid_equal(&cfg->type, &AVS_COPIER_MOD_UUID)) + return; + + /* Only I2S boards assign port instance in ->i2s_link_mask. */ + switch (cfg->copier.dma_type) { + case AVS_DMA_I2S_LINK_OUTPUT: + case AVS_DMA_I2S_LINK_INPUT: + break; + default: + return; + } + + mach = dev_get_platdata(comp->card->dev); + + /* Automatic assignment only when board describes single SSP. */ + if (hweight_long(mach->mach_params.i2s_link_mask) == 1 && !cfg->copier.vindex.i2s.instance) + cfg->copier.vindex.i2s.instance = __ffs(mach->mach_params.i2s_link_mask); +} + +static int avs_tplg_parse_modcfg_ext(struct snd_soc_component *comp, + struct avs_tplg_modcfg_ext *cfg, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + u32 esize; + int ret; + + /* See where pin block starts. */ + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_PIN_FMT_INDEX_U32, &esize); + if (ret) + return ret; + + ret = avs_parse_tokens(comp, cfg, modcfg_ext_parsers, + ARRAY_SIZE(modcfg_ext_parsers), tuples, esize); + if (ret) + return ret; + + /* Update copier gateway based on board's i2s_link_mask. */ + assign_copier_gtw_instance(comp, cfg); + + block_size -= esize; + /* Parse trailing in/out pin formats if any. */ + if (block_size) { + struct avs_tplg_pin_format *pins; + u32 num_pins; + + num_pins = cfg->generic.num_input_pins + cfg->generic.num_output_pins; + if (!num_pins) + return -EINVAL; + + pins = devm_kcalloc(comp->card->dev, num_pins, sizeof(*pins), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + tuples = avs_tplg_vendor_array_at(tuples, esize); + ret = parse_dictionary_entries(comp, tuples, block_size, + pins, num_pins, sizeof(*pins), + AVS_TKN_PIN_FMT_INDEX_U32, + pin_format_parsers, + ARRAY_SIZE(pin_format_parsers)); + if (ret) + return ret; + cfg->generic.pin_fmts = pins; + } + + return 0; +} + +static int avs_tplg_parse_modcfgs_ext(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + int ret, i; + + ret = parse_dictionary_header(comp, tuples, (void **)&tplg->modcfgs_ext, + &tplg->num_modcfgs_ext, + sizeof(*tplg->modcfgs_ext), + AVS_TKN_MANIFEST_NUM_MODCFGS_EXT_U32); + if (ret) + return ret; + + block_size -= le32_to_cpu(tuples->size); + /* With header parsed, move on to parsing entries. */ + tuples = avs_tplg_vendor_array_next(tuples); + + for (i = 0; i < tplg->num_modcfgs_ext; i++) { + struct avs_tplg_modcfg_ext *cfg = &tplg->modcfgs_ext[i]; + u32 esize; + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_MODCFG_EXT_ID_U32, &esize); + if (ret) + return ret; + + ret = avs_tplg_parse_modcfg_ext(comp, cfg, tuples, esize); + if (ret) + return ret; + + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } + + return 0; +} + +static const struct avs_tplg_token_parser pplcfg_parsers[] = { + { + .token = AVS_TKN_PPLCFG_REQ_SIZE_U16, + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .offset = offsetof(struct avs_tplg_pplcfg, req_size), + .parse = avs_parse_short_token, + }, + { + .token = AVS_TKN_PPLCFG_PRIORITY_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_pplcfg, priority), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_PPLCFG_LOW_POWER_BOOL, + .type = SND_SOC_TPLG_TUPLE_TYPE_BOOL, + .offset = offsetof(struct avs_tplg_pplcfg, lp), + .parse = avs_parse_bool_token, + }, + { + .token = AVS_TKN_PPLCFG_ATTRIBUTES_U16, + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .offset = offsetof(struct avs_tplg_pplcfg, attributes), + .parse = avs_parse_short_token, + }, + { + .token = AVS_TKN_PPLCFG_TRIGGER_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pplcfg, trigger), + .parse = avs_parse_word_token, + }, +}; + +static int avs_tplg_parse_pplcfgs(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->pplcfgs, + &tplg->num_pplcfgs, sizeof(*tplg->pplcfgs), + AVS_TKN_MANIFEST_NUM_PPLCFGS_U32, + AVS_TKN_PPLCFG_ID_U32, + pplcfg_parsers, ARRAY_SIZE(pplcfg_parsers)); +} + +static const struct avs_tplg_token_parser binding_parsers[] = { + { + .token = AVS_TKN_BINDING_TARGET_TPLG_NAME_STRING, + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .offset = offsetof(struct avs_tplg_binding, target_tplg_name), + .parse = parse_link_formatted_string, + }, + { + .token = AVS_TKN_BINDING_TARGET_PATH_TMPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_binding, target_path_tmpl_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_BINDING_TARGET_PPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_binding, target_ppl_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_BINDING_TARGET_MOD_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_binding, target_mod_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_BINDING_TARGET_MOD_PIN_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_binding, target_mod_pin), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_BINDING_MOD_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_binding, mod_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_BINDING_MOD_PIN_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_binding, mod_pin), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_BINDING_IS_SINK_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_binding, is_sink), + .parse = avs_parse_byte_token, + }, +}; + +static int avs_tplg_parse_bindings(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->bindings, + &tplg->num_bindings, sizeof(*tplg->bindings), + AVS_TKN_MANIFEST_NUM_BINDINGS_U32, + AVS_TKN_BINDING_ID_U32, + binding_parsers, ARRAY_SIZE(binding_parsers)); +} + +static const struct avs_tplg_token_parser module_parsers[] = { + { + .token = AVS_TKN_MOD_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MOD_MODCFG_BASE_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, cfg_base), + .parse = avs_parse_modcfg_base_ptr, + }, + { + .token = AVS_TKN_MOD_IN_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, in_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MOD_CORE_ID_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_module, core_id), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MOD_PROC_DOMAIN_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_module, domain), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MOD_MODCFG_EXT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, cfg_ext), + .parse = avs_parse_modcfg_ext_ptr, + }, +}; + +static struct avs_tplg_module * +avs_tplg_module_create(struct snd_soc_component *comp, struct avs_tplg_pipeline *owner, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_tplg_module *module; + int ret; + + module = devm_kzalloc(comp->card->dev, sizeof(*module), GFP_KERNEL); + if (!module) + return ERR_PTR(-ENOMEM); + + ret = avs_parse_tokens(comp, module, module_parsers, + ARRAY_SIZE(module_parsers), tuples, block_size); + if (ret < 0) + return ERR_PTR(ret); + + module->owner = owner; + INIT_LIST_HEAD(&module->node); + + return module; +} + +static const struct avs_tplg_token_parser pipeline_parsers[] = { + { + .token = AVS_TKN_PPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pipeline, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_PPL_PPLCFG_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pipeline, cfg), + .parse = avs_parse_pplcfg_ptr, + }, + { + .token = AVS_TKN_PPL_NUM_BINDING_IDS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pipeline, num_bindings), + .parse = avs_parse_word_token, + }, +}; + +static const struct avs_tplg_token_parser bindings_parsers[] = { + { + .token = AVS_TKN_PPL_BINDING_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = 0, /* to treat pipeline->bindings as dictionary */ + .parse = avs_parse_binding_ptr, + }, +}; + +static struct avs_tplg_pipeline * +avs_tplg_pipeline_create(struct snd_soc_component *comp, struct avs_tplg_path *owner, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_tplg_pipeline *pipeline; + u32 modblk_size, offset; + int ret; + + pipeline = devm_kzalloc(comp->card->dev, sizeof(*pipeline), GFP_KERNEL); + if (!pipeline) + return ERR_PTR(-ENOMEM); + + pipeline->owner = owner; + INIT_LIST_HEAD(&pipeline->mod_list); + + /* Pipeline header MUST be followed by at least one module. */ + ret = avs_tplg_vendor_array_lookup(tuples, block_size, + AVS_TKN_MOD_ID_U32, &offset); + if (!ret && !offset) + ret = -EINVAL; + if (ret) + return ERR_PTR(ret); + + /* Process header which precedes module sections. */ + ret = avs_parse_tokens(comp, pipeline, pipeline_parsers, + ARRAY_SIZE(pipeline_parsers), tuples, offset); + if (ret < 0) + return ERR_PTR(ret); + + block_size -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + /* Optionally, binding sections follow module ones. */ + ret = avs_tplg_vendor_array_lookup_next(tuples, block_size, + AVS_TKN_PPL_BINDING_ID_U32, &offset); + if (ret) { + if (ret != -ENOENT) + return ERR_PTR(ret); + + /* Does header information match actual block layout? */ + if (pipeline->num_bindings) + return ERR_PTR(-EINVAL); + + modblk_size = block_size; + } else { + pipeline->bindings = devm_kcalloc(comp->card->dev, pipeline->num_bindings, + sizeof(*pipeline->bindings), GFP_KERNEL); + if (!pipeline->bindings) + return ERR_PTR(-ENOMEM); + + modblk_size = offset; + } + + block_size -= modblk_size; + do { + struct avs_tplg_module *module; + u32 esize; + + ret = avs_tplg_vendor_entry_size(tuples, modblk_size, + AVS_TKN_MOD_ID_U32, &esize); + if (ret) + return ERR_PTR(ret); + + module = avs_tplg_module_create(comp, pipeline, tuples, esize); + if (IS_ERR(module)) { + dev_err(comp->dev, "parse module failed: %ld\n", + PTR_ERR(module)); + return ERR_CAST(module); + } + + list_add_tail(&module->node, &pipeline->mod_list); + modblk_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } while (modblk_size > 0); + + /* What's left is optional range of bindings. */ + ret = parse_dictionary_entries(comp, tuples, block_size, pipeline->bindings, + pipeline->num_bindings, sizeof(*pipeline->bindings), + AVS_TKN_PPL_BINDING_ID_U32, + bindings_parsers, ARRAY_SIZE(bindings_parsers)); + if (ret) + return ERR_PTR(ret); + + return pipeline; +} + +static const struct avs_tplg_token_parser path_parsers[] = { + { + .token = AVS_TKN_PATH_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_PATH_FE_FMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, fe_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_PATH_BE_FMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, be_fmt), + .parse = avs_parse_audio_format_ptr, + }, +}; + +static struct avs_tplg_path * +avs_tplg_path_create(struct snd_soc_component *comp, struct avs_tplg_path_template *owner, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size, + const struct avs_tplg_token_parser *parsers, u32 num_parsers) +{ + struct avs_tplg_pipeline *pipeline; + struct avs_tplg_path *path; + u32 offset; + int ret; + + path = devm_kzalloc(comp->card->dev, sizeof(*path), GFP_KERNEL); + if (!path) + return ERR_PTR(-ENOMEM); + + path->owner = owner; + INIT_LIST_HEAD(&path->ppl_list); + INIT_LIST_HEAD(&path->node); + + /* Path header MAY be followed by one or more pipelines. */ + ret = avs_tplg_vendor_array_lookup(tuples, block_size, + AVS_TKN_PPL_ID_U32, &offset); + if (ret == -ENOENT) + offset = block_size; + else if (ret) + return ERR_PTR(ret); + else if (!offset) + return ERR_PTR(-EINVAL); + + /* Process header which precedes pipeline sections. */ + ret = avs_parse_tokens(comp, path, parsers, num_parsers, tuples, offset); + if (ret < 0) + return ERR_PTR(ret); + + block_size -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + while (block_size > 0) { + u32 esize; + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_PPL_ID_U32, &esize); + if (ret) + return ERR_PTR(ret); + + pipeline = avs_tplg_pipeline_create(comp, path, tuples, esize); + if (IS_ERR(pipeline)) { + dev_err(comp->dev, "parse pipeline failed: %ld\n", + PTR_ERR(pipeline)); + return ERR_CAST(pipeline); + } + + list_add_tail(&pipeline->node, &path->ppl_list); + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } + + return path; +} + +static const struct avs_tplg_token_parser path_tmpl_parsers[] = { + { + .token = AVS_TKN_PATH_TMPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path_template, id), + .parse = avs_parse_word_token, + }, +}; + +static int parse_path_template(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size, + struct avs_tplg_path_template *template, + const struct avs_tplg_token_parser *tmpl_tokens, u32 num_tmpl_tokens, + const struct avs_tplg_token_parser *path_tokens, u32 num_path_tokens) +{ + struct avs_tplg_path *path; + u32 offset; + int ret; + + /* Path template header MUST be followed by at least one path variant. */ + ret = avs_tplg_vendor_array_lookup(tuples, block_size, + AVS_TKN_PATH_ID_U32, &offset); + if (ret) + return ret; + + /* Process header which precedes path variants sections. */ + ret = avs_parse_tokens(comp, template, tmpl_tokens, num_tmpl_tokens, tuples, offset); + if (ret < 0) + return ret; + + block_size -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + do { + u32 esize; + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_PATH_ID_U32, &esize); + if (ret) + return ret; + + path = avs_tplg_path_create(comp, template, tuples, esize, path_tokens, + num_path_tokens); + if (IS_ERR(path)) { + dev_err(comp->dev, "parse path failed: %ld\n", PTR_ERR(path)); + return PTR_ERR(path); + } + + list_add_tail(&path->node, &template->path_list); + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } while (block_size > 0); + + return 0; +} + +static struct avs_tplg_path_template * +avs_tplg_path_template_create(struct snd_soc_component *comp, struct avs_tplg *owner, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_tplg_path_template *template; + int ret; + + template = devm_kzalloc(comp->card->dev, sizeof(*template), GFP_KERNEL); + if (!template) + return ERR_PTR(-ENOMEM); + + template->owner = owner; /* Used to access component tplg is assigned to. */ + INIT_LIST_HEAD(&template->path_list); + INIT_LIST_HEAD(&template->node); + + ret = parse_path_template(comp, tuples, block_size, template, path_tmpl_parsers, + ARRAY_SIZE(path_tmpl_parsers), path_parsers, + ARRAY_SIZE(path_parsers)); + if (ret) + return ERR_PTR(ret); + + return template; +} + +static int avs_route_load(struct snd_soc_component *comp, int index, + struct snd_soc_dapm_route *route) +{ + struct snd_soc_acpi_mach *mach = dev_get_platdata(comp->card->dev); + size_t len = SNDRV_CTL_ELEM_ID_NAME_MAXLEN; + char buf[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + u32 port; + + /* See parse_link_formatted_string() for dynamic naming when(s). */ + if (hweight_long(mach->mach_params.i2s_link_mask) == 1) { + port = __ffs(mach->mach_params.i2s_link_mask); + + snprintf(buf, len, route->source, port); + strncpy((char *)route->source, buf, len); + snprintf(buf, len, route->sink, port); + strncpy((char *)route->sink, buf, len); + if (route->control) { + snprintf(buf, len, route->control, port); + strncpy((char *)route->control, buf, len); + } + } + + return 0; +} + +static int avs_widget_load(struct snd_soc_component *comp, int index, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *dw) +{ + struct snd_soc_acpi_mach *mach; + struct avs_tplg_path_template *template; + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg; + + if (!le32_to_cpu(dw->priv.size)) + return 0; + + tplg = acomp->tplg; + mach = dev_get_platdata(comp->card->dev); + + /* See parse_link_formatted_string() for dynamic naming when(s). */ + if (hweight_long(mach->mach_params.i2s_link_mask) == 1) { + kfree(w->name); + /* w->name is freed later by soc_tplg_dapm_widget_create() */ + w->name = kasprintf(GFP_KERNEL, dw->name, __ffs(mach->mach_params.i2s_link_mask)); + if (!w->name) + return -ENOMEM; + } + + template = avs_tplg_path_template_create(comp, tplg, dw->priv.array, + le32_to_cpu(dw->priv.size)); + if (IS_ERR(template)) { + dev_err(comp->dev, "widget %s load failed: %ld\n", dw->name, + PTR_ERR(template)); + return PTR_ERR(template); + } + + w->priv = template; /* link path information to widget */ + list_add_tail(&template->node, &tplg->path_tmpl_list); + return 0; +} + +static int avs_dai_load(struct snd_soc_component *comp, int index, + struct snd_soc_dai_driver *dai_drv, struct snd_soc_tplg_pcm *pcm, + struct snd_soc_dai *dai) +{ + if (pcm) + dai_drv->ops = &avs_dai_fe_ops; + return 0; +} + +static int avs_link_load(struct snd_soc_component *comp, int index, struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + if (!link->no_pcm) { + /* Stream control handled by IPCs. */ + link->nonatomic = true; + + /* Open LINK (BE) pipes last and close them first to prevent xruns. */ + link->trigger[0] = SND_SOC_DPCM_TRIGGER_PRE; + link->trigger[1] = SND_SOC_DPCM_TRIGGER_PRE; + } + + return 0; +} + +static const struct avs_tplg_token_parser manifest_parsers[] = { + { + .token = AVS_TKN_MANIFEST_NAME_STRING, + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .offset = offsetof(struct avs_tplg, name), + .parse = parse_link_formatted_string, + }, + { + .token = AVS_TKN_MANIFEST_VERSION_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg, version), + .parse = avs_parse_word_token, + }, +}; + +static int avs_manifest(struct snd_soc_component *comp, int index, + struct snd_soc_tplg_manifest *manifest) +{ + struct snd_soc_tplg_vendor_array *tuples = manifest->priv.array; + struct avs_soc_component *acomp = to_avs_soc_component(comp); + size_t remaining = le32_to_cpu(manifest->priv.size); + u32 offset; + int ret; + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_LIBRARIES_U32, &offset); + /* Manifest MUST begin with a header. */ + if (!ret && !offset) + ret = -EINVAL; + if (ret) { + dev_err(comp->dev, "incorrect manifest format: %d\n", ret); + return ret; + } + + /* Process header which precedes any of the dictionaries. */ + ret = avs_parse_tokens(comp, acomp->tplg, manifest_parsers, + ARRAY_SIZE(manifest_parsers), tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_AFMTS_U32, &offset); + if (ret) { + dev_err(comp->dev, "audio formats lookup failed: %d\n", ret); + return ret; + } + + /* Libraries dictionary. */ + ret = avs_tplg_parse_libraries(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_MODCFGS_BASE_U32, &offset); + if (ret) { + dev_err(comp->dev, "modcfgs_base lookup failed: %d\n", ret); + return ret; + } + + /* Audio formats dictionary. */ + ret = avs_tplg_parse_audio_formats(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_MODCFGS_EXT_U32, &offset); + if (ret) { + dev_err(comp->dev, "modcfgs_ext lookup failed: %d\n", ret); + return ret; + } + + /* Module configs-base dictionary. */ + ret = avs_tplg_parse_modcfgs_base(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_PPLCFGS_U32, &offset); + if (ret) { + dev_err(comp->dev, "pplcfgs lookup failed: %d\n", ret); + return ret; + } + + /* Module configs-ext dictionary. */ + ret = avs_tplg_parse_modcfgs_ext(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_BINDINGS_U32, &offset); + if (ret) { + dev_err(comp->dev, "bindings lookup failed: %d\n", ret); + return ret; + } + + /* Pipeline configs dictionary. */ + ret = avs_tplg_parse_pplcfgs(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + /* Bindings dictionary. */ + return avs_tplg_parse_bindings(comp, tuples, remaining); +} + +static struct snd_soc_tplg_ops avs_tplg_ops = { + .dapm_route_load = avs_route_load, + .widget_load = avs_widget_load, + .dai_load = avs_dai_load, + .link_load = avs_link_load, + .manifest = avs_manifest, +}; + +struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) +{ + struct avs_tplg *tplg; + + tplg = devm_kzalloc(comp->card->dev, sizeof(*tplg), GFP_KERNEL); + if (!tplg) + return NULL; + + tplg->comp = comp; + INIT_LIST_HEAD(&tplg->path_tmpl_list); + + return tplg; +} + +int avs_load_topology(struct snd_soc_component *comp, const char *filename) +{ + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, filename, comp->dev); + if (ret < 0) { + dev_err(comp->dev, "request topology \"%s\" failed: %d\n", filename, ret); + return ret; + } + + ret = snd_soc_tplg_component_load(comp, &avs_tplg_ops, fw); + if (ret < 0) + dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); + + release_firmware(fw); + return ret; +} + +int avs_remove_topology(struct snd_soc_component *comp) +{ + snd_soc_tplg_component_remove(comp); + + return 0; +} diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h new file mode 100644 index 000000000000..68e5f6312353 --- /dev/null +++ b/sound/soc/intel/avs/topology.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021 Intel Corporation. All rights reserved. + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_TPLG_H +#define __SOUND_SOC_INTEL_AVS_TPLG_H + +#include <linux/list.h> +#include "messages.h" + +#define INVALID_OBJECT_ID UINT_MAX + +struct snd_soc_component; + +struct avs_tplg { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + u32 version; + struct snd_soc_component *comp; + + struct avs_tplg_library *libs; + u32 num_libs; + struct avs_audio_format *fmts; + u32 num_fmts; + struct avs_tplg_modcfg_base *modcfgs_base; + u32 num_modcfgs_base; + struct avs_tplg_modcfg_ext *modcfgs_ext; + u32 num_modcfgs_ext; + struct avs_tplg_pplcfg *pplcfgs; + u32 num_pplcfgs; + struct avs_tplg_binding *bindings; + u32 num_bindings; + + struct list_head path_tmpl_list; +}; + +struct avs_tplg_library { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; +}; + +/* Matches header of struct avs_mod_cfg_base. */ +struct avs_tplg_modcfg_base { + u32 cpc; + u32 ibs; + u32 obs; + u32 is_pages; +}; + +struct avs_tplg_pin_format { + u32 pin_index; + u32 iobs; + struct avs_audio_format *fmt; +}; + +struct avs_tplg_modcfg_ext { + guid_t type; + + union { + struct { + u16 num_input_pins; + u16 num_output_pins; + struct avs_tplg_pin_format *pin_fmts; + } generic; + struct { + struct avs_audio_format *out_fmt; + struct avs_audio_format *blob_fmt; /* optional override */ + u32 feature_mask; + union avs_virtual_index vindex; + u32 dma_type; + u32 dma_buffer_size; + u32 config_length; + /* config_data part of priv data */ + } copier; + struct { + u32 out_channel_config; + u32 coefficients_select; + s32 coefficients[AVS_CHANNELS_MAX]; + u32 channel_map; + } updown_mix; + struct { + u32 out_freq; + } src; + struct { + u32 out_freq; + u8 mode; + u8 disable_jitter_buffer; + } asrc; + struct { + u32 cpc_lp_mode; + } wov; + struct { + struct avs_audio_format *ref_fmt; + struct avs_audio_format *out_fmt; + u32 cpc_lp_mode; + } aec; + struct { + struct avs_audio_format *ref_fmt; + struct avs_audio_format *out_fmt; + } mux; + struct { + struct avs_audio_format *out_fmt; + } micsel; + }; +}; + +/* Specifies path behaviour during PCM ->trigger(START) command. */ +enum avs_tplg_trigger { + AVS_TPLG_TRIGGER_AUTO = 0, +}; + +struct avs_tplg_pplcfg { + u16 req_size; + u8 priority; + bool lp; + u16 attributes; + enum avs_tplg_trigger trigger; +}; + +struct avs_tplg_binding { + char target_tplg_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + u32 target_path_tmpl_id; + u32 target_ppl_id; + u32 target_mod_id; + u8 target_mod_pin; + u32 mod_id; + u8 mod_pin; + u8 is_sink; +}; + +struct avs_tplg_path_template_id { + u32 id; + char tplg_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; +}; + +struct avs_tplg_path_template { + u32 id; + + struct list_head path_list; + + struct avs_tplg *owner; + /* Driver path templates management. */ + struct list_head node; +}; + +struct avs_tplg_path { + u32 id; + + /* Path format requirements. */ + struct avs_audio_format *fe_fmt; + struct avs_audio_format *be_fmt; + + struct list_head ppl_list; + + struct avs_tplg_path_template *owner; + /* Path template path-variants management. */ + struct list_head node; +}; + +struct avs_tplg_pipeline { + u32 id; + + struct avs_tplg_pplcfg *cfg; + struct avs_tplg_binding **bindings; + u32 num_bindings; + struct list_head mod_list; + + struct avs_tplg_path *owner; + /* Path pipelines management. */ + struct list_head node; +}; + +struct avs_tplg_module { + u32 id; + + struct avs_tplg_modcfg_base *cfg_base; + struct avs_audio_format *in_fmt; + u8 core_id; + u8 domain; + struct avs_tplg_modcfg_ext *cfg_ext; + + struct avs_tplg_pipeline *owner; + /* Pipeline modules management. */ + struct list_head node; +}; + +struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp); + +int avs_load_topology(struct snd_soc_component *comp, const char *filename); +int avs_remove_topology(struct snd_soc_component *comp); + +#endif diff --git a/sound/soc/intel/avs/trace.c b/sound/soc/intel/avs/trace.c new file mode 100644 index 000000000000..fcb7cfc823d6 --- /dev/null +++ b/sound/soc/intel/avs/trace.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/types.h> + +#define CREATE_TRACE_POINTS +#include "trace.h" + +#define BYTES_PER_LINE 16 +#define MAX_CHUNK_SIZE ((PAGE_SIZE - 150) /* Place for trace header */ \ + / (2 * BYTES_PER_LINE + 4) /* chars per line */ \ + * BYTES_PER_LINE) + +void trace_avs_msg_payload(const void *data, size_t size) +{ + size_t remaining = size; + size_t offset = 0; + + while (remaining > 0) { + u32 chunk; + + chunk = min(remaining, (size_t)MAX_CHUNK_SIZE); + trace_avs_ipc_msg_payload(data, chunk, offset, size); + + remaining -= chunk; + offset += chunk; + } +} diff --git a/sound/soc/intel/avs/trace.h b/sound/soc/intel/avs/trace.h new file mode 100644 index 000000000000..855b06bb14b0 --- /dev/null +++ b/sound/soc/intel/avs/trace.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM intel_avs + +#if !defined(_TRACE_INTEL_AVS_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_INTEL_AVS_H + +#include <linux/types.h> +#include <linux/tracepoint.h> + +TRACE_EVENT(avs_dsp_core_op, + + TP_PROTO(unsigned int reg, unsigned int mask, const char *op, bool flag), + + TP_ARGS(reg, mask, op, flag), + + TP_STRUCT__entry( + __field(unsigned int, reg ) + __field(unsigned int, mask ) + __string(op, op ) + __field(bool, flag ) + ), + + TP_fast_assign( + __entry->reg = reg; + __entry->mask = mask; + __assign_str(op, op); + __entry->flag = flag; + ), + + TP_printk("%s: %d, core mask: 0x%X, prev state: 0x%08X", + __get_str(op), __entry->flag, __entry->mask, __entry->reg) +); + +#ifndef __TRACE_INTEL_AVS_TRACE_HELPER +#define __TRACE_INTEL_AVS_TRACE_HELPER + +void trace_avs_msg_payload(const void *data, size_t size); + +#define trace_avs_request(msg, fwregs) \ +({ \ + trace_avs_ipc_request_msg((msg)->header, fwregs); \ + trace_avs_msg_payload((msg)->data, (msg)->size); \ +}) + +#define trace_avs_reply(msg, fwregs) \ +({ \ + trace_avs_ipc_reply_msg((msg)->header, fwregs); \ + trace_avs_msg_payload((msg)->data, (msg)->size); \ +}) + +#define trace_avs_notify(msg, fwregs) \ +({ \ + trace_avs_ipc_notify_msg((msg)->header, fwregs); \ + trace_avs_msg_payload((msg)->data, (msg)->size); \ +}) +#endif + +DECLARE_EVENT_CLASS(avs_ipc_msg_hdr, + + TP_PROTO(u64 header, u64 fwregs), + + TP_ARGS(header, fwregs), + + TP_STRUCT__entry( + __field(u64, header) + __field(u64, fwregs) + ), + + TP_fast_assign( + __entry->header = header; + __entry->fwregs = fwregs; + ), + + TP_printk("primary: 0x%08X, extension: 0x%08X,\n" + "fwstatus: 0x%08X, fwerror: 0x%08X", + lower_32_bits(__entry->header), upper_32_bits(__entry->header), + lower_32_bits(__entry->fwregs), upper_32_bits(__entry->fwregs)) +); + +DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_request_msg, + TP_PROTO(u64 header, u64 fwregs), + TP_ARGS(header, fwregs) +); + +DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_reply_msg, + TP_PROTO(u64 header, u64 fwregs), + TP_ARGS(header, fwregs) +); + +DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_notify_msg, + TP_PROTO(u64 header, u64 fwregs), + TP_ARGS(header, fwregs) +); + +TRACE_EVENT_CONDITION(avs_ipc_msg_payload, + + TP_PROTO(const u8 *data, size_t size, size_t offset, size_t total), + + TP_ARGS(data, size, offset, total), + + TP_CONDITION(data && size), + + TP_STRUCT__entry( + __dynamic_array(u8, buf, size ) + __field(size_t, offset ) + __field(size_t, pos ) + __field(size_t, total ) + ), + + TP_fast_assign( + memcpy(__get_dynamic_array(buf), data + offset, size); + __entry->offset = offset; + __entry->pos = offset + size; + __entry->total = total; + ), + + TP_printk("range %zu-%zu out of %zu bytes%s", + __entry->offset, __entry->pos, __entry->total, + __print_hex_dump("", DUMP_PREFIX_NONE, 16, 4, + __get_dynamic_array(buf), + __get_dynamic_array_len(buf), false)) +); + +TRACE_EVENT(avs_d0ix, + + TP_PROTO(const char *op, bool proceed, u64 header), + + TP_ARGS(op, proceed, header), + + TP_STRUCT__entry( + __string(op, op ) + __field(bool, proceed ) + __field(u64, header ) + ), + + TP_fast_assign( + __assign_str(op, op); + __entry->proceed = proceed; + __entry->header = header; + ), + + TP_printk("%s%s for request: 0x%08X 0x%08X", + __entry->proceed ? "" : "ignore ", __get_str(op), + lower_32_bits(__entry->header), upper_32_bits(__entry->header)) +); + +#endif /* _TRACE_INTEL_AVS_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE trace +#include <trace/define_trace.h> diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c new file mode 100644 index 000000000000..13611dee9787 --- /dev/null +++ b/sound/soc/intel/avs/utils.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/kfifo.h> +#include <linux/slab.h> +#include "avs.h" +#include "messages.h" + +/* Caller responsible for holding adev->modres_mutex. */ +static int avs_module_entry_index(struct avs_dev *adev, const guid_t *uuid) +{ + int i; + + for (i = 0; i < adev->mods_info->count; i++) { + struct avs_module_entry *module; + + module = &adev->mods_info->entries[i]; + if (guid_equal(&module->uuid, uuid)) + return i; + } + + return -ENOENT; +} + +/* Caller responsible for holding adev->modres_mutex. */ +static int avs_module_id_entry_index(struct avs_dev *adev, u32 module_id) +{ + int i; + + for (i = 0; i < adev->mods_info->count; i++) { + struct avs_module_entry *module; + + module = &adev->mods_info->entries[i]; + if (module->module_id == module_id) + return i; + } + + return -ENOENT; +} + +int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_module_entry *entry) +{ + int idx; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_entry_index(adev, uuid); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + + mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; +} + +int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_module_entry *entry) +{ + int idx; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + + mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; +} + +int avs_get_module_id(struct avs_dev *adev, const guid_t *uuid) +{ + struct avs_module_entry module; + int ret; + + ret = avs_get_module_entry(adev, uuid, &module); + return !ret ? module.module_id : -ENOENT; +} + +bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id) +{ + bool ret = false; + int idx; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + ret = ida_is_empty(adev->mod_idas[idx]); + + mutex_unlock(&adev->modres_mutex); + return ret; +} + +/* Caller responsible for holding adev->modres_mutex. */ +static void avs_module_ida_destroy(struct avs_dev *adev) +{ + int i = adev->mods_info ? adev->mods_info->count : 0; + + while (i--) { + ida_destroy(adev->mod_idas[i]); + kfree(adev->mod_idas[i]); + } + kfree(adev->mod_idas); +} + +/* Caller responsible for holding adev->modres_mutex. */ +static int +avs_module_ida_alloc(struct avs_dev *adev, struct avs_mods_info *newinfo, bool purge) +{ + struct avs_mods_info *oldinfo = adev->mods_info; + struct ida **ida_ptrs; + u32 tocopy_count = 0; + int i; + + if (!purge && oldinfo) { + if (oldinfo->count >= newinfo->count) + dev_warn(adev->dev, "refreshing %d modules info with %d\n", + oldinfo->count, newinfo->count); + tocopy_count = oldinfo->count; + } + + ida_ptrs = kcalloc(newinfo->count, sizeof(*ida_ptrs), GFP_KERNEL); + if (!ida_ptrs) + return -ENOMEM; + + if (tocopy_count) + memcpy(ida_ptrs, adev->mod_idas, tocopy_count * sizeof(*ida_ptrs)); + + for (i = tocopy_count; i < newinfo->count; i++) { + ida_ptrs[i] = kzalloc(sizeof(**ida_ptrs), GFP_KERNEL); + if (!ida_ptrs[i]) { + while (i--) + kfree(ida_ptrs[i]); + + kfree(ida_ptrs); + return -ENOMEM; + } + + ida_init(ida_ptrs[i]); + } + + /* If old elements have been reused, don't wipe them. */ + if (tocopy_count) + kfree(adev->mod_idas); + else + avs_module_ida_destroy(adev); + + adev->mod_idas = ida_ptrs; + return 0; +} + +int avs_module_info_init(struct avs_dev *adev, bool purge) +{ + struct avs_mods_info *info; + int ret; + + ret = avs_ipc_get_modules_info(adev, &info); + if (ret) + return AVS_IPC_RET(ret); + + mutex_lock(&adev->modres_mutex); + + ret = avs_module_ida_alloc(adev, info, purge); + if (ret < 0) { + dev_err(adev->dev, "initialize module idas failed: %d\n", ret); + goto exit; + } + + /* Refresh current information with newly received table. */ + kfree(adev->mods_info); + adev->mods_info = info; + +exit: + mutex_unlock(&adev->modres_mutex); + return ret; +} + +void avs_module_info_free(struct avs_dev *adev) +{ + mutex_lock(&adev->modres_mutex); + + avs_module_ida_destroy(adev); + kfree(adev->mods_info); + adev->mods_info = NULL; + + mutex_unlock(&adev->modres_mutex); +} + +int avs_module_id_alloc(struct avs_dev *adev, u16 module_id) +{ + int ret, idx, max_id; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); + ret = -EINVAL; + goto exit; + } + max_id = adev->mods_info->entries[idx].instance_max_count - 1; + ret = ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); +exit: + mutex_unlock(&adev->modres_mutex); + return ret; +} + +void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id) +{ + int idx; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); + goto exit; + } + + ida_free(adev->mod_idas[idx], instance_id); +exit: + mutex_unlock(&adev->modres_mutex); +} + +/* + * Once driver loads FW it should keep it in memory, so we are not affected + * by FW removal from filesystem or even worse by loading different FW at + * runtime suspend/resume. + */ +int avs_request_firmware(struct avs_dev *adev, const struct firmware **fw_p, const char *name) +{ + struct avs_fw_entry *entry; + int ret; + + /* first check in list if it is not already loaded */ + list_for_each_entry(entry, &adev->fw_list, node) { + if (!strcmp(name, entry->name)) { + *fw_p = entry->fw; + return 0; + } + } + + /* FW is not loaded, let's load it now and add to the list */ + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->name = kstrdup(name, GFP_KERNEL); + if (!entry->name) { + kfree(entry); + return -ENOMEM; + } + + ret = request_firmware(&entry->fw, name, adev->dev); + if (ret < 0) { + kfree(entry->name); + kfree(entry); + return ret; + } + + *fw_p = entry->fw; + + list_add_tail(&entry->node, &adev->fw_list); + + return 0; +} + +/* + * Release single FW entry, used to handle errors in functions calling + * avs_request_firmware() + */ +void avs_release_last_firmware(struct avs_dev *adev) +{ + struct avs_fw_entry *entry; + + entry = list_last_entry(&adev->fw_list, typeof(*entry), node); + + list_del(&entry->node); + release_firmware(entry->fw); + kfree(entry->name); + kfree(entry); +} + +/* + * Release all FW entries, used on driver removal + */ +void avs_release_firmwares(struct avs_dev *adev) +{ + struct avs_fw_entry *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, &adev->fw_list, node) { + list_del(&entry->node); + release_firmware(entry->fw); + kfree(entry->name); + kfree(entry); + } +} + +unsigned int __kfifo_fromio_locked(struct kfifo *fifo, const void __iomem *src, unsigned int len, + spinlock_t *lock) +{ + struct __kfifo *__fifo = &fifo->kfifo; + unsigned long flags; + unsigned int l, off; + + spin_lock_irqsave(lock, flags); + len = min(len, kfifo_avail(fifo)); + off = __fifo->in & __fifo->mask; + l = min(len, kfifo_size(fifo) - off); + + memcpy_fromio(__fifo->data + off, src, l); + memcpy_fromio(__fifo->data, src + l, len - l); + /* Make sure data copied from SRAM is visible to all CPUs. */ + smp_mb(); + __fifo->in += len; + spin_unlock_irqrestore(lock, flags); + + return len; +} diff --git a/sound/soc/intel/baytrail/Makefile b/sound/soc/intel/baytrail/Makefile deleted file mode 100644 index 4d0806aac6bd..000000000000 --- a/sound/soc/intel/baytrail/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -snd-soc-sst-baytrail-pcm-objs := \ - sst-baytrail-ipc.o sst-baytrail-pcm.o sst-baytrail-dsp.o - -obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o diff --git a/sound/soc/intel/baytrail/sst-baytrail-dsp.c b/sound/soc/intel/baytrail/sst-baytrail-dsp.c deleted file mode 100644 index 4116ba66a4c2..000000000000 --- a/sound/soc/intel/baytrail/sst-baytrail-dsp.c +++ /dev/null @@ -1,358 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Baytrail SST DSP driver - * Copyright (c) 2014, Intel Corporation. - */ - -#include <linux/delay.h> -#include <linux/fs.h> -#include <linux/slab.h> -#include <linux/device.h> -#include <linux/interrupt.h> -#include <linux/module.h> -#include <linux/dma-mapping.h> -#include <linux/platform_device.h> -#include <linux/firmware.h> - -#include "../common/sst-dsp.h" -#include "../common/sst-dsp-priv.h" -#include "sst-baytrail-ipc.h" - -#define SST_BYT_FW_SIGNATURE_SIZE 4 -#define SST_BYT_FW_SIGN "$SST" - -#define SST_BYT_IRAM_OFFSET 0xC0000 -#define SST_BYT_DRAM_OFFSET 0x100000 -#define SST_BYT_SHIM_OFFSET 0x140000 - -enum sst_ram_type { - SST_BYT_IRAM = 1, - SST_BYT_DRAM = 2, - SST_BYT_CACHE = 3, -}; - -struct dma_block_info { - enum sst_ram_type type; /* IRAM/DRAM */ - u32 size; /* Bytes */ - u32 ram_offset; /* Offset in I/DRAM */ - u32 rsvd; /* Reserved field */ -}; - -struct fw_header { - unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; - u32 file_size; /* size of fw minus this header */ - u32 modules; /* # of modules */ - u32 file_format; /* version of header format */ - u32 reserved[4]; -}; - -struct sst_byt_fw_module_header { - unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; - u32 mod_size; /* size of module */ - u32 blocks; /* # of blocks */ - u32 type; /* codec type, pp lib */ - u32 entry_point; -}; - -static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, - struct sst_byt_fw_module_header *module) -{ - struct dma_block_info *block; - struct sst_module *mod; - struct sst_module_template template; - int count; - - memset(&template, 0, sizeof(template)); - template.id = module->type; - template.entry = module->entry_point; - - mod = sst_module_new(fw, &template, NULL); - if (mod == NULL) - return -ENOMEM; - - block = (void *)module + sizeof(*module); - - for (count = 0; count < module->blocks; count++) { - - if (block->size <= 0) { - dev_err(dsp->dev, "block %d size invalid\n", count); - return -EINVAL; - } - - switch (block->type) { - case SST_BYT_IRAM: - mod->offset = block->ram_offset + - dsp->addr.iram_offset; - mod->type = SST_MEM_IRAM; - break; - case SST_BYT_DRAM: - mod->offset = block->ram_offset + - dsp->addr.dram_offset; - mod->type = SST_MEM_DRAM; - break; - case SST_BYT_CACHE: - mod->offset = block->ram_offset + - (dsp->addr.fw_ext - dsp->addr.lpe); - mod->type = SST_MEM_CACHE; - break; - default: - dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n", - block->type, count); - return -EINVAL; - } - - mod->size = block->size; - mod->data = (void *)block + sizeof(*block); - - sst_module_alloc_blocks(mod); - - block = (void *)block + sizeof(*block) + block->size; - } - return 0; -} - -static int sst_byt_parse_fw_image(struct sst_fw *sst_fw) -{ - struct fw_header *header; - struct sst_byt_fw_module_header *module; - struct sst_dsp *dsp = sst_fw->dsp; - int ret, count; - - /* Read the header information from the data pointer */ - header = (struct fw_header *)sst_fw->dma_buf; - - /* verify FW */ - if ((strncmp(header->signature, SST_BYT_FW_SIGN, 4) != 0) || - (sst_fw->size != header->file_size + sizeof(*header))) { - /* Invalid FW signature */ - dev_err(dsp->dev, "Invalid FW sign/filesize mismatch\n"); - return -EINVAL; - } - - dev_dbg(dsp->dev, - "header sign=%4s size=0x%x modules=0x%x fmt=0x%x size=%zu\n", - header->signature, header->file_size, header->modules, - header->file_format, sizeof(*header)); - - module = (void *)sst_fw->dma_buf + sizeof(*header); - for (count = 0; count < header->modules; count++) { - /* module */ - ret = sst_byt_parse_module(dsp, sst_fw, module); - if (ret < 0) { - dev_err(dsp->dev, "invalid module %d\n", count); - return ret; - } - module = (void *)module + sizeof(*module) + module->mod_size; - } - - return 0; -} - -static void sst_byt_dump_shim(struct sst_dsp *sst) -{ - int i; - u64 reg; - - for (i = 0; i <= 0xF0; i += 8) { - reg = sst_dsp_shim_read64_unlocked(sst, i); - if (reg) - dev_dbg(sst->dev, "shim 0x%2.2x value 0x%16.16llx\n", - i, reg); - } - - for (i = 0x00; i <= 0xff; i += 4) { - reg = readl(sst->addr.pci_cfg + i); - if (reg) - dev_dbg(sst->dev, "pci 0x%2.2x value 0x%8.8x\n", - i, (u32)reg); - } -} - -static irqreturn_t sst_byt_irq(int irq, void *context) -{ - struct sst_dsp *sst = (struct sst_dsp *) context; - u64 isrx; - irqreturn_t ret = IRQ_NONE; - - spin_lock(&sst->spinlock); - - isrx = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); - if (isrx & SST_ISRX_DONE) { - /* ADSP has processed the message request from IA */ - sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCX, - SST_BYT_IPCX_DONE, 0); - ret = IRQ_WAKE_THREAD; - } - if (isrx & SST_BYT_ISRX_REQUEST) { - /* mask message request from ADSP and do processing later */ - sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, - SST_BYT_IMRX_REQUEST, - SST_BYT_IMRX_REQUEST); - ret = IRQ_WAKE_THREAD; - } - - spin_unlock(&sst->spinlock); - - return ret; -} - -static void sst_byt_boot(struct sst_dsp *sst) -{ - int tries = 10; - - /* - * save the physical address of extended firmware block in the first - * 4 bytes of the mailbox - */ - memcpy_toio(sst->addr.lpe + SST_BYT_MAILBOX_OFFSET, - &sst->pdata->fw_base, sizeof(u32)); - - /* release stall and wait to unstall */ - sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_STALL, 0x0); - while (tries--) { - if (!(sst_dsp_shim_read64(sst, SST_CSR) & - SST_BYT_CSR_PWAITMODE)) - break; - msleep(100); - } - if (tries < 0) { - dev_err(sst->dev, "unable to start DSP\n"); - sst_byt_dump_shim(sst); - } -} - -static void sst_byt_reset(struct sst_dsp *sst) -{ - /* put DSP into reset, set reset vector and stall */ - sst_dsp_shim_update_bits64(sst, SST_CSR, - SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL, - SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL); - - udelay(10); - - /* take DSP out of reset and keep stalled for FW loading */ - sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_RST, 0); -} - -struct sst_adsp_memregion { - u32 start; - u32 end; - int blocks; - enum sst_mem_type type; -}; - -/* BYT test stuff */ -static const struct sst_adsp_memregion byt_region[] = { - {0xC0000, 0x100000, 8, SST_MEM_IRAM}, /* I-SRAM - 8 * 32kB */ - {0x100000, 0x140000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ -}; - -static int sst_byt_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) -{ - sst->addr.lpe_base = pdata->lpe_base; - sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); - if (!sst->addr.lpe) - return -ENODEV; - - /* ADSP PCI MMIO config space */ - sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); - if (!sst->addr.pci_cfg) { - iounmap(sst->addr.lpe); - return -ENODEV; - } - - /* SST Extended FW allocation */ - sst->addr.fw_ext = ioremap(pdata->fw_base, pdata->fw_size); - if (!sst->addr.fw_ext) { - iounmap(sst->addr.pci_cfg); - iounmap(sst->addr.lpe); - return -ENODEV; - } - - /* SST Shim */ - sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; - - sst_dsp_mailbox_init(sst, SST_BYT_MAILBOX_OFFSET + 0x204, - SST_BYT_IPC_MAX_PAYLOAD_SIZE, - SST_BYT_MAILBOX_OFFSET, - SST_BYT_IPC_MAX_PAYLOAD_SIZE); - - sst->irq = pdata->irq; - - return 0; -} - -static int sst_byt_init(struct sst_dsp *sst, struct sst_pdata *pdata) -{ - const struct sst_adsp_memregion *region; - struct device *dev; - int ret = -ENODEV, i, j, region_count; - u32 offset, size; - - dev = sst->dev; - - switch (sst->id) { - case SST_DEV_ID_BYT: - region = byt_region; - region_count = ARRAY_SIZE(byt_region); - sst->addr.iram_offset = SST_BYT_IRAM_OFFSET; - sst->addr.dram_offset = SST_BYT_DRAM_OFFSET; - sst->addr.shim_offset = SST_BYT_SHIM_OFFSET; - break; - default: - dev_err(dev, "failed to get mem resources\n"); - return ret; - } - - ret = sst_byt_resource_map(sst, pdata); - if (ret < 0) { - dev_err(dev, "failed to map resources\n"); - return ret; - } - - ret = dma_coerce_mask_and_coherent(sst->dma_dev, DMA_BIT_MASK(32)); - if (ret) - return ret; - - /* enable Interrupt from both sides */ - sst_dsp_shim_update_bits64(sst, SST_IMRX, 0x3, 0x0); - sst_dsp_shim_update_bits64(sst, SST_IMRD, 0x3, 0x0); - - /* register DSP memory blocks - ideally we should get this from ACPI */ - for (i = 0; i < region_count; i++) { - offset = region[i].start; - size = (region[i].end - region[i].start) / region[i].blocks; - - /* register individual memory blocks */ - for (j = 0; j < region[i].blocks; j++) { - sst_mem_block_register(sst, offset, size, - region[i].type, NULL, j, sst); - offset += size; - } - } - - return 0; -} - -static void sst_byt_free(struct sst_dsp *sst) -{ - sst_mem_block_unregister_all(sst); - iounmap(sst->addr.lpe); - iounmap(sst->addr.pci_cfg); - iounmap(sst->addr.fw_ext); -} - -struct sst_ops sst_byt_ops = { - .reset = sst_byt_reset, - .boot = sst_byt_boot, - .write = sst_shim32_write, - .read = sst_shim32_read, - .write64 = sst_shim32_write64, - .read64 = sst_shim32_read64, - .ram_read = sst_memcpy_fromio_32, - .ram_write = sst_memcpy_toio_32, - .irq_handler = sst_byt_irq, - .init = sst_byt_init, - .free = sst_byt_free, - .parse_fw = sst_byt_parse_fw_image, -}; diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.c b/sound/soc/intel/baytrail/sst-baytrail-ipc.c deleted file mode 100644 index 74274bd38f7a..000000000000 --- a/sound/soc/intel/baytrail/sst-baytrail-ipc.c +++ /dev/null @@ -1,772 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Baytrail SST IPC Support - * Copyright (c) 2014, Intel Corporation. - */ - -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/list.h> -#include <linux/device.h> -#include <linux/wait.h> -#include <linux/spinlock.h> -#include <linux/workqueue.h> -#include <linux/export.h> -#include <linux/slab.h> -#include <linux/delay.h> -#include <linux/platform_device.h> -#include <linux/firmware.h> -#include <linux/io.h> -#include <asm/div64.h> - -#include "sst-baytrail-ipc.h" -#include "../common/sst-dsp.h" -#include "../common/sst-dsp-priv.h" -#include "../common/sst-ipc.h" - -/* IPC message timeout */ -#define IPC_TIMEOUT_MSECS 300 -#define IPC_BOOT_MSECS 200 - -#define IPC_EMPTY_LIST_SIZE 8 - -/* IPC header bits */ -#define IPC_HEADER_MSG_ID_MASK 0xff -#define IPC_HEADER_MSG_ID(x) ((x) & IPC_HEADER_MSG_ID_MASK) -#define IPC_HEADER_STR_ID_SHIFT 8 -#define IPC_HEADER_STR_ID_MASK 0x1f -#define IPC_HEADER_STR_ID(x) (((x) & 0x1f) << IPC_HEADER_STR_ID_SHIFT) -#define IPC_HEADER_LARGE_SHIFT 13 -#define IPC_HEADER_LARGE(x) (((x) & 0x1) << IPC_HEADER_LARGE_SHIFT) -#define IPC_HEADER_DATA_SHIFT 16 -#define IPC_HEADER_DATA_MASK 0x3fff -#define IPC_HEADER_DATA(x) (((x) & 0x3fff) << IPC_HEADER_DATA_SHIFT) - -/* mask for differentiating between notification and reply message */ -#define IPC_NOTIFICATION (0x1 << 7) - -/* I2L Stream config/control msgs */ -#define IPC_IA_ALLOC_STREAM 0x20 -#define IPC_IA_FREE_STREAM 0x21 -#define IPC_IA_PAUSE_STREAM 0x24 -#define IPC_IA_RESUME_STREAM 0x25 -#define IPC_IA_DROP_STREAM 0x26 -#define IPC_IA_START_STREAM 0x30 - -/* notification messages */ -#define IPC_IA_FW_INIT_CMPLT 0x81 -#define IPC_SST_PERIOD_ELAPSED 0x97 - -/* IPC messages between host and ADSP */ -struct sst_byt_address_info { - u32 addr; - u32 size; -} __packed; - -struct sst_byt_str_type { - u8 codec_type; - u8 str_type; - u8 operation; - u8 protected_str; - u8 time_slots; - u8 reserved; - u16 result; -} __packed; - -struct sst_byt_pcm_params { - u8 num_chan; - u8 pcm_wd_sz; - u8 use_offload_path; - u8 reserved; - u32 sfreq; - u8 channel_map[8]; -} __packed; - -struct sst_byt_frames_info { - u16 num_entries; - u16 rsrvd; - u32 frag_size; - struct sst_byt_address_info ring_buf_info[8]; -} __packed; - -struct sst_byt_alloc_params { - struct sst_byt_str_type str_type; - struct sst_byt_pcm_params pcm_params; - struct sst_byt_frames_info frame_info; -} __packed; - -struct sst_byt_alloc_response { - struct sst_byt_str_type str_type; - u8 reserved[88]; -} __packed; - -struct sst_byt_start_stream_params { - u32 byte_offset; -} __packed; - -struct sst_byt_tstamp { - u64 ring_buffer_counter; - u64 hardware_counter; - u64 frames_decoded; - u64 bytes_decoded; - u64 bytes_copied; - u32 sampling_frequency; - u32 channel_peak[8]; -} __packed; - -struct sst_byt_fw_version { - u8 build; - u8 minor; - u8 major; - u8 type; -} __packed; - -struct sst_byt_fw_build_info { - u8 date[16]; - u8 time[16]; -} __packed; - -struct sst_byt_fw_init { - struct sst_byt_fw_version fw_version; - struct sst_byt_fw_build_info build_info; - u16 result; - u8 module_id; - u8 debug_info; -} __packed; - -struct sst_byt_stream; -struct sst_byt; - -/* stream infomation */ -struct sst_byt_stream { - struct list_head node; - - /* configuration */ - struct sst_byt_alloc_params request; - struct sst_byt_alloc_response reply; - - /* runtime info */ - struct sst_byt *byt; - int str_id; - bool commited; - bool running; - - /* driver callback */ - u32 (*notify_position)(struct sst_byt_stream *stream, void *data); - void *pdata; -}; - -/* SST Baytrail IPC data */ -struct sst_byt { - struct device *dev; - struct sst_dsp *dsp; - - /* stream */ - struct list_head stream_list; - - /* boot */ - wait_queue_head_t boot_wait; - bool boot_complete; - struct sst_fw *fw; - - /* IPC messaging */ - struct sst_generic_ipc ipc; -}; - -static inline u64 sst_byt_header(int msg_id, int data, bool large, int str_id) -{ - return IPC_HEADER_MSG_ID(msg_id) | IPC_HEADER_STR_ID(str_id) | - IPC_HEADER_LARGE(large) | IPC_HEADER_DATA(data) | - SST_BYT_IPCX_BUSY; -} - -static inline u16 sst_byt_header_msg_id(u64 header) -{ - return header & IPC_HEADER_MSG_ID_MASK; -} - -static inline u8 sst_byt_header_str_id(u64 header) -{ - return (header >> IPC_HEADER_STR_ID_SHIFT) & IPC_HEADER_STR_ID_MASK; -} - -static inline u16 sst_byt_header_data(u64 header) -{ - return (header >> IPC_HEADER_DATA_SHIFT) & IPC_HEADER_DATA_MASK; -} - -static struct sst_byt_stream *sst_byt_get_stream(struct sst_byt *byt, - int stream_id) -{ - struct sst_byt_stream *stream; - - list_for_each_entry(stream, &byt->stream_list, node) { - if (stream->str_id == stream_id) - return stream; - } - - return NULL; -} - -static void sst_byt_stream_update(struct sst_byt *byt, struct ipc_message *msg) -{ - struct sst_byt_stream *stream; - u64 header = msg->tx.header; - u8 stream_id = sst_byt_header_str_id(header); - u8 stream_msg = sst_byt_header_msg_id(header); - - stream = sst_byt_get_stream(byt, stream_id); - if (stream == NULL) - return; - - switch (stream_msg) { - case IPC_IA_DROP_STREAM: - case IPC_IA_PAUSE_STREAM: - case IPC_IA_FREE_STREAM: - stream->running = false; - break; - case IPC_IA_START_STREAM: - case IPC_IA_RESUME_STREAM: - stream->running = true; - break; - } -} - -static int sst_byt_process_reply(struct sst_byt *byt, u64 header) -{ - struct ipc_message *msg; - - msg = sst_ipc_reply_find_msg(&byt->ipc, header); - if (msg == NULL) - return 1; - - msg->rx.header = header; - if (header & IPC_HEADER_LARGE(true)) { - msg->rx.size = sst_byt_header_data(header); - sst_dsp_inbox_read(byt->dsp, msg->rx.data, msg->rx.size); - } - - /* update any stream states */ - sst_byt_stream_update(byt, msg); - - list_del(&msg->list); - /* wake up */ - sst_ipc_tx_msg_reply_complete(&byt->ipc, msg); - - return 1; -} - -static void sst_byt_fw_ready(struct sst_byt *byt, u64 header) -{ - dev_dbg(byt->dev, "ipc: DSP is ready 0x%llX\n", header); - - byt->boot_complete = true; - wake_up(&byt->boot_wait); -} - -static int sst_byt_process_notification(struct sst_byt *byt, - unsigned long *flags) -{ - struct sst_dsp *sst = byt->dsp; - struct sst_byt_stream *stream; - u64 header; - u8 msg_id, stream_id; - - header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); - msg_id = sst_byt_header_msg_id(header); - - switch (msg_id) { - case IPC_SST_PERIOD_ELAPSED: - stream_id = sst_byt_header_str_id(header); - stream = sst_byt_get_stream(byt, stream_id); - if (stream && stream->running && stream->notify_position) { - spin_unlock_irqrestore(&sst->spinlock, *flags); - stream->notify_position(stream, stream->pdata); - spin_lock_irqsave(&sst->spinlock, *flags); - } - break; - case IPC_IA_FW_INIT_CMPLT: - sst_byt_fw_ready(byt, header); - break; - } - - return 1; -} - -static irqreturn_t sst_byt_irq_thread(int irq, void *context) -{ - struct sst_dsp *sst = (struct sst_dsp *) context; - struct sst_byt *byt = sst_dsp_get_thread_context(sst); - struct sst_generic_ipc *ipc = &byt->ipc; - u64 header; - unsigned long flags; - - spin_lock_irqsave(&sst->spinlock, flags); - - header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); - if (header & SST_BYT_IPCD_BUSY) { - if (header & IPC_NOTIFICATION) { - /* message from ADSP */ - sst_byt_process_notification(byt, &flags); - } else { - /* reply from ADSP */ - sst_byt_process_reply(byt, header); - } - /* - * clear IPCD BUSY bit and set DONE bit. Tell DSP we have - * processed the message and can accept new. Clear data part - * of the header - */ - sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCD, - SST_BYT_IPCD_DONE | SST_BYT_IPCD_BUSY | - IPC_HEADER_DATA(IPC_HEADER_DATA_MASK), - SST_BYT_IPCD_DONE); - /* unmask message request interrupts */ - sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, - SST_BYT_IMRX_REQUEST, 0); - } - - spin_unlock_irqrestore(&sst->spinlock, flags); - - /* continue to send any remaining messages... */ - schedule_work(&ipc->kwork); - - return IRQ_HANDLED; -} - -/* stream API */ -struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, - u32 (*notify_position)(struct sst_byt_stream *stream, void *data), - void *data) -{ - struct sst_byt_stream *stream; - struct sst_dsp *sst = byt->dsp; - unsigned long flags; - - stream = kzalloc(sizeof(*stream), GFP_KERNEL); - if (stream == NULL) - return NULL; - - spin_lock_irqsave(&sst->spinlock, flags); - list_add(&stream->node, &byt->stream_list); - stream->notify_position = notify_position; - stream->pdata = data; - stream->byt = byt; - stream->str_id = id; - spin_unlock_irqrestore(&sst->spinlock, flags); - - return stream; -} - -int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, - int bits) -{ - stream->request.pcm_params.pcm_wd_sz = bits; - return 0; -} - -int sst_byt_stream_set_channels(struct sst_byt *byt, - struct sst_byt_stream *stream, u8 channels) -{ - stream->request.pcm_params.num_chan = channels; - return 0; -} - -int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, - unsigned int rate) -{ - stream->request.pcm_params.sfreq = rate; - return 0; -} - -/* stream sonfiguration */ -int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, - int codec_type, int stream_type, int operation) -{ - stream->request.str_type.codec_type = codec_type; - stream->request.str_type.str_type = stream_type; - stream->request.str_type.operation = operation; - stream->request.str_type.time_slots = 0xc; - - return 0; -} - -int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, - uint32_t buffer_addr, uint32_t buffer_size) -{ - stream->request.frame_info.num_entries = 1; - stream->request.frame_info.ring_buf_info[0].addr = buffer_addr; - stream->request.frame_info.ring_buf_info[0].size = buffer_size; - /* calculate bytes per 4 ms fragment */ - stream->request.frame_info.frag_size = - stream->request.pcm_params.sfreq * - stream->request.pcm_params.num_chan * - stream->request.pcm_params.pcm_wd_sz / 8 * - 4 / 1000; - return 0; -} - -int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - struct sst_ipc_message request, reply = {0}; - int ret; - - request.header = sst_byt_header(IPC_IA_ALLOC_STREAM, - sizeof(stream->request) + sizeof(u32), - true, stream->str_id); - request.data = &stream->request; - request.size = sizeof(stream->request); - reply.data = &stream->reply; - reply.size = sizeof(stream->reply); - - ret = sst_ipc_tx_message_wait(&byt->ipc, request, &reply); - if (ret < 0) { - dev_err(byt->dev, "ipc: error stream commit failed\n"); - return ret; - } - - stream->commited = true; - - return 0; -} - -int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - struct sst_ipc_message request = {0}; - int ret = 0; - struct sst_dsp *sst = byt->dsp; - unsigned long flags; - - if (!stream->commited) - goto out; - - request.header = sst_byt_header(IPC_IA_FREE_STREAM, - 0, false, stream->str_id); - ret = sst_ipc_tx_message_wait(&byt->ipc, request, NULL); - if (ret < 0) { - dev_err(byt->dev, "ipc: free stream %d failed\n", - stream->str_id); - return -EAGAIN; - } - - stream->commited = false; -out: - spin_lock_irqsave(&sst->spinlock, flags); - list_del(&stream->node); - kfree(stream); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return ret; -} - -static int sst_byt_stream_operations(struct sst_byt *byt, int type, - int stream_id, int wait) -{ - struct sst_ipc_message request = {0}; - - request.header = sst_byt_header(type, 0, false, stream_id); - if (wait) - return sst_ipc_tx_message_wait(&byt->ipc, request, NULL); - else - return sst_ipc_tx_message_nowait(&byt->ipc, request); -} - -/* stream ALSA trigger operations */ -int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, - u32 start_offset) -{ - struct sst_byt_start_stream_params start_stream; - struct sst_ipc_message request; - int ret; - - start_stream.byte_offset = start_offset; - request.header = sst_byt_header(IPC_IA_START_STREAM, - sizeof(start_stream) + sizeof(u32), - true, stream->str_id); - request.data = &start_stream; - request.size = sizeof(start_stream); - - ret = sst_ipc_tx_message_nowait(&byt->ipc, request); - if (ret < 0) - dev_err(byt->dev, "ipc: error failed to start stream %d\n", - stream->str_id); - - return ret; -} - -int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - int ret; - - /* don't stop streams that are not commited */ - if (!stream->commited) - return 0; - - ret = sst_byt_stream_operations(byt, IPC_IA_DROP_STREAM, - stream->str_id, 0); - if (ret < 0) - dev_err(byt->dev, "ipc: error failed to stop stream %d\n", - stream->str_id); - return ret; -} - -int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - int ret; - - ret = sst_byt_stream_operations(byt, IPC_IA_PAUSE_STREAM, - stream->str_id, 0); - if (ret < 0) - dev_err(byt->dev, "ipc: error failed to pause stream %d\n", - stream->str_id); - - return ret; -} - -int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream) -{ - int ret; - - ret = sst_byt_stream_operations(byt, IPC_IA_RESUME_STREAM, - stream->str_id, 0); - if (ret < 0) - dev_err(byt->dev, "ipc: error failed to resume stream %d\n", - stream->str_id); - - return ret; -} - -int sst_byt_get_dsp_position(struct sst_byt *byt, - struct sst_byt_stream *stream, int buffer_size) -{ - struct sst_dsp *sst = byt->dsp; - struct sst_byt_tstamp fw_tstamp; - u8 str_id = stream->str_id; - u32 tstamp_offset; - - tstamp_offset = SST_BYT_TIMESTAMP_OFFSET + str_id * sizeof(fw_tstamp); - memcpy_fromio(&fw_tstamp, - sst->addr.lpe + tstamp_offset, sizeof(fw_tstamp)); - - return do_div(fw_tstamp.ring_buffer_counter, buffer_size); -} - -struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt) -{ - return byt->dsp; -} - -static struct sst_dsp_device byt_dev = { - .thread = sst_byt_irq_thread, - .ops = &sst_byt_ops, -}; - -int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt = pdata->dsp; - - dev_dbg(byt->dev, "dsp reset\n"); - sst_dsp_reset(byt->dsp); - sst_ipc_drop_all(&byt->ipc); - dev_dbg(byt->dev, "dsp in reset\n"); - - dev_dbg(byt->dev, "free all blocks and unload fw\n"); - sst_fw_unload(byt->fw); - - return 0; -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_suspend_late); - -int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt = pdata->dsp; - int ret; - - dev_dbg(byt->dev, "reload dsp fw\n"); - - sst_dsp_reset(byt->dsp); - - ret = sst_fw_reload(byt->fw); - if (ret < 0) { - dev_err(dev, "error: failed to reload firmware\n"); - return ret; - } - - /* wait for DSP boot completion */ - byt->boot_complete = false; - sst_dsp_boot(byt->dsp); - dev_dbg(byt->dev, "dsp booting...\n"); - - return 0; -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_boot); - -int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt = pdata->dsp; - int err; - - dev_dbg(byt->dev, "wait for dsp reboot\n"); - - err = wait_event_timeout(byt->boot_wait, byt->boot_complete, - msecs_to_jiffies(IPC_BOOT_MSECS)); - if (err == 0) { - dev_err(byt->dev, "ipc: error DSP boot timeout\n"); - return -EIO; - } - - dev_dbg(byt->dev, "dsp rebooted\n"); - return 0; -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_wait_for_ready); - -static void byt_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) -{ - if (msg->tx.header & IPC_HEADER_LARGE(true)) - sst_dsp_outbox_write(ipc->dsp, msg->tx.data, msg->tx.size); - - sst_dsp_shim_write64_unlocked(ipc->dsp, SST_IPCX, msg->tx.header); -} - -static void byt_shim_dbg(struct sst_generic_ipc *ipc, const char *text) -{ - struct sst_dsp *sst = ipc->dsp; - u64 isr, ipcd, imrx, ipcx; - - ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX); - isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); - ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); - imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX); - - dev_err(ipc->dev, - "ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n", - text, ipcx, isr, ipcd, imrx); -} - -static void byt_tx_data_copy(struct ipc_message *msg, char *tx_data, - size_t tx_size) -{ - /* msg content = lower 32-bit of the header + data */ - *(u32 *)msg->tx.data = (u32)(msg->tx.header & (u32)-1); - memcpy(msg->tx.data + sizeof(u32), tx_data, tx_size); - msg->tx.size += sizeof(u32); -} - -static u64 byt_reply_msg_match(u64 header, u64 *mask) -{ - /* match reply to message sent based on msg and stream IDs */ - *mask = IPC_HEADER_MSG_ID_MASK | - IPC_HEADER_STR_ID_MASK << IPC_HEADER_STR_ID_SHIFT; - header &= *mask; - - return header; -} - -static bool byt_is_dsp_busy(struct sst_dsp *dsp) -{ - u64 ipcx; - - ipcx = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); - return (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)); -} - -int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt; - struct sst_generic_ipc *ipc; - struct sst_fw *byt_sst_fw; - struct sst_byt_fw_init init; - int err; - - dev_dbg(dev, "initialising Byt DSP IPC\n"); - - byt = devm_kzalloc(dev, sizeof(*byt), GFP_KERNEL); - if (byt == NULL) - return -ENOMEM; - - byt->dev = dev; - - ipc = &byt->ipc; - ipc->dev = dev; - ipc->ops.tx_msg = byt_tx_msg; - ipc->ops.shim_dbg = byt_shim_dbg; - ipc->ops.tx_data_copy = byt_tx_data_copy; - ipc->ops.reply_msg_match = byt_reply_msg_match; - ipc->ops.is_dsp_busy = byt_is_dsp_busy; - ipc->tx_data_max_size = IPC_MAX_MAILBOX_BYTES; - ipc->rx_data_max_size = IPC_MAX_MAILBOX_BYTES; - - err = sst_ipc_init(ipc); - if (err != 0) - goto ipc_init_err; - - INIT_LIST_HEAD(&byt->stream_list); - init_waitqueue_head(&byt->boot_wait); - byt_dev.thread_context = byt; - - /* init SST shim */ - byt->dsp = sst_dsp_new(dev, &byt_dev, pdata); - if (byt->dsp == NULL) { - err = -ENODEV; - goto dsp_new_err; - } - - ipc->dsp = byt->dsp; - - /* keep the DSP in reset state for base FW loading */ - sst_dsp_reset(byt->dsp); - - byt_sst_fw = sst_fw_new(byt->dsp, pdata->fw, byt); - if (byt_sst_fw == NULL) { - err = -ENODEV; - dev_err(dev, "error: failed to load firmware\n"); - goto fw_err; - } - - /* wait for DSP boot completion */ - sst_dsp_boot(byt->dsp); - err = wait_event_timeout(byt->boot_wait, byt->boot_complete, - msecs_to_jiffies(IPC_BOOT_MSECS)); - if (err == 0) { - err = -EIO; - dev_err(byt->dev, "ipc: error DSP boot timeout\n"); - goto boot_err; - } - - /* show firmware information */ - sst_dsp_inbox_read(byt->dsp, &init, sizeof(init)); - dev_info(byt->dev, "FW version: %02x.%02x.%02x.%02x\n", - init.fw_version.major, init.fw_version.minor, - init.fw_version.build, init.fw_version.type); - dev_info(byt->dev, "Build type: %x\n", init.fw_version.type); - dev_info(byt->dev, "Build date: %s %s\n", - init.build_info.date, init.build_info.time); - - pdata->dsp = byt; - byt->fw = byt_sst_fw; - - return 0; - -boot_err: - sst_dsp_reset(byt->dsp); - sst_fw_free(byt_sst_fw); -fw_err: - sst_dsp_free(byt->dsp); -dsp_new_err: - sst_ipc_fini(ipc); -ipc_init_err: - - return err; -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_init); - -void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_byt *byt = pdata->dsp; - - sst_dsp_reset(byt->dsp); - sst_fw_free_all(byt->dsp); - sst_dsp_free(byt->dsp); - sst_ipc_fini(&byt->ipc); -} -EXPORT_SYMBOL_GPL(sst_byt_dsp_free); diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.h b/sound/soc/intel/baytrail/sst-baytrail-ipc.h deleted file mode 100644 index 755098509327..000000000000 --- a/sound/soc/intel/baytrail/sst-baytrail-ipc.h +++ /dev/null @@ -1,65 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Intel Baytrail SST IPC Support - * Copyright (c) 2014, Intel Corporation. - */ - -#ifndef __SST_BYT_IPC_H -#define __SST_BYT_IPC_H - -#include <linux/types.h> - -struct sst_byt; -struct sst_byt_stream; -struct sst_pdata; -extern struct sst_ops sst_byt_ops; - - -#define SST_BYT_MAILBOX_OFFSET 0x144000 -#define SST_BYT_TIMESTAMP_OFFSET (SST_BYT_MAILBOX_OFFSET + 0x800) - -/** - * Upfront defined maximum message size that is - * expected by the in/out communication pipes in FW. - */ -#define SST_BYT_IPC_MAX_PAYLOAD_SIZE 200 - -/* stream API */ -struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, - uint32_t (*get_write_position)(struct sst_byt_stream *stream, - void *data), - void *data); - -/* stream configuration */ -int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, - int bits); -int sst_byt_stream_set_channels(struct sst_byt *byt, - struct sst_byt_stream *stream, u8 channels); -int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, - unsigned int rate); -int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, - int codec_type, int stream_type, int operation); -int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, - uint32_t buffer_addr, uint32_t buffer_size); -int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream); -int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream); - -/* stream ALSA trigger operations */ -int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, - u32 start_offset); -int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream); -int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream); -int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream); - -int sst_byt_get_dsp_position(struct sst_byt *byt, - struct sst_byt_stream *stream, int buffer_size); - -/* init */ -int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata); -void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata); -struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt); -int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata); -int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata); -int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata); - -#endif diff --git a/sound/soc/intel/baytrail/sst-baytrail-pcm.c b/sound/soc/intel/baytrail/sst-baytrail-pcm.c deleted file mode 100644 index 53383055c8dc..000000000000 --- a/sound/soc/intel/baytrail/sst-baytrail-pcm.c +++ /dev/null @@ -1,459 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Baytrail SST PCM Support - * Copyright (c) 2014, Intel Corporation. - */ - -#include <linux/module.h> -#include <linux/dma-mapping.h> -#include <linux/slab.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> -#include "sst-baytrail-ipc.h" -#include "../common/sst-dsp-priv.h" -#include "../common/sst-dsp.h" - -#define DRV_NAME "byt-dai" -#define BYT_PCM_COUNT 2 - -static const struct snd_pcm_hardware sst_byt_pcm_hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .period_bytes_min = 384, - .period_bytes_max = 48000, - .periods_min = 2, - .periods_max = 250, - .buffer_bytes_max = 96000, -}; - -/* private data for each PCM DSP stream */ -struct sst_byt_pcm_data { - struct sst_byt_stream *stream; - struct snd_pcm_substream *substream; - struct mutex mutex; - - /* latest DSP DMA hw pointer */ - u32 hw_ptr; - - struct work_struct work; -}; - -/* private data for the driver */ -struct sst_byt_priv_data { - /* runtime DSP */ - struct sst_byt *byt; - - /* DAI data */ - struct sst_byt_pcm_data pcm[BYT_PCM_COUNT]; - - /* flag indicating is stream context restore needed after suspend */ - bool restore_stream; -}; - -/* this may get called several times by oss emulation */ -static int sst_byt_pcm_hw_params(struct snd_soc_component *component, - struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - u32 rate, bits; - u8 channels; - int ret, playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); - - dev_dbg(rtd->dev, "PCM: hw_params, pcm_data %p\n", pcm_data); - - ret = sst_byt_stream_type(byt, pcm_data->stream, - 1, 1, !playback); - if (ret < 0) { - dev_err(rtd->dev, "failed to set stream format %d\n", ret); - return ret; - } - - rate = params_rate(params); - ret = sst_byt_stream_set_rate(byt, pcm_data->stream, rate); - if (ret < 0) { - dev_err(rtd->dev, "could not set rate %d\n", rate); - return ret; - } - - bits = snd_pcm_format_width(params_format(params)); - ret = sst_byt_stream_set_bits(byt, pcm_data->stream, bits); - if (ret < 0) { - dev_err(rtd->dev, "could not set formats %d\n", - params_rate(params)); - return ret; - } - - channels = (u8)(params_channels(params) & 0xF); - ret = sst_byt_stream_set_channels(byt, pcm_data->stream, channels); - if (ret < 0) { - dev_err(rtd->dev, "could not set channels %d\n", - params_rate(params)); - return ret; - } - - ret = sst_byt_stream_buffer(byt, pcm_data->stream, - substream->dma_buffer.addr, - params_buffer_bytes(params)); - if (ret < 0) { - dev_err(rtd->dev, "PCM: failed to set DMA buffer %d\n", ret); - return ret; - } - - ret = sst_byt_stream_commit(byt, pcm_data->stream); - if (ret < 0) { - dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); - return ret; - } - - return 0; -} - -static int sst_byt_pcm_restore_stream_context(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - int ret; - - /* commit stream using existing stream params */ - ret = sst_byt_stream_commit(byt, pcm_data->stream); - if (ret < 0) { - dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); - return ret; - } - - sst_byt_stream_start(byt, pcm_data->stream, pcm_data->hw_ptr); - - dev_dbg(rtd->dev, "stream context restored at offset %d\n", - pcm_data->hw_ptr); - - return 0; -} - -static void sst_byt_pcm_work(struct work_struct *work) -{ - struct sst_byt_pcm_data *pcm_data = - container_of(work, struct sst_byt_pcm_data, work); - - if (snd_pcm_running(pcm_data->substream)) - sst_byt_pcm_restore_stream_context(pcm_data->substream); -} - -static int sst_byt_pcm_trigger(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int cmd) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - - dev_dbg(rtd->dev, "PCM: trigger %d\n", cmd); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - pcm_data->hw_ptr = 0; - sst_byt_stream_start(byt, pcm_data->stream, 0); - break; - case SNDRV_PCM_TRIGGER_RESUME: - if (pdata->restore_stream) - schedule_work(&pcm_data->work); - else - sst_byt_stream_resume(byt, pcm_data->stream); - break; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - sst_byt_stream_resume(byt, pcm_data->stream); - break; - case SNDRV_PCM_TRIGGER_STOP: - sst_byt_stream_stop(byt, pcm_data->stream); - break; - case SNDRV_PCM_TRIGGER_SUSPEND: - pdata->restore_stream = false; - /* fallthrough */ - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - sst_byt_stream_pause(byt, pcm_data->stream); - break; - default: - break; - } - - return 0; -} - -static u32 byt_notify_pointer(struct sst_byt_stream *stream, void *data) -{ - struct sst_byt_pcm_data *pcm_data = data; - struct snd_pcm_substream *substream = pcm_data->substream; - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_byt *byt = pdata->byt; - u32 pos, hw_pos; - - hw_pos = sst_byt_get_dsp_position(byt, pcm_data->stream, - snd_pcm_lib_buffer_bytes(substream)); - pcm_data->hw_ptr = hw_pos; - pos = frames_to_bytes(runtime, - (runtime->control->appl_ptr % - runtime->buffer_size)); - - dev_dbg(rtd->dev, "PCM: App/DMA pointer %u/%u bytes\n", pos, hw_pos); - - snd_pcm_period_elapsed(substream); - return pos; -} - -static snd_pcm_uframes_t sst_byt_pcm_pointer(struct snd_soc_component *component, - struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - - dev_dbg(rtd->dev, "PCM: DMA pointer %u bytes\n", pcm_data->hw_ptr); - - return bytes_to_frames(runtime, pcm_data->hw_ptr); -} - -static int sst_byt_pcm_open(struct snd_soc_component *component, - struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - - dev_dbg(rtd->dev, "PCM: open\n"); - - mutex_lock(&pcm_data->mutex); - - pcm_data->substream = substream; - - snd_soc_set_runtime_hwparams(substream, &sst_byt_pcm_hardware); - - pcm_data->stream = sst_byt_stream_new(byt, substream->stream + 1, - byt_notify_pointer, pcm_data); - if (pcm_data->stream == NULL) { - dev_err(rtd->dev, "failed to create stream\n"); - mutex_unlock(&pcm_data->mutex); - return -EINVAL; - } - - mutex_unlock(&pcm_data->mutex); - return 0; -} - -static int sst_byt_pcm_close(struct snd_soc_component *component, - struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; - struct sst_byt *byt = pdata->byt; - int ret; - - dev_dbg(rtd->dev, "PCM: close\n"); - - cancel_work_sync(&pcm_data->work); - mutex_lock(&pcm_data->mutex); - ret = sst_byt_stream_free(byt, pcm_data->stream); - if (ret < 0) { - dev_dbg(rtd->dev, "Free stream fail\n"); - goto out; - } - pcm_data->stream = NULL; - -out: - mutex_unlock(&pcm_data->mutex); - return ret; -} - -static int sst_byt_pcm_mmap(struct snd_soc_component *component, - struct snd_pcm_substream *substream, - struct vm_area_struct *vma) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - - dev_dbg(rtd->dev, "PCM: mmap\n"); - return snd_pcm_lib_default_mmap(substream, vma); -} - -static int sst_byt_pcm_new(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd) -{ - struct snd_pcm *pcm = rtd->pcm; - size_t size; - struct sst_pdata *pdata = dev_get_platdata(component->dev); - - if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || - pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { - size = sst_byt_pcm_hardware.buffer_bytes_max; - snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, - pdata->dma_dev, size, size); - } - - return 0; -} - -static struct snd_soc_dai_driver byt_dais[] = { - { - .name = "Baytrail PCM", - .playback = { - .stream_name = "System Playback", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_3LE | - SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "Analog Capture", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - }, -}; - -static int sst_byt_pcm_probe(struct snd_soc_component *component) -{ - struct sst_pdata *plat_data = dev_get_platdata(component->dev); - struct sst_byt_priv_data *priv_data; - int i; - - if (!plat_data) - return -ENODEV; - - priv_data = devm_kzalloc(component->dev, sizeof(*priv_data), - GFP_KERNEL); - if (!priv_data) - return -ENOMEM; - priv_data->byt = plat_data->dsp; - snd_soc_component_set_drvdata(component, priv_data); - - for (i = 0; i < BYT_PCM_COUNT; i++) { - mutex_init(&priv_data->pcm[i].mutex); - INIT_WORK(&priv_data->pcm[i].work, sst_byt_pcm_work); - } - - return 0; -} - -static const struct snd_soc_component_driver byt_dai_component = { - .name = DRV_NAME, - .probe = sst_byt_pcm_probe, - .open = sst_byt_pcm_open, - .close = sst_byt_pcm_close, - .hw_params = sst_byt_pcm_hw_params, - .trigger = sst_byt_pcm_trigger, - .pointer = sst_byt_pcm_pointer, - .mmap = sst_byt_pcm_mmap, - .pcm_construct = sst_byt_pcm_new, -}; - -#ifdef CONFIG_PM -static int sst_byt_pcm_dev_suspend_late(struct device *dev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(dev); - struct sst_byt_priv_data *priv_data = dev_get_drvdata(dev); - int ret; - - dev_dbg(dev, "suspending late\n"); - - ret = sst_byt_dsp_suspend_late(dev, sst_pdata); - if (ret < 0) { - dev_err(dev, "failed to suspend %d\n", ret); - return ret; - } - - priv_data->restore_stream = true; - - return ret; -} - -static int sst_byt_pcm_dev_resume_early(struct device *dev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(dev); - int ret; - - dev_dbg(dev, "resume early\n"); - - /* load fw and boot DSP */ - ret = sst_byt_dsp_boot(dev, sst_pdata); - if (ret) - return ret; - - /* wait for FW to finish booting */ - return sst_byt_dsp_wait_for_ready(dev, sst_pdata); -} - -static const struct dev_pm_ops sst_byt_pm_ops = { - .suspend_late = sst_byt_pcm_dev_suspend_late, - .resume_early = sst_byt_pcm_dev_resume_early, -}; - -#define SST_BYT_PM_OPS (&sst_byt_pm_ops) -#else -#define SST_BYT_PM_OPS NULL -#endif - -static int sst_byt_pcm_dev_probe(struct platform_device *pdev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); - int ret; - - ret = sst_byt_dsp_init(&pdev->dev, sst_pdata); - if (ret < 0) - return -ENODEV; - - ret = devm_snd_soc_register_component(&pdev->dev, &byt_dai_component, - byt_dais, ARRAY_SIZE(byt_dais)); - if (ret < 0) - goto err_plat; - - return 0; - -err_plat: - sst_byt_dsp_free(&pdev->dev, sst_pdata); - return ret; -} - -static int sst_byt_pcm_dev_remove(struct platform_device *pdev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); - - sst_byt_dsp_free(&pdev->dev, sst_pdata); - - return 0; -} - -static struct platform_driver sst_byt_pcm_driver = { - .driver = { - .name = "baytrail-pcm-audio", - .pm = SST_BYT_PM_OPS, - }, - - .probe = sst_byt_pcm_dev_probe, - .remove = sst_byt_pcm_dev_remove, -}; -module_platform_driver(sst_byt_pcm_driver); - -MODULE_AUTHOR("Jarkko Nikula"); -MODULE_DESCRIPTION("Baytrail PCM"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:baytrail-pcm-audio"); diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 9ca2567d0059..aa12d7e3dd2f 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -26,10 +26,22 @@ config SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES interface. If unsure select N. -if SND_SOC_INTEL_HASWELL +config SND_SOC_INTEL_HDA_DSP_COMMON + tristate + +config SND_SOC_INTEL_SOF_MAXIM_COMMON + tristate + +config SND_SOC_INTEL_SOF_REALTEK_COMMON + tristate + +config SND_SOC_INTEL_SOF_CIRRUS_COMMON + tristate + +if SND_SOC_INTEL_CATPT config SND_SOC_INTEL_HASWELL_MACH - tristate "Haswell Lynxpoint" + tristate "Haswell with RT5640 I2S codec" depends on I2C depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST depends on X86_INTEL_LPSS || COMPILE_TEST @@ -40,9 +52,9 @@ config SND_SOC_INTEL_HASWELL_MACH Say Y or m if you have such a device. If unsure select "N". -endif ## SND_SOC_INTEL_HASWELL +endif ## SND_SOC_INTEL_CATPT -if SND_SOC_INTEL_HASWELL || SND_SOC_SOF_BROADWELL +if SND_SOC_INTEL_CATPT || SND_SOC_SOF_BROADWELL config SND_SOC_INTEL_BDW_RT5650_MACH tristate "Broadwell with RT5650 codec" @@ -73,7 +85,7 @@ config SND_SOC_INTEL_BDW_RT5677_MACH If unsure select "N". config SND_SOC_INTEL_BROADWELL_MACH - tristate "Broadwell Wildcatpoint" + tristate "Broadwell with RT286 I2S codec" depends on I2C depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST depends on X86_INTEL_LPSS || COMPILE_TEST @@ -83,32 +95,7 @@ config SND_SOC_INTEL_BROADWELL_MACH Ultrabook platforms. Say Y or m if you have such a device. This is a recommended option. If unsure select "N". -endif ## SND_SOC_INTEL_HASWELL || SND_SOC_SOF_BROADWELL - -if SND_SOC_INTEL_BAYTRAIL - -config SND_SOC_INTEL_BYT_MAX98090_MACH - tristate "Baytrail with MAX98090 codec" - depends on I2C - depends on X86_INTEL_LPSS || COMPILE_TEST - select SND_SOC_MAX98090 - help - This adds audio driver for Intel Baytrail platform based boards - with the MAX98090 audio codec. This driver is deprecated, use - SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH instead for better - functionality. - -config SND_SOC_INTEL_BYT_RT5640_MACH - tristate "Baytrail with RT5640 codec" - depends on I2C - depends on X86_INTEL_LPSS || COMPILE_TEST - select SND_SOC_RT5640 - help - This adds audio driver for Intel Baytrail platform based boards - with the RT5640 audio codec. This driver is deprecated, use - SND_SOC_INTEL_BYTCR_RT5640_MACH instead for better functionality. - -endif ## SND_SOC_INTEL_BAYTRAIL +endif ## SND_SOC_INTEL_CATPT || SND_SOC_SOF_BROADWELL if SND_SST_ATOM_HIFI2_PLATFORM || SND_SOC_SOF_BAYTRAIL @@ -116,6 +103,7 @@ config SND_SOC_INTEL_BYTCR_RT5640_MACH tristate "Baytrail and Baytrail-CR with RT5640 codec" depends on I2C && ACPI depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_RT5640 help @@ -128,6 +116,7 @@ config SND_SOC_INTEL_BYTCR_RT5651_MACH tristate "Baytrail and Baytrail-CR with RT5651 codec" depends on I2C && ACPI depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_RT5651 help @@ -136,10 +125,24 @@ config SND_SOC_INTEL_BYTCR_RT5651_MACH Say Y or m if you have such a device. This is a recommended option. If unsure select "N". +config SND_SOC_INTEL_BYTCR_WM5102_MACH + tristate "Baytrail and Baytrail-CR with WM5102 codec" + depends on MFD_ARIZONA && MFD_WM5102 && SPI_MASTER && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST + select SND_SOC_ACPI + select SND_SOC_WM5102 + help + This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR + platforms with WM5102 audio codec. + Say Y if you have such a device. + If unsure select "N". + config SND_SOC_INTEL_CHT_BSW_RT5672_MACH tristate "Cherrytrail & Braswell with RT5672 codec" depends on I2C && ACPI depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_RT5670 help @@ -164,6 +167,7 @@ config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH tristate "Cherrytrail & Braswell with MAX98090 & TI codec" depends on I2C && ACPI depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST select SND_SOC_MAX98090 select SND_SOC_TS3A227E help @@ -188,6 +192,7 @@ config SND_SOC_INTEL_BYT_CHT_CX2072X_MACH tristate "Baytrail & Cherrytrail with CX2072X codec" depends on I2C && ACPI depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_CX2072X help @@ -212,6 +217,7 @@ config SND_SOC_INTEL_BYT_CHT_ES8316_MACH tristate "Baytrail & Cherrytrail with ES8316 codec" depends on I2C && ACPI depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_ES8316 help @@ -288,9 +294,10 @@ config SND_SOC_INTEL_DA7219_MAX98357A_GENERIC tristate select SND_SOC_DA7219 select SND_SOC_MAX98357A + select SND_SOC_MAX98390 select SND_SOC_DMIC - select SND_HDA_CODEC_HDMI if SND_SOC_SOF_HDA_AUDIO_CODEC select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON config SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON tristate @@ -299,13 +306,14 @@ config SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON if SND_SOC_INTEL_APL config SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH - tristate "Broxton with DA7219 and MAX98357A in I2S Mode" + tristate "Broxton with DA7219 and MAX98357A/MAX98390 in I2S Mode" depends on I2C && ACPI depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on SND_HDA_CODEC_HDMI select SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON help This adds support for ASoC machine driver for Broxton-P platforms - with DA7219 + MAX98357A I2S audio codec. + with DA7219 + MAX98357A/MAX98390 I2S audio codec. Say Y or m if you have such a device. This is a recommended option. If unsure select "N". @@ -316,6 +324,7 @@ config SND_SOC_INTEL_BXT_RT298_MACH select SND_SOC_RT298 select SND_SOC_DMIC select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON help This adds support for ASoC machine driver for Broxton platforms with RT286 I2S audio codec. @@ -324,6 +333,22 @@ config SND_SOC_INTEL_BXT_RT298_MACH endif ## SND_SOC_INTEL_APL +if SND_SOC_SOF_APOLLOLAKE + +config SND_SOC_INTEL_SOF_WM8804_MACH + tristate "SOF with Wolfson/Cirrus WM8804 codec" + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST + select SND_SOC_WM8804_I2C + help + This adds support for ASoC machine driver for Intel platforms + with the Wolfson/Cirrus WM8804 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +endif ## SND_SOC_SOF_APOLLOLAKE + if SND_SOC_INTEL_KBL config SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH @@ -374,7 +399,7 @@ config SND_SOC_INTEL_KBL_DA7219_MAX98927_MACH depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_DA7219 select SND_SOC_MAX98927 - select SND_SOC_MAX98373 + select SND_SOC_MAX98373_I2C select SND_SOC_DMIC select SND_SOC_HDAC_HDMI help @@ -387,6 +412,7 @@ config SND_SOC_INTEL_KBL_RT5660_MACH tristate "KBL with RT5660 in I2S Mode" depends on I2C && ACPI depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST select SND_SOC_RT5660 select SND_SOC_HDAC_HDMI help @@ -396,12 +422,13 @@ config SND_SOC_INTEL_KBL_RT5660_MACH endif ## SND_SOC_INTEL_KBL -if SND_SOC_SOF_GEMINILAKE && SND_SOC_SOF_HDA_LINK +if SND_SOC_SOF_GEMINILAKE config SND_SOC_INTEL_GLK_DA7219_MAX98357A_MACH tristate "GLK with DA7219 and MAX98357A in I2S Mode" depends on I2C && ACPI depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC select SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON help This adds support for ASoC machine driver for Geminilake platforms @@ -413,30 +440,34 @@ config SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH tristate "GLK with RT5682 and MAX98357A in I2S Mode" depends on I2C && ACPI depends on MFD_INTEL_LPSS || COMPILE_TEST - select SND_SOC_RT5682 + depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC + select SND_SOC_RT5682_I2C + select SND_SOC_RT5682S select SND_SOC_MAX98357A select SND_SOC_DMIC - select SND_HDA_CODEC_HDMI if SND_SOC_SOF_HDA_AUDIO_CODEC select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON help This adds support for ASoC machine driver for Geminilake platforms with RT5682 + MAX98357A I2S audio codec. Say Y if you have such a device. If unsure select "N". -endif ## SND_SOC_SOF_GEMINILAKE && SND_SOC_SOF_HDA_LINK +endif ## SND_SOC_SOF_GEMINILAKE if SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC || SND_SOC_SOF_HDA_AUDIO_CODEC config SND_SOC_INTEL_SKL_HDA_DSP_GENERIC_MACH - tristate "SKL/KBL/BXT/APL with HDA Codecs" - select SND_HDA_CODEC_HDMI if SND_SOC_SOF_HDA_AUDIO_CODEC + tristate "Skylake+ with HDA Codecs" + depends on SND_HDA_CODEC_HDMI select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON select SND_SOC_DMIC # SND_SOC_HDAC_HDA is already selected help - This adds support for ASoC machine driver for Intel platforms - SKL/KBL/BXT/APL with iDisp, HDA audio codecs. + This adds support for ASoC machine driver for Intel Skylake+ + platforms with display (HDMI/DP) and HDA audio codecs, and + Smart Sound Technology (SST) integrated audio DSP. Say Y or m if you have such a device. This is a recommended option. If unsure select "N". @@ -446,20 +477,96 @@ if SND_SOC_SOF_HDA_LINK || SND_SOC_SOF_BAYTRAIL config SND_SOC_INTEL_SOF_RT5682_MACH tristate "SOF with rt5682 codec in I2S Mode" depends on I2C && ACPI - depends on (SND_SOC_SOF_HDA_LINK && (MFD_INTEL_LPSS || COMPILE_TEST)) ||\ + depends on ((SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC) &&\ + (MFD_INTEL_LPSS || COMPILE_TEST)) ||\ (SND_SOC_SOF_BAYTRAIL && (X86_INTEL_LPSS || COMPILE_TEST)) - select SND_SOC_RT5682 + select SND_SOC_MAX98373_I2C + select SND_SOC_MAX98390 + select SND_SOC_RT1011 + select SND_SOC_RT1015 + select SND_SOC_RT1015P + select SND_SOC_RT5682_I2C + select SND_SOC_RT5682S select SND_SOC_DMIC - select SND_HDA_CODEC_HDMI if SND_SOC_SOF_HDA_AUDIO_CODEC select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON + select SND_SOC_INTEL_SOF_MAXIM_COMMON + select SND_SOC_INTEL_SOF_REALTEK_COMMON help This adds support for ASoC machine driver for SOF platforms with rt5682 codec. Say Y if you have such a device. If unsure select "N". + +config SND_SOC_INTEL_SOF_CS42L42_MACH + tristate "SOF with cs42l42 codec in I2S Mode" + depends on I2C && ACPI + depends on ((SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC) &&\ + (MFD_INTEL_LPSS || COMPILE_TEST)) + select SND_SOC_CS42L42 + select SND_SOC_MAX98357A + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON + select SND_SOC_INTEL_SOF_MAXIM_COMMON + help + This adds support for ASoC machine driver for SOF platforms + with cs42l42 codec. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_INTEL_SOF_PCM512x_MACH + tristate "SOF with TI PCM512x codec" + depends on I2C && ACPI + depends on (SND_SOC_SOF_HDA_AUDIO_CODEC && (MFD_INTEL_LPSS || COMPILE_TEST)) ||\ + (SND_SOC_SOF_BAYTRAIL && (X86_INTEL_LPSS || COMPILE_TEST)) + depends on SND_HDA_CODEC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON + select SND_SOC_PCM512x_I2C + help + This adds support for ASoC machine driver for SOF platforms + with TI PCM512x I2S audio codec. + Say Y or m if you have such a device. + If unsure select "N". + +config SND_SOC_INTEL_SOF_ES8336_MACH + tristate "SOF with ES8336 or ES8326 codec in I2S mode" + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST + depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC + select SND_SOC_ES8316 + select SND_SOC_ES8326 + select SND_SOC_DMIC + select SND_SOC_INTEL_HDA_DSP_COMMON + help + This adds support for ASoC machine driver for SOF platforms + with es8336 codec. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_INTEL_SOF_NAU8825_MACH + tristate "SOF with nau8825 codec in I2S Mode" + depends on I2C && ACPI + depends on ((SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC) &&\ + (MFD_INTEL_LPSS || COMPILE_TEST)) + select SND_SOC_NAU8825 + select SND_SOC_RT1015P + select SND_SOC_MAX98373_I2C + select SND_SOC_MAX98357A + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON + select SND_SOC_INTEL_SOF_MAXIM_COMMON + help + This adds support for ASoC machine driver for SOF platforms + with nau8825 codec. + Say Y if you have such a device. + If unsure select "N". + endif ## SND_SOC_SOF_HDA_LINK || SND_SOC_SOF_BAYTRAIL -if (SND_SOC_SOF_COMETLAKE_LP && SND_SOC_SOF_HDA_LINK) +if (SND_SOC_SOF_COMETLAKE && SND_SOC_SOF_HDA_LINK) config SND_SOC_INTEL_CML_LP_DA7219_MAX98357A_MACH tristate "CML_LP with DA7219 and MAX98357A in I2S Mode" @@ -476,35 +583,103 @@ config SND_SOC_INTEL_SOF_CML_RT1011_RT5682_MACH tristate "CML with RT1011 and RT5682 in I2S Mode" depends on I2C && ACPI depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC select SND_SOC_RT1011 - select SND_SOC_RT5682 + select SND_SOC_RT5682_I2C select SND_SOC_DMIC select SND_SOC_HDAC_HDMI - select SND_HDA_CODEC_HDMI if SND_SOC_SOF_HDA_AUDIO_CODEC + select SND_SOC_INTEL_HDA_DSP_COMMON help This adds support for ASoC machine driver for SOF platform with RT1011 + RT5682 I2S codec. Say Y if you have such a device. If unsure select "N". -endif ## SND_SOC_SOF_COMETLAKE_LP && SND_SOC_SOF_HDA_LINK +endif ## SND_SOC_SOF_COMETLAKE && SND_SOC_SOF_HDA_LINK if SND_SOC_SOF_JASPERLAKE config SND_SOC_INTEL_SOF_DA7219_MAX98373_MACH - tristate "SOF with DA7219 and MAX98373 in I2S Mode" + tristate "SOF with DA7219 and MAX98373/MAX98360A in I2S Mode" depends on I2C && ACPI depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC + select SND_SOC_INTEL_HDA_DSP_COMMON select SND_SOC_DA7219 - select SND_SOC_MAX98373 + select SND_SOC_MAX98373_I2C select SND_SOC_DMIC - select SND_HDA_CODEC_HDMI if SND_SOC_SOF_HDA_AUDIO_CODEC help This adds support for ASoC machine driver for SOF platforms - with DA7219 + MAX98373 I2S audio codec. + with DA7219 + MAX98373/MAX98360A I2S audio codec. Say Y if you have such a device. If unsure select "N". endif ## SND_SOC_SOF_JASPERLAKE +if SND_SOC_SOF_HDA_LINK + +config SND_SOC_INTEL_SOF_SSP_AMP_MACH + tristate "SOF with amplifiers in I2S Mode" + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT1308 + select SND_SOC_CS35L41_I2C + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_HDA_DSP_COMMON + select SND_SOC_INTEL_SOF_REALTEK_COMMON + select SND_SOC_INTEL_SOF_CIRRUS_COMMON + help + This adds support for ASoC machine driver for SOF platforms + with RT1308/CS35L41 I2S audio codec. + Say Y if you have such a device. + If unsure select "N". +endif ## SND_SOC_SOF_HDA_LINK + +if SND_SOC_SOF_ELKHARTLAKE + +config SND_SOC_INTEL_EHL_RT5660_MACH + tristate "EHL with RT5660 in I2S mode" + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC + select SND_SOC_RT5660 + select SND_SOC_DMIC + select SND_SOC_INTEL_HDA_DSP_COMMON + help + This adds support for ASoC machine driver for Elkhart Lake + platform with RT5660 I2S audio codec. + +endif ## SND_SOC_SOF_ELKHARTLAKE + +if SND_SOC_SOF_INTEL_SOUNDWIRE + +config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH + tristate "SoundWire generic machine driver" + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES || COMPILE_TEST + depends on SOUNDWIRE + select SND_SOC_MAX98373_I2C + select SND_SOC_MAX98373_SDW + select SND_SOC_RT700_SDW + select SND_SOC_RT711_SDW + select SND_SOC_RT711_SDCA_SDW + select SND_SOC_RT1308_SDW + select SND_SOC_RT1308 + select SND_SOC_RT1316_SDW + select SND_SOC_RT715_SDW + select SND_SOC_RT715_SDCA_SDW + select SND_SOC_RT5682_SDW + select SND_SOC_DMIC + select SND_SOC_INTEL_HDA_DSP_COMMON + select SND_SOC_INTEL_SOF_MAXIM_COMMON + imply SND_SOC_SDW_MOCKUP + help + Add support for Intel SoundWire-based platforms connected to + MAX98373, RT700, RT711, RT1308 and RT715 + If unsure select "N". + +endif + endif ## SND_SOC_INTEL_MACH diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index b74ddd49bd39..53458e748191 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -1,15 +1,16 @@ -# SPDX-License-Identifier: GPL-2.0 -snd-soc-sst-haswell-objs := haswell.o -snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o -snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o +# SPDX-License-Identifier: GPL-2.0-only +snd-soc-hsw-rt5640-objs := hsw_rt5640.o snd-soc-sst-bdw-rt5650-mach-objs := bdw-rt5650.o snd-soc-sst-bdw-rt5677-mach-objs := bdw-rt5677.o -snd-soc-sst-broadwell-objs := broadwell.o -snd-soc-sst-bxt-da7219_max98357a-objs := bxt_da7219_max98357a.o hda_dsp_common.o -snd-soc-sst-bxt-rt298-objs := bxt_rt298.o hda_dsp_common.o -snd-soc-sst-glk-rt5682_max98357a-objs := glk_rt5682_max98357a.o hda_dsp_common.o +snd-soc-bdw-rt286-objs := bdw_rt286.o +snd-soc-sst-bxt-da7219_max98357a-objs := bxt_da7219_max98357a.o +snd-soc-sst-bxt-rt298-objs := bxt_rt298.o +snd-soc-sst-sof-pcm512x-objs := sof_pcm512x.o +snd-soc-sst-sof-wm8804-objs := sof_wm8804.o +snd-soc-sst-glk-rt5682_max98357a-objs := glk_rt5682_max98357a.o snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o +snd-soc-sst-bytcr-wm5102-objs := bytcr_wm5102.o snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o snd-soc-sst-cht-bsw-max98090_ti-objs := cht_bsw_max98090_ti.o @@ -18,31 +19,46 @@ snd-soc-sst-byt-cht-cx2072x-objs := bytcht_cx2072x.o snd-soc-sst-byt-cht-da7213-objs := bytcht_da7213.o snd-soc-sst-byt-cht-es8316-objs := bytcht_es8316.o snd-soc-sst-byt-cht-nocodec-objs := bytcht_nocodec.o -snd-soc-sof_rt5682-objs := sof_rt5682.o hda_dsp_common.o -snd-soc-cml_rt1011_rt5682-objs := cml_rt1011_rt5682.o hda_dsp_common.o +snd-soc-sof_rt5682-objs := sof_rt5682.o +snd-soc-sof_cs42l42-objs := sof_cs42l42.o +snd-soc-sof_es8336-objs := sof_es8336.o +snd-soc-sof_nau8825-objs := sof_nau8825.o +snd-soc-cml_rt1011_rt5682-objs := cml_rt1011_rt5682.o snd-soc-kbl_da7219_max98357a-objs := kbl_da7219_max98357a.o snd-soc-kbl_da7219_max98927-objs := kbl_da7219_max98927.o snd-soc-kbl_rt5663_max98927-objs := kbl_rt5663_max98927.o snd-soc-kbl_rt5663_rt5514_max98927-objs := kbl_rt5663_rt5514_max98927.o snd-soc-kbl_rt5660-objs := kbl_rt5660.o snd-soc-skl_rt286-objs := skl_rt286.o -snd-soc-skl_hda_dsp-objs := skl_hda_dsp_generic.o skl_hda_dsp_common.o hda_dsp_common.o +snd-soc-skl_hda_dsp-objs := skl_hda_dsp_generic.o skl_hda_dsp_common.o snd-skl_nau88l25_max98357a-objs := skl_nau88l25_max98357a.o snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o -snd-soc-sof_da7219_max98373-objs := sof_da7219_max98373.o hda_dsp_common.o - +snd-soc-sof_da7219_max98373-objs := sof_da7219_max98373.o +snd-soc-ehl-rt5660-objs := ehl_rt5660.o +snd-soc-sof-ssp-amp-objs := sof_ssp_amp.o +snd-soc-sof-sdw-objs += sof_sdw.o \ + sof_sdw_max98373.o \ + sof_sdw_rt1308.o sof_sdw_rt1316.o \ + sof_sdw_rt5682.o sof_sdw_rt700.o \ + sof_sdw_rt711.o sof_sdw_rt711_sdca.o \ + sof_sdw_rt715.o sof_sdw_rt715_sdca.o \ + sof_sdw_dmic.o sof_sdw_hdmi.o obj-$(CONFIG_SND_SOC_INTEL_SOF_RT5682_MACH) += snd-soc-sof_rt5682.o -obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o -obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o -obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_CS42L42_MACH) += snd-soc-sof_cs42l42.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_ES8336_MACH) += snd-soc-sof_es8336.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_NAU8825_MACH) += snd-soc-sof_nau8825.o +obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-hsw-rt5640.o obj-$(CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON) += snd-soc-sst-bxt-da7219_max98357a.o obj-$(CONFIG_SND_SOC_INTEL_BXT_RT298_MACH) += snd-soc-sst-bxt-rt298.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_PCM512x_MACH) += snd-soc-sst-sof-pcm512x.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_WM8804_MACH) += snd-soc-sst-sof-wm8804.o obj-$(CONFIG_SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH) += snd-soc-sst-glk-rt5682_max98357a.o -obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o +obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-bdw-rt286.o obj-$(CONFIG_SND_SOC_INTEL_BDW_RT5650_MACH) += snd-soc-sst-bdw-rt5650-mach.o obj-$(CONFIG_SND_SOC_INTEL_BDW_RT5677_MACH) += snd-soc-sst-bdw-rt5677-mach.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o +obj-$(CONFIG_SND_SOC_INTEL_BYTCR_WM5102_MACH) += snd-soc-sst-bytcr-wm5102.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o @@ -62,4 +78,19 @@ obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH) += snd-skl_nau88l25_max9 obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH) += snd-soc-skl_nau88l25_ssm4567.o obj-$(CONFIG_SND_SOC_INTEL_SKL_HDA_DSP_GENERIC_MACH) += snd-soc-skl_hda_dsp.o obj-$(CONFIG_SND_SOC_INTEL_SOF_DA7219_MAX98373_MACH) += snd-soc-sof_da7219_max98373.o +obj-$(CONFIG_SND_SOC_INTEL_EHL_RT5660_MACH) += snd-soc-ehl-rt5660.o +obj-$(CONFIG_SND_SOC_INTEL_SOUNDWIRE_SOF_MACH) += snd-soc-sof-sdw.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_SSP_AMP_MACH) += snd-soc-sof-ssp-amp.o + +# common modules +snd-soc-intel-hda-dsp-common-objs := hda_dsp_common.o +obj-$(CONFIG_SND_SOC_INTEL_HDA_DSP_COMMON) += snd-soc-intel-hda-dsp-common.o + +snd-soc-intel-sof-maxim-common-objs += sof_maxim_common.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_MAXIM_COMMON) += snd-soc-intel-sof-maxim-common.o + +snd-soc-intel-sof-realtek-common-objs += sof_realtek_common.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_REALTEK_COMMON) += snd-soc-intel-sof-realtek-common.o +snd-soc-intel-sof-cirrus-common-objs += sof_cirrus_common.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_CIRRUS_COMMON) += snd-soc-intel-sof-cirrus-common.o diff --git a/sound/soc/intel/boards/bdw-rt5650.c b/sound/soc/intel/boards/bdw-rt5650.c index 1a302436d450..67c3f49b924c 100644 --- a/sound/soc/intel/boards/bdw-rt5650.c +++ b/sound/soc/intel/boards/bdw-rt5650.c @@ -16,9 +16,6 @@ #include <sound/soc.h> #include <sound/soc-acpi.h> -#include "../common/sst-dsp.h" -#include "../haswell/sst-haswell-ipc.h" - #include "../../codecs/rt5645.h" struct bdw_rt5650_priv { @@ -87,14 +84,14 @@ static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *chan = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); /* The ADSP will covert the FE rate to 48k, max 4-channels */ rate->min = rate->max = 48000; - channels->min = 2; - channels->max = 4; + chan->min = 2; + chan->max = 4; /* set SSP0 to 24 bit */ snd_mask_set_format(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), @@ -106,8 +103,8 @@ static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, static int bdw_rt5650_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; /* Workaround: set codec PLL to 19.2MHz that PLL source is @@ -138,36 +135,40 @@ static struct snd_soc_ops bdw_rt5650_ops = { .hw_params = bdw_rt5650_hw_params, }; -#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) -static int bdw_rt5650_rtd_init(struct snd_soc_pcm_runtime *rtd) +static const unsigned int channels[] = { + 2, 4, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int bdw_rt5650_fe_startup(struct snd_pcm_substream *substream) { - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct sst_pdata *pdata = dev_get_platdata(component->dev); - struct sst_hsw *broadwell = pdata->dsp; - int ret; + struct snd_pcm_runtime *runtime = substream->runtime; - /* Set ADSP SSP port settings - * clock_divider = 4 means BCLK = MCLK/5 = 24MHz/5 = 4.8MHz - */ - ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, - SST_HSW_DEVICE_TDM_CLOCK_MASTER, 4); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to set device config\n"); - return ret; - } + /* Board supports stereo and quad configurations for capture */ + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return 0; - return 0; + runtime->hw.channels_max = 4; + return snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); } -#endif + +static const struct snd_soc_ops bdw_rt5650_fe_ops = { + .startup = bdw_rt5650_fe_startup, +}; static int bdw_rt5650_init(struct snd_soc_pcm_runtime *rtd) { struct bdw_rt5650_priv *bdw_rt5650 = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; int ret; /* Enable codec ASRC function for Stereo DAC/Stereo1 ADC/DMIC/I2S1. @@ -191,15 +192,15 @@ static int bdw_rt5650_init(struct snd_soc_pcm_runtime *rtd) } /* Create and initialize headphone jack */ - if (snd_soc_card_jack_new(rtd->card, "Headphone Jack", + if (snd_soc_card_jack_new_pins(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE, &headphone_jack, &headphone_jack_pin, 1)) { dev_err(component->dev, "Can't create headphone jack\n"); } /* Create and initialize mic jack */ - if (snd_soc_card_jack_new(rtd->card, "Mic Jack", SND_JACK_MICROPHONE, - &mic_jack, &mic_jack_pin, 1)) { + if (snd_soc_card_jack_new_pins(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, &mic_jack, &mic_jack_pin, 1)) { dev_err(component->dev, "Can't create mic jack\n"); } @@ -223,23 +224,17 @@ SND_SOC_DAILINK_DEF(platform, SND_SOC_DAILINK_DEF(be, DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5650:00", "rt5645-aif1"))); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) SND_SOC_DAILINK_DEF(ssp0_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port"))); -#else -SND_SOC_DAILINK_DEF(ssp0_port, - DAILINK_COMP_ARRAY(COMP_DUMMY())); -#endif static struct snd_soc_dai_link bdw_rt5650_dais[] = { /* Front End DAI links */ { .name = "System PCM", .stream_name = "System Playback", + .nonatomic = 1, .dynamic = 1, -#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) - .init = bdw_rt5650_rtd_init, -#endif + .ops = &bdw_rt5650_fe_ops, .trigger = { SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST @@ -254,10 +249,10 @@ static struct snd_soc_dai_link bdw_rt5650_dais[] = { /* SSP0 - Codec */ .name = "Codec", .id = 0, + .nonatomic = 1, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - .ignore_suspend = 1, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broadwell_ssp0_fixup, .ops = &bdw_rt5650_ops, @@ -268,9 +263,17 @@ static struct snd_soc_dai_link bdw_rt5650_dais[] = { }, }; +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bdw rt5650" /* card name will be 'sof-bdw rt5650' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "bdw-rt5650" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + /* ASoC machine driver for Broadwell DSP + RT5650 */ static struct snd_soc_card bdw_rt5650_card = { - .name = "bdw-rt5650", + .name = CARD_NAME, + .driver_name = DRIVER_NAME, .owner = THIS_MODULE, .dai_link = bdw_rt5650_dais, .num_links = ARRAY_SIZE(bdw_rt5650_dais), @@ -297,14 +300,23 @@ static int bdw_rt5650_probe(struct platform_device *pdev) if (!bdw_rt5650) return -ENOMEM; - /* override plaform name, if required */ - mach = (&pdev->dev)->platform_data; + /* override platform name, if required */ + mach = pdev->dev.platform_data; ret = snd_soc_fixup_dai_links_platform_name(&bdw_rt5650_card, mach->mach_params.platform); if (ret) return ret; + /* set card and driver name */ + if (snd_soc_acpi_sof_parent(&pdev->dev)) { + bdw_rt5650_card.name = SOF_CARD_NAME; + bdw_rt5650_card.driver_name = SOF_DRIVER_NAME; + } else { + bdw_rt5650_card.name = CARD_NAME; + bdw_rt5650_card.driver_name = DRIVER_NAME; + } + snd_soc_card_set_drvdata(&bdw_rt5650_card, bdw_rt5650); return devm_snd_soc_register_card(&pdev->dev, &bdw_rt5650_card); diff --git a/sound/soc/intel/boards/bdw-rt5677.c b/sound/soc/intel/boards/bdw-rt5677.c index bb643c99069d..31488702768e 100644 --- a/sound/soc/intel/boards/bdw-rt5677.c +++ b/sound/soc/intel/boards/bdw-rt5677.c @@ -17,9 +17,6 @@ #include <sound/jack.h> #include <sound/soc-acpi.h> -#include "../common/sst-dsp.h" -#include "../haswell/sst-haswell-ipc.h" - #include "../../codecs/rt5677.h" struct bdw_rt5677_priv { @@ -140,13 +137,13 @@ static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *chan = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); /* The ADSP will covert the FE rate to 48k, stereo */ rate->min = rate->max = 48000; - channels->min = channels->max = 2; + chan->min = chan->max = 2; /* set SSP0 to 16 bit */ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); @@ -156,8 +153,8 @@ static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, static int bdw_rt5677_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_MCLK, 24576000, @@ -173,8 +170,8 @@ static int bdw_rt5677_hw_params(struct snd_pcm_substream *substream, static int bdw_rt5677_dsp_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_PLL1, 24576000, @@ -201,32 +198,36 @@ static const struct snd_soc_ops bdw_rt5677_dsp_ops = { .hw_params = bdw_rt5677_dsp_hw_params, }; -#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) -static int bdw_rt5677_rtd_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct sst_pdata *pdata = dev_get_platdata(component->dev); - struct sst_hsw *broadwell = pdata->dsp; - int ret; +static const unsigned int channels[] = { + 2, +}; - /* Set ADSP SSP port settings */ - ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, - SST_HSW_DEVICE_CLOCK_MASTER, 9); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to set device config\n"); - return ret; - } +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; - return 0; +static int bdw_rt5677_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* Board supports stereo configuration only */ + runtime->hw.channels_max = 2; + return snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); } -#endif + +static const struct snd_soc_ops bdw_rt5677_fe_ops = { + .startup = bdw_rt5677_fe_startup, +}; static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd) { struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); int ret; @@ -247,15 +248,15 @@ static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd) RT5677_CLK_SEL_SYS2); /* Request rt5677 GPIO for headphone amp control */ - bdw_rt5677->gpio_hp_en = devm_gpiod_get(component->dev, "headphone-enable", - GPIOD_OUT_LOW); + bdw_rt5677->gpio_hp_en = gpiod_get(component->dev, "headphone-enable", + GPIOD_OUT_LOW); if (IS_ERR(bdw_rt5677->gpio_hp_en)) { dev_err(component->dev, "Can't find HP_AMP_SHDN_L gpio\n"); return PTR_ERR(bdw_rt5677->gpio_hp_en); } /* Create and initialize headphone jack */ - if (!snd_soc_card_jack_new(rtd->card, "Headphone Jack", + if (!snd_soc_card_jack_new_pins(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE, &headphone_jack, &headphone_jack_pin, 1)) { headphone_jack_gpio.gpiod_dev = component->dev; @@ -267,7 +268,7 @@ static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd) } /* Create and initialize mic jack */ - if (!snd_soc_card_jack_new(rtd->card, "Mic Jack", + if (!snd_soc_card_jack_new_pins(rtd->card, "Mic Jack", SND_JACK_MICROPHONE, &mic_jack, &mic_jack_pin, 1)) { mic_jack_gpio.gpiod_dev = component->dev; @@ -282,6 +283,19 @@ static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd) return 0; } +static void bdw_rt5677_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct bdw_rt5677_priv *bdw_rt5677 = + snd_soc_card_get_drvdata(rtd->card); + + /* + * The .exit() can be reached without going through the .init() + * so explicitly test if the gpiod is valid + */ + if (!IS_ERR_OR_NULL(bdw_rt5677->gpio_hp_en)) + gpiod_put(bdw_rt5677->gpio_hp_en); +} + /* broadwell digital audio interface glue - connects codec <--> CPU */ SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); @@ -295,13 +309,8 @@ SND_SOC_DAILINK_DEF(platform, SND_SOC_DAILINK_DEF(be, DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RT5677CE:00", "rt5677-aif1"))); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) SND_SOC_DAILINK_DEF(ssp0_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port"))); -#else -SND_SOC_DAILINK_DEF(ssp0_port, - DAILINK_COMP_ARRAY(COMP_DUMMY())); -#endif /* Wake on voice interface */ SND_SOC_DAILINK_DEFS(dsp, @@ -314,16 +323,15 @@ static struct snd_soc_dai_link bdw_rt5677_dais[] = { { .name = "System PCM", .stream_name = "System Playback/Capture", + .nonatomic = 1, .dynamic = 1, -#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) - .init = bdw_rt5677_rtd_init, -#endif .trigger = { SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST }, .dpcm_capture = 1, .dpcm_playback = 1, + .ops = &bdw_rt5677_fe_ops, SND_SOC_DAILINK_REG(fe, dummy, platform), }, @@ -331,6 +339,7 @@ static struct snd_soc_dai_link bdw_rt5677_dais[] = { { .name = "Codec DSP", .stream_name = "Wake on Voice", + .capture_only = 1, .ops = &bdw_rt5677_dsp_ops, SND_SOC_DAILINK_REG(dsp), }, @@ -340,16 +349,17 @@ static struct snd_soc_dai_link bdw_rt5677_dais[] = { /* SSP0 - Codec */ .name = "Codec", .id = 0, + .nonatomic = 1, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - .ignore_suspend = 1, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broadwell_ssp0_fixup, .ops = &bdw_rt5677_ops, .dpcm_playback = 1, .dpcm_capture = 1, .init = bdw_rt5677_init, + .exit = bdw_rt5677_exit, SND_SOC_DAILINK_REG(ssp0_port, be, platform), }, }; @@ -378,9 +388,17 @@ static int bdw_rt5677_resume_post(struct snd_soc_card *card) return 0; } +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bdw rt5677" /* card name will be 'sof-bdw rt5677' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "bdw-rt5677" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + /* ASoC machine driver for Broadwell DSP + RT5677 */ static struct snd_soc_card bdw_rt5677_card = { - .name = "bdw-rt5677", + .name = CARD_NAME, + .driver_name = DRIVER_NAME, .owner = THIS_MODULE, .dai_link = bdw_rt5677_dais, .num_links = ARRAY_SIZE(bdw_rt5677_dais), @@ -406,18 +424,25 @@ static int bdw_rt5677_probe(struct platform_device *pdev) /* Allocate driver private struct */ bdw_rt5677 = devm_kzalloc(&pdev->dev, sizeof(struct bdw_rt5677_priv), GFP_KERNEL); - if (!bdw_rt5677) { - dev_err(&pdev->dev, "Can't allocate bdw_rt5677\n"); + if (!bdw_rt5677) return -ENOMEM; - } - /* override plaform name, if required */ - mach = (&pdev->dev)->platform_data; + /* override platform name, if required */ + mach = pdev->dev.platform_data; ret = snd_soc_fixup_dai_links_platform_name(&bdw_rt5677_card, mach->mach_params.platform); if (ret) return ret; + /* set card and driver name */ + if (snd_soc_acpi_sof_parent(&pdev->dev)) { + bdw_rt5677_card.name = SOF_CARD_NAME; + bdw_rt5677_card.driver_name = SOF_DRIVER_NAME; + } else { + bdw_rt5677_card.name = CARD_NAME; + bdw_rt5677_card.driver_name = DRIVER_NAME; + } + snd_soc_card_set_drvdata(&bdw_rt5677_card, bdw_rt5677); return devm_snd_soc_register_card(&pdev->dev, &bdw_rt5677_card); @@ -427,6 +452,7 @@ static struct platform_driver bdw_rt5677_audio = { .probe = bdw_rt5677_probe, .driver = { .name = "bdw-rt5677", + .pm = &snd_soc_pm_ops }, }; diff --git a/sound/soc/intel/boards/bdw_rt286.c b/sound/soc/intel/boards/bdw_rt286.c new file mode 100644 index 000000000000..6b76df0e7c9b --- /dev/null +++ b/sound/soc/intel/boards/bdw_rt286.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sound card driver for Intel Broadwell Wildcat Point with Realtek 286 + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../codecs/rt286.h" + +static struct snd_soc_jack card_headset; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("DMIC1", NULL), + SND_SOC_DAPM_MIC("DMIC2", NULL), + SND_SOC_DAPM_LINE("Line Jack", NULL), +}; + +static const struct snd_soc_dapm_route card_routes[] = { + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, + + {"Headphone Jack", NULL, "HPO Pin"}, + + {"MIC1", NULL, "Mic Jack"}, + {"LINE1", NULL, "Line Jack"}, + + {"DMIC1 Pin", NULL, "DMIC1"}, + {"DMIC2 Pin", NULL, "DMIC2"}, + + /* CODEC BE connections */ + {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, + {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, +}; + +static int codec_link_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component; + int ret; + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0, + &card_headset, card_headset_pins, + ARRAY_SIZE(card_headset_pins)); + if (ret) + return ret; + + return snd_soc_component_set_jack(codec, &card_headset, NULL); +} + +static int codec_link_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + /* The ADSP will convert the FE rate to 48kHz, stereo. */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + /* Set SSP0 to 16 bit. */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static int codec_link_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "set codec sysclk failed: %d\n", ret); + return ret; + } + + return ret; +} + +static const struct snd_soc_ops codec_link_ops = { + .hw_params = codec_link_hw_params, +}; + +SND_SOC_DAILINK_DEF(system, DAILINK_COMP_ARRAY(COMP_CPU("System Pin"))); +SND_SOC_DAILINK_DEF(offload0, DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin"))); +SND_SOC_DAILINK_DEF(offload1, DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin"))); +SND_SOC_DAILINK_DEF(loopback, DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin"))); + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); +SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio"))); +SND_SOC_DAILINK_DEF(codec, DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT343A:00", "rt286-aif1"))); +SND_SOC_DAILINK_DEF(ssp0_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port"))); + +static struct snd_soc_dai_link card_dai_links[] = { + /* Front End DAI links */ + { + .name = "System PCM", + .stream_name = "System Playback/Capture", + .nonatomic = 1, + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(system, dummy, platform), + }, + { + .name = "Offload0", + .stream_name = "Offload0 Playback", + .nonatomic = 1, + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(offload0, dummy, platform), + }, + { + .name = "Offload1", + .stream_name = "Offload1 Playback", + .nonatomic = 1, + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(offload1, dummy, platform), + }, + { + .name = "Loopback PCM", + .stream_name = "Loopback", + .nonatomic = 1, + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(loopback, dummy, platform), + }, + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "Codec", + .id = 0, + .nonatomic = 1, + .no_pcm = 1, + .init = codec_link_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = codec_link_hw_params_fixup, + .ops = &codec_link_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(ssp0_port, codec, platform), + }, +}; + +static void bdw_rt286_disable_jack(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) { + if (!strcmp(component->name, "i2c-INT343A:00")) { + dev_dbg(component->dev, "disabling jack detect before going to suspend.\n"); + snd_soc_component_set_jack(component, NULL, NULL); + break; + } + } +} + +static int bdw_rt286_suspend(struct snd_soc_card *card) +{ + bdw_rt286_disable_jack(card); + + return 0; +} + +static int bdw_rt286_resume(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) { + if (!strcmp(component->name, "i2c-INT343A:00")) { + dev_dbg(component->dev, "enabling jack detect for resume.\n"); + snd_soc_component_set_jack(component, &card_headset, NULL); + break; + } + } + + return 0; +} + +static struct snd_soc_card bdw_rt286_card = { + .owner = THIS_MODULE, + .dai_link = card_dai_links, + .num_links = ARRAY_SIZE(card_dai_links), + .controls = card_controls, + .num_controls = ARRAY_SIZE(card_controls), + .dapm_widgets = card_widgets, + .num_dapm_widgets = ARRAY_SIZE(card_widgets), + .dapm_routes = card_routes, + .num_dapm_routes = ARRAY_SIZE(card_routes), + .fully_routed = true, + .suspend_pre = bdw_rt286_suspend, + .resume_post = bdw_rt286_resume, +}; + +/* Use space before codec name to simplify card ID, and simplify driver name. */ +#define SOF_CARD_NAME "bdw rt286" /* card name will be 'sof-bdw rt286' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "broadwell-rt286" + +static int bdw_rt286_probe(struct platform_device *pdev) +{ + struct snd_soc_acpi_mach *mach; + struct device *dev = &pdev->dev; + int ret; + + bdw_rt286_card.dev = dev; + mach = dev_get_platdata(dev); + + ret = snd_soc_fixup_dai_links_platform_name(&bdw_rt286_card, mach->mach_params.platform); + if (ret) + return ret; + + if (snd_soc_acpi_sof_parent(dev)) { + bdw_rt286_card.name = SOF_CARD_NAME; + bdw_rt286_card.driver_name = SOF_DRIVER_NAME; + } else { + bdw_rt286_card.name = CARD_NAME; + } + + return devm_snd_soc_register_card(dev, &bdw_rt286_card); +} + +static int bdw_rt286_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + bdw_rt286_disable_jack(card); + + return 0; +} + +static struct platform_driver bdw_rt286_driver = { + .probe = bdw_rt286_probe, + .remove = bdw_rt286_remove, + .driver = { + .name = "bdw_rt286", + .pm = &snd_soc_pm_ops + }, +}; + +module_platform_driver(bdw_rt286_driver) + +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Sound card driver for Intel Broadwell Wildcat Point with Realtek 286"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bdw_rt286"); diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c deleted file mode 100644 index b9c12e24c70b..000000000000 --- a/sound/soc/intel/boards/broadwell.c +++ /dev/null @@ -1,308 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Broadwell Wildcatpoint SST Audio - * - * Copyright (C) 2013, Intel Corporation. All rights reserved. - */ - -#include <linux/module.h> -#include <linux/platform_device.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> -#include <sound/jack.h> -#include <sound/pcm_params.h> -#include <sound/soc-acpi.h> - -#include "../common/sst-dsp.h" -#include "../haswell/sst-haswell-ipc.h" - -#include "../../codecs/rt286.h" - -static struct snd_soc_jack broadwell_headset; -/* Headset jack detection DAPM pins */ -static struct snd_soc_jack_pin broadwell_headset_pins[] = { - { - .pin = "Mic Jack", - .mask = SND_JACK_MICROPHONE, - }, - { - .pin = "Headphone Jack", - .mask = SND_JACK_HEADPHONE, - }, -}; - -static const struct snd_kcontrol_new broadwell_controls[] = { - SOC_DAPM_PIN_SWITCH("Speaker"), - SOC_DAPM_PIN_SWITCH("Headphone Jack"), -}; - -static const struct snd_soc_dapm_widget broadwell_widgets[] = { - SND_SOC_DAPM_HP("Headphone Jack", NULL), - SND_SOC_DAPM_SPK("Speaker", NULL), - SND_SOC_DAPM_MIC("Mic Jack", NULL), - SND_SOC_DAPM_MIC("DMIC1", NULL), - SND_SOC_DAPM_MIC("DMIC2", NULL), - SND_SOC_DAPM_LINE("Line Jack", NULL), -}; - -static const struct snd_soc_dapm_route broadwell_rt286_map[] = { - - /* speaker */ - {"Speaker", NULL, "SPOR"}, - {"Speaker", NULL, "SPOL"}, - - /* HP jack connectors - unknown if we have jack deteck */ - {"Headphone Jack", NULL, "HPO Pin"}, - - /* other jacks */ - {"MIC1", NULL, "Mic Jack"}, - {"LINE1", NULL, "Line Jack"}, - - /* digital mics */ - {"DMIC1 Pin", NULL, "DMIC1"}, - {"DMIC2 Pin", NULL, "DMIC2"}, - - /* CODEC BE connections */ - {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, - {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, -}; - -static int broadwell_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_component *component = rtd->codec_dai->component; - int ret = 0; - ret = snd_soc_card_jack_new(rtd->card, "Headset", - SND_JACK_HEADSET | SND_JACK_BTN_0, &broadwell_headset, - broadwell_headset_pins, ARRAY_SIZE(broadwell_headset_pins)); - if (ret) - return ret; - - rt286_mic_detect(component, &broadwell_headset); - return 0; -} - - -static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); - - /* The ADSP will covert the FE rate to 48k, stereo */ - rate->min = rate->max = 48000; - channels->min = channels->max = 2; - - /* set SSP0 to 16 bit */ - params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); - return 0; -} - -static int broadwell_rt286_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, - SND_SOC_CLOCK_IN); - - if (ret < 0) { - dev_err(rtd->dev, "can't set codec sysclk configuration\n"); - return ret; - } - - return ret; -} - -static const struct snd_soc_ops broadwell_rt286_ops = { - .hw_params = broadwell_rt286_hw_params, -}; - -#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) -static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct sst_pdata *pdata = dev_get_platdata(component->dev); - struct sst_hsw *broadwell = pdata->dsp; - int ret; - - /* Set ADSP SSP port settings */ - ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, - SST_HSW_DEVICE_CLOCK_MASTER, 9); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to set device config\n"); - return ret; - } - - return 0; -} -#endif - -SND_SOC_DAILINK_DEF(system, - DAILINK_COMP_ARRAY(COMP_CPU("System Pin"))); - -SND_SOC_DAILINK_DEF(offload0, - DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin"))); - -SND_SOC_DAILINK_DEF(offload1, - DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin"))); - -SND_SOC_DAILINK_DEF(loopback, - DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin"))); - -SND_SOC_DAILINK_DEF(dummy, - DAILINK_COMP_ARRAY(COMP_DUMMY())); - -SND_SOC_DAILINK_DEF(platform, - DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio"))); - -SND_SOC_DAILINK_DEF(codec, - DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT343A:00", "rt286-aif1"))); - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) -SND_SOC_DAILINK_DEF(ssp0_port, - DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port"))); -#else -SND_SOC_DAILINK_DEF(ssp0_port, - DAILINK_COMP_ARRAY(COMP_DUMMY())); -#endif - -/* broadwell digital audio interface glue - connects codec <--> CPU */ -static struct snd_soc_dai_link broadwell_rt286_dais[] = { - /* Front End DAI links */ - { - .name = "System PCM", - .stream_name = "System Playback/Capture", - .dynamic = 1, -#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) - .init = broadwell_rtd_init, -#endif - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - .dpcm_capture = 1, - SND_SOC_DAILINK_REG(system, dummy, platform), - }, - { - .name = "Offload0", - .stream_name = "Offload0 Playback", - .dynamic = 1, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - SND_SOC_DAILINK_REG(offload0, dummy, platform), - }, - { - .name = "Offload1", - .stream_name = "Offload1 Playback", - .dynamic = 1, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - SND_SOC_DAILINK_REG(offload1, dummy, platform), - }, - { - .name = "Loopback PCM", - .stream_name = "Loopback", - .dynamic = 1, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_capture = 1, - SND_SOC_DAILINK_REG(loopback, dummy, platform), - }, - /* Back End DAI links */ - { - /* SSP0 - Codec */ - .name = "Codec", - .id = 0, - .no_pcm = 1, - .init = broadwell_rt286_codec_init, - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - .ignore_suspend = 1, - .ignore_pmdown_time = 1, - .be_hw_params_fixup = broadwell_ssp0_fixup, - .ops = &broadwell_rt286_ops, - .dpcm_playback = 1, - .dpcm_capture = 1, - SND_SOC_DAILINK_REG(ssp0_port, codec, platform), - }, -}; - -static int broadwell_suspend(struct snd_soc_card *card){ - struct snd_soc_component *component; - - for_each_card_components(card, component) { - if (!strcmp(component->name, "i2c-INT343A:00")) { - - dev_dbg(component->dev, "disabling jack detect before going to suspend.\n"); - rt286_mic_detect(component, NULL); - break; - } - } - return 0; -} - -static int broadwell_resume(struct snd_soc_card *card){ - struct snd_soc_component *component; - - for_each_card_components(card, component) { - if (!strcmp(component->name, "i2c-INT343A:00")) { - - dev_dbg(component->dev, "enabling jack detect for resume.\n"); - rt286_mic_detect(component, &broadwell_headset); - break; - } - } - return 0; -} - -/* broadwell audio machine driver for WPT + RT286S */ -static struct snd_soc_card broadwell_rt286 = { - .name = "broadwell-rt286", - .owner = THIS_MODULE, - .dai_link = broadwell_rt286_dais, - .num_links = ARRAY_SIZE(broadwell_rt286_dais), - .controls = broadwell_controls, - .num_controls = ARRAY_SIZE(broadwell_controls), - .dapm_widgets = broadwell_widgets, - .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets), - .dapm_routes = broadwell_rt286_map, - .num_dapm_routes = ARRAY_SIZE(broadwell_rt286_map), - .fully_routed = true, - .suspend_pre = broadwell_suspend, - .resume_post = broadwell_resume, -}; - -static int broadwell_audio_probe(struct platform_device *pdev) -{ - struct snd_soc_acpi_mach *mach; - int ret; - - broadwell_rt286.dev = &pdev->dev; - - /* override plaform name, if required */ - mach = (&pdev->dev)->platform_data; - ret = snd_soc_fixup_dai_links_platform_name(&broadwell_rt286, - mach->mach_params.platform); - if (ret) - return ret; - - return devm_snd_soc_register_card(&pdev->dev, &broadwell_rt286); -} - -static struct platform_driver broadwell_audio = { - .probe = broadwell_audio_probe, - .driver = { - .name = "broadwell-audio", - }, -}; - -module_platform_driver(broadwell_audio) - -/* Module information */ -MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); -MODULE_DESCRIPTION("Intel SST Audio for WPT/Broadwell"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:broadwell-audio"); diff --git a/sound/soc/intel/boards/bxt_da7219_max98357a.c b/sound/soc/intel/boards/bxt_da7219_max98357a.c index 9177401c37a5..7c6c95e99ade 100644 --- a/sound/soc/intel/boards/bxt_da7219_max98357a.c +++ b/sound/soc/intel/boards/bxt_da7219_max98357a.c @@ -25,9 +25,14 @@ #define BXT_DIALOG_CODEC_DAI "da7219-hifi" #define BXT_MAXIM_CODEC_DAI "HiFi" +#define MAX98390_DEV0_NAME "i2c-MX98390:00" +#define MAX98390_DEV1_NAME "i2c-MX98390:01" #define DUAL_CHANNEL 2 #define QUAD_CHANNEL 4 +#define SPKAMP_MAX98357A 1 +#define SPKAMP_MAX98390 2 + static struct snd_soc_jack broxton_headset; static struct snd_soc_jack broxton_hdmi[3]; @@ -40,6 +45,7 @@ struct bxt_hdmi_pcm { struct bxt_card_private { struct list_head hdmi_pcm_list; bool common_hdmi_codec_drv; + int spkamp; }; enum { @@ -85,13 +91,20 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, static const struct snd_kcontrol_new broxton_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone Jack"), SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_kcontrol_new max98357a_controls[] = { SOC_DAPM_PIN_SWITCH("Spk"), }; +static const struct snd_kcontrol_new max98390_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + static const struct snd_soc_dapm_widget broxton_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_SPK("Spk", NULL), SND_SOC_DAPM_MIC("SoC DMIC", NULL), SND_SOC_DAPM_SPK("HDMI1", NULL), SND_SOC_DAPM_SPK("HDMI2", NULL), @@ -100,14 +113,20 @@ static const struct snd_soc_dapm_widget broxton_widgets[] = { platform_clock_control, SND_SOC_DAPM_POST_PMD|SND_SOC_DAPM_PRE_PMU), }; +static const struct snd_soc_dapm_widget max98357a_widgets[] = { + SND_SOC_DAPM_SPK("Spk", NULL), +}; + +static const struct snd_soc_dapm_widget max98390_widgets[] = { + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + static const struct snd_soc_dapm_route audio_map[] = { /* HP jack connectors - unknown if we have jack detection */ {"Headphone Jack", NULL, "HPL"}, {"Headphone Jack", NULL, "HPR"}, - /* speaker */ - {"Spk", NULL, "Speaker"}, - /* other jacks */ {"MIC", NULL, "Headset Mic"}, @@ -134,6 +153,17 @@ static const struct snd_soc_dapm_route audio_map[] = { { "Headset Mic", NULL, "Platform Clock" }, }; +static const struct snd_soc_dapm_route max98357a_routes[] = { + /* speaker */ + {"Spk", NULL, "Speaker"}, +}; + +static const struct snd_soc_dapm_route max98390_routes[] = { + /* Speaker */ + {"Left Spk", NULL, "Left BE_OUT"}, + {"Right Spk", NULL, "Right BE_OUT"}, +}; + static const struct snd_soc_dapm_route broxton_map[] = { {"HiFi Playback", NULL, "ssp5 Tx"}, {"ssp5 Tx", NULL, "codec0_out"}, @@ -156,6 +186,17 @@ static const struct snd_soc_dapm_route gemini_map[] = { {"ssp2 Rx", NULL, "Capture"}, }; +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static int broxton_ssp_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { @@ -179,8 +220,8 @@ static int broxton_ssp_fixup(struct snd_soc_pcm_runtime *rtd, static int broxton_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) { int ret; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; int clk_freq; /* Configure sysclk for codec */ @@ -201,10 +242,12 @@ static int broxton_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, - &broxton_headset, NULL, 0); + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, + &broxton_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); return ret; @@ -226,7 +269,7 @@ static int broxton_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd) { struct bxt_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct bxt_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -244,7 +287,7 @@ static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd) static int broxton_da7219_fe_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; dapm = snd_soc_component_get_dapm(component); snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); @@ -404,6 +447,10 @@ SND_SOC_DAILINK_DEF(ssp5_pin, SND_SOC_DAILINK_DEF(ssp5_codec, DAILINK_COMP_ARRAY(COMP_CODEC("MX98357A:00", BXT_MAXIM_CODEC_DAI))); +SND_SOC_DAILINK_DEF(max98390_codec, + DAILINK_COMP_ARRAY( + /* Left */ COMP_CODEC(MAX98390_DEV0_NAME, "max98390-aif1"), + /* Right */ COMP_CODEC(MAX98390_DEV1_NAME, "max98390-aif1"))); SND_SOC_DAILINK_DEF(ssp1_pin, DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin"))); @@ -538,7 +585,7 @@ static struct snd_soc_dai_link broxton_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broxton_ssp_fixup, .dpcm_playback = 1, @@ -551,7 +598,7 @@ static struct snd_soc_dai_link broxton_dais[] = { .no_pcm = 1, .init = broxton_da7219_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broxton_ssp_fixup, .dpcm_playback = 1, @@ -601,15 +648,69 @@ static struct snd_soc_dai_link broxton_dais[] = { }, }; +static struct snd_soc_codec_conf max98390_codec_confs[] = { + { + .dlc = COMP_CODEC_CONF(MAX98390_DEV0_NAME), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF(MAX98390_DEV1_NAME), + .name_prefix = "Right", + }, +}; + #define NAME_SIZE 32 static int bxt_card_late_probe(struct snd_soc_card *card) { struct bxt_card_private *ctx = snd_soc_card_get_drvdata(card); struct bxt_hdmi_pcm *pcm; struct snd_soc_component *component = NULL; - int err, i = 0; + const struct snd_kcontrol_new *controls; + const struct snd_soc_dapm_widget *widgets; + const struct snd_soc_dapm_route *routes; + int num_controls, num_widgets, num_routes, err, i = 0; char jack_name[NAME_SIZE]; + switch (ctx->spkamp) { + case SPKAMP_MAX98357A: + controls = max98357a_controls; + num_controls = ARRAY_SIZE(max98357a_controls); + widgets = max98357a_widgets; + num_widgets = ARRAY_SIZE(max98357a_widgets); + routes = max98357a_routes; + num_routes = ARRAY_SIZE(max98357a_routes); + break; + case SPKAMP_MAX98390: + controls = max98390_controls; + num_controls = ARRAY_SIZE(max98390_controls); + widgets = max98390_widgets; + num_widgets = ARRAY_SIZE(max98390_widgets); + routes = max98390_routes; + num_routes = ARRAY_SIZE(max98390_routes); + break; + default: + dev_err(card->dev, "Invalid speaker amplifier %d\n", ctx->spkamp); + return -EINVAL; + } + + err = snd_soc_dapm_new_controls(&card->dapm, widgets, num_widgets); + if (err) { + dev_err(card->dev, "Fail to new widgets\n"); + return err; + } + + err = snd_soc_add_card_controls(card, controls, num_controls); + if (err) { + dev_err(card->dev, "Fail to add controls\n"); + return err; + } + + err = snd_soc_dapm_add_routes(&card->dapm, routes, num_routes); + if (err) { + dev_err(card->dev, "Fail to add routes\n"); + return err; + } + if (soc_intel_is_glk()) snd_soc_dapm_add_routes(&card->dapm, gemini_map, ARRAY_SIZE(gemini_map)); @@ -632,8 +733,7 @@ static int bxt_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &broxton_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &broxton_hdmi[i]); if (err) return err; @@ -678,6 +778,11 @@ static int broxton_audio_probe(struct platform_device *pdev) INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + if (acpi_dev_present("MX98390", NULL, -1)) + ctx->spkamp = SPKAMP_MAX98390; + else + ctx->spkamp = SPKAMP_MAX98357A; + broxton_audio_card.dev = &pdev->dev; snd_soc_card_set_drvdata(&broxton_audio_card, ctx); if (soc_intel_is_glk()) { @@ -702,7 +807,13 @@ static int broxton_audio_probe(struct platform_device *pdev) } else if (soc_intel_is_cml()) { unsigned int i; - broxton_audio_card.name = "cmlda7219max"; + if (ctx->spkamp == SPKAMP_MAX98390) { + broxton_audio_card.name = "cml_max98390_da7219"; + + broxton_audio_card.codec_conf = max98390_codec_confs; + broxton_audio_card.num_configs = ARRAY_SIZE(max98390_codec_confs); + } else + broxton_audio_card.name = "cmlda7219max"; for (i = 0; i < ARRAY_SIZE(broxton_dais); i++) { /* MAXIM_CODEC is connected to SSP1. */ @@ -710,6 +821,12 @@ static int broxton_audio_probe(struct platform_device *pdev) BXT_MAXIM_CODEC_DAI)) { broxton_dais[i].name = "SSP1-Codec"; broxton_dais[i].cpus->dai_name = "SSP1 Pin"; + + if (ctx->spkamp == SPKAMP_MAX98390) { + broxton_dais[i].codecs = max98390_codec; + broxton_dais[i].num_codecs = ARRAY_SIZE(max98390_codec); + broxton_dais[i].dpcm_capture = 1; + } } /* DIALOG_CODEC is connected to SSP0 */ else if (!strcmp(broxton_dais[i].codecs->dai_name, @@ -720,8 +837,8 @@ static int broxton_audio_probe(struct platform_device *pdev) } } - /* override plaform name, if required */ - mach = (&pdev->dev)->platform_data; + /* override platform name, if required */ + mach = pdev->dev.platform_data; platform_name = mach->mach_params.platform; ret = snd_soc_fixup_dai_links_platform_name(&broxton_audio_card, @@ -735,11 +852,12 @@ static int broxton_audio_probe(struct platform_device *pdev) } static const struct platform_device_id bxt_board_ids[] = { - { .name = "bxt_da7219_max98357a" }, - { .name = "glk_da7219_max98357a" }, - { .name = "cml_da7219_max98357a" }, + { .name = "bxt_da7219_mx98357a" }, + { .name = "glk_da7219_mx98357a" }, + { .name = "cml_da7219_mx98357a" }, { } }; +MODULE_DEVICE_TABLE(platform, bxt_board_ids); static struct platform_driver broxton_audio = { .probe = broxton_audio_probe, @@ -759,7 +877,6 @@ MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>"); MODULE_AUTHOR("Conrad Cooke <conrad.cooke@intel.com>"); MODULE_AUTHOR("Naveen Manohar <naveen.m@intel.com>"); MODULE_AUTHOR("Mac Chiang <mac.chiang@intel.com>"); +MODULE_AUTHOR("Brent Lu <brent.lu@intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:bxt_da7219_max98357a"); -MODULE_ALIAS("platform:glk_da7219_max98357a"); -MODULE_ALIAS("platform:cml_da7219_max98357a"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/bxt_rt298.c b/sound/soc/intel/boards/bxt_rt298.c index 4b67f261377c..4bd93c3ba377 100644 --- a/sound/soc/intel/boards/bxt_rt298.c +++ b/sound/soc/intel/boards/bxt_rt298.c @@ -155,7 +155,7 @@ static const struct snd_soc_dapm_route geminilake_rt298_map[] = { static int broxton_rt298_fe_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; dapm = snd_soc_component_get_dapm(component); snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); @@ -165,10 +165,10 @@ static int broxton_rt298_fe_init(struct snd_soc_pcm_runtime *rtd) static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; int ret = 0; - ret = snd_soc_card_jack_new(rtd->card, "Headset", + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0, &broxton_headset, broxton_headset_pins, ARRAY_SIZE(broxton_headset_pins)); @@ -176,7 +176,7 @@ static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd) if (ret) return ret; - rt298_mic_detect(component, &broxton_headset); + snd_soc_component_set_jack(component, &broxton_headset, NULL); snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); @@ -186,7 +186,7 @@ static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd) static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd) { struct bxt_rt286_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct bxt_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -224,8 +224,8 @@ static int broxton_ssp5_fixup(struct snd_soc_pcm_runtime *rtd, static int broxton_rt298_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, RT298_SCLK_S_PLL, @@ -468,7 +468,7 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .no_pcm = 1, .init = broxton_rt298_codec_init, .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broxton_ssp5_fixup, .ops = &broxton_rt298_ops, @@ -544,8 +544,7 @@ static int bxt_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &broxton_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &broxton_hdmi[i]); if (err) return err; @@ -565,6 +564,7 @@ static int bxt_card_late_probe(struct snd_soc_card *card) /* broxton audio machine driver for SPT + RT298S */ static struct snd_soc_card broxton_rt298 = { .name = "broxton-rt298", + .owner = THIS_MODULE, .dai_link = broxton_rt298_dais, .num_links = ARRAY_SIZE(broxton_rt298_dais), .controls = broxton_controls, @@ -580,6 +580,7 @@ static struct snd_soc_card broxton_rt298 = { static struct snd_soc_card geminilake_rt298 = { .name = "geminilake-rt298", + .owner = THIS_MODULE, .dai_link = broxton_rt298_dais, .num_links = ARRAY_SIZE(broxton_rt298_dais), .controls = broxton_controls, @@ -626,8 +627,8 @@ static int broxton_audio_probe(struct platform_device *pdev) card->dev = &pdev->dev; snd_soc_card_set_drvdata(card, ctx); - /* override plaform name, if required */ - mach = (&pdev->dev)->platform_data; + /* override platform name, if required */ + mach = pdev->dev.platform_data; platform_name = mach->mach_params.platform; ret = snd_soc_fixup_dai_links_platform_name(card, @@ -647,6 +648,7 @@ static const struct platform_device_id bxt_board_ids[] = { (unsigned long)&geminilake_rt298 }, {} }; +MODULE_DEVICE_TABLE(platform, bxt_board_ids); static struct platform_driver broxton_audio = { .probe = broxton_audio_probe, @@ -663,5 +665,4 @@ MODULE_AUTHOR("Ramesh Babu <Ramesh.Babu@intel.com>"); MODULE_AUTHOR("Senthilnathan Veppur <senthilnathanx.veppur@intel.com>"); MODULE_DESCRIPTION("Intel SST Audio for Broxton"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:bxt_alc298s_i2s"); -MODULE_ALIAS("platform:glk_alc298s_i2s"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/byt-max98090.c b/sound/soc/intel/boards/byt-max98090.c deleted file mode 100644 index 01739ad75b12..000000000000 --- a/sound/soc/intel/boards/byt-max98090.c +++ /dev/null @@ -1,182 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Baytrail SST MAX98090 machine driver - * Copyright (c) 2014, Intel Corporation. - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/acpi.h> -#include <linux/device.h> -#include <linux/gpio.h> -#include <linux/gpio/consumer.h> -#include <linux/slab.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> -#include <sound/jack.h> -#include "../../codecs/max98090.h" - -struct byt_max98090_private { - struct snd_soc_jack jack; -}; - -static const struct snd_soc_dapm_widget byt_max98090_widgets[] = { - SND_SOC_DAPM_HP("Headphone", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Int Mic", NULL), - SND_SOC_DAPM_SPK("Ext Spk", NULL), -}; - -static const struct snd_soc_dapm_route byt_max98090_audio_map[] = { - {"IN34", NULL, "Headset Mic"}, - {"Headset Mic", NULL, "MICBIAS"}, - {"DMICL", NULL, "Int Mic"}, - {"Headphone", NULL, "HPL"}, - {"Headphone", NULL, "HPR"}, - {"Ext Spk", NULL, "SPKL"}, - {"Ext Spk", NULL, "SPKR"}, -}; - -static const struct snd_kcontrol_new byt_max98090_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Int Mic"), - SOC_DAPM_PIN_SWITCH("Ext Spk"), -}; - -static struct snd_soc_jack_pin hs_jack_pins[] = { - { - .pin = "Headphone", - .mask = SND_JACK_HEADPHONE, - }, - { - .pin = "Headset Mic", - .mask = SND_JACK_MICROPHONE, - }, -}; - -static struct snd_soc_jack_gpio hs_jack_gpios[] = { - { - .name = "hp", - .report = SND_JACK_HEADPHONE | SND_JACK_LINEOUT, - .debounce_time = 200, - }, - { - .name = "mic", - .invert = 1, - .report = SND_JACK_MICROPHONE, - .debounce_time = 200, - }, -}; - -static const struct acpi_gpio_params hp_gpios = { 0, 0, false }; -static const struct acpi_gpio_params mic_gpios = { 1, 0, false }; - -static const struct acpi_gpio_mapping acpi_byt_max98090_gpios[] = { - { "hp-gpios", &hp_gpios, 1 }, - { "mic-gpios", &mic_gpios, 1 }, - {}, -}; - -static int byt_max98090_init(struct snd_soc_pcm_runtime *runtime) -{ - int ret; - struct snd_soc_card *card = runtime->card; - struct byt_max98090_private *drv = snd_soc_card_get_drvdata(card); - struct snd_soc_jack *jack = &drv->jack; - - card->dapm.idle_bias_off = true; - - ret = snd_soc_dai_set_sysclk(runtime->codec_dai, - M98090_REG_SYSTEM_CLOCK, - 25000000, SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(card->dev, "Can't set codec clock %d\n", ret); - return ret; - } - - /* Enable jack detection */ - ret = snd_soc_card_jack_new(runtime->card, "Headset", - SND_JACK_LINEOUT | SND_JACK_HEADSET, jack, - hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); - if (ret) - return ret; - - return snd_soc_jack_add_gpiods(card->dev->parent, jack, - ARRAY_SIZE(hs_jack_gpios), - hs_jack_gpios); -} - -SND_SOC_DAILINK_DEFS(baytrail, - DAILINK_COMP_ARRAY(COMP_CPU("baytrail-pcm-audio")), - DAILINK_COMP_ARRAY(COMP_CODEC("i2c-193C9890:00", "HiFi")), - DAILINK_COMP_ARRAY(COMP_PLATFORM("baytrail-pcm-audio"))); - -static struct snd_soc_dai_link byt_max98090_dais[] = { - { - .name = "Baytrail Audio", - .stream_name = "Audio", - .init = byt_max98090_init, - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - SND_SOC_DAILINK_REG(baytrail), - }, -}; - -static struct snd_soc_card byt_max98090_card = { - .name = "byt-max98090", - .owner = THIS_MODULE, - .dai_link = byt_max98090_dais, - .num_links = ARRAY_SIZE(byt_max98090_dais), - .dapm_widgets = byt_max98090_widgets, - .num_dapm_widgets = ARRAY_SIZE(byt_max98090_widgets), - .dapm_routes = byt_max98090_audio_map, - .num_dapm_routes = ARRAY_SIZE(byt_max98090_audio_map), - .controls = byt_max98090_controls, - .num_controls = ARRAY_SIZE(byt_max98090_controls), - .fully_routed = true, -}; - -static int byt_max98090_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct byt_max98090_private *priv; - int ret_val; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) { - dev_err(&pdev->dev, "allocation failed\n"); - return -ENOMEM; - } - - ret_val = devm_acpi_dev_add_driver_gpios(dev->parent, acpi_byt_max98090_gpios); - if (ret_val) - dev_dbg(dev, "Unable to add GPIO mapping table\n"); - - byt_max98090_card.dev = &pdev->dev; - snd_soc_card_set_drvdata(&byt_max98090_card, priv); - ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_max98090_card); - if (ret_val) { - dev_err(&pdev->dev, - "snd_soc_register_card failed %d\n", ret_val); - return ret_val; - } - - return 0; -} - -static struct platform_driver byt_max98090_driver = { - .probe = byt_max98090_probe, - .driver = { - .name = "byt-max98090", - .pm = &snd_soc_pm_ops, - }, -}; -module_platform_driver(byt_max98090_driver) - -MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); -MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:byt-max98090"); diff --git a/sound/soc/intel/boards/byt-rt5640.c b/sound/soc/intel/boards/byt-rt5640.c deleted file mode 100644 index 0c76dafdd572..000000000000 --- a/sound/soc/intel/boards/byt-rt5640.c +++ /dev/null @@ -1,224 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Baytrail SST RT5640 machine driver - * Copyright (c) 2014, Intel Corporation. - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/acpi.h> -#include <linux/device.h> -#include <linux/dmi.h> -#include <linux/slab.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> -#include <sound/jack.h> -#include "../../codecs/rt5640.h" - -#include "../common/sst-dsp.h" - -static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = { - SND_SOC_DAPM_HP("Headphone", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Internal Mic", NULL), - SND_SOC_DAPM_SPK("Speaker", NULL), -}; - -static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { - {"Headset Mic", NULL, "MICBIAS1"}, - {"IN2P", NULL, "Headset Mic"}, - {"Headphone", NULL, "HPOL"}, - {"Headphone", NULL, "HPOR"}, - {"Speaker", NULL, "SPOLP"}, - {"Speaker", NULL, "SPOLN"}, - {"Speaker", NULL, "SPORP"}, - {"Speaker", NULL, "SPORN"}, -}; - -static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = { - {"DMIC1", NULL, "Internal Mic"}, -}; - -static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = { - {"DMIC2", NULL, "Internal Mic"}, -}; - -static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = { - {"Internal Mic", NULL, "MICBIAS1"}, - {"IN1P", NULL, "Internal Mic"}, -}; - -enum { - BYT_RT5640_DMIC1_MAP, - BYT_RT5640_DMIC2_MAP, - BYT_RT5640_IN1_MAP, -}; - -#define BYT_RT5640_MAP(quirk) ((quirk) & 0xff) -#define BYT_RT5640_DMIC_EN BIT(16) - -static unsigned long byt_rt5640_quirk = BYT_RT5640_DMIC1_MAP | - BYT_RT5640_DMIC_EN; - -static const struct snd_kcontrol_new byt_rt5640_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Internal Mic"), - SOC_DAPM_PIN_SWITCH("Speaker"), -}; - -static int byt_rt5640_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, - params_rate(params) * 256, - SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(codec_dai->dev, "can't set codec clock %d\n", ret); - return ret; - } - ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1, - params_rate(params) * 64, - params_rate(params) * 256); - if (ret < 0) { - dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret); - return ret; - } - return 0; -} - -static int byt_rt5640_quirk_cb(const struct dmi_system_id *id) -{ - byt_rt5640_quirk = (unsigned long)id->driver_data; - return 1; -} - -static const struct dmi_system_id byt_rt5640_quirk_table[] = { - { - .callback = byt_rt5640_quirk_cb, - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"), - }, - .driver_data = (unsigned long *)BYT_RT5640_IN1_MAP, - }, - { - .callback = byt_rt5640_quirk_cb, - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "DellInc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5830"), - }, - .driver_data = (unsigned long *)(BYT_RT5640_DMIC2_MAP | - BYT_RT5640_DMIC_EN), - }, - {} -}; - -static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) -{ - int ret; - struct snd_soc_component *component = runtime->codec_dai->component; - struct snd_soc_card *card = runtime->card; - const struct snd_soc_dapm_route *custom_map; - int num_routes; - - card->dapm.idle_bias_off = true; - - ret = snd_soc_add_card_controls(card, byt_rt5640_controls, - ARRAY_SIZE(byt_rt5640_controls)); - if (ret) { - dev_err(card->dev, "unable to add card controls\n"); - return ret; - } - - dmi_check_system(byt_rt5640_quirk_table); - switch (BYT_RT5640_MAP(byt_rt5640_quirk)) { - case BYT_RT5640_IN1_MAP: - custom_map = byt_rt5640_intmic_in1_map; - num_routes = ARRAY_SIZE(byt_rt5640_intmic_in1_map); - break; - case BYT_RT5640_DMIC2_MAP: - custom_map = byt_rt5640_intmic_dmic2_map; - num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic2_map); - break; - default: - custom_map = byt_rt5640_intmic_dmic1_map; - num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map); - } - - ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); - if (ret) - return ret; - - if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) { - ret = rt5640_dmic_enable(component, 0, 0); - if (ret) - return ret; - } - - snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); - snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); - - return ret; -} - -static struct snd_soc_ops byt_rt5640_ops = { - .hw_params = byt_rt5640_hw_params, -}; - -SND_SOC_DAILINK_DEFS(audio, - DAILINK_COMP_ARRAY(COMP_CPU("baytrail-pcm-audio")), - DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5640:00", "rt5640-aif1")), - DAILINK_COMP_ARRAY(COMP_PLATFORM("baytrail-pcm-audio"))); - -static struct snd_soc_dai_link byt_rt5640_dais[] = { - { - .name = "Baytrail Audio", - .stream_name = "Audio", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - .init = byt_rt5640_init, - .ops = &byt_rt5640_ops, - SND_SOC_DAILINK_REG(audio), - }, -}; - -static struct snd_soc_card byt_rt5640_card = { - .name = "byt-rt5640", - .owner = THIS_MODULE, - .dai_link = byt_rt5640_dais, - .num_links = ARRAY_SIZE(byt_rt5640_dais), - .dapm_widgets = byt_rt5640_widgets, - .num_dapm_widgets = ARRAY_SIZE(byt_rt5640_widgets), - .dapm_routes = byt_rt5640_audio_map, - .num_dapm_routes = ARRAY_SIZE(byt_rt5640_audio_map), - .fully_routed = true, -}; - -static int byt_rt5640_probe(struct platform_device *pdev) -{ - struct snd_soc_card *card = &byt_rt5640_card; - - card->dev = &pdev->dev; - return devm_snd_soc_register_card(&pdev->dev, card); -} - -static struct platform_driver byt_rt5640_audio = { - .probe = byt_rt5640_probe, - .driver = { - .name = "byt-rt5640", - .pm = &snd_soc_pm_ops, - }, -}; -module_platform_driver(byt_rt5640_audio) - -MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); -MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:byt-rt5640"); diff --git a/sound/soc/intel/boards/bytcht_cx2072x.c b/sound/soc/intel/boards/bytcht_cx2072x.c index 67f06c95eec5..ae899866863e 100644 --- a/sound/soc/intel/boards/bytcht_cx2072x.c +++ b/sound/soc/intel/boards/bytcht_cx2072x.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // // ASoC DPCM Machine driver for Baytrail / Cherrytrail platforms with // CX2072X codec @@ -70,7 +70,7 @@ static const struct acpi_gpio_mapping byt_cht_cx2072x_acpi_gpios[] = { static int byt_cht_cx2072x_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_card *card = rtd->card; - struct snd_soc_component *codec = rtd->codec_dai->component; + struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component; int ret; if (devm_acpi_dev_add_driver_gpios(codec->dev, @@ -80,26 +80,26 @@ static int byt_cht_cx2072x_init(struct snd_soc_pcm_runtime *rtd) card->dapm.idle_bias_off = true; /* set the default PLL rate, the clock is handled by the codec driver */ - ret = snd_soc_dai_set_sysclk(rtd->codec_dai, CX2072X_MCLK_EXTERNAL_PLL, + ret = snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), CX2072X_MCLK_EXTERNAL_PLL, 19200000, SND_SOC_CLOCK_IN); if (ret) { dev_err(rtd->dev, "Could not set sysclk\n"); return ret; } - ret = snd_soc_card_jack_new(card, "Headset", - SND_JACK_HEADSET | SND_JACK_BTN_0, - &byt_cht_cx2072x_headset, - byt_cht_cx2072x_headset_pins, - ARRAY_SIZE(byt_cht_cx2072x_headset_pins)); + ret = snd_soc_card_jack_new_pins(card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &byt_cht_cx2072x_headset, + byt_cht_cx2072x_headset_pins, + ARRAY_SIZE(byt_cht_cx2072x_headset_pins)); if (ret) return ret; snd_soc_component_set_jack(codec, &byt_cht_cx2072x_headset, NULL); - snd_soc_dai_set_bclk_ratio(rtd->codec_dai, 50); + snd_soc_dai_set_bclk_ratio(asoc_rtd_to_codec(rtd, 0), 50); - return ret; + return 0; } static int byt_cht_cx2072x_fixup(struct snd_soc_pcm_runtime *rtd, @@ -123,16 +123,16 @@ static int byt_cht_cx2072x_fixup(struct snd_soc_pcm_runtime *rtd, * with explicit setting to I2S 2ch 24-bit. The word length is set with * dai_set_tdm_slot() since there is no other API exposed */ - ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_BP_FP); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; } - ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 24); if (ret < 0) { dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); return ret; @@ -147,7 +147,7 @@ static int byt_cht_cx2072x_aif1_startup(struct snd_pcm_substream *substream) SNDRV_PCM_HW_PARAM_RATE, 48000); } -static struct snd_soc_ops byt_cht_cx2072x_aif1_ops = { +static const struct snd_soc_ops byt_cht_cx2072x_aif1_ops = { .startup = byt_cht_cx2072x_aif1_startup, }; @@ -195,19 +195,26 @@ static struct snd_soc_dai_link byt_cht_cx2072x_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .init = byt_cht_cx2072x_init, .be_hw_params_fixup = byt_cht_cx2072x_fixup, - .nonatomic = true, .dpcm_playback = 1, .dpcm_capture = 1, SND_SOC_DAILINK_REG(ssp2, cx2072x, platform), }, }; +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht cx2072x" /* card name will be 'sof-bytcht cx2072x' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "bytcht-cx2072x" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + /* SoC card */ static struct snd_soc_card byt_cht_cx2072x_card = { - .name = "bytcht-cx2072x", + .name = CARD_NAME, + .driver_name = DRIVER_NAME, .owner = THIS_MODULE, .dai_link = byt_cht_cx2072x_dais, .num_links = ARRAY_SIZE(byt_cht_cx2072x_dais), @@ -226,6 +233,7 @@ static int snd_byt_cht_cx2072x_probe(struct platform_device *pdev) struct snd_soc_acpi_mach *mach; struct acpi_device *adev; int dai_index = 0; + bool sof_parent; int i, ret; byt_cht_cx2072x_card.dev = &pdev->dev; @@ -249,12 +257,27 @@ static int snd_byt_cht_cx2072x_probe(struct platform_device *pdev) byt_cht_cx2072x_dais[dai_index].codecs->name = codec_name; } - /* override plaform name, if required */ + /* override platform name, if required */ ret = snd_soc_fixup_dai_links_platform_name(&byt_cht_cx2072x_card, mach->mach_params.platform); if (ret) return ret; + sof_parent = snd_soc_acpi_sof_parent(&pdev->dev); + + /* set card and driver name */ + if (sof_parent) { + byt_cht_cx2072x_card.name = SOF_CARD_NAME; + byt_cht_cx2072x_card.driver_name = SOF_DRIVER_NAME; + } else { + byt_cht_cx2072x_card.name = CARD_NAME; + byt_cht_cx2072x_card.driver_name = DRIVER_NAME; + } + + /* set pm ops */ + if (sof_parent) + pdev->dev.driver->pm = &snd_soc_pm_ops; + return devm_snd_soc_register_card(&pdev->dev, &byt_cht_cx2072x_card); } diff --git a/sound/soc/intel/boards/bytcht_da7213.c b/sound/soc/intel/boards/bytcht_da7213.c index eda7a500cad6..a0c8f1d3f8ce 100644 --- a/sound/soc/intel/boards/bytcht_da7213.c +++ b/sound/soc/intel/boards/bytcht_da7213.c @@ -78,16 +78,16 @@ static int codec_fixup(struct snd_soc_pcm_runtime *rtd, * with explicit setting to I2S 2ch 24-bit. The word length is set with * dai_set_tdm_slot() since there is no other API exposed */ - ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_BP_FP); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; } - ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 24); if (ret < 0) { dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); return ret; @@ -105,8 +105,8 @@ static int aif1_startup(struct snd_pcm_substream *substream) static int aif1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, @@ -126,8 +126,8 @@ static int aif1_hw_params(struct snd_pcm_substream *substream, static int aif1_hw_free(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_pll(codec_dai, 0, @@ -195,9 +195,8 @@ static struct snd_soc_dai_link dailink[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = codec_fixup, - .nonatomic = true, .dpcm_playback = 1, .dpcm_capture = 1, .ops = &ssp2_ops, @@ -205,9 +204,17 @@ static struct snd_soc_dai_link dailink[] = { }, }; +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht da7213" /* card name will be 'sof-bytcht da7213' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "bytcht-da7213" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + /* SoC card */ static struct snd_soc_card bytcht_da7213_card = { - .name = "bytcht-da7213", + .name = CARD_NAME, + .driver_name = DRIVER_NAME, .owner = THIS_MODULE, .dai_link = dailink, .num_links = ARRAY_SIZE(dailink), @@ -227,11 +234,12 @@ static int bytcht_da7213_probe(struct platform_device *pdev) struct snd_soc_acpi_mach *mach; const char *platform_name; struct acpi_device *adev; + bool sof_parent; int dai_index = 0; int ret_val = 0; int i; - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; card = &bytcht_da7213_card; card->dev = &pdev->dev; @@ -252,13 +260,28 @@ static int bytcht_da7213_probe(struct platform_device *pdev) dailink[dai_index].codecs->name = codec_name; } - /* override plaform name, if required */ + /* override platform name, if required */ platform_name = mach->mach_params.platform; ret_val = snd_soc_fixup_dai_links_platform_name(card, platform_name); if (ret_val) return ret_val; + sof_parent = snd_soc_acpi_sof_parent(&pdev->dev); + + /* set card and driver name */ + if (sof_parent) { + bytcht_da7213_card.name = SOF_CARD_NAME; + bytcht_da7213_card.driver_name = SOF_DRIVER_NAME; + } else { + bytcht_da7213_card.name = CARD_NAME; + bytcht_da7213_card.driver_name = DRIVER_NAME; + } + + /* set pm ops */ + if (sof_parent) + pdev->dev.driver->pm = &snd_soc_pm_ops; + ret_val = devm_snd_soc_register_card(&pdev->dev, card); if (ret_val) { dev_err(&pdev->dev, diff --git a/sound/soc/intel/boards/bytcht_es8316.c b/sound/soc/intel/boards/bytcht_es8316.c index 0adc5a5e134a..6432b83f616f 100644 --- a/sound/soc/intel/boards/bytcht_es8316.c +++ b/sound/soc/intel/boards/bytcht_es8316.c @@ -28,7 +28,6 @@ #include <sound/soc.h> #include <sound/soc-acpi.h> #include "../atom/sst-atom-controls.h" -#include "../common/sst-dsp.h" #include "../common/soc-intel-quirks.h" /* jd-inv + terminating entry */ @@ -38,6 +37,7 @@ struct byt_cht_es8316_private { struct clk *mclk; struct snd_soc_jack jack; struct gpio_desc *speaker_en_gpio; + struct device *codec_dev; bool speaker_en; }; @@ -157,7 +157,7 @@ static struct snd_soc_jack_pin byt_cht_es8316_jack_pins[] = { static int byt_cht_es8316_init(struct snd_soc_pcm_runtime *runtime) { - struct snd_soc_component *codec = runtime->codec_dai->component; + struct snd_soc_component *codec = asoc_rtd_to_codec(runtime, 0)->component; struct snd_soc_card *card = runtime->card; struct byt_cht_es8316_private *priv = snd_soc_card_get_drvdata(card); const struct snd_soc_dapm_route *custom_map; @@ -212,17 +212,17 @@ static int byt_cht_es8316_init(struct snd_soc_pcm_runtime *runtime) if (ret) dev_err(card->dev, "unable to enable MCLK\n"); - ret = snd_soc_dai_set_sysclk(runtime->codec_dai, 0, 19200000, + ret = snd_soc_dai_set_sysclk(asoc_rtd_to_codec(runtime, 0), 0, 19200000, SND_SOC_CLOCK_IN); if (ret < 0) { dev_err(card->dev, "can't set codec clock %d\n", ret); return ret; } - ret = snd_soc_card_jack_new(card, "Headset", - SND_JACK_HEADSET | SND_JACK_BTN_0, - &priv->jack, byt_cht_es8316_jack_pins, - ARRAY_SIZE(byt_cht_es8316_jack_pins)); + ret = snd_soc_card_jack_new_pins(card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &priv->jack, byt_cht_es8316_jack_pins, + ARRAY_SIZE(byt_cht_es8316_jack_pins)); if (ret) { dev_err(card->dev, "jack creation failed %d\n", ret); return ret; @@ -262,17 +262,17 @@ static int byt_cht_es8316_codec_fixup(struct snd_soc_pcm_runtime *rtd, * with explicit setting to I2S 2ch 24-bit. The word length is set with * dai_set_tdm_slot() since there is no other API exposed */ - ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS + SND_SOC_DAIFMT_BP_FP ); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; } - ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, bits); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, bits); if (ret < 0) { dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); return ret; @@ -332,16 +332,12 @@ static struct snd_soc_dai_link byt_cht_es8316_dais[] = { /* back ends */ { - /* Only SSP2 has been tested here, so BYT-CR platforms that - * require SSP0 will not work. - */ .name = "SSP2-Codec", .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = byt_cht_es8316_codec_fixup, - .nonatomic = true, .dpcm_playback = 1, .dpcm_capture = 1, .init = byt_cht_es8316_init, @@ -407,8 +403,14 @@ static int byt_cht_es8316_resume(struct snd_soc_card *card) return 0; } +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht es8316" /* card name will be 'sof-bytcht es8316' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "bytcht-es8316" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + static struct snd_soc_card byt_cht_es8316_card = { - .name = "bytcht-es8316", .owner = THIS_MODULE, .dai_link = byt_cht_es8316_dais, .num_links = ARRAY_SIZE(byt_cht_es8316_dais), @@ -454,15 +456,17 @@ static const struct dmi_system_id byt_cht_es8316_quirk_table[] = { static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; static const char * const mic_name[] = { "in1", "in2" }; + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); struct property_entry props[MAX_NO_PROPS] = {}; struct byt_cht_es8316_private *priv; const struct dmi_system_id *dmi_id; - struct device *dev = &pdev->dev; - struct snd_soc_acpi_mach *mach; + struct fwnode_handle *fwnode; const char *platform_name; struct acpi_device *adev; struct device *codec_dev; + bool sof_parent; unsigned int cnt = 0; int dai_index = 0; int i; @@ -472,7 +476,6 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; - mach = dev->platform_data; /* fix index of codec dai */ for (i = 0; i < ARRAY_SIZE(byt_cht_es8316_dais); i++) { if (!strcmp(byt_cht_es8316_dais[i].codecs->name, @@ -489,9 +492,12 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) "i2c-%s", acpi_dev_name(adev)); put_device(&adev->dev); byt_cht_es8316_dais[dai_index].codecs->name = codec_name; + } else { + dev_err(dev, "Error cannot find '%s' dev\n", mach->id); + return -ENXIO; } - /* override plaform name, if required */ + /* override platform name, if required */ byt_cht_es8316_card.dev = dev; platform_name = mach->mach_params.platform; @@ -515,9 +521,8 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) BYT_CHT_ES8316_MONO_SPEAKER; } if (quirk_override != -1) { - dev_info(dev, "Overriding quirk 0x%x => 0x%x\n", - (unsigned int)quirk, - quirk_override); + dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n", + quirk, quirk_override); quirk = quirk_override; } log_quirks(dev); @@ -527,45 +532,44 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) /* get the clock */ priv->mclk = devm_clk_get(dev, "pmc_plt_clk_3"); - if (IS_ERR(priv->mclk)) { - ret = PTR_ERR(priv->mclk); - dev_err(dev, "clk_get pmc_plt_clk_3 failed: %d\n", ret); - return ret; - } + if (IS_ERR(priv->mclk)) + return dev_err_probe(dev, PTR_ERR(priv->mclk), "clk_get pmc_plt_clk_3 failed\n"); - /* get speaker enable GPIO */ - codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL, codec_name); + codec_dev = acpi_get_first_physical_node(adev); if (!codec_dev) return -EPROBE_DEFER; + priv->codec_dev = get_device(codec_dev); if (quirk & BYT_CHT_ES8316_JD_INVERTED) props[cnt++] = PROPERTY_ENTRY_BOOL("everest,jack-detect-inverted"); if (cnt) { - ret = device_add_properties(codec_dev, props); - if (ret) + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) { + put_device(codec_dev); + return PTR_ERR(fwnode); + } + + ret = device_add_software_node(codec_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + if (ret) { + put_device(codec_dev); return ret; + } } + /* get speaker enable GPIO */ devm_acpi_dev_add_driver_gpios(codec_dev, byt_cht_es8316_gpios); priv->speaker_en_gpio = - gpiod_get_index(codec_dev, "speaker-enable", 0, - /* see comment in byt_cht_es8316_resume */ - GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_NONEXCLUSIVE); - put_device(codec_dev); - + gpiod_get_optional(codec_dev, "speaker-enable", + /* see comment in byt_cht_es8316_resume() */ + GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_NONEXCLUSIVE); if (IS_ERR(priv->speaker_en_gpio)) { - ret = PTR_ERR(priv->speaker_en_gpio); - switch (ret) { - case -ENOENT: - priv->speaker_en_gpio = NULL; - break; - default: - dev_err(dev, "get speaker GPIO failed: %d\n", ret); - /* fall through */ - case -EPROBE_DEFER: - return ret; - } + ret = dev_err_probe(dev, PTR_ERR(priv->speaker_en_gpio), + "get speaker GPIO failed\n"); + goto err_put_codec; } snprintf(components_string, sizeof(components_string), @@ -580,6 +584,21 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) byt_cht_es8316_card.long_name = long_name; #endif + sof_parent = snd_soc_acpi_sof_parent(dev); + + /* set card and driver name */ + if (sof_parent) { + byt_cht_es8316_card.name = SOF_CARD_NAME; + byt_cht_es8316_card.driver_name = SOF_DRIVER_NAME; + } else { + byt_cht_es8316_card.name = CARD_NAME; + byt_cht_es8316_card.driver_name = DRIVER_NAME; + } + + /* set pm ops */ + if (sof_parent) + dev->driver->pm = &snd_soc_pm_ops; + /* register the soc card */ snd_soc_card_set_drvdata(&byt_cht_es8316_card, priv); @@ -587,10 +606,15 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) if (ret) { gpiod_put(priv->speaker_en_gpio); dev_err(dev, "snd_soc_register_card failed: %d\n", ret); - return ret; + goto err_put_codec; } platform_set_drvdata(pdev, &byt_cht_es8316_card); return 0; + +err_put_codec: + device_remove_software_node(priv->codec_dev); + put_device(priv->codec_dev); + return ret; } static int snd_byt_cht_es8316_mc_remove(struct platform_device *pdev) @@ -599,6 +623,8 @@ static int snd_byt_cht_es8316_mc_remove(struct platform_device *pdev) struct byt_cht_es8316_private *priv = snd_soc_card_get_drvdata(card); gpiod_put(priv->speaker_en_gpio); + device_remove_software_node(priv->codec_dev); + put_device(priv->codec_dev); return 0; } diff --git a/sound/soc/intel/boards/bytcht_nocodec.c b/sound/soc/intel/boards/bytcht_nocodec.c index 479af808ef43..7fc03f2efd35 100644 --- a/sound/soc/intel/boards/bytcht_nocodec.c +++ b/sound/soc/intel/boards/bytcht_nocodec.c @@ -58,17 +58,17 @@ static int codec_fixup(struct snd_soc_pcm_runtime *rtd, * with explicit setting to I2S 2ch 24-bit. The word length is set with * dai_set_tdm_slot() since there is no other API exposed */ - ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_BP_FP); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; } - ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 24); if (ret < 0) { dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); return ret; @@ -93,7 +93,7 @@ static int aif1_startup(struct snd_pcm_substream *substream) &constraints_48000); } -static struct snd_soc_ops aif1_ops = { +static const struct snd_soc_ops aif1_ops = { .startup = aif1_startup, }; @@ -141,10 +141,9 @@ static struct snd_soc_dai_link dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = codec_fixup, .ignore_suspend = 1, - .nonatomic = true, .dpcm_playback = 1, .dpcm_capture = 1, SND_SOC_DAILINK_REG(ssp2_port, dummy, platform), diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index 6bd9ae813be2..fb9d9e271845 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -18,6 +18,8 @@ #include <linux/clk.h> #include <linux/device.h> #include <linux/dmi.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/machine.h> #include <linux/input.h> #include <linux/slab.h> #include <sound/pcm.h> @@ -28,7 +30,6 @@ #include <dt-bindings/sound/rt5640.h> #include "../../codecs/rt5640.h" #include "../atom/sst-atom-controls.h" -#include "../common/sst-dsp.h" #include "../common/soc-intel-quirks.h" enum { @@ -36,8 +37,11 @@ enum { BYT_RT5640_DMIC2_MAP, BYT_RT5640_IN1_MAP, BYT_RT5640_IN3_MAP, + BYT_RT5640_NO_INTERNAL_MIC_MAP, }; +#define RT5640_JD_SRC_EXT_GPIO 0x0f + enum { BYT_RT5640_JD_SRC_GPIO1 = (RT5640_JD_SRC_GPIO1 << 4), BYT_RT5640_JD_SRC_JD1_IN4P = (RT5640_JD_SRC_JD1_IN4P << 4), @@ -45,6 +49,7 @@ enum { BYT_RT5640_JD_SRC_GPIO2 = (RT5640_JD_SRC_GPIO2 << 4), BYT_RT5640_JD_SRC_GPIO3 = (RT5640_JD_SRC_GPIO3 << 4), BYT_RT5640_JD_SRC_GPIO4 = (RT5640_JD_SRC_GPIO4 << 4), + BYT_RT5640_JD_SRC_EXT_GPIO = (RT5640_JD_SRC_EXT_GPIO << 4) }; enum { @@ -72,6 +77,12 @@ enum { #define BYT_RT5640_SSP0_AIF2 BIT(21) #define BYT_RT5640_MCLK_EN BIT(22) #define BYT_RT5640_MCLK_25MHZ BIT(23) +#define BYT_RT5640_NO_SPEAKERS BIT(24) +#define BYT_RT5640_LINEOUT BIT(25) +#define BYT_RT5640_LINEOUT_AS_HP2 BIT(26) +#define BYT_RT5640_HSMIC2_ON_IN1 BIT(27) +#define BYT_RT5640_JD_HP_ELITEP_1000G2 BIT(28) +#define BYT_RT5640_USE_AMCR0F28 BIT(29) #define BYTCR_INPUT_DEFAULTS \ (BYT_RT5640_IN3_MAP | \ @@ -85,7 +96,11 @@ enum { struct byt_rt5640_private { struct snd_soc_jack jack; + struct snd_soc_jack jack2; + struct rt5640_set_jack_data jack_data; + struct gpio_desc *hsmic_detect; struct clk *mclk; + struct device *codec_dev; }; static bool is_bytcr; @@ -117,10 +132,15 @@ static void log_quirks(struct device *dev) case BYT_RT5640_IN3_MAP: dev_info(dev, "quirk IN3_MAP enabled\n"); break; + case BYT_RT5640_NO_INTERNAL_MIC_MAP: + dev_info(dev, "quirk NO_INTERNAL_MIC_MAP enabled\n"); + break; default: dev_err(dev, "quirk map 0x%x is not supported, microphone input will not work\n", map); break; } + if (byt_rt5640_quirk & BYT_RT5640_HSMIC2_ON_IN1) + dev_info(dev, "quirk HSMIC2_ON_IN1 enabled\n"); if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) { dev_info(dev, "quirk realtek,jack-detect-source %ld\n", BYT_RT5640_JDSRC(byt_rt5640_quirk)); @@ -131,8 +151,16 @@ static void log_quirks(struct device *dev) } if (byt_rt5640_quirk & BYT_RT5640_JD_NOT_INV) dev_info(dev, "quirk JD_NOT_INV enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_JD_HP_ELITEP_1000G2) + dev_info(dev, "quirk JD_HP_ELITEPAD_1000G2 enabled\n"); if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) dev_info(dev, "quirk MONO_SPEAKER enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_NO_SPEAKERS) + dev_info(dev, "quirk NO_SPEAKERS enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_LINEOUT) + dev_info(dev, "quirk LINEOUT enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_LINEOUT_AS_HP2) + dev_info(dev, "quirk LINEOUT_AS_HP2 enabled\n"); if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC) dev_info(dev, "quirk DIFF_MIC enabled\n"); if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) { @@ -218,6 +246,20 @@ static int byt_rt5640_prepare_and_enable_pll1(struct snd_soc_dai *codec_dai, #define BYT_CODEC_DAI1 "rt5640-aif1" #define BYT_CODEC_DAI2 "rt5640-aif2" +static struct snd_soc_dai *byt_rt5640_get_codec_dai(struct snd_soc_dapm_context *dapm) +{ + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + + codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI1); + if (!codec_dai) + codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI2); + if (!codec_dai) + dev_err(card->dev, "Error codec dai not found\n"); + + return codec_dai; +} + static int platform_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { @@ -227,24 +269,15 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card); int ret; - codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI1); + codec_dai = byt_rt5640_get_codec_dai(dapm); if (!codec_dai) - codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI2); - - if (!codec_dai) { - dev_err(card->dev, - "Codec dai not found; Unable to set platform clock\n"); return -EIO; - } if (SND_SOC_DAPM_EVENT_ON(event)) { - if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { - ret = clk_prepare_enable(priv->mclk); - if (ret < 0) { - dev_err(card->dev, - "could not configure MCLK state\n"); - return ret; - } + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(card->dev, "could not configure MCLK state\n"); + return ret; } ret = byt_rt5640_prepare_and_enable_pll1(codec_dai, 48000); } else { @@ -256,10 +289,8 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_RCCLK, 48000 * 512, SND_SOC_CLOCK_IN); - if (!ret) { - if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) - clk_disable_unprepare(priv->mclk); - } + if (!ret) + clk_disable_unprepare(priv->mclk); } if (ret < 0) { @@ -270,23 +301,48 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, return 0; } +static int byt_rt5640_event_lineout(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + unsigned int gpio_ctrl3_val = RT5640_GP1_PF_OUT; + struct snd_soc_dai *codec_dai; + + if (!(byt_rt5640_quirk & BYT_RT5640_LINEOUT_AS_HP2)) + return 0; + + /* + * On devices which use line-out as a second headphones output, + * the codec's GPIO1 pin is used to enable an external HP-amp. + */ + + codec_dai = byt_rt5640_get_codec_dai(w->dapm); + if (!codec_dai) + return -EIO; + + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_ctrl3_val |= RT5640_GP1_OUT_HI; + + snd_soc_component_update_bits(codec_dai->component, RT5640_GPIO_CTRL3, + RT5640_GP1_PF_MASK | RT5640_GP1_OUT_MASK, gpio_ctrl3_val); + + return 0; +} + static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic 2", NULL), SND_SOC_DAPM_MIC("Internal Mic", NULL), SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_LINE("Line Out", byt_rt5640_event_lineout), SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), - }; static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { {"Headphone", NULL, "Platform Clock"}, {"Headset Mic", NULL, "Platform Clock"}, - {"Internal Mic", NULL, "Platform Clock"}, - {"Speaker", NULL, "Platform Clock"}, - {"Headset Mic", NULL, "MICBIAS1"}, {"IN2P", NULL, "Headset Mic"}, {"Headphone", NULL, "HPOL"}, @@ -294,23 +350,33 @@ static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { }; static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = { + {"Internal Mic", NULL, "Platform Clock"}, {"DMIC1", NULL, "Internal Mic"}, }; static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = { + {"Internal Mic", NULL, "Platform Clock"}, {"DMIC2", NULL, "Internal Mic"}, }; static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = { + {"Internal Mic", NULL, "Platform Clock"}, {"Internal Mic", NULL, "MICBIAS1"}, {"IN1P", NULL, "Internal Mic"}, }; static const struct snd_soc_dapm_route byt_rt5640_intmic_in3_map[] = { + {"Internal Mic", NULL, "Platform Clock"}, {"Internal Mic", NULL, "MICBIAS1"}, {"IN3P", NULL, "Internal Mic"}, }; +static const struct snd_soc_dapm_route byt_rt5640_hsmic2_in1_map[] = { + {"Headset Mic 2", NULL, "Platform Clock"}, + {"Headset Mic 2", NULL, "MICBIAS1"}, + {"IN1P", NULL, "Headset Mic 2"}, +}; + static const struct snd_soc_dapm_route byt_rt5640_ssp2_aif1_map[] = { {"ssp2 Tx", NULL, "codec_out0"}, {"ssp2 Tx", NULL, "codec_out1"}, @@ -348,6 +414,7 @@ static const struct snd_soc_dapm_route byt_rt5640_ssp0_aif2_map[] = { }; static const struct snd_soc_dapm_route byt_rt5640_stereo_spk_map[] = { + {"Speaker", NULL, "Platform Clock"}, {"Speaker", NULL, "SPOLP"}, {"Speaker", NULL, "SPOLN"}, {"Speaker", NULL, "SPORP"}, @@ -355,15 +422,24 @@ static const struct snd_soc_dapm_route byt_rt5640_stereo_spk_map[] = { }; static const struct snd_soc_dapm_route byt_rt5640_mono_spk_map[] = { + {"Speaker", NULL, "Platform Clock"}, {"Speaker", NULL, "SPOLP"}, {"Speaker", NULL, "SPOLN"}, }; +static const struct snd_soc_dapm_route byt_rt5640_lineout_map[] = { + {"Line Out", NULL, "Platform Clock"}, + {"Line Out", NULL, "LOUTR"}, + {"Line Out", NULL, "LOUTL"}, +}; + static const struct snd_kcontrol_new byt_rt5640_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone"), SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic 2"), SOC_DAPM_PIN_SWITCH("Internal Mic"), SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Line Out"), }; static struct snd_soc_jack_pin rt5640_pins[] = { @@ -377,11 +453,80 @@ static struct snd_soc_jack_pin rt5640_pins[] = { }, }; +static struct snd_soc_jack_pin rt5640_pins2[] = { + { + /* The 2nd headset jack uses lineout with an external HP-amp */ + .pin = "Line Out", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic 2", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static struct snd_soc_jack_gpio rt5640_jack_gpio = { + .name = "hp-detect", + .report = SND_JACK_HEADSET, + .invert = true, + .debounce_time = 200, +}; + +static struct snd_soc_jack_gpio rt5640_jack2_gpio = { + .name = "hp2-detect", + .report = SND_JACK_HEADSET, + .invert = true, + .debounce_time = 200, +}; + +static const struct acpi_gpio_params acpi_gpio0 = { 0, 0, false }; +static const struct acpi_gpio_params acpi_gpio1 = { 1, 0, false }; +static const struct acpi_gpio_params acpi_gpio2 = { 2, 0, false }; + +static const struct acpi_gpio_mapping byt_rt5640_hp_elitepad_1000g2_gpios[] = { + { "hp-detect-gpios", &acpi_gpio0, 1, }, + { "headset-mic-detect-gpios", &acpi_gpio1, 1, }, + { "hp2-detect-gpios", &acpi_gpio2, 1, }, + { }, +}; + +static int byt_rt5640_hp_elitepad_1000g2_jack1_check(void *data) +{ + struct byt_rt5640_private *priv = data; + int jack_status, mic_status; + + jack_status = gpiod_get_value_cansleep(rt5640_jack_gpio.desc); + if (jack_status) + return 0; + + mic_status = gpiod_get_value_cansleep(priv->hsmic_detect); + if (mic_status) + return SND_JACK_HEADPHONE; + else + return SND_JACK_HEADSET; +} + +static int byt_rt5640_hp_elitepad_1000g2_jack2_check(void *data) +{ + struct snd_soc_component *component = data; + int jack_status, report; + + jack_status = gpiod_get_value_cansleep(rt5640_jack2_gpio.desc); + if (jack_status) + return 0; + + rt5640_enable_micbias1_for_ovcd(component); + report = rt5640_detect_headset(component, rt5640_jack2_gpio.desc); + rt5640_disable_micbias1_for_ovcd(component); + + return report; +} + static int byt_rt5640_aif1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); return byt_rt5640_prepare_and_enable_pll1(dai, params_rate(params)); } @@ -400,6 +545,19 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { BYT_RT5640_SSP0_AIF1 | BYT_RT5640_MCLK_EN), }, + { /* Acer One 10 S1002 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "One S1002"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF2 | + BYT_RT5640_MCLK_EN), + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), @@ -424,6 +582,18 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { }, { .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ARCHOS"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ARCHOS 140 CESIUM"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ME176C"), }, @@ -432,7 +602,8 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { BYT_RT5640_OVCD_TH_2000UA | BYT_RT5640_OVCD_SF_0P75 | BYT_RT5640_SSP0_AIF1 | - BYT_RT5640_MCLK_EN), + BYT_RT5640_MCLK_EN | + BYT_RT5640_USE_AMCR0F28), }, { .matches = { @@ -451,11 +622,27 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100TAF"), }, .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | BYT_RT5640_MONO_SPEAKER | BYT_RT5640_DIFF_MIC | BYT_RT5640_SSP0_AIF2 | BYT_RT5640_MCLK_EN), }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "TF103C"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_EXT_GPIO | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN | + BYT_RT5640_USE_AMCR0F28), + }, { /* Chuwi Vi8 (CWI506) */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Insyde"), @@ -485,6 +672,23 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { BYT_RT5640_MCLK_EN), }, { + /* Chuwi Hi8 (CWI509) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_BOARD_NAME, "BYT-PA03C"), + DMI_MATCH(DMI_SYS_VENDOR, "ilife"), + DMI_MATCH(DMI_PRODUCT_NAME, "S806"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Circuitco"), DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Max B3 PLATFORM"), @@ -513,18 +717,66 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { BYT_RT5640_MONO_SPEAKER | BYT_RT5640_MCLK_EN), }, + { /* Estar Beauty HD MID 7316R */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Estar"), + DMI_MATCH(DMI_PRODUCT_NAME, "eSTAR BEAUTY HD Intel Quad core"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Glavey TM800A550L */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* Above strings are too generic, also match on BIOS version */ + DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, { .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP ElitePad 1000 G2"), }, - .driver_data = (void *)(BYT_RT5640_IN1_MAP | + .driver_data = (void *)(BYT_RT5640_DMIC2_MAP | + BYT_RT5640_MCLK_EN | + BYT_RT5640_LINEOUT | + BYT_RT5640_LINEOUT_AS_HP2 | + BYT_RT5640_HSMIC2_ON_IN1 | + BYT_RT5640_JD_HP_ELITEP_1000G2), + }, + { /* HP Pavilion x2 10-k0XX, 10-n0XX */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_1500UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF1 | BYT_RT5640_MCLK_EN), }, - { /* HP Pavilion x2 10-n000nd */ + { /* HP Pavilion x2 10-p0XX */ .matches = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP x2 Detachable 10-p0XX"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD1_IN4P | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MCLK_EN), + }, + { /* HP Pro Tablet 408 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Pro Tablet 408"), }, .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | BYT_RT5640_JD_SRC_JD2_IN4N | @@ -580,6 +832,20 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { BYT_RT5640_MONO_SPEAKER | BYT_RT5640_MCLK_EN), }, + { /* Lenovo Miix 3-830 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 3-830"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, { /* Linx Linx7 tablet */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LINX"), @@ -591,6 +857,36 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { BYT_RT5640_SSP0_AIF1 | BYT_RT5640_MCLK_EN), }, + { /* Mele PCG03 Mini PC */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Mini PC"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Mini PC"), + }, + .driver_data = (void *)(BYT_RT5640_NO_INTERNAL_MIC_MAP | + BYT_RT5640_NO_SPEAKERS | + BYT_RT5640_SSP0_AIF1), + }, + { /* MPMAN Converter 9, similar hw as the I.T.Works TW891 2-in-1 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MPMAN"), + DMI_MATCH(DMI_PRODUCT_NAME, "Converter9"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + /* MPMAN MPWIN895CL */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MPMAN"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MPWIN8900CL"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, { /* MSI S100 tablet */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), @@ -731,6 +1027,44 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { BYT_RT5640_SSP0_AIF1 | BYT_RT5640_MCLK_EN), }, + { /* Toshiba Encore WT8-A */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TOSHIBA WT8-A"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_JD_NOT_INV | + BYT_RT5640_MCLK_EN), + }, + { /* Toshiba Encore WT10-A */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TOSHIBA WT10-A-103"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD1_IN4P | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF2 | + BYT_RT5640_MCLK_EN), + }, + { /* Voyo Winpad A15 */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* Above strings are too generic, also match on BIOS date */ + DMI_MATCH(DMI_BIOS_DATE, "11/20/2014"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_MCLK_EN), + }, { /* Catch-all for generic Insyde tablets, must be last */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), @@ -747,15 +1081,13 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = { * Note this MUST be called before snd_soc_register_card(), so that the props * are in place before the codec component driver's probe function parses them. */ -static int byt_rt5640_add_codec_device_props(const char *i2c_dev_name) +static int byt_rt5640_add_codec_device_props(struct device *i2c_dev, + struct byt_rt5640_private *priv) { struct property_entry props[MAX_NO_PROPS] = {}; - struct device *i2c_dev; - int ret, cnt = 0; - - i2c_dev = bus_find_device_by_name(&i2c_bus_type, NULL, i2c_dev_name); - if (!i2c_dev) - return -EPROBE_DEFER; + struct fwnode_handle *fwnode; + int cnt = 0; + int ret; switch (BYT_RT5640_MAP(byt_rt5640_quirk)) { case BYT_RT5640_DMIC1_MAP: @@ -779,9 +1111,11 @@ static int byt_rt5640_add_codec_device_props(const char *i2c_dev_name) } if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) { - props[cnt++] = PROPERTY_ENTRY_U32( - "realtek,jack-detect-source", - BYT_RT5640_JDSRC(byt_rt5640_quirk)); + if (BYT_RT5640_JDSRC(byt_rt5640_quirk) != RT5640_JD_SRC_EXT_GPIO) { + props[cnt++] = PROPERTY_ENTRY_U32( + "realtek,jack-detect-source", + BYT_RT5640_JDSRC(byt_rt5640_quirk)); + } props[cnt++] = PROPERTY_ENTRY_U32( "realtek,over-current-threshold-microamp", @@ -795,9 +1129,61 @@ static int byt_rt5640_add_codec_device_props(const char *i2c_dev_name) if (byt_rt5640_quirk & BYT_RT5640_JD_NOT_INV) props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,jack-detect-not-inverted"); - ret = device_add_properties(i2c_dev, props); - put_device(i2c_dev); + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) { + /* put_device() is handled in caller */ + return PTR_ERR(fwnode); + } + + ret = device_add_software_node(i2c_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + return ret; +} + +/* Some Android devs specify IRQs/GPIOS in a special AMCR0F28 ACPI device */ +static const struct acpi_gpio_params amcr0f28_jd_gpio = { 1, 0, false }; + +static const struct acpi_gpio_mapping amcr0f28_gpios[] = { + { "rt5640-jd-gpios", &amcr0f28_jd_gpio, 1 }, + { } +}; +static int byt_rt5640_get_amcr0f28_settings(struct snd_soc_card *card) +{ + struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card); + struct rt5640_set_jack_data *data = &priv->jack_data; + struct acpi_device *adev; + int ret = 0; + + adev = acpi_dev_get_first_match_dev("AMCR0F28", "1", -1); + if (!adev) { + dev_err(card->dev, "error cannot find AMCR0F28 adev\n"); + return -ENOENT; + } + + data->codec_irq_override = acpi_dev_gpio_irq_get(adev, 0); + if (data->codec_irq_override < 0) { + ret = data->codec_irq_override; + dev_err(card->dev, "error %d getting codec IRQ\n", ret); + goto put_adev; + } + + if (BYT_RT5640_JDSRC(byt_rt5640_quirk) == RT5640_JD_SRC_EXT_GPIO) { + acpi_dev_add_driver_gpios(adev, amcr0f28_gpios); + data->jd_gpio = devm_fwnode_gpiod_get(card->dev, acpi_fwnode_handle(adev), + "rt5640-jd", GPIOD_IN, "rt5640-jd"); + acpi_dev_remove_driver_gpios(adev); + + if (IS_ERR(data->jd_gpio)) { + ret = PTR_ERR(data->jd_gpio); + dev_err(card->dev, "error %d getting jd GPIO\n", ret); + } + } + +put_adev: + acpi_dev_put(adev); return ret; } @@ -805,12 +1191,14 @@ static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) { struct snd_soc_card *card = runtime->card; struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card); - struct snd_soc_component *component = runtime->codec_dai->component; - const struct snd_soc_dapm_route *custom_map; - int num_routes; + struct rt5640_set_jack_data *jack_data = &priv->jack_data; + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + const struct snd_soc_dapm_route *custom_map = NULL; + int num_routes = 0; int ret; card->dapm.idle_bias_off = true; + jack_data->use_platform_clock = true; /* Start with RC clk for jack-detect (we disable MCLK below) */ if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) @@ -842,19 +1230,28 @@ static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) custom_map = byt_rt5640_intmic_in3_map; num_routes = ARRAY_SIZE(byt_rt5640_intmic_in3_map); break; + case BYT_RT5640_DMIC1_MAP: + custom_map = byt_rt5640_intmic_dmic1_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map); + break; case BYT_RT5640_DMIC2_MAP: custom_map = byt_rt5640_intmic_dmic2_map; num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic2_map); break; - default: - custom_map = byt_rt5640_intmic_dmic1_map; - num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map); } ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); if (ret) return ret; + if (byt_rt5640_quirk & BYT_RT5640_HSMIC2_ON_IN1) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5640_hsmic2_in1_map, + ARRAY_SIZE(byt_rt5640_hsmic2_in1_map)); + if (ret) + return ret; + } + if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) { ret = snd_soc_dapm_add_routes(&card->dapm, byt_rt5640_ssp2_aif2_map, @@ -879,7 +1276,7 @@ static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) ret = snd_soc_dapm_add_routes(&card->dapm, byt_rt5640_mono_spk_map, ARRAY_SIZE(byt_rt5640_mono_spk_map)); - } else { + } else if (!(byt_rt5640_quirk & BYT_RT5640_NO_SPEAKERS)) { ret = snd_soc_dapm_add_routes(&card->dapm, byt_rt5640_stereo_spk_map, ARRAY_SIZE(byt_rt5640_stereo_spk_map)); @@ -887,52 +1284,103 @@ static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) if (ret) return ret; - snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); - snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); - - if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { - /* - * The firmware might enable the clock at - * boot (this information may or may not - * be reflected in the enable clock register). - * To change the rate we must disable the clock - * first to cover these cases. Due to common - * clock framework restrictions that do not allow - * to disable a clock that has not been enabled, - * we need to enable the clock first. - */ - ret = clk_prepare_enable(priv->mclk); - if (!ret) - clk_disable_unprepare(priv->mclk); - - if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) - ret = clk_set_rate(priv->mclk, 25000000); - else - ret = clk_set_rate(priv->mclk, 19200000); - - if (ret) { - dev_err(card->dev, "unable to set MCLK rate\n"); + if (byt_rt5640_quirk & BYT_RT5640_LINEOUT) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5640_lineout_map, + ARRAY_SIZE(byt_rt5640_lineout_map)); + if (ret) return ret; - } + } + + /* + * The firmware might enable the clock at boot (this information + * may or may not be reflected in the enable clock register). + * To change the rate we must disable the clock first to cover + * these cases. Due to common clock framework restrictions that + * do not allow to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(priv->mclk); + if (!ret) + clk_disable_unprepare(priv->mclk); + + if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) + ret = clk_set_rate(priv->mclk, 25000000); + else + ret = clk_set_rate(priv->mclk, 19200000); + if (ret) { + dev_err(card->dev, "unable to set MCLK rate\n"); + return ret; } if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) { - ret = snd_soc_card_jack_new(card, "Headset", - SND_JACK_HEADSET | SND_JACK_BTN_0, - &priv->jack, rt5640_pins, - ARRAY_SIZE(rt5640_pins)); + ret = snd_soc_card_jack_new_pins(card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &priv->jack, rt5640_pins, + ARRAY_SIZE(rt5640_pins)); if (ret) { dev_err(card->dev, "Jack creation failed %d\n", ret); return ret; } snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); - snd_soc_component_set_jack(component, &priv->jack, NULL); + + if (byt_rt5640_quirk & BYT_RT5640_USE_AMCR0F28) { + ret = byt_rt5640_get_amcr0f28_settings(card); + if (ret) + return ret; + } + + snd_soc_component_set_jack(component, &priv->jack, &priv->jack_data); + } + + if (byt_rt5640_quirk & BYT_RT5640_JD_HP_ELITEP_1000G2) { + ret = snd_soc_card_jack_new_pins(card, "Headset", + SND_JACK_HEADSET, + &priv->jack, rt5640_pins, + ARRAY_SIZE(rt5640_pins)); + if (ret) + return ret; + + ret = snd_soc_card_jack_new_pins(card, "Headset 2", + SND_JACK_HEADSET, + &priv->jack2, rt5640_pins2, + ARRAY_SIZE(rt5640_pins2)); + if (ret) + return ret; + + rt5640_jack_gpio.data = priv; + rt5640_jack_gpio.gpiod_dev = priv->codec_dev; + rt5640_jack_gpio.jack_status_check = byt_rt5640_hp_elitepad_1000g2_jack1_check; + ret = snd_soc_jack_add_gpios(&priv->jack, 1, &rt5640_jack_gpio); + if (ret) + return ret; + + rt5640_set_ovcd_params(component); + rt5640_jack2_gpio.data = component; + rt5640_jack2_gpio.gpiod_dev = priv->codec_dev; + rt5640_jack2_gpio.jack_status_check = byt_rt5640_hp_elitepad_1000g2_jack2_check; + ret = snd_soc_jack_add_gpios(&priv->jack2, 1, &rt5640_jack2_gpio); + if (ret) { + snd_soc_jack_free_gpios(&priv->jack, 1, &rt5640_jack_gpio); + return ret; + } } return 0; } +static void byt_rt5640_exit(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card); + + if (byt_rt5640_quirk & BYT_RT5640_JD_HP_ELITEP_1000G2) { + snd_soc_jack_free_gpios(&priv->jack2, 1, &rt5640_jack2_gpio); + snd_soc_jack_free_gpios(&priv->jack, 1, &rt5640_jack_gpio); + } +} + static int byt_rt5640_codec_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { @@ -962,16 +1410,16 @@ static int byt_rt5640_codec_fixup(struct snd_soc_pcm_runtime *rtd, * with explicit setting to I2S 2ch. The word length is set with * dai_set_tdm_slot() since there is no other API exposed */ - ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_BP_FP); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; } - ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, bits); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, bits); if (ret < 0) { dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); return ret; @@ -1040,13 +1488,12 @@ static struct snd_soc_dai_link byt_rt5640_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = byt_rt5640_codec_fixup, - .ignore_suspend = 1, - .nonatomic = true, .dpcm_playback = 1, .dpcm_capture = 1, .init = byt_rt5640_init, + .exit = byt_rt5640_exit, .ops = &byt_rt5640_be_ssp2_ops, SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform), }, @@ -1057,7 +1504,7 @@ static char byt_rt5640_codec_name[SND_ACPI_I2C_ID_LEN]; #if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES) static char byt_rt5640_long_name[40]; /* = "bytcr-rt5640-*-spk-*-mic" */ #endif -static char byt_rt5640_components[32]; /* = "cfg-spk:* cfg-mic:*" */ +static char byt_rt5640_components[64]; /* = "cfg-spk:* cfg-mic:* ..." */ static int byt_rt5640_suspend(struct snd_soc_card *card) { @@ -1088,7 +1535,8 @@ static int byt_rt5640_resume(struct snd_soc_card *card) for_each_card_components(card, component) { if (!strcmp(component->name, byt_rt5640_codec_name)) { dev_dbg(component->dev, "re-enabling jack detect after resume\n"); - snd_soc_component_set_jack(component, &priv->jack, NULL); + snd_soc_component_set_jack(component, &priv->jack, + &priv->jack_data); break; } } @@ -1096,8 +1544,14 @@ static int byt_rt5640_resume(struct snd_soc_card *card) return 0; } +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht rt5640" /* card name will be 'sof-bytcht rt5640' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "bytcr-rt5640" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + static struct snd_soc_card byt_rt5640_card = { - .name = "bytcr-rt5640", .owner = THIS_MODULE, .dai_link = byt_rt5640_dais, .num_links = ARRAY_SIZE(byt_rt5640_dais), @@ -1117,24 +1571,30 @@ struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) { - static const char * const map_name[] = { "dmic1", "dmic2", "in1", "in3" }; + struct device *dev = &pdev->dev; + static const char * const map_name[] = { "dmic1", "dmic2", "in1", "in3", "none" }; + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); + __maybe_unused const char *spk_type; const struct dmi_system_id *dmi_id; + const char *headset2_string = ""; + const char *lineout_string = ""; struct byt_rt5640_private *priv; - struct snd_soc_acpi_mach *mach; const char *platform_name; struct acpi_device *adev; + struct device *codec_dev; + bool sof_parent; int ret_val = 0; int dai_index = 0; - int i; + int i, cfg_spk; + int aif; is_bytcr = false; - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; /* register the soc card */ - byt_rt5640_card.dev = &pdev->dev; - mach = byt_rt5640_card.dev->platform_data; + byt_rt5640_card.dev = dev; snd_soc_card_set_drvdata(&byt_rt5640_card, priv); /* fix index of codec dai */ @@ -1153,6 +1613,9 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) "i2c-%s", acpi_dev_name(adev)); put_device(&adev->dev); byt_rt5640_dais[dai_index].codecs->name = byt_rt5640_codec_name; + } else { + dev_err(dev, "Error cannot find '%s' dev\n", mach->id); + return -ENXIO; } /* @@ -1173,7 +1636,7 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) * with the codec driver/pdata are non-existent */ - struct acpi_chan_package chan_package; + struct acpi_chan_package chan_package = { 0 }; /* format specified: 2 64-bit integers */ struct acpi_buffer format = {sizeof("NN"), "NN"}; @@ -1194,13 +1657,13 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) &pkg_ctx); if (pkg_found) { if (chan_package.aif_value == 1) { - dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); + dev_info(dev, "BIOS Routing: AIF1 connected\n"); byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF1; } else if (chan_package.aif_value == 2) { - dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); + dev_info(dev, "BIOS Routing: AIF2 connected\n"); byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF2; } else { - dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); + dev_info(dev, "BIOS Routing isn't valid, ignored\n"); pkg_found = false; } } @@ -1224,77 +1687,149 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) if (dmi_id) byt_rt5640_quirk = (unsigned long)dmi_id->driver_data; if (quirk_override != -1) { - dev_info(&pdev->dev, "Overriding quirk 0x%x => 0x%x\n", - (unsigned int)byt_rt5640_quirk, quirk_override); + dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n", + byt_rt5640_quirk, quirk_override); byt_rt5640_quirk = quirk_override; } + codec_dev = acpi_get_first_physical_node(adev); + if (!codec_dev) + return -EPROBE_DEFER; + priv->codec_dev = get_device(codec_dev); + + if (byt_rt5640_quirk & BYT_RT5640_JD_HP_ELITEP_1000G2) { + acpi_dev_add_driver_gpios(ACPI_COMPANION(priv->codec_dev), + byt_rt5640_hp_elitepad_1000g2_gpios); + + priv->hsmic_detect = devm_fwnode_gpiod_get(dev, codec_dev->fwnode, + "headset-mic-detect", GPIOD_IN, + "headset-mic-detect"); + if (IS_ERR(priv->hsmic_detect)) { + ret_val = dev_err_probe(dev, PTR_ERR(priv->hsmic_detect), + "getting hsmic-detect GPIO\n"); + goto err_device; + } + } + /* Must be called before register_card, also see declaration comment. */ - ret_val = byt_rt5640_add_codec_device_props(byt_rt5640_codec_name); + ret_val = byt_rt5640_add_codec_device_props(codec_dev, priv); if (ret_val) - return ret_val; + goto err_remove_gpios; - log_quirks(&pdev->dev); + log_quirks(dev); if ((byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) || - (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) + (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) { byt_rt5640_dais[dai_index].codecs->dai_name = "rt5640-aif2"; + aif = 2; + } else { + aif = 1; + } if ((byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) || (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) byt_rt5640_dais[dai_index].cpus->dai_name = "ssp0-port"; if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { - priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + priv->mclk = devm_clk_get_optional(dev, "pmc_plt_clk_3"); if (IS_ERR(priv->mclk)) { - ret_val = PTR_ERR(priv->mclk); - - dev_err(&pdev->dev, - "Failed to get MCLK from pmc_plt_clk_3: %d\n", - ret_val); - - /* - * Fall back to bit clock usage for -ENOENT (clock not - * available likely due to missing dependencies), bail - * for all other errors, including -EPROBE_DEFER - */ - if (ret_val != -ENOENT) - return ret_val; - byt_rt5640_quirk &= ~BYT_RT5640_MCLK_EN; + ret_val = dev_err_probe(dev, PTR_ERR(priv->mclk), + "Failed to get MCLK from pmc_plt_clk_3\n"); + goto err; } + /* + * Fall back to bit clock usage when clock is not + * available likely due to missing dependencies. + */ + if (!priv->mclk) + byt_rt5640_quirk &= ~BYT_RT5640_MCLK_EN; } + if (byt_rt5640_quirk & BYT_RT5640_NO_SPEAKERS) { + cfg_spk = 0; + spk_type = "none"; + } else if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) { + cfg_spk = 1; + spk_type = "mono"; + } else { + cfg_spk = 2; + spk_type = "stereo"; + } + + if (byt_rt5640_quirk & BYT_RT5640_LINEOUT) { + if (byt_rt5640_quirk & BYT_RT5640_LINEOUT_AS_HP2) + lineout_string = " cfg-hp2:lineout"; + else + lineout_string = " cfg-lineout:2"; + } + + if (byt_rt5640_quirk & BYT_RT5640_HSMIC2_ON_IN1) + headset2_string = " cfg-hs2:in1"; + snprintf(byt_rt5640_components, sizeof(byt_rt5640_components), - "cfg-spk:%s cfg-mic:%s", - (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) ? "1" : "2", - map_name[BYT_RT5640_MAP(byt_rt5640_quirk)]); + "cfg-spk:%d cfg-mic:%s aif:%d%s%s", cfg_spk, + map_name[BYT_RT5640_MAP(byt_rt5640_quirk)], aif, + lineout_string, headset2_string); byt_rt5640_card.components = byt_rt5640_components; #if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES) snprintf(byt_rt5640_long_name, sizeof(byt_rt5640_long_name), - "bytcr-rt5640-%s-spk-%s-mic", - (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) ? - "mono" : "stereo", + "bytcr-rt5640-%s-spk-%s-mic", spk_type, map_name[BYT_RT5640_MAP(byt_rt5640_quirk)]); byt_rt5640_card.long_name = byt_rt5640_long_name; #endif - /* override plaform name, if required */ + /* override platform name, if required */ platform_name = mach->mach_params.platform; ret_val = snd_soc_fixup_dai_links_platform_name(&byt_rt5640_card, platform_name); if (ret_val) - return ret_val; + goto err; + + sof_parent = snd_soc_acpi_sof_parent(dev); - ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5640_card); + /* set card and driver name */ + if (sof_parent) { + byt_rt5640_card.name = SOF_CARD_NAME; + byt_rt5640_card.driver_name = SOF_DRIVER_NAME; + } else { + byt_rt5640_card.name = CARD_NAME; + byt_rt5640_card.driver_name = DRIVER_NAME; + } + /* set pm ops */ + if (sof_parent) + dev->driver->pm = &snd_soc_pm_ops; + + ret_val = devm_snd_soc_register_card(dev, &byt_rt5640_card); if (ret_val) { - dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", - ret_val); - return ret_val; + dev_err(dev, "devm_snd_soc_register_card failed %d\n", ret_val); + goto err; } platform_set_drvdata(pdev, &byt_rt5640_card); return ret_val; + +err: + device_remove_software_node(priv->codec_dev); +err_remove_gpios: + if (byt_rt5640_quirk & BYT_RT5640_JD_HP_ELITEP_1000G2) + acpi_dev_remove_driver_gpios(ACPI_COMPANION(priv->codec_dev)); +err_device: + put_device(priv->codec_dev); + return ret_val; +} + +static int snd_byt_rt5640_mc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card); + + if (byt_rt5640_quirk & BYT_RT5640_JD_HP_ELITEP_1000G2) + acpi_dev_remove_driver_gpios(ACPI_COMPANION(priv->codec_dev)); + + device_remove_software_node(priv->codec_dev); + put_device(priv->codec_dev); + return 0; } static struct platform_driver snd_byt_rt5640_mc_driver = { @@ -1302,6 +1837,7 @@ static struct platform_driver snd_byt_rt5640_mc_driver = { .name = "bytcr_rt5640", }, .probe = snd_byt_rt5640_mc_probe, + .remove = snd_byt_rt5640_mc_remove, }; module_platform_driver(snd_byt_rt5640_mc_driver); diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index 5074bb53f98e..2beb686768f2 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -85,6 +85,7 @@ struct byt_rt5651_private { struct gpio_desc *ext_amp_gpio; struct gpio_desc *hp_detect; struct snd_soc_jack jack; + struct device *codec_dev; }; static const struct acpi_gpio_mapping *byt_rt5651_gpios; @@ -143,7 +144,7 @@ static int byt_rt5651_prepare_and_enable_pll1(struct snd_soc_dai *codec_dai, /* Configure the PLL before selecting it */ if (!(byt_rt5651_quirk & BYT_RT5651_MCLK_EN)) { - clk_id = RT5651_PLL1_S_BCLK1, + clk_id = RT5651_PLL1_S_BCLK1; clk_freq = rate * bclk_ratio; } else { clk_id = RT5651_PLL1_S_MCLK; @@ -187,13 +188,10 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, } if (SND_SOC_DAPM_EVENT_ON(event)) { - if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { - ret = clk_prepare_enable(priv->mclk); - if (ret < 0) { - dev_err(card->dev, - "could not configure MCLK state"); - return ret; - } + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(card->dev, "could not configure MCLK state"); + return ret; } ret = byt_rt5651_prepare_and_enable_pll1(codec_dai, 48000, 50); } else { @@ -206,8 +204,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, 48000 * 512, SND_SOC_CLOCK_IN); if (!ret) - if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) - clk_disable_unprepare(priv->mclk); + clk_disable_unprepare(priv->mclk); } if (ret < 0) { @@ -347,8 +344,8 @@ static struct snd_soc_jack_pin bytcr_jack_pins[] = { static int byt_rt5651_aif1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); snd_pcm_format_t format = params_format(params); int rate = params_rate(params); int bclk_ratio; @@ -436,6 +433,19 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { BYT_RT5651_MONO_SPEAKER), }, { + /* Jumper EZpad 7 */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Jumper"), + DMI_MATCH(DMI_PRODUCT_NAME, "EZpad"), + /* Jumper12x.WJ2012.bsBKRCP05 with the version dropped */ + DMI_MATCH(DMI_BIOS_VERSION, "Jumper12x.WJ2012.bsBKRCP"), + }, + .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN2_MAP | + BYT_RT5651_JD_NOT_INV), + }, + { /* KIANO SlimNote 14.2 */ .callback = byt_rt5651_quirk_cb, .matches = { @@ -514,10 +524,13 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { * Note this MUST be called before snd_soc_register_card(), so that the props * are in place before the codec component driver's probe function parses them. */ -static int byt_rt5651_add_codec_device_props(struct device *i2c_dev) +static int byt_rt5651_add_codec_device_props(struct device *i2c_dev, + struct byt_rt5651_private *priv) { struct property_entry props[MAX_NO_PROPS] = {}; + struct fwnode_handle *fwnode; int cnt = 0; + int ret; props[cnt++] = PROPERTY_ENTRY_U32("realtek,jack-detect-source", BYT_RT5651_JDSRC(byt_rt5651_quirk)); @@ -534,13 +547,23 @@ static int byt_rt5651_add_codec_device_props(struct device *i2c_dev) if (byt_rt5651_quirk & BYT_RT5651_JD_NOT_INV) props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,jack-detect-not-inverted"); - return device_add_properties(i2c_dev, props); + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) { + /* put_device(i2c_dev) is handled in caller */ + return PTR_ERR(fwnode); + } + + ret = device_add_software_node(i2c_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + return ret; } static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) { struct snd_soc_card *card = runtime->card; - struct snd_soc_component *codec = runtime->codec_dai->component; + struct snd_soc_component *codec = asoc_rtd_to_codec(runtime, 0)->component; struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card); const struct snd_soc_dapm_route *custom_map; int num_routes; @@ -601,32 +624,26 @@ static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) dev_err(card->dev, "unable to add card controls\n"); return ret; } - snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); - snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); - if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { - /* - * The firmware might enable the clock at - * boot (this information may or may not - * be reflected in the enable clock register). - * To change the rate we must disable the clock - * first to cover these cases. Due to common - * clock framework restrictions that do not allow - * to disable a clock that has not been enabled, - * we need to enable the clock first. - */ - ret = clk_prepare_enable(priv->mclk); - if (!ret) - clk_disable_unprepare(priv->mclk); + /* + * The firmware might enable the clock at boot (this information + * may or may not be reflected in the enable clock register). + * To change the rate we must disable the clock first to cover + * these cases. Due to common clock framework restrictions that + * do not allow to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(priv->mclk); + if (!ret) + clk_disable_unprepare(priv->mclk); - if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) - ret = clk_set_rate(priv->mclk, 25000000); - else - ret = clk_set_rate(priv->mclk, 19200000); + if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) + ret = clk_set_rate(priv->mclk, 25000000); + else + ret = clk_set_rate(priv->mclk, 19200000); - if (ret) - dev_err(card->dev, "unable to set MCLK rate\n"); - } + if (ret) + dev_err(card->dev, "unable to set MCLK rate\n"); report = 0; if (BYT_RT5651_JDSRC(byt_rt5651_quirk)) @@ -635,9 +652,10 @@ static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) report = SND_JACK_HEADSET; if (report) { - ret = snd_soc_card_jack_new(runtime->card, "Headset", - report, &priv->jack, bytcr_jack_pins, - ARRAY_SIZE(bytcr_jack_pins)); + ret = snd_soc_card_jack_new_pins(runtime->card, "Headset", + report, &priv->jack, + bytcr_jack_pins, + ARRAY_SIZE(bytcr_jack_pins)); if (ret) { dev_err(runtime->dev, "jack creation failed %d\n", ret); return ret; @@ -685,10 +703,10 @@ static int byt_rt5651_codec_fixup(struct snd_soc_pcm_runtime *rtd, * with explicit setting to I2S 2ch. The word length is set with * dai_set_tdm_slot() since there is no other API exposed */ - ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS + SND_SOC_DAIFMT_BP_FP ); if (ret < 0) { @@ -696,7 +714,7 @@ static int byt_rt5651_codec_fixup(struct snd_soc_pcm_runtime *rtd, return ret; } - ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, bits); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, bits); if (ret < 0) { dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); return ret; @@ -773,10 +791,8 @@ static struct snd_soc_dai_link byt_rt5651_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = byt_rt5651_codec_fixup, - .ignore_suspend = 1, - .nonatomic = true, .dpcm_playback = 1, .dpcm_capture = 1, .init = byt_rt5651_init, @@ -830,8 +846,16 @@ static int byt_rt5651_resume(struct snd_soc_card *card) return 0; } +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht rt5651" /* card name will be 'sof-bytcht rt5651' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "bytcr-rt5651" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + static struct snd_soc_card byt_rt5651_card = { - .name = "bytcr-rt5651", + .name = CARD_NAME, + .driver_name = DRIVER_NAME, .owner = THIS_MODULE, .dai_link = byt_rt5651_dais, .num_links = ARRAY_SIZE(byt_rt5651_dais), @@ -863,25 +887,25 @@ struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; static const char * const mic_name[] = { "dmic", "in1", "in2", "in12" }; + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); struct byt_rt5651_private *priv; - struct snd_soc_acpi_mach *mach; const char *platform_name; struct acpi_device *adev; struct device *codec_dev; + bool sof_parent; bool is_bytcr = false; int ret_val = 0; int dai_index = 0; int i; - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; /* register the soc card */ - byt_rt5651_card.dev = &pdev->dev; - - mach = byt_rt5651_card.dev->platform_data; + byt_rt5651_card.dev = dev; snd_soc_card_set_drvdata(&byt_rt5651_card, priv); /* fix index of codec dai */ @@ -901,14 +925,14 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) put_device(&adev->dev); byt_rt5651_dais[dai_index].codecs->name = byt_rt5651_codec_name; } else { - dev_err(&pdev->dev, "Error cannot find '%s' dev\n", mach->id); - return -ENODEV; + dev_err(dev, "Error cannot find '%s' dev\n", mach->id); + return -ENXIO; } - codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL, - byt_rt5651_codec_name); + codec_dev = acpi_get_first_physical_node(adev); if (!codec_dev) return -EPROBE_DEFER; + priv->codec_dev = get_device(codec_dev); /* * swap SSP0 if bytcr is detected @@ -928,7 +952,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) * with the codec driver/pdata are non-existent */ - struct acpi_chan_package chan_package; + struct acpi_chan_package chan_package = { 0 }; /* format specified: 2 64-bit integers */ struct acpi_buffer format = {sizeof("NN"), "NN"}; @@ -949,13 +973,13 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) &pkg_ctx); if (pkg_found) { if (chan_package.aif_value == 1) { - dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); + dev_info(dev, "BIOS Routing: AIF1 connected\n"); byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF1; } else if (chan_package.aif_value == 2) { - dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); + dev_info(dev, "BIOS Routing: AIF2 connected\n"); byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF2; } else { - dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); + dev_info(dev, "BIOS Routing isn't valid, ignored\n"); pkg_found = false; } } @@ -970,17 +994,15 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) dmi_check_system(byt_rt5651_quirk_table); if (quirk_override != -1) { - dev_info(&pdev->dev, "Overriding quirk 0x%x => 0x%x\n", - (unsigned int)byt_rt5651_quirk, quirk_override); + dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n", + byt_rt5651_quirk, quirk_override); byt_rt5651_quirk = quirk_override; } /* Must be called before register_card, also see declaration comment. */ - ret_val = byt_rt5651_add_codec_device_props(codec_dev); - if (ret_val) { - put_device(codec_dev); - return ret_val; - } + ret_val = byt_rt5651_add_codec_device_props(codec_dev, priv); + if (ret_val) + goto err_device; /* Cherry Trail devices use an external amplifier enable gpio */ if (soc_intel_is_cht() && !byt_rt5651_gpios) @@ -988,8 +1010,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) if (byt_rt5651_gpios) { devm_acpi_dev_add_driver_gpios(codec_dev, byt_rt5651_gpios); - priv->ext_amp_gpio = devm_fwnode_gpiod_get(&pdev->dev, - codec_dev->fwnode, + priv->ext_amp_gpio = devm_fwnode_gpiod_get(dev, codec_dev->fwnode, "ext-amp-enable", GPIOD_OUT_LOW, "speaker-amp"); @@ -1000,16 +1021,13 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) priv->ext_amp_gpio = NULL; break; default: - dev_err(&pdev->dev, "Failed to get ext-amp-enable GPIO: %d\n", - ret_val); - /* fall through */ + dev_err(dev, "Failed to get ext-amp-enable GPIO: %d\n", ret_val); + fallthrough; case -EPROBE_DEFER: - put_device(codec_dev); - return ret_val; + goto err; } } - priv->hp_detect = devm_fwnode_gpiod_get(&pdev->dev, - codec_dev->fwnode, + priv->hp_detect = devm_fwnode_gpiod_get(dev, codec_dev->fwnode, "hp-detect", GPIOD_IN, "hp-detect"); @@ -1020,19 +1038,15 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) priv->hp_detect = NULL; break; default: - dev_err(&pdev->dev, "Failed to get hp-detect GPIO: %d\n", - ret_val); - /* fall through */ + dev_err(dev, "Failed to get hp-detect GPIO: %d\n", ret_val); + fallthrough; case -EPROBE_DEFER: - put_device(codec_dev); - return ret_val; + goto err; } } } - put_device(codec_dev); - - log_quirks(&pdev->dev); + log_quirks(dev); if ((byt_rt5651_quirk & BYT_RT5651_SSP2_AIF2) || (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2)) @@ -1043,21 +1057,18 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) byt_rt5651_dais[dai_index].cpus->dai_name = "ssp0-port"; if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { - priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + priv->mclk = devm_clk_get_optional(dev, "pmc_plt_clk_3"); if (IS_ERR(priv->mclk)) { - ret_val = PTR_ERR(priv->mclk); - dev_err(&pdev->dev, - "Failed to get MCLK from pmc_plt_clk_3: %d\n", - ret_val); - /* - * Fall back to bit clock usage for -ENOENT (clock not - * available likely due to missing dependencies), bail - * for all other errors, including -EPROBE_DEFER - */ - if (ret_val != -ENOENT) - return ret_val; - byt_rt5651_quirk &= ~BYT_RT5651_MCLK_EN; + ret_val = dev_err_probe(dev, PTR_ERR(priv->mclk), + "Failed to get MCLK from pmc_plt_clk_3\n"); + goto err; } + /* + * Fall back to bit clock usage when clock is not + * available likely due to missing dependencies. + */ + if (!priv->mclk) + byt_rt5651_quirk &= ~BYT_RT5651_MCLK_EN; } snprintf(byt_rt5651_components, sizeof(byt_rt5651_components), @@ -1078,23 +1089,52 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) byt_rt5651_card.long_name = byt_rt5651_long_name; #endif - /* override plaform name, if required */ + /* override platform name, if required */ platform_name = mach->mach_params.platform; ret_val = snd_soc_fixup_dai_links_platform_name(&byt_rt5651_card, platform_name); if (ret_val) - return ret_val; + goto err; - ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5651_card); + sof_parent = snd_soc_acpi_sof_parent(dev); + /* set card and driver name */ + if (sof_parent) { + byt_rt5651_card.name = SOF_CARD_NAME; + byt_rt5651_card.driver_name = SOF_DRIVER_NAME; + } else { + byt_rt5651_card.name = CARD_NAME; + byt_rt5651_card.driver_name = DRIVER_NAME; + } + + /* set pm ops */ + if (sof_parent) + dev->driver->pm = &snd_soc_pm_ops; + + ret_val = devm_snd_soc_register_card(dev, &byt_rt5651_card); if (ret_val) { - dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", - ret_val); - return ret_val; + dev_err(dev, "devm_snd_soc_register_card failed %d\n", ret_val); + goto err; } platform_set_drvdata(pdev, &byt_rt5651_card); return ret_val; + +err: + device_remove_software_node(priv->codec_dev); +err_device: + put_device(priv->codec_dev); + return ret_val; +} + +static int snd_byt_rt5651_mc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card); + + device_remove_software_node(priv->codec_dev); + put_device(priv->codec_dev); + return 0; } static struct platform_driver snd_byt_rt5651_mc_driver = { @@ -1102,6 +1142,7 @@ static struct platform_driver snd_byt_rt5651_mc_driver = { .name = "bytcr_rt5651", }, .probe = snd_byt_rt5651_mc_probe, + .remove = snd_byt_rt5651_mc_remove, }; module_platform_driver(snd_byt_rt5651_mc_driver); diff --git a/sound/soc/intel/boards/bytcr_wm5102.c b/sound/soc/intel/boards/bytcr_wm5102.c new file mode 100644 index 000000000000..45a6805787f5 --- /dev/null +++ b/sound/soc/intel/boards/bytcr_wm5102.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bytcr_wm5102.c - ASoc Machine driver for Intel Baytrail platforms with a + * Wolfson Microelectronics WM5102 codec + * + * Copyright (C) 2020 Hans de Goede <hdegoede@redhat.com> + * Loosely based on bytcr_rt5640.c which is: + * Copyright (C) 2014-2020 Intel Corp + * Author: Subhransu S. Prusty <subhransu.s.prusty@intel.com> + */ + +#include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../codecs/wm5102.h" +#include "../atom/sst-atom-controls.h" + +#define MCLK_FREQ 25000000 + +#define WM5102_MAX_SYSCLK_4K 49152000 /* max sysclk for 4K family */ +#define WM5102_MAX_SYSCLK_11025 45158400 /* max sysclk for 11.025K family */ + +struct byt_wm5102_private { + struct snd_soc_jack jack; + struct clk *mclk; + struct gpio_desc *spkvdd_en_gpio; +}; + +static int byt_wm5102_spkvdd_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct byt_wm5102_private *priv = snd_soc_card_get_drvdata(card); + + gpiod_set_value_cansleep(priv->spkvdd_en_gpio, + !!SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static int byt_wm5102_prepare_and_enable_pll1(struct snd_soc_dai *codec_dai, int rate) +{ + struct snd_soc_component *codec_component = codec_dai->component; + int sr_mult = ((rate % 4000) == 0) ? + (WM5102_MAX_SYSCLK_4K / rate) : + (WM5102_MAX_SYSCLK_11025 / rate); + int ret; + + /* Reset FLL1 */ + snd_soc_dai_set_pll(codec_dai, WM5102_FLL1_REFCLK, ARIZONA_FLL_SRC_NONE, 0, 0); + snd_soc_dai_set_pll(codec_dai, WM5102_FLL1, ARIZONA_FLL_SRC_NONE, 0, 0); + + /* Configure the FLL1 PLL before selecting it */ + ret = snd_soc_dai_set_pll(codec_dai, WM5102_FLL1, ARIZONA_CLK_SRC_MCLK1, + MCLK_FREQ, rate * sr_mult); + if (ret) { + dev_err(codec_component->dev, "Error setting PLL: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(codec_component, ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, rate * sr_mult, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(codec_component->dev, "Error setting SYSCLK: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, ARIZONA_CLK_SYSCLK, + rate * 512, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(codec_component->dev, "Error setting clock: %d\n", ret); + return ret; + } + + return 0; +} + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + struct byt_wm5102_private *priv = snd_soc_card_get_drvdata(card); + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, "wm5102-aif1"); + if (!codec_dai) { + dev_err(card->dev, "Error codec DAI not found\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = clk_prepare_enable(priv->mclk); + if (ret) { + dev_err(card->dev, "Error enabling MCLK: %d\n", ret); + return ret; + } + ret = byt_wm5102_prepare_and_enable_pll1(codec_dai, 48000); + if (ret) { + dev_err(card->dev, "Error setting codec sysclk: %d\n", ret); + return ret; + } + } else { + /* + * The WM5102 has a separate 32KHz clock for jack-detect + * so we can disable the PLL, followed by disabling the + * platform clock which is the source-clock for the PLL. + */ + snd_soc_dai_set_pll(codec_dai, WM5102_FLL1, ARIZONA_FLL_SRC_NONE, 0, 0); + clk_disable_unprepare(priv->mclk); + } + + return 0; +} + +static const struct snd_soc_dapm_widget byt_wm5102_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("Speaker VDD", SND_SOC_NOPM, 0, 0, + byt_wm5102_spkvdd_power_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +}; + +static const struct snd_soc_dapm_route byt_wm5102_audio_map[] = { + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Internal Mic", NULL, "Platform Clock"}, + {"Speaker", NULL, "Platform Clock"}, + + {"Speaker", NULL, "SPKOUTLP"}, + {"Speaker", NULL, "SPKOUTLN"}, + {"Speaker", NULL, "SPKOUTRP"}, + {"Speaker", NULL, "SPKOUTRN"}, + {"Speaker", NULL, "Speaker VDD"}, + + {"Headphone", NULL, "HPOUT1L"}, + {"Headphone", NULL, "HPOUT1R"}, + + {"Internal Mic", NULL, "MICBIAS3"}, + {"IN3L", NULL, "Internal Mic"}, + + /* + * The Headset Mix uses MICBIAS1 or 2 depending on if a CTIA/OMTP Headset + * is connected, as the MICBIAS is applied after the CTIA/OMTP cross-switch. + */ + {"Headset Mic", NULL, "MICBIAS1"}, + {"Headset Mic", NULL, "MICBIAS2"}, + {"IN1L", NULL, "Headset Mic"}, + + {"AIF1 Playback", NULL, "ssp0 Tx"}, + {"ssp0 Tx", NULL, "modem_out"}, + + {"modem_in", NULL, "ssp0 Rx"}, + {"ssp0 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_kcontrol_new byt_wm5102_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static struct snd_soc_jack_pin byt_wm5102_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int byt_wm5102_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct byt_wm5102_private *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + int ret, jack_type; + + card->dapm.idle_bias_off = true; + + ret = snd_soc_add_card_controls(card, byt_wm5102_controls, + ARRAY_SIZE(byt_wm5102_controls)); + if (ret) { + dev_err(card->dev, "Error adding card controls: %d\n", ret); + return ret; + } + + /* + * The firmware might enable the clock at boot (this information + * may or may not be reflected in the enable clock register). + * To change the rate we must disable the clock first to cover these + * cases. Due to common clock framework restrictions that do not allow + * to disable a clock that has not been enabled, we need to enable + * the clock first. + */ + ret = clk_prepare_enable(priv->mclk); + if (!ret) + clk_disable_unprepare(priv->mclk); + + ret = clk_set_rate(priv->mclk, MCLK_FREQ); + if (ret) { + dev_err(card->dev, "Error setting MCLK rate: %d\n", ret); + return ret; + } + + jack_type = ARIZONA_JACK_MASK | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3; + ret = snd_soc_card_jack_new_pins(card, "Headset", jack_type, + &priv->jack, byt_wm5102_pins, + ARRAY_SIZE(byt_wm5102_pins)); + if (ret) { + dev_err(card->dev, "Error creating jack: %d\n", ret); + return ret; + } + + snd_soc_component_set_jack(component, &priv->jack, NULL); + + return 0; +} + +static int byt_wm5102_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret; + + /* The DSP will covert the FE rate to 48k, stereo */ + rate->min = 48000; + rate->max = 48000; + channels->min = 2; + channels->max = 2; + + /* set SSP0 to 16-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 16-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_BP_FP); + if (ret) { + dev_err(rtd->dev, "Error setting format to I2S: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 16); + if (ret) { + dev_err(rtd->dev, "Error setting I2S config: %d\n", ret); + return ret; + } + + return 0; +} + +static int byt_wm5102_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops byt_wm5102_aif1_ops = { + .startup = byt_wm5102_aif1_startup, +}; + +SND_SOC_DAILINK_DEF(dummy, + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +SND_SOC_DAILINK_DEF(media, + DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai"))); + +SND_SOC_DAILINK_DEF(deepbuffer, + DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai"))); + +SND_SOC_DAILINK_DEF(ssp0_port, + DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port"))); + +SND_SOC_DAILINK_DEF(ssp0_codec, + DAILINK_COMP_ARRAY(COMP_CODEC( + /* + * Note there is no need to overwrite the codec-name as is done in + * other bytcr machine drivers, because the codec is a MFD child-dev. + */ + "wm5102-codec", + "wm5102-aif1"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform"))); + +static struct snd_soc_dai_link byt_wm5102_dais[] = { + [MERR_DPCM_AUDIO] = { + .name = "Baytrail Audio Port", + .stream_name = "Baytrail Audio", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &byt_wm5102_aif1_ops, + SND_SOC_DAILINK_REG(media, dummy, platform), + + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &byt_wm5102_aif1_ops, + SND_SOC_DAILINK_REG(deepbuffer, dummy, platform), + }, + /* back ends */ + { + /* + * This must be named SSP2-Codec even though this machine driver + * always uses SSP0. Most machine drivers support both and dynamically + * update the dailink to point to SSP0 or SSP2, while keeping the name + * as "SSP2-Codec". The SOF tplg files hardcode the "SSP2-Codec" even + * in the byt-foo-ssp0.tplg versions because the other machine-drivers + * use "SSP2-Codec" even when SSP0 is used. + */ + .name = "SSP2-Codec", + .id = 0, + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBC_CFC, + .be_hw_params_fixup = byt_wm5102_codec_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + .init = byt_wm5102_init, + SND_SOC_DAILINK_REG(ssp0_port, ssp0_codec, platform), + }, +}; + +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht wm5102" /* card name will be 'sof-bytcht wm5102' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "bytcr-wm5102" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + +/* SoC card */ +static struct snd_soc_card byt_wm5102_card = { + .owner = THIS_MODULE, + .dai_link = byt_wm5102_dais, + .num_links = ARRAY_SIZE(byt_wm5102_dais), + .dapm_widgets = byt_wm5102_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_wm5102_widgets), + .dapm_routes = byt_wm5102_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_wm5102_audio_map), + .fully_routed = true, +}; + +static int snd_byt_wm5102_mc_probe(struct platform_device *pdev) +{ + char codec_name[SND_ACPI_I2C_ID_LEN]; + struct device *dev = &pdev->dev; + struct byt_wm5102_private *priv; + struct snd_soc_acpi_mach *mach; + const char *platform_name; + struct acpi_device *adev; + struct device *codec_dev; + bool sof_parent; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Get MCLK */ + priv->mclk = devm_clk_get(dev, "pmc_plt_clk_3"); + if (IS_ERR(priv->mclk)) + return dev_err_probe(dev, PTR_ERR(priv->mclk), "getting pmc_plt_clk_3\n"); + + /* + * Get speaker VDD enable GPIO: + * 1. Get codec-device-name + * 2. Get codec-device + * 3. Get GPIO from codec-device + */ + mach = dev->platform_data; + adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); + if (!adev) { + dev_err(dev, "Error cannot find acpi-dev for codec\n"); + return -ENOENT; + } + snprintf(codec_name, sizeof(codec_name), "spi-%s", acpi_dev_name(adev)); + put_device(&adev->dev); + + codec_dev = bus_find_device_by_name(&spi_bus_type, NULL, codec_name); + if (!codec_dev) + return -EPROBE_DEFER; + + /* Note no devm_ here since we call gpiod_get on codec_dev rather then dev */ + priv->spkvdd_en_gpio = gpiod_get(codec_dev, "wlf,spkvdd-ena", GPIOD_OUT_LOW); + put_device(codec_dev); + + if (IS_ERR(priv->spkvdd_en_gpio)) { + ret = PTR_ERR(priv->spkvdd_en_gpio); + /* + * The spkvdd gpio-lookup is registered by: drivers/mfd/arizona-spi.c, + * so -ENOENT means that arizona-spi hasn't probed yet. + */ + if (ret == -ENOENT) + ret = -EPROBE_DEFER; + + return dev_err_probe(dev, ret, "getting spkvdd-GPIO\n"); + } + + /* override platform name, if required */ + byt_wm5102_card.dev = dev; + platform_name = mach->mach_params.platform; + ret = snd_soc_fixup_dai_links_platform_name(&byt_wm5102_card, platform_name); + if (ret) + goto out_put_gpio; + + /* set card and driver name and pm-ops */ + sof_parent = snd_soc_acpi_sof_parent(dev); + if (sof_parent) { + byt_wm5102_card.name = SOF_CARD_NAME; + byt_wm5102_card.driver_name = SOF_DRIVER_NAME; + dev->driver->pm = &snd_soc_pm_ops; + } else { + byt_wm5102_card.name = CARD_NAME; + byt_wm5102_card.driver_name = DRIVER_NAME; + } + + snd_soc_card_set_drvdata(&byt_wm5102_card, priv); + ret = devm_snd_soc_register_card(dev, &byt_wm5102_card); + if (ret) { + dev_err_probe(dev, ret, "registering card\n"); + goto out_put_gpio; + } + + platform_set_drvdata(pdev, &byt_wm5102_card); + return 0; + +out_put_gpio: + gpiod_put(priv->spkvdd_en_gpio); + return ret; +} + +static int snd_byt_wm5102_mc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct byt_wm5102_private *priv = snd_soc_card_get_drvdata(card); + + gpiod_put(priv->spkvdd_en_gpio); + return 0; +} + +static struct platform_driver snd_byt_wm5102_mc_driver = { + .driver = { + .name = "bytcr_wm5102", + }, + .probe = snd_byt_wm5102_mc_probe, + .remove = snd_byt_wm5102_mc_remove, +}; + +module_platform_driver(snd_byt_wm5102_mc_driver); + +MODULE_DESCRIPTION("ASoC Baytrail with WM5102 codec machine driver"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcr_wm5102"); diff --git a/sound/soc/intel/boards/cht_bsw_max98090_ti.c b/sound/soc/intel/boards/cht_bsw_max98090_ti.c index 70bb86f3342f..64eb73525ee3 100644 --- a/sound/soc/intel/boards/cht_bsw_max98090_ti.c +++ b/sound/soc/intel/boards/cht_bsw_max98090_ti.c @@ -112,8 +112,8 @@ static const struct snd_kcontrol_new cht_mc_controls[] = { static int cht_aif1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, M98090_REG_SYSTEM_CLOCK, @@ -201,9 +201,10 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE; - ret = snd_soc_card_jack_new(runtime->card, "Headset Jack", - jack_type, jack, - hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); + ret = snd_soc_card_jack_new_pins(runtime->card, "Headset Jack", + jack_type, jack, + hs_jack_pins, + ARRAY_SIZE(hs_jack_pins)); if (ret) { dev_err(runtime->dev, "Headset Jack creation failed %d\n", ret); return ret; @@ -257,16 +258,15 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, int ret = 0; unsigned int fmt = 0; - ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 16); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 16); if (ret < 0) { dev_err(rtd->dev, "can't set cpu_dai slot fmt: %d\n", ret); return ret; } - fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS; + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_BP_FP; - ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), fmt); if (ret < 0) { dev_err(rtd->dev, "can't set cpu_dai set fmt: %d\n", ret); return ret; @@ -296,7 +296,7 @@ static int cht_max98090_headset_init(struct snd_soc_component *component) int ret; /* - * TI supports 4 butons headset detection + * TI supports 4 buttons headset detection * KEY_MEDIA * KEY_VOICECOMMAND * KEY_VOLUMEUP @@ -306,8 +306,7 @@ static int cht_max98090_headset_init(struct snd_soc_component *component) SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3; - ret = snd_soc_card_jack_new(card, "Headset Jack", jack_type, - jack, NULL, 0); + ret = snd_soc_card_jack_new(card, "Headset Jack", jack_type, jack); if (ret) { dev_err(card->dev, "Headset Jack creation failed %d\n", ret); return ret; @@ -372,7 +371,7 @@ static struct snd_soc_dai_link cht_dailink[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .init = cht_codec_init, .be_hw_params_fixup = cht_codec_fixup, .dpcm_playback = 1, @@ -382,9 +381,15 @@ static struct snd_soc_dai_link cht_dailink[] = { }, }; +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht max98090" /* card name will be 'sof-bytcht max98090 */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "chtmax98090" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + /* SoC card */ static struct snd_soc_card snd_soc_card_cht = { - .name = "chtmax98090", .owner = THIS_MODULE, .dai_link = cht_dailink, .num_links = ARRAY_SIZE(cht_dailink), @@ -530,8 +535,9 @@ static int snd_cht_mc_probe(struct platform_device *pdev) const char *mclk_name; struct snd_soc_acpi_mach *mach; const char *platform_name; + bool sof_parent; - drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); if (!drv) return -ENOMEM; @@ -551,9 +557,9 @@ static int snd_cht_mc_probe(struct platform_device *pdev) dev_dbg(dev, "Unable to add GPIO mapping table\n"); } - /* override plaform name, if required */ - snd_soc_card_cht.dev = &pdev->dev; - mach = (&pdev->dev)->platform_data; + /* override platform name, if required */ + snd_soc_card_cht.dev = dev; + mach = dev->platform_data; platform_name = mach->mach_params.platform; ret_val = snd_soc_fixup_dai_links_platform_name(&snd_soc_card_cht, @@ -569,9 +575,9 @@ static int snd_cht_mc_probe(struct platform_device *pdev) else mclk_name = "pmc_plt_clk_3"; - drv->mclk = devm_clk_get(&pdev->dev, mclk_name); + drv->mclk = devm_clk_get(dev, mclk_name); if (IS_ERR(drv->mclk)) { - dev_err(&pdev->dev, + dev_err(dev, "Failed to get MCLK from %s: %ld\n", mclk_name, PTR_ERR(drv->mclk)); return PTR_ERR(drv->mclk); @@ -587,14 +593,29 @@ static int snd_cht_mc_probe(struct platform_device *pdev) if (drv->quirks & QUIRK_PMC_PLT_CLK_0) { ret_val = clk_prepare_enable(drv->mclk); if (ret_val < 0) { - dev_err(&pdev->dev, "MCLK enable error: %d\n", ret_val); + dev_err(dev, "MCLK enable error: %d\n", ret_val); return ret_val; } } - ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); + sof_parent = snd_soc_acpi_sof_parent(dev); + + /* set card and driver name */ + if (sof_parent) { + snd_soc_card_cht.name = SOF_CARD_NAME; + snd_soc_card_cht.driver_name = SOF_DRIVER_NAME; + } else { + snd_soc_card_cht.name = CARD_NAME; + snd_soc_card_cht.driver_name = DRIVER_NAME; + } + + /* set pm ops */ + if (sof_parent) + dev->driver->pm = &snd_soc_pm_ops; + + ret_val = devm_snd_soc_register_card(dev, &snd_soc_card_cht); if (ret_val) { - dev_err(&pdev->dev, + dev_err(dev, "snd_soc_register_card failed %d\n", ret_val); return ret_val; } diff --git a/sound/soc/intel/boards/cht_bsw_nau8824.c b/sound/soc/intel/boards/cht_bsw_nau8824.c index 501bad3976fb..4c1d83b317c7 100644 --- a/sound/soc/intel/boards/cht_bsw_nau8824.c +++ b/sound/soc/intel/boards/cht_bsw_nau8824.c @@ -72,8 +72,8 @@ static const struct snd_kcontrol_new cht_mc_controls[] = { static int cht_aif1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, NAU8824_CLK_FLL_FS, 0, @@ -96,33 +96,26 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) { struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); struct snd_soc_jack *jack = &ctx->jack; - struct snd_soc_dai *codec_dai = runtime->codec_dai; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); struct snd_soc_component *component = codec_dai->component; int ret, jack_type; - /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ - ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xf, 0x1, 4, 24); - if (ret < 0) { - dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret); - return ret; - } - - /* NAU88L24 supports 4 butons headset detection - * KEY_MEDIA + /* NAU88L24 supports 4 buttons headset detection + * KEY_PLAYPAUSE * KEY_VOICECOMMAND * KEY_VOLUMEUP * KEY_VOLUMEDOWN */ jack_type = SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3; - ret = snd_soc_card_jack_new(runtime->card, "Headset", jack_type, jack, - cht_bsw_jack_pins, ARRAY_SIZE(cht_bsw_jack_pins)); + ret = snd_soc_card_jack_new_pins(runtime->card, "Headset", jack_type, + jack, cht_bsw_jack_pins, ARRAY_SIZE(cht_bsw_jack_pins)); if (ret) { dev_err(runtime->dev, "Headset Jack creation failed %d\n", ret); return ret; } - snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA); + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); @@ -141,6 +134,7 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + int ret; /* The DSP will covert the FE rate to 48k, stereo, 24bits */ rate->min = rate->max = 48000; @@ -150,6 +144,13 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, snd_mask_none(fmt); params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(rtd, 0), 0xf, 0x1, 4, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret); + return ret; + } + return 0; } @@ -176,9 +177,6 @@ SND_SOC_DAILINK_DEF(media, SND_SOC_DAILINK_DEF(deepbuffer, DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai"))); -SND_SOC_DAILINK_DEF(compress, - DAILINK_COMP_ARRAY(COMP_CPU("compress-cpu-dai"))); - SND_SOC_DAILINK_DEF(ssp2_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port"))); SND_SOC_DAILINK_DEF(ssp2_codec, @@ -209,19 +207,14 @@ static struct snd_soc_dai_link cht_dailink[] = { .ops = &cht_aif1_ops, SND_SOC_DAILINK_REG(deepbuffer, dummy, platform), }, - [MERR_DPCM_COMPR] = { - .name = "Compressed Port", - .stream_name = "Compress", - SND_SOC_DAILINK_REG(compress, dummy, platform), - }, /* Back End DAI links */ { /* SSP2 - Codec */ .name = "SSP2-Codec", - .id = 1, + .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .init = cht_codec_init, .be_hw_params_fixup = cht_codec_fixup, .dpcm_playback = 1, @@ -231,9 +224,15 @@ static struct snd_soc_dai_link cht_dailink[] = { }, }; +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht nau8824" /* card name will be 'sof-bytcht nau8824 */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "chtnau8824" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + /* SoC card */ static struct snd_soc_card snd_soc_card_cht = { - .name = "chtnau8824", .owner = THIS_MODULE, .dai_link = cht_dailink, .num_links = ARRAY_SIZE(cht_dailink), @@ -250,6 +249,7 @@ static int snd_cht_mc_probe(struct platform_device *pdev) struct cht_mc_private *drv; struct snd_soc_acpi_mach *mach; const char *platform_name; + bool sof_parent; int ret_val; drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); @@ -257,9 +257,9 @@ static int snd_cht_mc_probe(struct platform_device *pdev) return -ENOMEM; snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); - /* override plaform name, if required */ + /* override platform name, if required */ snd_soc_card_cht.dev = &pdev->dev; - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; platform_name = mach->mach_params.platform; ret_val = snd_soc_fixup_dai_links_platform_name(&snd_soc_card_cht, @@ -267,6 +267,23 @@ static int snd_cht_mc_probe(struct platform_device *pdev) if (ret_val) return ret_val; + sof_parent = snd_soc_acpi_sof_parent(&pdev->dev); + + /* set card and driver name */ + if (sof_parent) { + snd_soc_card_cht.name = SOF_CARD_NAME; + snd_soc_card_cht.driver_name = SOF_DRIVER_NAME; + } else { + snd_soc_card_cht.name = CARD_NAME; + snd_soc_card_cht.driver_name = DRIVER_NAME; + } + + snd_soc_card_cht.components = nau8824_components(); + + /* set pm ops */ + if (sof_parent) + pdev->dev.driver->pm = &snd_soc_pm_ops; + /* register the soc card */ ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); if (ret_val) { diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c index b5b016d493f1..96501aed8bee 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5645.c +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -207,8 +207,8 @@ static struct snd_soc_jack_pin cht_bsw_jack_pins[] = { static int cht_aif1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ @@ -252,7 +252,7 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) { struct snd_soc_card *card = runtime->card; struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); - struct snd_soc_component *component = runtime->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; int jack_type; int ret; @@ -302,9 +302,9 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) else jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE; - ret = snd_soc_card_jack_new(runtime->card, "Headset", - jack_type, &ctx->jack, - cht_bsw_jack_pins, ARRAY_SIZE(cht_bsw_jack_pins)); + ret = snd_soc_card_jack_new_pins(runtime->card, "Headset", jack_type, + &ctx->jack, cht_bsw_jack_pins, + ARRAY_SIZE(cht_bsw_jack_pins)); if (ret) { dev_err(runtime->dev, "Headset jack creation failed %d\n", ret); return ret; @@ -359,27 +359,27 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, * with explicit setting to I2S 2ch 16-bit. The word length is set with * dai_set_tdm_slot() since there is no other API exposed */ - ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS + SND_SOC_DAIFMT_BP_FP ); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; } - ret = snd_soc_dai_set_fmt(rtd->codec_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_codec(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS + SND_SOC_DAIFMT_BC_FC ); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; } - ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 16); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 16); if (ret < 0) { dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); return ret; @@ -393,17 +393,17 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, /* * Default mode for SSP configuration is TDM 4 slot */ - ret = snd_soc_dai_set_fmt(rtd->codec_dai, + ret = snd_soc_dai_set_fmt(asoc_rtd_to_codec(rtd, 0), SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_BC_FC); if (ret < 0) { dev_err(rtd->dev, "can't set format to TDM %d\n", ret); return ret; } /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ - ret = snd_soc_dai_set_tdm_slot(rtd->codec_dai, 0xF, 0xF, 4, 24); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(rtd, 0), 0xF, 0xF, 4, 24); if (ret < 0) { dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret); return ret; @@ -471,7 +471,6 @@ static struct snd_soc_dai_link cht_dailink[] = { .no_pcm = 1, .init = cht_codec_init, .be_hw_params_fixup = cht_codec_fixup, - .nonatomic = true, .dpcm_playback = 1, .dpcm_capture = 1, .ops = &cht_be_ssp2_ops, @@ -479,9 +478,17 @@ static struct snd_soc_dai_link cht_dailink[] = { }, }; +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_RT5645_NAME "bytcht rt5645" /* card name 'sof-bytcht rt5645' */ +#define SOF_CARD_RT5650_NAME "bytcht rt5650" /* card name 'sof-bytcht rt5650' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_RT5645_NAME "chtrt5645" +#define CARD_RT5650_NAME "chtrt5650" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + /* SoC card */ static struct snd_soc_card snd_soc_card_chtrt5645 = { - .name = "chtrt5645", .owner = THIS_MODULE, .dai_link = cht_dailink, .num_links = ARRAY_SIZE(cht_dailink), @@ -494,7 +501,6 @@ static struct snd_soc_card snd_soc_card_chtrt5645 = { }; static struct snd_soc_card snd_soc_card_chtrt5650 = { - .name = "chtrt5650", .owner = THIS_MODULE, .dai_link = cht_dailink, .num_links = ARRAY_SIZE(cht_dailink), @@ -528,6 +534,7 @@ static int snd_cht_mc_probe(struct platform_device *pdev) const char *platform_name; struct cht_mc_private *drv; struct acpi_device *adev; + bool sof_parent; bool found = false; bool is_bytcr = false; int dai_index = 0; @@ -539,7 +546,7 @@ static int snd_cht_mc_probe(struct platform_device *pdev) if (!drv) return -ENOMEM; - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; for (i = 0; i < ARRAY_SIZE(snd_soc_cards); i++) { if (acpi_dev_found(snd_soc_cards[i].codec_id) && @@ -596,7 +603,7 @@ static int snd_cht_mc_probe(struct platform_device *pdev) * with the codec driver/pdata are non-existent */ - struct acpi_chan_package chan_package; + struct acpi_chan_package chan_package = { 0 }; /* format specified: 2 64-bit integers */ struct acpi_buffer format = {sizeof("NN"), "NN"}; @@ -646,7 +653,7 @@ static int snd_cht_mc_probe(struct platform_device *pdev) (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) cht_dailink[dai_index].cpus->dai_name = "ssp0-port"; - /* override plaform name, if required */ + /* override platform name, if required */ platform_name = mach->mach_params.platform; ret_val = snd_soc_fixup_dai_links_platform_name(card, @@ -667,6 +674,26 @@ static int snd_cht_mc_probe(struct platform_device *pdev) } snd_soc_card_set_drvdata(card, drv); + + sof_parent = snd_soc_acpi_sof_parent(&pdev->dev); + + /* set card and driver name */ + if (sof_parent) { + snd_soc_card_chtrt5645.name = SOF_CARD_RT5645_NAME; + snd_soc_card_chtrt5645.driver_name = SOF_DRIVER_NAME; + snd_soc_card_chtrt5650.name = SOF_CARD_RT5650_NAME; + snd_soc_card_chtrt5650.driver_name = SOF_DRIVER_NAME; + } else { + snd_soc_card_chtrt5645.name = CARD_RT5645_NAME; + snd_soc_card_chtrt5645.driver_name = DRIVER_NAME; + snd_soc_card_chtrt5650.name = CARD_RT5650_NAME; + snd_soc_card_chtrt5650.driver_name = DRIVER_NAME; + } + + /* set pm ops */ + if (sof_parent) + pdev->dev.driver->pm = &snd_soc_pm_ops; + ret_val = devm_snd_soc_register_card(&pdev->dev, card); if (ret_val) { dev_err(&pdev->dev, diff --git a/sound/soc/intel/boards/cht_bsw_rt5672.c b/sound/soc/intel/boards/cht_bsw_rt5672.c index 9d657421730a..ca47f6476b07 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5672.c +++ b/sound/soc/intel/boards/cht_bsw_rt5672.c @@ -21,6 +21,7 @@ #include <sound/soc-acpi.h> #include "../../codecs/rt5670.h" #include "../atom/sst-atom-controls.h" +#include "../common/soc-intel-quirks.h" /* The platform clock #3 outputs 19.2Mhz clock to codec as I2S MCLK */ @@ -31,6 +32,7 @@ struct cht_mc_private { struct snd_soc_jack headset; char codec_name[SND_ACPI_I2C_ID_LEN]; struct clk *mclk; + bool use_ssp0; }; /* Headset jack detection DAPM pins */ @@ -121,16 +123,26 @@ static const struct snd_soc_dapm_route cht_audio_map[] = { {"Ext Spk", NULL, "SPOLN"}, {"Ext Spk", NULL, "SPORP"}, {"Ext Spk", NULL, "SPORN"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, + {"Ext Spk", NULL, "Platform Clock"}, +}; + +static const struct snd_soc_dapm_route cht_audio_ssp0_map[] = { + {"AIF1 Playback", NULL, "ssp0 Tx"}, + {"ssp0 Tx", NULL, "modem_out"}, + {"modem_in", NULL, "ssp0 Rx"}, + {"ssp0 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_soc_dapm_route cht_audio_ssp2_map[] = { {"AIF1 Playback", NULL, "ssp2 Tx"}, {"ssp2 Tx", NULL, "codec_out0"}, {"ssp2 Tx", NULL, "codec_out1"}, {"codec_in0", NULL, "ssp2 Rx"}, {"codec_in1", NULL, "ssp2 Rx"}, {"ssp2 Rx", NULL, "AIF1 Capture"}, - {"Headphone", NULL, "Platform Clock"}, - {"Headset Mic", NULL, "Platform Clock"}, - {"Int Mic", NULL, "Platform Clock"}, - {"Ext Spk", NULL, "Platform Clock"}, }; static const struct snd_kcontrol_new cht_mc_controls[] = { @@ -143,8 +155,8 @@ static const struct snd_kcontrol_new cht_mc_controls[] = { static int cht_aif1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ @@ -176,7 +188,7 @@ static const struct acpi_gpio_mapping cht_rt5672_gpios[] = { static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) { int ret; - struct snd_soc_dai *codec_dai = runtime->codec_dai; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); struct snd_soc_component *component = codec_dai->component; struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); @@ -197,12 +209,24 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) | RT5670_AD_MONO_R_FILTER, RT5670_CLK_SEL_I2S1_ASRC); - ret = snd_soc_card_jack_new(runtime->card, "Headset", - SND_JACK_HEADSET | SND_JACK_BTN_0 | - SND_JACK_BTN_1 | SND_JACK_BTN_2, - &ctx->headset, - cht_bsw_headset_pins, - ARRAY_SIZE(cht_bsw_headset_pins)); + if (ctx->use_ssp0) { + ret = snd_soc_dapm_add_routes(&runtime->card->dapm, + cht_audio_ssp0_map, + ARRAY_SIZE(cht_audio_ssp0_map)); + } else { + ret = snd_soc_dapm_add_routes(&runtime->card->dapm, + cht_audio_ssp2_map, + ARRAY_SIZE(cht_audio_ssp2_map)); + } + if (ret) + return ret; + + ret = snd_soc_card_jack_new_pins(runtime->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2, + &ctx->headset, + cht_bsw_headset_pins, + ARRAY_SIZE(cht_bsw_headset_pins)); if (ret) return ret; @@ -239,35 +263,52 @@ static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - int ret; + int ret, bits; /* The DSP will covert the FE rate to 48k, stereo, 24bits */ rate->min = rate->max = 48000; channels->min = channels->max = 2; - /* set SSP2 to 24-bit */ - params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + if (ctx->use_ssp0) { + /* set SSP0 to 16-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + bits = 16; + } else { + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + bits = 24; + } /* - * Default mode for SSP configuration is TDM 4 slot + * The default mode for the cpu-dai is TDM 4 slot. The default mode + * for the codec-dai is I2S. So we need to either set the cpu-dai to + * I2S mode to match the codec-dai, or set the codec-dai to TDM 4 slot + * (or program both to yet another mode). + * One board, the Lenovo Miix 2 10, uses not 1 but 2 codecs connected + * to SSP2. The second piggy-backed, output-only codec is inside the + * keyboard-dock (which has extra speakers). Unlike the main rt5672 + * codec, we cannot configure this codec, it is hard coded to use + * 2 channel 24 bit I2S. For this to work we must use I2S mode on this + * board. Since we only support 2 channels anyways, there is no need + * for TDM on any cht-bsw-rt5672 designs. So we use I2S 2ch everywhere. */ - ret = snd_soc_dai_set_fmt(rtd->codec_dai, - SND_SOC_DAIFMT_DSP_B | - SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFS); + ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_BP_FP); if (ret < 0) { - dev_err(rtd->dev, "can't set format to TDM %d\n", ret); + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; } - /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ - ret = snd_soc_dai_set_tdm_slot(rtd->codec_dai, 0xF, 0xF, 4, 24); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, bits); if (ret < 0) { - dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret); + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); return ret; } @@ -334,7 +375,6 @@ static struct snd_soc_dai_link cht_dailink[] = { .name = "SSP2-Codec", .id = 0, .no_pcm = 1, - .nonatomic = true, .init = cht_codec_init, .be_hw_params_fixup = cht_codec_fixup, .dpcm_playback = 1, @@ -379,9 +419,15 @@ static int cht_resume_post(struct snd_soc_card *card) return 0; } +/* use space before codec name to simplify card ID, and simplify driver name */ +#define SOF_CARD_NAME "bytcht rt5672" /* card name will be 'sof-bytcht rt5672' */ +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "cht-bsw-rt5672" +#define DRIVER_NAME NULL /* card name will be used for driver name */ + /* SoC card */ static struct snd_soc_card snd_soc_card_cht = { - .name = "cht-bsw-rt5672", .owner = THIS_MODULE, .dai_link = cht_dailink, .num_links = ARRAY_SIZE(cht_dailink), @@ -404,6 +450,8 @@ static int snd_cht_mc_probe(struct platform_device *pdev) struct snd_soc_acpi_mach *mach = pdev->dev.platform_data; const char *platform_name; struct acpi_device *adev; + bool sof_parent; + int dai_index = 0; int i; drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); @@ -412,22 +460,30 @@ static int snd_cht_mc_probe(struct platform_device *pdev) strcpy(drv->codec_name, RT5672_I2C_DEFAULT); + /* find index of codec dai */ + for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) { + if (!strcmp(cht_dailink[i].codecs->name, RT5672_I2C_DEFAULT)) { + dai_index = i; + break; + } + } + /* fixup codec name based on HID */ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); if (adev) { snprintf(drv->codec_name, sizeof(drv->codec_name), "i2c-%s", acpi_dev_name(adev)); put_device(&adev->dev); - for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) { - if (!strcmp(cht_dailink[i].codecs->name, - RT5672_I2C_DEFAULT)) { - cht_dailink[i].codecs->name = drv->codec_name; - break; - } - } + cht_dailink[dai_index].codecs->name = drv->codec_name; + } + + /* Use SSP0 on Bay Trail CR devices */ + if (soc_intel_is_byt() && mach->mach_params.acpi_ipc_irq_index == 0) { + cht_dailink[dai_index].cpus->dai_name = "ssp0-port"; + drv->use_ssp0 = true; } - /* override plaform name, if required */ + /* override platform name, if required */ snd_soc_card_cht.dev = &pdev->dev; platform_name = mach->mach_params.platform; @@ -436,6 +492,8 @@ static int snd_cht_mc_probe(struct platform_device *pdev) if (ret_val) return ret_val; + snd_soc_card_cht.components = rt5670_components(); + drv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); if (IS_ERR(drv->mclk)) { dev_err(&pdev->dev, @@ -445,6 +503,21 @@ static int snd_cht_mc_probe(struct platform_device *pdev) } snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); + sof_parent = snd_soc_acpi_sof_parent(&pdev->dev); + + /* set card and driver name */ + if (sof_parent) { + snd_soc_card_cht.name = SOF_CARD_NAME; + snd_soc_card_cht.driver_name = SOF_DRIVER_NAME; + } else { + snd_soc_card_cht.name = CARD_NAME; + snd_soc_card_cht.driver_name = DRIVER_NAME; + } + + /* set pm ops */ + if (sof_parent) + pdev->dev.driver->pm = &snd_soc_pm_ops; + /* register the soc card */ ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); if (ret_val) { diff --git a/sound/soc/intel/boards/cml_rt1011_rt5682.c b/sound/soc/intel/boards/cml_rt1011_rt5682.c index dd80d0186a6c..20da83d9eece 100644 --- a/sound/soc/intel/boards/cml_rt1011_rt5682.c +++ b/sound/soc/intel/boards/cml_rt1011_rt5682.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2019 Intel Corporation. /* @@ -30,6 +30,35 @@ #define CML_RT5682_CODEC_DAI "rt5682-aif1" #define NAME_SIZE 32 +#define SOF_RT1011_SPEAKER_WL BIT(0) +#define SOF_RT1011_SPEAKER_WR BIT(1) +#define SOF_RT1011_SPEAKER_TL BIT(2) +#define SOF_RT1011_SPEAKER_TR BIT(3) + +/* Default: Woofer speakers */ +static unsigned long sof_rt1011_quirk = SOF_RT1011_SPEAKER_WL | + SOF_RT1011_SPEAKER_WR; + +static int sof_rt1011_quirk_cb(const struct dmi_system_id *id) +{ + sof_rt1011_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id sof_rt1011_quirk_table[] = { + { + .callback = sof_rt1011_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + DMI_MATCH(DMI_PRODUCT_NAME, "Helios"), + }, + .driver_data = (void *)(SOF_RT1011_SPEAKER_WL | SOF_RT1011_SPEAKER_WR | + SOF_RT1011_SPEAKER_TL | SOF_RT1011_SPEAKER_TR), + }, + { + } +}; + static struct snd_soc_jack hdmi_jack[3]; struct hdmi_pcm { @@ -48,15 +77,16 @@ struct card_private { static const struct snd_kcontrol_new cml_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone Jack"), SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("TL Ext Spk"), - SOC_DAPM_PIN_SWITCH("TR Ext Spk"), SOC_DAPM_PIN_SWITCH("WL Ext Spk"), SOC_DAPM_PIN_SWITCH("WR Ext Spk"), }; +static const struct snd_kcontrol_new cml_rt1011_tt_controls[] = { + SOC_DAPM_PIN_SWITCH("TL Ext Spk"), + SOC_DAPM_PIN_SWITCH("TR Ext Spk"), +}; + static const struct snd_soc_dapm_widget cml_rt1011_rt5682_widgets[] = { - SND_SOC_DAPM_SPK("TL Ext Spk", NULL), - SND_SOC_DAPM_SPK("TR Ext Spk", NULL), SND_SOC_DAPM_SPK("WL Ext Spk", NULL), SND_SOC_DAPM_SPK("WR Ext Spk", NULL), SND_SOC_DAPM_HP("Headphone Jack", NULL), @@ -64,10 +94,13 @@ static const struct snd_soc_dapm_widget cml_rt1011_rt5682_widgets[] = { SND_SOC_DAPM_MIC("SoC DMIC", NULL), }; +static const struct snd_soc_dapm_widget cml_rt1011_tt_widgets[] = { + SND_SOC_DAPM_SPK("TL Ext Spk", NULL), + SND_SOC_DAPM_SPK("TR Ext Spk", NULL), +}; + static const struct snd_soc_dapm_route cml_rt1011_rt5682_map[] = { - /*speaker*/ - {"TL Ext Spk", NULL, "TL SPO"}, - {"TR Ext Spk", NULL, "TR SPO"}, + /*WL/WR speaker*/ {"WL Ext Spk", NULL, "WL SPO"}, {"WR Ext Spk", NULL, "WR SPO"}, @@ -82,10 +115,27 @@ static const struct snd_soc_dapm_route cml_rt1011_rt5682_map[] = { {"DMic", NULL, "SoC DMIC"}, }; +static const struct snd_soc_dapm_route cml_rt1011_tt_map[] = { + /*TL/TR speaker*/ + {"TL Ext Spk", NULL, "TL SPO" }, + {"TR Ext Spk", NULL, "TR SPO" }, +}; + +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static int cml_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) { struct card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; struct snd_soc_jack *jack; int ret; @@ -98,11 +148,13 @@ static int cml_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | - SND_JACK_BTN_1 | SND_JACK_BTN_2 | - SND_JACK_BTN_3, - &ctx->headset, NULL, 0); + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); return ret; @@ -121,11 +173,47 @@ static int cml_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; }; +static void cml_rt5682_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_set_jack(component, NULL, NULL); +} + +static int cml_rt1011_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_soc_card *card = rtd->card; + + if (sof_rt1011_quirk & (SOF_RT1011_SPEAKER_TL | + SOF_RT1011_SPEAKER_TR)) { + + ret = snd_soc_add_card_controls(card, cml_rt1011_tt_controls, + ARRAY_SIZE(cml_rt1011_tt_controls)); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, + cml_rt1011_tt_widgets, + ARRAY_SIZE(cml_rt1011_tt_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(&card->dapm, cml_rt1011_tt_map, + ARRAY_SIZE(cml_rt1011_tt_map)); + + if (ret) + return ret; + } + + return ret; +} + static int cml_rt5682_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int clk_id, clk_freq, pll_out, ret; clk_id = RT5682_PLL1_S_MCLK; @@ -157,15 +245,14 @@ static int cml_rt5682_hw_params(struct snd_pcm_substream *substream, static int cml_rt1011_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *codec_dai; struct snd_soc_card *card = rtd->card; int srate, i, ret = 0; srate = params_rate(params); - for (i = 0; i < rtd->num_codecs; i++) { - codec_dai = rtd->codec_dais[i]; + for_each_rtd_codec_dais(rtd, i, codec_dai) { /* 100 Fs to drive 24 bit data */ ret = snd_soc_dai_set_pll(codec_dai, 0, RT1011_PLL1_S_BCLK, @@ -192,30 +279,38 @@ static int cml_rt1011_hw_params(struct snd_pcm_substream *substream, * The feedback is captured for each codec individually. * Hence all 4 codecs use 1 Tx slot each for feedback. */ - if (!strcmp(codec_dai->component->name, "i2c-10EC1011:00")) { - ret = snd_soc_dai_set_tdm_slot(codec_dai, - 0x4, 0x1, 4, 24); - if (ret < 0) - break; + if (sof_rt1011_quirk & (SOF_RT1011_SPEAKER_WL | + SOF_RT1011_SPEAKER_WR)) { + if (!strcmp(codec_dai->component->name, "i2c-10EC1011:00")) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, + 0x4, 0x1, 4, 24); + if (ret < 0) + break; + } + + if (!strcmp(codec_dai->component->name, "i2c-10EC1011:01")) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, + 0x8, 0x2, 4, 24); + if (ret < 0) + break; + } } - if (!strcmp(codec_dai->component->name, "i2c-10EC1011:02")) { - ret = snd_soc_dai_set_tdm_slot(codec_dai, - 0x1, 0x1, 4, 24); - if (ret < 0) - break; - } - /* TDM Rx slot 2 is used for Right Woofer & Tweeters pair */ - if (!strcmp(codec_dai->component->name, "i2c-10EC1011:01")) { - ret = snd_soc_dai_set_tdm_slot(codec_dai, - 0x8, 0x2, 4, 24); - if (ret < 0) - break; - } - if (!strcmp(codec_dai->component->name, "i2c-10EC1011:03")) { - ret = snd_soc_dai_set_tdm_slot(codec_dai, - 0x2, 0x2, 4, 24); - if (ret < 0) - break; + + if (sof_rt1011_quirk & (SOF_RT1011_SPEAKER_TL | + SOF_RT1011_SPEAKER_TR)) { + if (!strcmp(codec_dai->component->name, "i2c-10EC1011:02")) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, + 0x1, 0x1, 4, 24); + if (ret < 0) + break; + } + + if (!strcmp(codec_dai->component->name, "i2c-10EC1011:03")) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, + 0x2, 0x2, 4, 24); + if (ret < 0) + break; + } } } if (ret < 0) @@ -256,8 +351,7 @@ static int sof_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); ret = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &hdmi_jack[i], - NULL, 0); + SND_JACK_AVOUT, &hdmi_jack[i]); if (ret) return ret; @@ -275,7 +369,7 @@ static int sof_card_late_probe(struct snd_soc_card *card) static int hdmi_init(struct snd_soc_pcm_runtime *rtd) { struct card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -300,13 +394,18 @@ SND_SOC_DAILINK_DEF(ssp0_codec, SND_SOC_DAILINK_DEF(ssp1_pin, DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin"))); -SND_SOC_DAILINK_DEF(ssp1_codec, +SND_SOC_DAILINK_DEF(ssp1_codec_2spk, + DAILINK_COMP_ARRAY( + /* WL */ COMP_CODEC("i2c-10EC1011:00", CML_RT1011_CODEC_DAI), + /* WR */ COMP_CODEC("i2c-10EC1011:01", CML_RT1011_CODEC_DAI))); +SND_SOC_DAILINK_DEF(ssp1_codec_4spk, DAILINK_COMP_ARRAY( /* WL */ COMP_CODEC("i2c-10EC1011:00", CML_RT1011_CODEC_DAI), /* WR */ COMP_CODEC("i2c-10EC1011:01", CML_RT1011_CODEC_DAI), /* TL */ COMP_CODEC("i2c-10EC1011:02", CML_RT1011_CODEC_DAI), /* TR */ COMP_CODEC("i2c-10EC1011:03", CML_RT1011_CODEC_DAI))); + SND_SOC_DAILINK_DEF(dmic_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin"))); @@ -341,6 +440,7 @@ static struct snd_soc_dai_link cml_rt1011_rt5682_dailink[] = { .name = "SSP0-Codec", .id = 0, .init = cml_rt5682_codec_init, + .exit = cml_rt5682_codec_exit, .ignore_pmdown_time = 1, .ops = &cml_rt5682_ops, .dpcm_playback = 1, @@ -399,8 +499,9 @@ static struct snd_soc_dai_link cml_rt1011_rt5682_dailink[] = { .dpcm_playback = 1, .dpcm_capture = 1, /* Capture stream provides Feedback */ .no_pcm = 1, + .init = cml_rt1011_spk_init, .ops = &cml_rt1011_ops, - SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform), + SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec_2spk, platform), }, }; @@ -413,6 +514,7 @@ static struct snd_soc_codec_conf rt1011_conf[] = { .dlc = COMP_CODEC_CONF("i2c-10EC1011:01"), .name_prefix = "WR", }, + /* single configuration structure for 2 and 4 channels */ { .dlc = COMP_CODEC_CONF("i2c-10EC1011:02"), .name_prefix = "TL", @@ -426,6 +528,7 @@ static struct snd_soc_codec_conf rt1011_conf[] = { /* Cometlake audio machine driver for RT1011 and RT5682 */ static struct snd_soc_card snd_soc_card_cml = { .name = "cml_rt1011_rt5682", + .owner = THIS_MODULE, .dai_link = cml_rt1011_rt5682_dailink, .num_links = ARRAY_SIZE(cml_rt1011_rt5682_dailink), .codec_conf = rt1011_conf, @@ -442,20 +545,37 @@ static struct snd_soc_card snd_soc_card_cml = { static int snd_cml_rt1011_probe(struct platform_device *pdev) { + struct snd_soc_dai_link *dai_link; struct card_private *ctx; struct snd_soc_acpi_mach *mach; const char *platform_name; - int ret; + int ret, i; - ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; INIT_LIST_HEAD(&ctx->hdmi_pcm_list); - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; snd_soc_card_cml.dev = &pdev->dev; platform_name = mach->mach_params.platform; + dmi_check_system(sof_rt1011_quirk_table); + + dev_dbg(&pdev->dev, "sof_rt1011_quirk = %lx\n", sof_rt1011_quirk); + + /* when 4 speaker is available, update codec config */ + if (sof_rt1011_quirk & (SOF_RT1011_SPEAKER_TL | + SOF_RT1011_SPEAKER_TR)) { + for_each_card_prelinks(&snd_soc_card_cml, i, dai_link) { + if (!strcmp(dai_link->codecs[0].dai_name, + CML_RT1011_CODEC_DAI)) { + dai_link->codecs = ssp1_codec_4spk; + dai_link->num_codecs = ARRAY_SIZE(ssp1_codec_4spk); + } + } + } + /* set platform name for each dailink */ ret = snd_soc_fixup_dai_links_platform_name(&snd_soc_card_cml, platform_name); @@ -483,5 +603,7 @@ MODULE_DESCRIPTION("Cometlake Audio Machine driver - RT1011 and RT5682 in I2S mo MODULE_AUTHOR("Naveen Manohar <naveen.m@intel.com>"); MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>"); MODULE_AUTHOR("Shuming Fan <shumingf@realtek.com>"); +MODULE_AUTHOR("Mac Chiang <mac.chiang@intel.com>"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:cml_rt1011_rt5682"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/ehl_rt5660.c b/sound/soc/intel/boards/ehl_rt5660.c new file mode 100644 index 000000000000..d5235c294c4c --- /dev/null +++ b/sound/soc/intel/boards/ehl_rt5660.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * ehl_rt5660 - ASOC Machine driver for Elkhart Lake platforms + * with rt5660 codec + */ + +#include <linux/acpi.h> +#include <sound/core.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/gfp.h> +#include <sound/jack.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> + +#include "hda_dsp_common.h" +#include "../../codecs/rt5660.h" + +#define DUAL_CHANNEL 2 +#define HDMI_LINK_START 3 +#define HDMI_LINE_END 6 +#define NAME_SIZE 32 +#define IDISP_CODEC_MASK 0x4 + +struct sof_card_private { + struct list_head hdmi_pcm_list; + bool idisp_codec; +}; + +static const struct snd_kcontrol_new rt5660_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + /* There are two MICBIAS in rt5660, each for one MIC */ + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic2"), + SOC_DAPM_PIN_SWITCH("Line Out"), +}; + +static const struct snd_soc_dapm_widget rt5660_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic2", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +static const struct snd_soc_dapm_route rt5660_map[] = { + {"Speaker", NULL, "SPO"}, + + {"Headset Mic", NULL, "MICBIAS1"}, + {"Headset Mic2", NULL, "MICBIAS2"}, + + {"IN1P", NULL, "Headset Mic"}, + {"IN2P", NULL, "Headset Mic2"}, + + {"Line Out", NULL, "LOUTL"}, + {"Line Out", NULL, "LOUTR"}, + + {"DMic", NULL, "SoC DMIC"}, +}; + +struct sof_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +static int hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); + struct sof_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int card_late_probe(struct snd_soc_card *card) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(card); + struct sof_hdmi_pcm *pcm; + + if (list_empty(&ctx->hdmi_pcm_list)) + return -ENOENT; + + if (!ctx->idisp_codec) + return 0; + + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm, head); + + return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component); +} + +static int rt5660_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, + RT5660_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, + RT5660_PLL1_S_BCLK, + params_rate(params) * 50, + params_rate(params) * 512); + if (ret < 0) + dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret); + + return ret; +} + +static struct snd_soc_ops rt5660_ops = { + .hw_params = rt5660_hw_params, +}; + +SND_SOC_DAILINK_DEF(ssp0_pin, + DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin"))); + +SND_SOC_DAILINK_DEF(rt5660_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5660:00", "rt5660-aif1"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3"))); + +SND_SOC_DAILINK_DEF(dmic_pin, + DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin"))); +SND_SOC_DAILINK_DEF(dmic_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); +SND_SOC_DAILINK_DEF(dmic16k, + DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin"))); + +SND_SOC_DAILINK_DEF(idisp1_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin"))); +SND_SOC_DAILINK_DEF(idisp1_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1"))); + +SND_SOC_DAILINK_DEF(idisp2_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin"))); +SND_SOC_DAILINK_DEF(idisp2_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2"))); + +SND_SOC_DAILINK_DEF(idisp3_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin"))); +SND_SOC_DAILINK_DEF(idisp3_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3"))); + +SND_SOC_DAILINK_DEF(idisp4_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp4 Pin"))); +SND_SOC_DAILINK_DEF(idisp4_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi4"))); + +static struct snd_soc_dai_link ehl_rt5660_dailink[] = { + /* back ends */ + { + .name = "SSP0-Codec", + .id = 0, + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &rt5660_ops, + SND_SOC_DAILINK_REG(ssp0_pin, rt5660_codec, platform), + }, + { + .name = "dmic48k", + .id = 1, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform), + }, + { + .name = "dmic16k", + .id = 2, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(dmic16k, dmic_codec, platform), + }, + { + .name = "iDisp1", + .id = 5, + .init = hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform), + }, + { + .name = "iDisp2", + .id = 6, + .init = hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform), + }, + { + .name = "iDisp3", + .id = 7, + .init = hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform), + }, + { + .name = "iDisp4", + .id = 8, + .init = hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(idisp4_pin, idisp4_codec, platform), + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_ehl_rt5660 = { + .name = "ehl-rt5660", + .owner = THIS_MODULE, + .dai_link = ehl_rt5660_dailink, + .num_links = ARRAY_SIZE(ehl_rt5660_dailink), + .dapm_widgets = rt5660_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5660_widgets), + .dapm_routes = rt5660_map, + .num_dapm_routes = ARRAY_SIZE(rt5660_map), + .controls = rt5660_controls, + .num_controls = ARRAY_SIZE(rt5660_controls), + .fully_routed = true, + .late_probe = card_late_probe, +}; + +/* If hdmi codec is not supported, switch to use dummy codec */ +static void hdmi_link_init(struct snd_soc_card *card, + struct sof_card_private *ctx, + struct snd_soc_acpi_mach *mach) +{ + struct snd_soc_dai_link *link; + int i; + + if (mach->mach_params.common_hdmi_codec_drv && + (mach->mach_params.codec_mask & IDISP_CODEC_MASK)) { + ctx->idisp_codec = true; + return; + } + + /* + * if HDMI is not enabled in kernel config, or + * hdmi codec is not supported + */ + for (i = HDMI_LINK_START; i <= HDMI_LINE_END; i++) { + link = &card->dai_link[i]; + link->codecs[0].name = "snd-soc-dummy"; + link->codecs[0].dai_name = "snd-soc-dummy-dai"; + } +} + +static int snd_ehl_rt5660_probe(struct platform_device *pdev) +{ + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card = &snd_soc_card_ehl_rt5660; + struct sof_card_private *ctx; + int ret; + + card->dev = &pdev->dev; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + snd_soc_card_set_drvdata(card, ctx); + + mach = pdev->dev.platform_data; + ret = snd_soc_fixup_dai_links_platform_name(card, + mach->mach_params.platform); + if (ret) + return ret; + + hdmi_link_init(card, ctx, mach); + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static const struct platform_device_id ehl_board_ids[] = { + { .name = "ehl_rt5660" }, + { } +}; +MODULE_DEVICE_TABLE(platform, ehl_board_ids); + +static struct platform_driver snd_ehl_rt5660_driver = { + .driver = { + .name = "ehl_rt5660", + .pm = &snd_soc_pm_ops, + }, + .probe = snd_ehl_rt5660_probe, + .id_table = ehl_board_ids, +}; + +module_platform_driver(snd_ehl_rt5660_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Elkhartlake + rt5660 Machine driver"); +MODULE_AUTHOR("libin.yang@intel.com"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/glk_rt5682_max98357a.c b/sound/soc/intel/boards/glk_rt5682_max98357a.c index 8e947bad143c..cf0f89db3e20 100644 --- a/sound/soc/intel/boards/glk_rt5682_max98357a.c +++ b/sound/soc/intel/boards/glk_rt5682_max98357a.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2018 Intel Corporation. /* @@ -18,14 +18,18 @@ #include <sound/soc.h> #include <sound/soc-acpi.h> #include "../../codecs/rt5682.h" +#include "../../codecs/rt5682s.h" #include "../../codecs/hdac_hdmi.h" #include "hda_dsp_common.h" /* The platform clock outputs 19.2Mhz clock to codec as I2S MCLK */ #define GLK_PLAT_CLK_FREQ 19200000 #define RT5682_PLL_FREQ (48000 * 512) -#define GLK_REALTEK_CODEC_DAI "rt5682-aif1" +#define RT5682_DAI_NAME "rt5682-aif1" +#define RT5682S_DAI_NAME "rt5682s-aif1" #define GLK_MAXIM_CODEC_DAI "HiFi" +#define RT5682_DEV0_NAME "i2c-10EC5682:00" +#define RT5682S_DEV0_NAME "i2c-RTL5682:00" #define MAXIM_DEV0_NAME "MX98357A:00" #define DUAL_CHANNEL 2 #define QUAD_CHANNEL 4 @@ -43,6 +47,7 @@ struct glk_card_private { struct snd_soc_jack geminilake_headset; struct list_head hdmi_pcm_list; bool common_hdmi_codec_drv; + int is_rt5682s; }; enum { @@ -73,6 +78,17 @@ static const struct snd_soc_dapm_widget geminilake_widgets[] = { SND_SOC_DAPM_SPK("HDMI3", NULL), }; +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static const struct snd_soc_dapm_route geminilake_map[] = { /* HP jack connectors - unknown if we have jack detection */ { "Headphone Jack", NULL, "HPOL" }, @@ -136,12 +152,22 @@ static int geminilake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, static int geminilake_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) { struct glk_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct snd_soc_jack *jack; - int ret; + int pll_id, pll_source, clk_id, ret; + + if (ctx->is_rt5682s) { + pll_id = RT5682S_PLL2; + pll_source = RT5682S_PLL_S_MCLK; + clk_id = RT5682S_SCLK_S_PLL2; + } else { + pll_id = RT5682_PLL1; + pll_source = RT5682_PLL1_S_MCLK; + clk_id = RT5682_SCLK_S_PLL1; + } - ret = snd_soc_dai_set_pll(codec_dai, 0, RT5682_PLL1_S_MCLK, + ret = snd_soc_dai_set_pll(codec_dai, pll_id, pll_source, GLK_PLAT_CLK_FREQ, RT5682_PLL_FREQ); if (ret < 0) { dev_err(rtd->dev, "can't set codec pll: %d\n", ret); @@ -149,7 +175,7 @@ static int geminilake_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) } /* Configure sysclk for codec */ - ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, + ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); if (ret < 0) dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); @@ -158,10 +184,12 @@ static int geminilake_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, - &ctx->geminilake_headset, NULL, 0); + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, + &ctx->geminilake_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); return ret; @@ -187,8 +215,8 @@ static int geminilake_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) static int geminilake_rt5682_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; /* Set valid bitmask & configuration for I2S in 24 bit */ @@ -208,7 +236,7 @@ static struct snd_soc_ops geminilake_rt5682_ops = { static int geminilake_hdmi_init(struct snd_soc_pcm_runtime *rtd) { struct glk_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct glk_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -225,7 +253,7 @@ static int geminilake_hdmi_init(struct snd_soc_pcm_runtime *rtd) static int geminilake_rt5682_fe_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; struct snd_soc_dapm_context *dapm; int ret; @@ -344,9 +372,12 @@ SND_SOC_DAILINK_DEF(ssp1_codec, SND_SOC_DAILINK_DEF(ssp2_pin, DAILINK_COMP_ARRAY(COMP_CPU("SSP2 Pin"))); -SND_SOC_DAILINK_DEF(ssp2_codec, - DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", - GLK_REALTEK_CODEC_DAI))); +SND_SOC_DAILINK_DEF(ssp2_codec_5682, + DAILINK_COMP_ARRAY(COMP_CODEC(RT5682_DEV0_NAME, + RT5682_DAI_NAME))); +SND_SOC_DAILINK_DEF(ssp2_codec_5682s, + DAILINK_COMP_ARRAY(COMP_CODEC(RT5682S_DEV0_NAME, + RT5682S_DAI_NAME))); SND_SOC_DAILINK_DEF(dmic_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin"))); @@ -407,8 +438,9 @@ static struct snd_soc_dai_link geminilake_dais[] = { .name = "Glk Audio Echo Reference cap", .stream_name = "Echoreference Capture", .init = NULL, - .capture_only = 1, + .dpcm_capture = 1, .nonatomic = 1, + .dynamic = 1, SND_SOC_DAILINK_REG(echoref, dummy, platform), }, [GLK_DPCM_AUDIO_REF_CP] = { @@ -472,7 +504,7 @@ static struct snd_soc_dai_link geminilake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = geminilake_ssp_fixup, .dpcm_playback = 1, @@ -485,13 +517,13 @@ static struct snd_soc_dai_link geminilake_dais[] = { .no_pcm = 1, .init = geminilake_rt5682_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = geminilake_ssp_fixup, .ops = &geminilake_rt5682_ops, .dpcm_playback = 1, .dpcm_capture = 1, - SND_SOC_DAILINK_REG(ssp2_pin, ssp2_codec, platform), + SND_SOC_DAILINK_REG(ssp2_pin, ssp2_codec_5682, platform), }, { .name = "dmic01", @@ -552,8 +584,7 @@ static int glk_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &geminilake_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &geminilake_hdmi[i]); if (err) return err; @@ -591,20 +622,36 @@ static int geminilake_audio_probe(struct platform_device *pdev) struct snd_soc_acpi_mach *mach; const char *platform_name; struct snd_soc_card *card; - int ret; + int ret, i; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; + /* Detect the headset codec variant */ + if (acpi_dev_present("RTL5682", NULL, -1)) { + /* ALC5682I-VS is detected */ + ctx->is_rt5682s = 1; + + for (i = 0; i < glk_audio_card_rt5682_m98357a.num_links; i++) { + if (strcmp(geminilake_dais[i].name, "SSP2-Codec")) + continue; + + /* update the dai link to use rt5682s codec */ + geminilake_dais[i].codecs = ssp2_codec_5682s; + geminilake_dais[i].num_codecs = ARRAY_SIZE(ssp2_codec_5682s); + break; + } + } + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); card = &glk_audio_card_rt5682_m98357a; card->dev = &pdev->dev; snd_soc_card_set_drvdata(card, ctx); - /* override plaform name, if required */ - mach = (&pdev->dev)->platform_data; + /* override platform name, if required */ + mach = pdev->dev.platform_data; platform_name = mach->mach_params.platform; ret = snd_soc_fixup_dai_links_platform_name(card, platform_name); @@ -618,12 +665,13 @@ static int geminilake_audio_probe(struct platform_device *pdev) static const struct platform_device_id glk_board_ids[] = { { - .name = "glk_rt5682_max98357a", + .name = "glk_rt5682_mx98357a", .driver_data = (kernel_ulong_t)&glk_audio_card_rt5682_m98357a, }, { } }; +MODULE_DEVICE_TABLE(platform, glk_board_ids); static struct platform_driver geminilake_audio = { .probe = geminilake_audio_probe, @@ -640,4 +688,4 @@ MODULE_DESCRIPTION("Geminilake Audio Machine driver-RT5682 & MAX98357A in I2S mo MODULE_AUTHOR("Naveen Manohar <naveen.m@intel.com>"); MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:glk_rt5682_max98357a"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/haswell.c b/sound/soc/intel/boards/haswell.c deleted file mode 100644 index 3dadf9bff796..000000000000 --- a/sound/soc/intel/boards/haswell.c +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Haswell Lynxpoint SST Audio - * - * Copyright (C) 2013, Intel Corporation. All rights reserved. - */ - -#include <linux/module.h> -#include <linux/platform_device.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> -#include <sound/soc-acpi.h> -#include <sound/pcm_params.h> - -#include "../common/sst-dsp.h" -#include "../haswell/sst-haswell-ipc.h" - -#include "../../codecs/rt5640.h" - -/* Haswell ULT platforms have a Headphone and Mic jack */ -static const struct snd_soc_dapm_widget haswell_widgets[] = { - SND_SOC_DAPM_HP("Headphones", NULL), - SND_SOC_DAPM_MIC("Mic", NULL), -}; - -static const struct snd_soc_dapm_route haswell_rt5640_map[] = { - - {"Headphones", NULL, "HPOR"}, - {"Headphones", NULL, "HPOL"}, - {"IN2P", NULL, "Mic"}, - - /* CODEC BE connections */ - {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, - {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, -}; - -static int haswell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct snd_interval *rate = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = hw_param_interval(params, - SNDRV_PCM_HW_PARAM_CHANNELS); - - /* The ADSP will covert the FE rate to 48k, stereo */ - rate->min = rate->max = 48000; - channels->min = channels->max = 2; - - /* set SSP0 to 16 bit */ - params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); - return 0; -} - -static int haswell_rt5640_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, 12288000, - SND_SOC_CLOCK_IN); - - if (ret < 0) { - dev_err(rtd->dev, "can't set codec sysclk configuration\n"); - return ret; - } - - /* set correct codec filter for DAI format and clock config */ - snd_soc_component_update_bits(codec_dai->component, 0x83, 0xffff, 0x8000); - - return ret; -} - -static const struct snd_soc_ops haswell_rt5640_ops = { - .hw_params = haswell_rt5640_hw_params, -}; - -static int haswell_rtd_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct sst_pdata *pdata = dev_get_platdata(component->dev); - struct sst_hsw *haswell = pdata->dsp; - int ret; - - /* Set ADSP SSP port settings */ - ret = sst_hsw_device_set_config(haswell, SST_HSW_DEVICE_SSP_0, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, - SST_HSW_DEVICE_CLOCK_MASTER, 9); - if (ret < 0) { - dev_err(rtd->dev, "failed to set device config\n"); - return ret; - } - - return 0; -} - -SND_SOC_DAILINK_DEF(dummy, - DAILINK_COMP_ARRAY(COMP_DUMMY())); - -SND_SOC_DAILINK_DEF(system, - DAILINK_COMP_ARRAY(COMP_CPU("System Pin"))); - -SND_SOC_DAILINK_DEF(offload0, - DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin"))); - -SND_SOC_DAILINK_DEF(offload1, - DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin"))); - -SND_SOC_DAILINK_DEF(loopback, - DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin"))); - -SND_SOC_DAILINK_DEF(codec, - DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT33CA:00", "rt5640-aif1"))); - -SND_SOC_DAILINK_DEF(platform, - DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio"))); - -static struct snd_soc_dai_link haswell_rt5640_dais[] = { - /* Front End DAI links */ - { - .name = "System", - .stream_name = "System Playback/Capture", - .dynamic = 1, - .init = haswell_rtd_init, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - .dpcm_capture = 1, - SND_SOC_DAILINK_REG(system, dummy, platform), - }, - { - .name = "Offload0", - .stream_name = "Offload0 Playback", - .dynamic = 1, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - SND_SOC_DAILINK_REG(offload0, dummy, platform), - }, - { - .name = "Offload1", - .stream_name = "Offload1 Playback", - .dynamic = 1, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_playback = 1, - SND_SOC_DAILINK_REG(offload1, dummy, platform), - }, - { - .name = "Loopback", - .stream_name = "Loopback", - .dynamic = 1, - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_capture = 1, - SND_SOC_DAILINK_REG(loopback, dummy, platform), - }, - - /* Back End DAI links */ - { - /* SSP0 - Codec */ - .name = "Codec", - .id = 0, - .no_pcm = 1, - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, - .ignore_suspend = 1, - .ignore_pmdown_time = 1, - .be_hw_params_fixup = haswell_ssp0_fixup, - .ops = &haswell_rt5640_ops, - .dpcm_playback = 1, - .dpcm_capture = 1, - SND_SOC_DAILINK_REG(dummy, codec, dummy), - }, -}; - -/* audio machine driver for Haswell Lynxpoint DSP + RT5640 */ -static struct snd_soc_card haswell_rt5640 = { - .name = "haswell-rt5640", - .owner = THIS_MODULE, - .dai_link = haswell_rt5640_dais, - .num_links = ARRAY_SIZE(haswell_rt5640_dais), - .dapm_widgets = haswell_widgets, - .num_dapm_widgets = ARRAY_SIZE(haswell_widgets), - .dapm_routes = haswell_rt5640_map, - .num_dapm_routes = ARRAY_SIZE(haswell_rt5640_map), - .fully_routed = true, -}; - -static int haswell_audio_probe(struct platform_device *pdev) -{ - struct snd_soc_acpi_mach *mach; - int ret; - - haswell_rt5640.dev = &pdev->dev; - - /* override plaform name, if required */ - mach = (&pdev->dev)->platform_data; - ret = snd_soc_fixup_dai_links_platform_name(&haswell_rt5640, - mach->mach_params.platform); - if (ret) - return ret; - - return devm_snd_soc_register_card(&pdev->dev, &haswell_rt5640); -} - -static struct platform_driver haswell_audio = { - .probe = haswell_audio_probe, - .driver = { - .name = "haswell-audio", - }, -}; - -module_platform_driver(haswell_audio) - -/* Module information */ -MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); -MODULE_DESCRIPTION("Intel SST Audio for Haswell Lynxpoint"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:haswell-audio"); diff --git a/sound/soc/intel/boards/hda_dsp_common.c b/sound/soc/intel/boards/hda_dsp_common.c index 9179f07f9ee4..04b7d4f7f9e2 100644 --- a/sound/soc/intel/boards/hda_dsp_common.c +++ b/sound/soc/intel/boards/hda_dsp_common.c @@ -1,7 +1,8 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // // Copyright(c) 2019 Intel Corporation. All rights reserved. +#include <linux/module.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/hda_codec.h> @@ -10,12 +11,14 @@ #include "hda_dsp_common.h" +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) + /* * Search card topology and return PCM device number * matching Nth HDMI device (zero-based index). */ -struct snd_pcm *hda_dsp_hdmi_pcm_handle(struct snd_soc_card *card, - int hdmi_idx) +static struct snd_pcm *hda_dsp_hdmi_pcm_handle(struct snd_soc_card *card, + int hdmi_idx) { struct snd_soc_pcm_runtime *rtd; struct snd_pcm *spcm; @@ -34,7 +37,6 @@ struct snd_pcm *hda_dsp_hdmi_pcm_handle(struct snd_soc_card *card, return NULL; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) /* * Search card topology and register HDMI PCM related controls * to codec driver. @@ -52,7 +54,7 @@ int hda_dsp_hdmi_build_controls(struct snd_soc_card *card, return -EINVAL; hda_pvt = snd_soc_component_get_drvdata(comp); - hcodec = &hda_pvt->codec; + hcodec = hda_pvt->codec; list_for_each_entry(hpcm, &hcodec->pcm_list_head, list) { spcm = hda_dsp_hdmi_pcm_handle(card, i); @@ -60,13 +62,13 @@ int hda_dsp_hdmi_build_controls(struct snd_soc_card *card, hpcm->pcm = spcm; hpcm->device = spcm->device; dev_dbg(card->dev, - "%s: mapping HDMI converter %d to PCM %d (%p)\n", - __func__, i, hpcm->device, spcm); + "mapping HDMI converter %d to PCM %d (%p)\n", + i, hpcm->device, spcm); } else { hpcm->pcm = NULL; hpcm->device = SNDRV_PCM_INVALID_DEVICE; dev_warn(card->dev, - "%s: no PCM in topology for HDMI converter %d\n\n", + "%s: no PCM in topology for HDMI converter %d\n", __func__, i); } i++; @@ -81,5 +83,9 @@ int hda_dsp_hdmi_build_controls(struct snd_soc_card *card, return err; } +EXPORT_SYMBOL_NS(hda_dsp_hdmi_build_controls, SND_SOC_INTEL_HDA_DSP_COMMON); #endif + +MODULE_DESCRIPTION("ASoC Intel HDMI helpers"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/boards/hda_dsp_common.h b/sound/soc/intel/boards/hda_dsp_common.h index 431f7f09dccb..ea4ae9285cf0 100644 --- a/sound/soc/intel/boards/hda_dsp_common.h +++ b/sound/soc/intel/boards/hda_dsp_common.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright(c) 2019 Intel Corporation. */ @@ -15,9 +15,6 @@ #include <sound/hda_i915.h> #include "../../codecs/hdac_hda.h" -struct snd_pcm *hda_dsp_hdmi_pcm_handle(struct snd_soc_card *card, - int hdmi_idx); - #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) int hda_dsp_hdmi_build_controls(struct snd_soc_card *card, struct snd_soc_component *comp); diff --git a/sound/soc/intel/boards/hsw_rt5640.c b/sound/soc/intel/boards/hsw_rt5640.c new file mode 100644 index 000000000000..050c53ebd6ba --- /dev/null +++ b/sound/soc/intel/boards/hsw_rt5640.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sound card driver for Intel Haswell Lynx Point with Realtek 5640 + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../codecs/rt5640.h" + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route card_routes[] = { + {"Headphones", NULL, "HPOR"}, + {"Headphones", NULL, "HPOL"}, + {"IN2P", NULL, "Mic"}, + + /* CODEC BE connections */ + {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, + {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, +}; + +static int codec_link_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + /* The ADSP will convert the FE rate to 48k, stereo. */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + /* Set SSP0 to 16 bit. */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static int codec_link_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, 12288000, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "set codec sysclk failed: %d\n", ret); + return ret; + } + + /* Set correct codec filter for DAI format and clock config. */ + snd_soc_component_update_bits(codec_dai->component, 0x83, 0xffff, 0x8000); + + return ret; +} + +static const struct snd_soc_ops codec_link_ops = { + .hw_params = codec_link_hw_params, +}; + +SND_SOC_DAILINK_DEF(system, DAILINK_COMP_ARRAY(COMP_CPU("System Pin"))); +SND_SOC_DAILINK_DEF(offload0, DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin"))); +SND_SOC_DAILINK_DEF(offload1, DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin"))); +SND_SOC_DAILINK_DEF(loopback, DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin"))); + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); +SND_SOC_DAILINK_DEF(codec, DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT33CA:00", "rt5640-aif1"))); +SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio"))); +SND_SOC_DAILINK_DEF(ssp0_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port"))); + +static struct snd_soc_dai_link card_dai_links[] = { + /* Front End DAI links */ + { + .name = "System", + .stream_name = "System Playback/Capture", + .nonatomic = 1, + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(system, dummy, platform), + }, + { + .name = "Offload0", + .stream_name = "Offload0 Playback", + .nonatomic = 1, + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(offload0, dummy, platform), + }, + { + .name = "Offload1", + .stream_name = "Offload1 Playback", + .nonatomic = 1, + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(offload1, dummy, platform), + }, + { + .name = "Loopback", + .stream_name = "Loopback", + .nonatomic = 1, + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(loopback, dummy, platform), + }, + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "Codec", + .id = 0, + .nonatomic = 1, + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = codec_link_hw_params_fixup, + .ops = &codec_link_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(ssp0_port, codec, platform), + }, +}; + +static struct snd_soc_card hsw_rt5640_card = { + .name = "haswell-rt5640", + .owner = THIS_MODULE, + .dai_link = card_dai_links, + .num_links = ARRAY_SIZE(card_dai_links), + .dapm_widgets = card_widgets, + .num_dapm_widgets = ARRAY_SIZE(card_widgets), + .dapm_routes = card_routes, + .num_dapm_routes = ARRAY_SIZE(card_routes), + .fully_routed = true, +}; + +static int hsw_rt5640_probe(struct platform_device *pdev) +{ + struct snd_soc_acpi_mach *mach; + struct device *dev = &pdev->dev; + int ret; + + hsw_rt5640_card.dev = dev; + mach = dev_get_platdata(dev); + + ret = snd_soc_fixup_dai_links_platform_name(&hsw_rt5640_card, mach->mach_params.platform); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, &hsw_rt5640_card); +} + +static struct platform_driver hsw_rt5640_driver = { + .probe = hsw_rt5640_probe, + .driver = { + .name = "hsw_rt5640", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(hsw_rt5640_driver) + +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Sound card driver for Intel Haswell Lynx Point with Realtek 5640"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:hsw_rt5640"); diff --git a/sound/soc/intel/boards/kbl_da7219_max98357a.c b/sound/soc/intel/boards/kbl_da7219_max98357a.c index bc7f9a9ce9af..329457e3e3a2 100644 --- a/sound/soc/intel/boards/kbl_da7219_max98357a.c +++ b/sound/soc/intel/boards/kbl_da7219_max98357a.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2017-18 Intel Corporation. /* @@ -44,6 +44,7 @@ struct kbl_codec_private { enum { KBL_DPCM_AUDIO_PB = 0, KBL_DPCM_AUDIO_CP, + KBL_DPCM_AUDIO_REF_CP, KBL_DPCM_AUDIO_DMIC_CP, KBL_DPCM_AUDIO_HDMI1_PB, KBL_DPCM_AUDIO_HDMI2_PB, @@ -90,13 +91,25 @@ static const struct snd_soc_dapm_widget kabylake_widgets[] = { SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_SPK("Spk", NULL), SND_SOC_DAPM_MIC("SoC DMIC", NULL), - SND_SOC_DAPM_SPK("DP", NULL), - SND_SOC_DAPM_SPK("HDMI", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), }; +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static const struct snd_soc_dapm_route kabylake_map[] = { { "Headphone Jack", NULL, "HPL" }, { "Headphone Jack", NULL, "HPR" }, @@ -108,8 +121,9 @@ static const struct snd_soc_dapm_route kabylake_map[] = { { "MIC", NULL, "Headset Mic" }, { "DMic", NULL, "SoC DMIC" }, - { "HDMI", NULL, "hif5 Output" }, - { "DP", NULL, "hif6 Output" }, + {"HDMI1", NULL, "hif5-0 Output"}, + {"HDMI2", NULL, "hif6-0 Output"}, + {"HDMI3", NULL, "hif7-0 Output"}, /* CODEC BE connections */ { "HiFi Playback", NULL, "ssp0 Tx" }, @@ -159,8 +173,8 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) { struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct snd_soc_jack *jack; int ret; @@ -176,10 +190,12 @@ static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(kabylake_audio_card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, - &ctx->kabylake_headset, NULL, 0); + ret = snd_soc_card_jack_new_pins(kabylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, + &ctx->kabylake_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); return ret; @@ -203,7 +219,7 @@ static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device) { struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct kbl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -236,7 +252,7 @@ static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) static int kabylake_da7219_fe_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; dapm = snd_soc_component_get_dapm(component); snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); @@ -336,12 +352,49 @@ static struct snd_soc_ops kabylake_dmic_ops = { .startup = kabylake_dmic_startup, }; +static unsigned int rates_16000[] = { + 16000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16000 = { + .count = ARRAY_SIZE(rates_16000), + .list = rates_16000, +}; + +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + +static int kabylake_refcap_startup(struct snd_pcm_substream *substream) +{ + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_16000); +} + +static struct snd_soc_ops skylake_refcap_ops = { + .startup = kabylake_refcap_startup, +}; + SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); SND_SOC_DAILINK_DEF(system, DAILINK_COMP_ARRAY(COMP_CPU("System Pin"))); +SND_SOC_DAILINK_DEF(reference, + DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin"))); + SND_SOC_DAILINK_DEF(dmic, DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin"))); @@ -416,6 +469,16 @@ static struct snd_soc_dai_link kabylake_dais[] = { .ops = &kabylake_da7219_fe_ops, SND_SOC_DAILINK_REG(system, dummy, platform), }, + [KBL_DPCM_AUDIO_REF_CP] = { + .name = "Kbl Audio Reference cap", + .stream_name = "Wake on Voice", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &skylake_refcap_ops, + SND_SOC_DAILINK_REG(reference, dummy, platform), + }, [KBL_DPCM_AUDIO_DMIC_CP] = { .name = "Kbl Audio DMIC cap", .stream_name = "dmiccap", @@ -468,7 +531,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -481,7 +544,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .init = kabylake_da7219_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -537,8 +600,7 @@ static int kabylake_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &skylake_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &skylake_hdmi[i]); if (err) return err; @@ -594,12 +656,13 @@ static int kabylake_audio_probe(struct platform_device *pdev) static const struct platform_device_id kbl_board_ids[] = { { - .name = "kbl_da7219_max98357a", + .name = "kbl_da7219_mx98357a", .driver_data = (kernel_ulong_t)&kabylake_audio_card_da7219_m98357a, }, { } }; +MODULE_DEVICE_TABLE(platform, kbl_board_ids); static struct platform_driver kabylake_audio = { .probe = kabylake_audio_probe, @@ -616,4 +679,3 @@ module_platform_driver(kabylake_audio) MODULE_DESCRIPTION("Audio Machine driver-DA7219 & MAX98357A in I2S mode"); MODULE_AUTHOR("Naveen Manohar <naveen.m@intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:kbl_da7219_max98357a"); diff --git a/sound/soc/intel/boards/kbl_da7219_max98927.c b/sound/soc/intel/boards/kbl_da7219_max98927.c index 7a13e9b35187..362579f25835 100644 --- a/sound/soc/intel/boards/kbl_da7219_max98927.c +++ b/sound/soc/intel/boards/kbl_da7219_max98927.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2018 Intel Corporation. /* @@ -111,13 +111,25 @@ static const struct snd_soc_dapm_widget kabylake_widgets[] = { SND_SOC_DAPM_SPK("Left Spk", NULL), SND_SOC_DAPM_SPK("Right Spk", NULL), SND_SOC_DAPM_MIC("SoC DMIC", NULL), - SND_SOC_DAPM_SPK("DP", NULL), - SND_SOC_DAPM_SPK("HDMI", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), }; +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static const struct snd_soc_dapm_route kabylake_map[] = { /* speaker */ { "Left Spk", NULL, "Left BE_OUT" }, @@ -126,8 +138,9 @@ static const struct snd_soc_dapm_route kabylake_map[] = { /* other jacks */ { "DMic", NULL, "SoC DMIC" }, - { "HDMI", NULL, "hif5 Output" }, - { "DP", NULL, "hif6 Output" }, + {"HDMI1", NULL, "hif5-0 Output"}, + {"HDMI2", NULL, "hif6-0 Output"}, + {"HDMI3", NULL, "hif7-0 Output"}, /* CODEC BE connections */ { "Left HiFi Playback", NULL, "ssp0 Tx" }, @@ -175,11 +188,11 @@ static const struct snd_soc_dapm_route kabylake_ssp1_map[] = { static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *runtime = substream->private_data; + struct snd_soc_pcm_runtime *runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; int ret, j; - for (j = 0; j < runtime->num_codecs; j++) { - struct snd_soc_dai *codec_dai = runtime->codec_dais[j]; + for_each_rtd_codec_dais(runtime, j, codec_dai) { if (!strcmp(codec_dai->component->name, MAX98927_DEV0_NAME)) { ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16); @@ -197,7 +210,7 @@ static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream, } if (!strcmp(codec_dai->component->name, MAX98373_DEV0_NAME)) { ret = snd_soc_dai_set_tdm_slot(codec_dai, - 0x03, 3, 8, 24); + 0x30, 3, 8, 16); if (ret < 0) { dev_err(runtime->dev, "DEV0 TDM slot err:%d\n", ret); @@ -206,10 +219,10 @@ static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream, } if (!strcmp(codec_dai->component->name, MAX98373_DEV1_NAME)) { ret = snd_soc_dai_set_tdm_slot(codec_dai, - 0x0C, 3, 8, 24); + 0xC0, 3, 8, 16); if (ret < 0) { dev_err(runtime->dev, - "DEV0 TDM slot err:%d\n", ret); + "DEV1 TDM slot err:%d\n", ret); return ret; } } @@ -220,11 +233,11 @@ static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream, static int kabylake_ssp0_trigger(struct snd_pcm_substream *substream, int cmd) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; int j, ret; - for (j = 0; j < rtd->num_codecs; j++) { - struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + for_each_rtd_codec_dais(rtd, j, codec_dai) { const char *name = codec_dai->component->name; struct snd_soc_component *component = codec_dai->component; struct snd_soc_dapm_context *dapm = @@ -282,36 +295,40 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_interval *chan = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); - struct snd_soc_dpcm *dpcm = container_of( - params, struct snd_soc_dpcm, hw_params); - struct snd_soc_dai_link *fe_dai_link = dpcm->fe->dai_link; - struct snd_soc_dai_link *be_dai_link = dpcm->be->dai_link; + struct snd_soc_dpcm *dpcm, *rtd_dpcm = NULL; /* - * Topology for kblda7219m98373 & kblmax98373 supports only S24_LE, - * where as kblda7219m98927 & kblmax98927 supports S16_LE by default. - * Skipping the port wise FE and BE configuration for kblda7219m98373 & - * kblmax98373 as the topology (FE & BE) supports S24_LE only. + * The following loop will be called only for playback stream + * In this platform, there is only one playback device on every SSP */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { + rtd_dpcm = dpcm; + break; + } - if (!strcmp(rtd->card->name, "kblda7219m98373") || - !strcmp(rtd->card->name, "kblmax98373")) { - /* The ADSP will convert the FE rate to 48k, stereo */ - rate->min = rate->max = 48000; - chan->min = chan->max = DUAL_CHANNEL; - - /* set SSP to 24 bit */ - snd_mask_none(fmt); - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); - return 0; + /* + * This following loop will be called only for capture stream + * In this platform, there is only one capture device on every SSP + */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) { + rtd_dpcm = dpcm; + break; } + if (!rtd_dpcm) + return -EINVAL; + + /* + * The above 2 loops are mutually exclusive based on the stream direction, + * thus rtd_dpcm variable will never be overwritten + */ + /* * The ADSP will convert the FE rate to 48k, stereo, 24 bit */ - if (!strcmp(fe_dai_link->name, "Kbl Audio Port") || - !strcmp(fe_dai_link->name, "Kbl Audio Headset Playback") || - !strcmp(fe_dai_link->name, "Kbl Audio Capture Port")) { + if (!strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Port") || + !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Headset Playback") || + !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Capture Port")) { rate->min = rate->max = 48000; chan->min = chan->max = 2; snd_mask_none(fmt); @@ -322,7 +339,7 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, * The speaker on the SSP0 supports S16_LE and not S24_LE. * thus changing the mask here */ - if (!strcmp(be_dai_link->name, "SSP0-Codec")) + if (!strcmp(rtd_dpcm->be->dai_link->name, "SSP0-Codec")) snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); return 0; @@ -331,7 +348,7 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) { struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; struct snd_soc_jack *jack; struct snd_soc_card *card = rtd->card; int ret; @@ -348,10 +365,12 @@ static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(kabylake_audio_card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, - &ctx->kabylake_headset, NULL, 0); + ret = snd_soc_card_jack_new_pins(kabylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, + &ctx->kabylake_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); return ret; @@ -381,7 +400,7 @@ static int kabylake_dmic_init(struct snd_soc_pcm_runtime *rtd) static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device) { struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct kbl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -414,7 +433,7 @@ static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) static int kabylake_da7219_fe_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; dapm = snd_soc_component_get_dapm(component); snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); @@ -455,31 +474,20 @@ static struct snd_pcm_hw_constraint_list constraints_channels_quad = { static int kbl_fe_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *soc_rt = substream->private_data; /* * On this platform for PCM device we support, * 48Khz * stereo + * 16 bit audio */ runtime->hw.channels_max = DUAL_CHANNEL; snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &constraints_channels); - /* - * Setup S24_LE (32 bit container and 24 bit valid data) for - * kblda7219m98373 & kblmax98373. For kblda7219m98927 & - * kblmax98927 keeping it as 16/16 due to topology FW dependency. - */ - if (!strcmp(soc_rt->card->name, "kblda7219m98373") || - !strcmp(soc_rt->card->name, "kblmax98373")) { - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_LE; - snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); - - } else { - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); - } + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); @@ -512,23 +520,11 @@ static int kabylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, static int kabylake_dmic_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *soc_rt = substream->private_data; runtime->hw.channels_min = runtime->hw.channels_max = QUAD_CHANNEL; snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &constraints_channels_quad); - /* - * Topology for kblda7219m98373 & kblmax98373 supports only S24_LE. - * The DMIC also configured for S24_LE. Forcing the DMIC format to - * S24_LE due to the topology FW dependency. - */ - if (!strcmp(soc_rt->card->name, "kblda7219m98373") || - !strcmp(soc_rt->card->name, "kblmax98373")) { - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_LE; - snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); - } - return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); } @@ -692,7 +688,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .name = "Kbl Audio Echo Reference cap", .stream_name = "Echoreference Capture", .init = NULL, - .capture_only = 1, + .dpcm_capture = 1, .nonatomic = 1, SND_SOC_DAILINK_REG(echoref, dummy, platform), }, @@ -781,7 +777,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .dpcm_playback = 1, .dpcm_capture = 1, .ignore_pmdown_time = 1, @@ -796,7 +792,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .init = kabylake_da7219_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -858,7 +854,7 @@ static struct snd_soc_dai_link kabylake_max98_927_373_dais[] = { .name = "Kbl Audio Echo Reference cap", .stream_name = "Echoreference Capture", .init = NULL, - .capture_only = 1, + .dpcm_capture = 1, .nonatomic = 1, SND_SOC_DAILINK_REG(echoref, dummy, platform), }, @@ -924,7 +920,7 @@ static struct snd_soc_dai_link kabylake_max98_927_373_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .dpcm_playback = 1, .dpcm_capture = 1, .ignore_pmdown_time = 1, @@ -982,8 +978,7 @@ static int kabylake_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &kabylake_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &kabylake_hdmi[i]); if (err) return err; @@ -1151,6 +1146,7 @@ static const struct platform_device_id kbl_board_ids[] = { }, { } }; +MODULE_DEVICE_TABLE(platform, kbl_board_ids); static struct platform_driver kabylake_audio = { .probe = kabylake_audio_probe, @@ -1167,7 +1163,3 @@ module_platform_driver(kabylake_audio) MODULE_DESCRIPTION("Audio KabyLake Machine driver for MAX98927/MAX98373 & DA7219"); MODULE_AUTHOR("Mac Chiang <mac.chiang@intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:kbl_da7219_max98927"); -MODULE_ALIAS("platform:kbl_max98927"); -MODULE_ALIAS("platform:kbl_da7219_max98373"); -MODULE_ALIAS("platform:kbl_max98373"); diff --git a/sound/soc/intel/boards/kbl_rt5660.c b/sound/soc/intel/boards/kbl_rt5660.c index e23dea9ab79a..2c7a547f63c9 100644 --- a/sound/soc/intel/boards/kbl_rt5660.c +++ b/sound/soc/intel/boards/kbl_rt5660.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2018-19 Canonical Corporation. /* @@ -157,7 +157,7 @@ static int kabylake_rt5660_codec_init(struct snd_soc_pcm_runtime *rtd) { int ret; struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); ret = devm_acpi_dev_add_driver_gpios(component->dev, acpi_rt5660_gpios); @@ -165,17 +165,17 @@ static int kabylake_rt5660_codec_init(struct snd_soc_pcm_runtime *rtd) dev_warn(component->dev, "Failed to add driver gpios\n"); /* Request rt5660 GPIO for lineout mute control, return if fails */ - ctx->gpio_lo_mute = devm_gpiod_get(component->dev, "lineout-mute", - GPIOD_OUT_HIGH); + ctx->gpio_lo_mute = gpiod_get(component->dev, "lineout-mute", + GPIOD_OUT_HIGH); if (IS_ERR(ctx->gpio_lo_mute)) { dev_err(component->dev, "Can't find GPIO_MUTE# gpio\n"); return PTR_ERR(ctx->gpio_lo_mute); } /* Create and initialize headphone jack, this jack is not mandatory, don't return if fails */ - ret = snd_soc_card_jack_new(rtd->card, "Lineout Jack", - SND_JACK_LINEOUT, &lineout_jack, - &lineout_jack_pin, 1); + ret = snd_soc_card_jack_new_pins(rtd->card, "Lineout Jack", + SND_JACK_LINEOUT, &lineout_jack, + &lineout_jack_pin, 1); if (ret) dev_warn(component->dev, "Can't create Lineout jack\n"); else { @@ -187,9 +187,9 @@ static int kabylake_rt5660_codec_init(struct snd_soc_pcm_runtime *rtd) } /* Create and initialize mic jack, this jack is not mandatory, don't return if fails */ - ret = snd_soc_card_jack_new(rtd->card, "Mic Jack", - SND_JACK_MICROPHONE, &mic_jack, - &mic_jack_pin, 1); + ret = snd_soc_card_jack_new_pins(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, &mic_jack, + &mic_jack_pin, 1); if (ret) dev_warn(component->dev, "Can't create mic jack\n"); else { @@ -207,10 +207,22 @@ static int kabylake_rt5660_codec_init(struct snd_soc_pcm_runtime *rtd) return 0; } +static void kabylake_rt5660_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); + + /* + * The .exit() can be reached without going through the .init() + * so explicitly test if the gpiod is valid + */ + if (!IS_ERR_OR_NULL(ctx->gpio_lo_mute)) + gpiod_put(ctx->gpio_lo_mute); +} + static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device) { struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct kbl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -243,8 +255,8 @@ static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) static int kabylake_rt5660_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, @@ -421,9 +433,10 @@ static struct snd_soc_dai_link kabylake_rt5660_dais[] = { .id = 0, .no_pcm = 1, .init = kabylake_rt5660_codec_init, + .exit = kabylake_rt5660_codec_exit, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp0_fixup, .ops = &kabylake_rt5660_ops, @@ -472,8 +485,7 @@ static int kabylake_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &skylake_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &skylake_hdmi[i]); if (err) return err; @@ -535,6 +547,7 @@ static const struct platform_device_id kbl_board_ids[] = { }, { } }; +MODULE_DEVICE_TABLE(platform, kbl_board_ids); static struct platform_driver kabylake_audio = { .probe = kabylake_audio_probe, @@ -551,4 +564,3 @@ module_platform_driver(kabylake_audio) MODULE_DESCRIPTION("Audio Machine driver-RT5660 in I2S mode"); MODULE_AUTHOR("Hui Wang <hui.wang@canonical.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:kbl_rt5660"); diff --git a/sound/soc/intel/boards/kbl_rt5663_max98927.c b/sound/soc/intel/boards/kbl_rt5663_max98927.c index d8f2ff7139a9..2d4224c5b152 100644 --- a/sound/soc/intel/boards/kbl_rt5663_max98927.c +++ b/sound/soc/intel/boards/kbl_rt5663_max98927.c @@ -151,6 +151,10 @@ static const struct snd_soc_dapm_route kabylake_map[] = { { "IN1N", NULL, "Headset Mic" }, { "DMic", NULL, "SoC DMIC" }, + {"HDMI1", NULL, "hif5-0 Output"}, + {"HDMI2", NULL, "hif6-0 Output"}, + {"HDMI3", NULL, "hif7-0 Output"}, + /* CODEC BE connections */ { "Left HiFi Playback", NULL, "ssp0 Tx" }, { "Right HiFi Playback", NULL, "ssp0 Tx" }, @@ -194,13 +198,25 @@ static const struct snd_kcontrol_new kabylake_5663_controls[] = { static const struct snd_soc_dapm_widget kabylake_5663_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_SPK("DP", NULL), - SND_SOC_DAPM_SPK("HDMI", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), }; +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static const struct snd_soc_dapm_route kabylake_5663_map[] = { { "Headphone Jack", NULL, "Platform Clock" }, { "Headphone Jack", NULL, "HPOL" }, @@ -211,8 +227,9 @@ static const struct snd_soc_dapm_route kabylake_5663_map[] = { { "IN1P", NULL, "Headset Mic" }, { "IN1N", NULL, "Headset Mic" }, - { "HDMI", NULL, "hif5 Output" }, - { "DP", NULL, "hif6 Output" }, + {"HDMI1", NULL, "hif5-0 Output"}, + {"HDMI2", NULL, "hif6-0 Output"}, + {"HDMI3", NULL, "hif7-0 Output"}, /* CODEC BE connections */ { "AIF Playback", NULL, "ssp1 Tx" }, @@ -242,7 +259,7 @@ static int kabylake_rt5663_fe_init(struct snd_soc_pcm_runtime *rtd) { int ret; struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; dapm = snd_soc_component_get_dapm(component); ret = snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); @@ -258,17 +275,19 @@ static int kabylake_rt5663_codec_init(struct snd_soc_pcm_runtime *rtd) { int ret; struct kbl_rt5663_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; struct snd_soc_jack *jack; /* * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(kabylake_audio_card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3, &ctx->kabylake_headset, - NULL, 0); + ret = snd_soc_card_jack_new_pins(kabylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &ctx->kabylake_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); return ret; @@ -305,7 +324,7 @@ static int kabylake_rt5663_max98927_codec_init(struct snd_soc_pcm_runtime *rtd) static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device) { struct kbl_rt5663_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct kbl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -401,17 +420,40 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_interval *chan = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); - struct snd_soc_dpcm *dpcm = container_of( - params, struct snd_soc_dpcm, hw_params); - struct snd_soc_dai_link *fe_dai_link = dpcm->fe->dai_link; - struct snd_soc_dai_link *be_dai_link = dpcm->be->dai_link; + struct snd_soc_dpcm *dpcm, *rtd_dpcm = NULL; + + /* + * The following loop will be called only for playback stream + * In this platform, there is only one playback device on every SSP + */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { + rtd_dpcm = dpcm; + break; + } + + /* + * This following loop will be called only for capture stream + * In this platform, there is only one capture device on every SSP + */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) { + rtd_dpcm = dpcm; + break; + } + + if (!rtd_dpcm) + return -EINVAL; + + /* + * The above 2 loops are mutually exclusive based on the stream direction, + * thus rtd_dpcm variable will never be overwritten + */ /* * The ADSP will convert the FE rate to 48k, stereo, 24 bit */ - if (!strcmp(fe_dai_link->name, "Kbl Audio Port") || - !strcmp(fe_dai_link->name, "Kbl Audio Headset Playback") || - !strcmp(fe_dai_link->name, "Kbl Audio Capture Port")) { + if (!strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Port") || + !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Headset Playback") || + !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Capture Port")) { rate->min = rate->max = 48000; chan->min = chan->max = 2; snd_mask_none(fmt); @@ -421,7 +463,7 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, * The speaker on the SSP0 supports S16_LE and not S24_LE. * thus changing the mask here */ - if (!strcmp(be_dai_link->name, "SSP0-Codec")) + if (!strcmp(rtd_dpcm->be->dai_link->name, "SSP0-Codec")) snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); return 0; @@ -430,8 +472,8 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, static int kabylake_rt5663_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; /* use ASRC for internal clocks, as PLL rate isn't multiple of BCLK */ @@ -468,11 +510,11 @@ static int kabylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *codec_dai; int ret = 0, j; - for_each_rtd_codec_dai(rtd, j, codec_dai) { + for_each_rtd_codec_dais(rtd, j, codec_dai) { if (!strcmp(codec_dai->component->name, MAXIM_DEV0_NAME)) { /* * Use channel 4 and 5 for the first amp @@ -672,7 +714,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .name = "Kbl Audio Echo Reference cap", .stream_name = "Echoreference Capture", .init = NULL, - .capture_only = 1, + .dpcm_capture = 1, .nonatomic = 1, SND_SOC_DAILINK_REG(echoref, dummy, platform), }, @@ -738,7 +780,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -752,7 +794,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .init = kabylake_rt5663_max98927_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .ops = &kabylake_rt5663_ops, @@ -850,7 +892,7 @@ static struct snd_soc_dai_link kabylake_5663_dais[] = { .no_pcm = 1, .init = kabylake_rt5663_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .ops = &kabylake_rt5663_ops, @@ -890,8 +932,7 @@ static int kabylake_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &skylake_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &skylake_hdmi[i]); if (err) return err; @@ -962,7 +1003,7 @@ static int kabylake_audio_probe(struct platform_device *pdev) kabylake_audio_card->dev = &pdev->dev; snd_soc_card_set_drvdata(kabylake_audio_card, ctx); - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; if (mach) dmic_constraints = mach->mach_params.dmic_num == 2 ? &constraints_dmic_2ch : &constraints_dmic_channels; @@ -1010,6 +1051,7 @@ static const struct platform_device_id kbl_board_ids[] = { }, { } }; +MODULE_DEVICE_TABLE(platform, kbl_board_ids); static struct platform_driver kabylake_audio = { .probe = kabylake_audio_probe, @@ -1027,5 +1069,3 @@ MODULE_DESCRIPTION("Audio Machine driver-RT5663 & MAX98927 in I2S mode"); MODULE_AUTHOR("Naveen M <naveen.m@intel.com>"); MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:kbl_rt5663"); -MODULE_ALIAS("platform:kbl_rt5663_m98927"); diff --git a/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c b/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c index 96c814f36458..2c79fca57b19 100644 --- a/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c +++ b/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c @@ -145,6 +145,17 @@ static const struct snd_soc_dapm_widget kabylake_widgets[] = { }; +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static const struct snd_soc_dapm_route kabylake_map[] = { /* Headphones */ { "Headphone Jack", NULL, "Platform Clock" }, @@ -206,7 +217,7 @@ static struct snd_soc_codec_conf max98927_codec_conf[] = { static int kabylake_rt5663_fe_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; int ret; dapm = snd_soc_component_get_dapm(component); @@ -221,17 +232,19 @@ static int kabylake_rt5663_codec_init(struct snd_soc_pcm_runtime *rtd) { int ret; struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; struct snd_soc_jack *jack; /* * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(&kabylake_audio_card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3, &ctx->kabylake_headset, - NULL, 0); + ret = snd_soc_card_jack_new_pins(&kabylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &ctx->kabylake_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); return ret; @@ -255,7 +268,7 @@ static int kabylake_rt5663_codec_init(struct snd_soc_pcm_runtime *rtd) static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device) { struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct kbl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -336,22 +349,45 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_interval *chan = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); - struct snd_soc_dpcm *dpcm = container_of( - params, struct snd_soc_dpcm, hw_params); - struct snd_soc_dai_link *fe_dai_link = dpcm->fe->dai_link; - struct snd_soc_dai_link *be_dai_link = dpcm->be->dai_link; + struct snd_soc_dpcm *dpcm, *rtd_dpcm = NULL; + + /* + * The following loop will be called only for playback stream + * In this platform, there is only one playback device on every SSP + */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { + rtd_dpcm = dpcm; + break; + } + + /* + * This following loop will be called only for capture stream + * In this platform, there is only one capture device on every SSP + */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) { + rtd_dpcm = dpcm; + break; + } + + if (!rtd_dpcm) + return -EINVAL; + + /* + * The above 2 loops are mutually exclusive based on the stream direction, + * thus rtd_dpcm variable will never be overwritten + */ /* * The ADSP will convert the FE rate to 48k, stereo, 24 bit */ - if (!strcmp(fe_dai_link->name, "Kbl Audio Port") || - !strcmp(fe_dai_link->name, "Kbl Audio Headset Playback") || - !strcmp(fe_dai_link->name, "Kbl Audio Capture Port")) { + if (!strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Port") || + !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Headset Playback") || + !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Capture Port")) { rate->min = rate->max = 48000; chan->min = chan->max = 2; snd_mask_none(fmt); snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); - } else if (!strcmp(fe_dai_link->name, "Kbl Audio DMIC cap")) { + } else if (!strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio DMIC cap")) { if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2) chan->min = chan->max = 2; @@ -362,7 +398,7 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, * The speaker on the SSP0 supports S16_LE and not S24_LE. * thus changing the mask here */ - if (!strcmp(be_dai_link->name, "SSP0-Codec")) + if (!strcmp(rtd_dpcm->be->dai_link->name, "SSP0-Codec")) snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); return 0; @@ -371,8 +407,8 @@ static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, static int kabylake_rt5663_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; /* use ASRC for internal clocks, as PLL rate isn't multiple of BCLK */ @@ -395,11 +431,11 @@ static struct snd_soc_ops kabylake_rt5663_ops = { static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *codec_dai; int ret = 0, j; - for_each_rtd_codec_dai(rtd, j, codec_dai) { + for_each_rtd_codec_dais(rtd, j, codec_dai) { if (!strcmp(codec_dai->component->name, RT5514_DEV_NAME)) { ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0, 8, 16); if (ret < 0) { @@ -566,7 +602,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .name = "Kbl Audio Echo Reference cap", .stream_name = "Echoreference Capture", .init = NULL, - .capture_only = 1, + .dpcm_capture = 1, .nonatomic = 1, SND_SOC_DAILINK_REG(echoref, dummy, platform), }, @@ -616,7 +652,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -630,7 +666,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .init = kabylake_rt5663_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .ops = &kabylake_rt5663_ops, @@ -677,6 +713,8 @@ static int kabylake_set_bias_level(struct snd_soc_card *card, switch (level) { case SND_SOC_BIAS_PREPARE: if (dapm->bias_level == SND_SOC_BIAS_ON) { + if (!__clk_is_enabled(priv->mclk)) + return 0; dev_dbg(card->dev, "Disable mclk"); clk_disable_unprepare(priv->mclk); } else { @@ -718,8 +756,7 @@ static int kabylake_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP,pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &ctx->kabylake_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &ctx->kabylake_hdmi[i]); if (err) return err; @@ -772,7 +809,7 @@ static int kabylake_audio_probe(struct platform_device *pdev) kabylake_audio_card.dev = &pdev->dev; snd_soc_card_set_drvdata(&kabylake_audio_card, ctx); - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; if (mach) dmic_constraints = mach->mach_params.dmic_num == 2 ? &constraints_dmic_2ch : &constraints_dmic_channels; @@ -812,6 +849,7 @@ static const struct platform_device_id kbl_board_ids[] = { { .name = "kbl_r5514_5663_max" }, { } }; +MODULE_DEVICE_TABLE(platform, kbl_board_ids); static struct platform_driver kabylake_audio = { .probe = kabylake_audio_probe, @@ -828,4 +866,3 @@ module_platform_driver(kabylake_audio) MODULE_DESCRIPTION("Audio Machine driver-RT5663 RT5514 & MAX98927"); MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:kbl_r5514_5663_max"); diff --git a/sound/soc/intel/boards/skl_hda_dsp_common.c b/sound/soc/intel/boards/skl_hda_dsp_common.c index 78ff5f24c40e..e9cefa4ae56d 100644 --- a/sound/soc/intel/boards/skl_hda_dsp_common.c +++ b/sound/soc/intel/boards/skl_hda_dsp_common.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2015-18 Intel Corporation. /* @@ -150,17 +150,11 @@ int skl_hda_hdmi_jack_init(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &pcm->hdmi_jack, - NULL, 0); + SND_JACK_AVOUT, &pcm->hdmi_jack); if (err) return err; - err = snd_jack_add_new_kctl(pcm->hdmi_jack.jack, - jack_name, SND_JACK_AVOUT); - if (err) - dev_warn(component->dev, "failed creating Jack kctl\n"); - err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, &pcm->hdmi_jack); if (err < 0) diff --git a/sound/soc/intel/boards/skl_hda_dsp_common.h b/sound/soc/intel/boards/skl_hda_dsp_common.h index d6150670ca05..4b0b3959182e 100644 --- a/sound/soc/intel/boards/skl_hda_dsp_common.h +++ b/sound/soc/intel/boards/skl_hda_dsp_common.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright(c) 2015-18 Intel Corporation. */ @@ -33,6 +33,7 @@ struct skl_hda_private { int dai_index; const char *platform_name; bool common_hdmi_codec_drv; + bool idisp_codec; }; extern struct snd_soc_dai_link skl_hda_be_dai_links[HDA_DSP_MAX_BE_DAI_LINKS]; @@ -49,6 +50,10 @@ static inline int skl_hda_hdmi_build_controls(struct snd_soc_card *card) struct snd_soc_component *component; struct skl_hda_hdmi_pcm *pcm; + /* HDMI disabled, do not create controls */ + if (list_empty(&ctx->hdmi_pcm_list)) + return 0; + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct skl_hda_hdmi_pcm, head); component = pcm->codec_dai->component; diff --git a/sound/soc/intel/boards/skl_hda_dsp_generic.c b/sound/soc/intel/boards/skl_hda_dsp_generic.c index 11eaee9ae41f..879ebba52832 100644 --- a/sound/soc/intel/boards/skl_hda_dsp_generic.c +++ b/sound/soc/intel/boards/skl_hda_dsp_generic.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2015-18 Intel Corporation. /* @@ -61,6 +61,9 @@ static const struct snd_soc_dapm_route skl_hda_map[] = { { "Alt Analog CPU Capture", NULL, "Alt Analog Codec Capture" }, }; +SND_SOC_DAILINK_DEF(dummy_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai"))); + static int skl_hda_card_late_probe(struct snd_soc_card *card) { return skl_hda_hdmi_jack_init(card); @@ -72,10 +75,13 @@ skl_hda_add_dai_link(struct snd_soc_card *card, struct snd_soc_dai_link *link) struct skl_hda_private *ctx = snd_soc_card_get_drvdata(card); int ret = 0; - dev_dbg(card->dev, "%s: dai link name - %s\n", __func__, link->name); + dev_dbg(card->dev, "dai link name - %s\n", link->name); link->platforms->name = ctx->platform_name; link->nonatomic = 1; + if (!ctx->idisp_codec) + return 0; + if (strstr(link->name, "HDMI")) { ret = skl_hda_hdmi_add_pcm(card, ctx->pcm_count); @@ -110,17 +116,26 @@ static char hda_soc_components[30]; #define IDISP_ROUTE_COUNT (IDISP_DAI_COUNT * 2) #define IDISP_CODEC_MASK 0x4 +#define HDA_CODEC_AUTOSUSPEND_DELAY_MS 1000 + static int skl_hda_fill_card_info(struct snd_soc_acpi_mach_params *mach_params) { struct snd_soc_card *card = &hda_soc_card; + struct skl_hda_private *ctx = snd_soc_card_get_drvdata(card); struct snd_soc_dai_link *dai_link; u32 codec_count, codec_mask; int i, num_links, num_route; codec_mask = mach_params->codec_mask; codec_count = hweight_long(codec_mask); + ctx->idisp_codec = !!(codec_mask & IDISP_CODEC_MASK); + + if (!codec_count || codec_count > 2 || + (codec_count == 2 && !ctx->idisp_codec)) + return -EINVAL; - if (codec_count == 1 && codec_mask & IDISP_CODEC_MASK) { + if (codec_mask == IDISP_CODEC_MASK) { + /* topology with iDisp as the only HDA codec */ num_links = IDISP_DAI_COUNT + DMIC_DAI_COUNT; num_route = IDISP_ROUTE_COUNT; @@ -135,13 +150,19 @@ static int skl_hda_fill_card_info(struct snd_soc_acpi_mach_params *mach_params) skl_hda_be_dai_links[IDISP_DAI_COUNT + HDAC_DAI_COUNT + i]; } - } else if (codec_count == 2 && codec_mask & IDISP_CODEC_MASK) { + } else { + /* topology with external and iDisp HDA codecs */ num_links = ARRAY_SIZE(skl_hda_be_dai_links); num_route = ARRAY_SIZE(skl_hda_map); card->dapm_widgets = skl_hda_widgets; card->num_dapm_widgets = ARRAY_SIZE(skl_hda_widgets); - } else { - return -EINVAL; + if (!ctx->idisp_codec) { + for (i = 0; i < IDISP_DAI_COUNT; i++) { + skl_hda_be_dai_links[i].codecs = dummy_codec; + skl_hda_be_dai_links[i].num_codecs = + ARRAY_SIZE(dummy_codec); + } + } } card->num_links = num_links; @@ -153,13 +174,36 @@ static int skl_hda_fill_card_info(struct snd_soc_acpi_mach_params *mach_params) return 0; } +static void skl_set_hda_codec_autosuspend_delay(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct hdac_hda_priv *hda_pvt; + struct snd_soc_dai *dai; + + for_each_card_rtds(card, rtd) { + if (!strstr(rtd->dai_link->codecs->name, "ehdaudio0D0")) + continue; + dai = asoc_rtd_to_codec(rtd, 0); + hda_pvt = snd_soc_component_get_drvdata(dai->component); + if (hda_pvt) { + /* + * all codecs are on the same bus, so it's sufficient + * to look up only the first one + */ + snd_hda_set_power_save(hda_pvt->codec->bus, + HDA_CODEC_AUTOSUSPEND_DELAY_MS); + break; + } + } +} + static int skl_hda_audio_probe(struct platform_device *pdev) { struct snd_soc_acpi_mach *mach; struct skl_hda_private *ctx; int ret; - dev_dbg(&pdev->dev, "%s: entry\n", __func__); + dev_dbg(&pdev->dev, "entry\n"); ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) @@ -167,10 +211,12 @@ static int skl_hda_audio_probe(struct platform_device *pdev) INIT_LIST_HEAD(&ctx->hdmi_pcm_list); - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; if (!mach) return -EINVAL; + snd_soc_card_set_drvdata(&hda_soc_card, ctx); + ret = skl_hda_fill_card_info(&mach->mach_params); if (ret < 0) { dev_err(&pdev->dev, "Unsupported HDAudio/iDisp configuration found\n"); @@ -183,7 +229,6 @@ static int skl_hda_audio_probe(struct platform_device *pdev) ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv; hda_soc_card.dev = &pdev->dev; - snd_soc_card_set_drvdata(&hda_soc_card, ctx); if (mach->mach_params.dmic_num > 0) { snprintf(hda_soc_components, sizeof(hda_soc_components), @@ -191,7 +236,11 @@ static int skl_hda_audio_probe(struct platform_device *pdev) hda_soc_card.components = hda_soc_components; } - return devm_snd_soc_register_card(&pdev->dev, &hda_soc_card); + ret = devm_snd_soc_register_card(&pdev->dev, &hda_soc_card); + if (!ret) + skl_set_hda_codec_autosuspend_delay(&hda_soc_card); + + return ret; } static struct platform_driver skl_hda_audio = { @@ -209,3 +258,4 @@ MODULE_DESCRIPTION("SKL/KBL/BXT/APL HDA Generic Machine driver"); MODULE_AUTHOR("Rakesh Ughreja <rakesh.a.ughreja@intel.com>"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:skl_hda_dsp_generic"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/skl_nau88l25_max98357a.c b/sound/soc/intel/boards/skl_nau88l25_max98357a.c index e6de3b28d840..8dceb0b02581 100644 --- a/sound/soc/intel/boards/skl_nau88l25_max98357a.c +++ b/sound/soc/intel/boards/skl_nau88l25_max98357a.c @@ -97,6 +97,17 @@ static const struct snd_soc_dapm_widget skylake_widgets[] = { SND_SOC_DAPM_POST_PMD), }; +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static const struct snd_soc_dapm_route skylake_map[] = { /* HP jack connectors - unknown if we have jack detection */ { "Headphone Jack", NULL, "HPOL" }, @@ -157,16 +168,17 @@ static int skylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) { int ret; - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; /* * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset, - NULL, 0); + ret = snd_soc_card_jack_new_pins(&skylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); return ret; @@ -182,7 +194,7 @@ static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) { struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct skl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -200,7 +212,7 @@ static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) { struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct skl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -218,7 +230,7 @@ static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) { struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct skl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -236,7 +248,7 @@ static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; dapm = snd_soc_component_get_dapm(component); snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); @@ -295,8 +307,8 @@ static const struct snd_soc_ops skylake_nau8825_fe_ops = { static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, @@ -539,7 +551,7 @@ static struct snd_soc_dai_link skylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp_fixup, .dpcm_playback = 1, @@ -552,7 +564,7 @@ static struct snd_soc_dai_link skylake_dais[] = { .no_pcm = 1, .init = skylake_nau8825_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp_fixup, .ops = &skylake_nau8825_ops, @@ -610,8 +622,7 @@ static int skylake_card_late_probe(struct snd_soc_card *card) "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, SND_JACK_AVOUT, - &skylake_hdmi[i], - NULL, 0); + &skylake_hdmi[i]); if (err) return err; @@ -660,7 +671,7 @@ static int skylake_audio_probe(struct platform_device *pdev) skylake_audio_card.dev = &pdev->dev; snd_soc_card_set_drvdata(&skylake_audio_card, ctx); - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; if (mach) dmic_constraints = mach->mach_params.dmic_num == 2 ? &constraints_dmic_2ch : &constraints_dmic_channels; @@ -673,6 +684,7 @@ static const struct platform_device_id skl_board_ids[] = { { .name = "kbl_n88l25_m98357a" }, { } }; +MODULE_DEVICE_TABLE(platform, skl_board_ids); static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, @@ -689,5 +701,3 @@ module_platform_driver(skylake_audio) MODULE_DESCRIPTION("Audio Machine driver-NAU88L25 & MAX98357A in I2S mode"); MODULE_AUTHOR("Rohit Ainapure <rohit.m.ainapure@intel.com"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:skl_n88l25_m98357a"); -MODULE_ALIAS("platform:kbl_n88l25_m98357a"); diff --git a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c index c99c8b23e509..62c0d46d0086 100644 --- a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c +++ b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c @@ -101,6 +101,17 @@ static const struct snd_soc_dapm_widget skylake_widgets[] = { SND_SOC_DAPM_POST_PMD), }; +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static const struct snd_soc_dapm_route skylake_map[] = { /* HP jack connectors - unknown if we have jack detection */ {"Headphone Jack", NULL, "HPOL"}, @@ -161,12 +172,12 @@ static int skylake_ssm4567_codec_init(struct snd_soc_pcm_runtime *rtd) int ret; /* Slot 1 for left */ - ret = snd_soc_dai_set_tdm_slot(rtd->codec_dais[0], 0x01, 0x01, 2, 48); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(rtd, 0), 0x01, 0x01, 2, 48); if (ret < 0) return ret; /* Slot 2 for right */ - ret = snd_soc_dai_set_tdm_slot(rtd->codec_dais[1], 0x02, 0x02, 2, 48); + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(rtd, 1), 0x02, 0x02, 2, 48); if (ret < 0) return ret; @@ -176,16 +187,17 @@ static int skylake_ssm4567_codec_init(struct snd_soc_pcm_runtime *rtd) static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) { int ret; - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; /* * 4 buttons here map to the google Reference headset * The use of these buttons can be decided by the user space. */ - ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset, - NULL, 0); + ret = snd_soc_card_jack_new_pins(&skylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); return ret; @@ -201,7 +213,7 @@ static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) { struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct skl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -219,7 +231,7 @@ static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) { struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct skl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -238,7 +250,7 @@ static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) { struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct skl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -256,7 +268,7 @@ static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; dapm = snd_soc_component_get_dapm(component); snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); @@ -347,8 +359,8 @@ static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, @@ -578,7 +590,7 @@ static struct snd_soc_dai_link skylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .init = skylake_ssm4567_codec_init, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp_fixup, @@ -593,7 +605,7 @@ static struct snd_soc_dai_link skylake_dais[] = { .no_pcm = 1, .init = skylake_nau8825_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp_fixup, .ops = &skylake_nau8825_ops, @@ -651,8 +663,7 @@ static int skylake_card_late_probe(struct snd_soc_card *card) "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, SND_JACK_AVOUT, - &skylake_hdmi[i], - NULL, 0); + &skylake_hdmi[i]); if (err) return err; @@ -686,6 +697,7 @@ static struct snd_soc_card skylake_audio_card = { .codec_conf = ssm4567_codec_conf, .num_configs = ARRAY_SIZE(ssm4567_codec_conf), .fully_routed = true, + .disable_route_checks = true, .late_probe = skylake_card_late_probe, }; @@ -703,7 +715,7 @@ static int skylake_audio_probe(struct platform_device *pdev) skylake_audio_card.dev = &pdev->dev; snd_soc_card_set_drvdata(&skylake_audio_card, ctx); - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; if (mach) dmic_constraints = mach->mach_params.dmic_num == 2 ? &constraints_dmic_2ch : &constraints_dmic_channels; @@ -716,6 +728,7 @@ static const struct platform_device_id skl_board_ids[] = { { .name = "kbl_n88l25_s4567" }, { } }; +MODULE_DEVICE_TABLE(platform, skl_board_ids); static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, @@ -736,5 +749,3 @@ MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>"); MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>"); MODULE_DESCRIPTION("Intel Audio Machine driver for SKL with NAU88L25 and SSM4567 in I2S Mode"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:skl_n88l25_s4567"); -MODULE_ALIAS("platform:kbl_n88l25_s4567"); diff --git a/sound/soc/intel/boards/skl_rt286.c b/sound/soc/intel/boards/skl_rt286.c index a9aec66a2351..4f3d655e2bfa 100644 --- a/sound/soc/intel/boards/skl_rt286.c +++ b/sound/soc/intel/boards/skl_rt286.c @@ -112,7 +112,7 @@ static const struct snd_soc_dapm_route skylake_rt286_map[] = { static int skylake_rt286_fe_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dapm_context *dapm; - struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component; dapm = snd_soc_component_get_dapm(component); snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); @@ -122,10 +122,10 @@ static int skylake_rt286_fe_init(struct snd_soc_pcm_runtime *rtd) static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; int ret; - ret = snd_soc_card_jack_new(rtd->card, "Headset", + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0, &skylake_headset, skylake_headset_pins, ARRAY_SIZE(skylake_headset_pins)); @@ -133,7 +133,7 @@ static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) if (ret) return ret; - rt286_mic_detect(component, &skylake_headset); + snd_soc_component_set_jack(component, &skylake_headset, NULL); snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); @@ -143,7 +143,7 @@ static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) static int skylake_hdmi_init(struct snd_soc_pcm_runtime *rtd) { struct skl_rt286_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct skl_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -228,8 +228,8 @@ static int skylake_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, static int skylake_rt286_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); int ret; ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, @@ -434,7 +434,7 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = { .init = skylake_rt286_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp0_fixup, .ops = &skylake_rt286_ops, @@ -491,8 +491,7 @@ static int skylake_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &skylake_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &skylake_hdmi[i]); if (err) return err; @@ -548,6 +547,7 @@ static const struct platform_device_id skl_board_ids[] = { { .name = "kbl_alc286s_i2s" }, { } }; +MODULE_DEVICE_TABLE(platform, skl_board_ids); static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, @@ -565,5 +565,3 @@ module_platform_driver(skylake_audio) MODULE_AUTHOR("Omair Mohammed Abdullah <omair.m.abdullah@intel.com>"); MODULE_DESCRIPTION("Intel SST Audio for Skylake"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:skl_alc286s_i2s"); -MODULE_ALIAS("platform:kbl_alc286s_i2s"); diff --git a/sound/soc/intel/boards/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c new file mode 100644 index 000000000000..6e39eda77385 --- /dev/null +++ b/sound/soc/intel/boards/sof_cirrus_common.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file defines data structures and functions used in Machine + * Driver for Intel platforms with Cirrus Logic Codecs. + * + * Copyright 2022 Intel Corporation. + */ +#include <linux/module.h> +#include <sound/sof.h> +#include "../../codecs/cs35l41.h" +#include "sof_cirrus_common.h" + +#define CS35L41_HID "CSC3541" +#define CS35L41_MAX_AMPS 4 + +/* + * Cirrus Logic CS35L41/CS35L53 + */ +static const struct snd_kcontrol_new cs35l41_kcontrols[] = { + SOC_DAPM_PIN_SWITCH("WL Spk"), + SOC_DAPM_PIN_SWITCH("WR Spk"), + SOC_DAPM_PIN_SWITCH("TL Spk"), + SOC_DAPM_PIN_SWITCH("TR Spk"), +}; + +static const struct snd_soc_dapm_widget cs35l41_dapm_widgets[] = { + SND_SOC_DAPM_SPK("WL Spk", NULL), + SND_SOC_DAPM_SPK("WR Spk", NULL), + SND_SOC_DAPM_SPK("TL Spk", NULL), + SND_SOC_DAPM_SPK("TR Spk", NULL), +}; + +static const struct snd_soc_dapm_route cs35l41_dapm_routes[] = { + /* speaker */ + {"WL Spk", NULL, "WL SPK"}, + {"WR Spk", NULL, "WR SPK"}, + {"TL Spk", NULL, "TL SPK"}, + {"TR Spk", NULL, "TR SPK"}, +}; + +static struct snd_soc_dai_link_component cs35l41_components[CS35L41_MAX_AMPS]; + +/* + * Mapping between ACPI instance id and speaker position. + */ +static struct snd_soc_codec_conf cs35l41_codec_conf[CS35L41_MAX_AMPS]; + +static int cs35l41_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, cs35l41_dapm_widgets, + ARRAY_SIZE(cs35l41_dapm_widgets)); + if (ret) { + dev_err(rtd->dev, "fail to add dapm controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_add_card_controls(card, cs35l41_kcontrols, + ARRAY_SIZE(cs35l41_kcontrols)); + if (ret) { + dev_err(rtd->dev, "fail to add card controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, cs35l41_dapm_routes, + ARRAY_SIZE(cs35l41_dapm_routes)); + + if (ret) + dev_err(rtd->dev, "fail to add dapm routes, ret %d\n", ret); + + return ret; +} + +/* + * Channel map: + * + * TL/WL: ASPRX1 on slot 0, ASPRX2 on slot 1 (default) + * TR/WR: ASPRX1 on slot 1, ASPRX2 on slot 0 + */ +static const struct { + unsigned int rx[2]; +} cs35l41_channel_map[] = { + {.rx = {0, 1}}, /* WL */ + {.rx = {1, 0}}, /* WR */ + {.rx = {0, 1}}, /* TL */ + {.rx = {1, 0}}, /* TR */ +}; + +static int cs35l41_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + int clk_freq, i, ret; + + clk_freq = sof_dai_get_bclk(rtd); /* BCLK freq */ + + if (clk_freq <= 0) { + dev_err(rtd->dev, "fail to get bclk freq, ret %d\n", clk_freq); + return -EINVAL; + } + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + /* call dai driver's set_sysclk() callback */ + ret = snd_soc_dai_set_sysclk(codec_dai, CS35L41_CLKID_SCLK, + clk_freq, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "fail to set sysclk, ret %d\n", + ret); + return ret; + } + + /* call component driver's set_sysclk() callback */ + ret = snd_soc_component_set_sysclk(codec_dai->component, + CS35L41_CLKID_SCLK, 0, + clk_freq, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "fail to set component sysclk, ret %d\n", + ret); + return ret; + } + + /* setup channel map */ + ret = snd_soc_dai_set_channel_map(codec_dai, 0, NULL, + ARRAY_SIZE(cs35l41_channel_map[i].rx), + (unsigned int *)cs35l41_channel_map[i].rx); + if (ret < 0) { + dev_err(codec_dai->dev, "fail to set channel map, ret %d\n", + ret); + return ret; + } + } + + return 0; +} + +static const struct snd_soc_ops cs35l41_ops = { + .hw_params = cs35l41_hw_params, +}; + +static const char * const cs35l41_name_prefixes[] = { "WL", "WR", "TL", "TR" }; + +/* + * Expected UIDs are integers (stored as strings). + * UID Mapping is fixed: + * UID 0x0 -> WL + * UID 0x1 -> WR + * UID 0x2 -> TL + * UID 0x3 -> TR + * Note: If there are less than 4 Amps, UIDs still map to WL/WR/TL/TR. Dynamic code will only create + * dai links for UIDs which exist, and ignore non-existant ones. Only 2 or 4 amps are expected. + * Return number of codecs found. + */ +static int cs35l41_compute_codec_conf(void) +{ + const char * const uid_strings[] = { "0", "1", "2", "3" }; + unsigned int uid, sz = 0; + struct acpi_device *adev; + struct device *physdev; + + for (uid = 0; uid < CS35L41_MAX_AMPS; uid++) { + adev = acpi_dev_get_first_match_dev(CS35L41_HID, uid_strings[uid], -1); + if (!adev) { + pr_devel("Cannot find match for HID %s UID %u (%s)\n", CS35L41_HID, uid, + cs35l41_name_prefixes[uid]); + continue; + } + physdev = get_device(acpi_get_first_physical_node(adev)); + cs35l41_components[sz].name = dev_name(physdev); + cs35l41_components[sz].dai_name = CS35L41_CODEC_DAI; + cs35l41_codec_conf[sz].dlc.name = dev_name(physdev); + cs35l41_codec_conf[sz].name_prefix = cs35l41_name_prefixes[uid]; + acpi_dev_put(adev); + sz++; + } + + if (sz != 2 && sz != 4) + pr_warn("Invalid number of cs35l41 amps found: %d, expected 2 or 4\n", sz); + return sz; +} + +void cs35l41_set_dai_link(struct snd_soc_dai_link *link) +{ + link->num_codecs = cs35l41_compute_codec_conf(); + link->codecs = cs35l41_components; + link->init = cs35l41_init; + link->ops = &cs35l41_ops; +} +EXPORT_SYMBOL_NS(cs35l41_set_dai_link, SND_SOC_INTEL_SOF_CIRRUS_COMMON); + +void cs35l41_set_codec_conf(struct snd_soc_card *card) +{ + card->codec_conf = cs35l41_codec_conf; + card->num_configs = ARRAY_SIZE(cs35l41_codec_conf); +} +EXPORT_SYMBOL_NS(cs35l41_set_codec_conf, SND_SOC_INTEL_SOF_CIRRUS_COMMON); + +MODULE_DESCRIPTION("ASoC Intel SOF Cirrus Logic helpers"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/boards/sof_cirrus_common.h b/sound/soc/intel/boards/sof_cirrus_common.h new file mode 100644 index 000000000000..ca438c12c386 --- /dev/null +++ b/sound/soc/intel/boards/sof_cirrus_common.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * This file defines data structures used in Machine Driver for Intel + * platforms with Cirrus Logic Codecs. + * + * Copyright 2022 Intel Corporation. + */ +#ifndef __SOF_CIRRUS_COMMON_H +#define __SOF_CIRRUS_COMMON_H + +#include <sound/soc.h> + +/* + * Cirrus Logic CS35L41/CS35L53 + */ +#define CS35L41_CODEC_DAI "cs35l41-pcm" +#define CS35L41_DEV0_NAME "i2c-CSC3541:00" +#define CS35L41_DEV1_NAME "i2c-CSC3541:01" +#define CS35L41_DEV2_NAME "i2c-CSC3541:02" +#define CS35L41_DEV3_NAME "i2c-CSC3541:03" + +void cs35l41_set_dai_link(struct snd_soc_dai_link *link); +void cs35l41_set_codec_conf(struct snd_soc_card *card); + +#endif /* __SOF_CIRRUS_COMMON_H */ diff --git a/sound/soc/intel/boards/sof_cs42l42.c b/sound/soc/intel/boards/sof_cs42l42.c new file mode 100644 index 000000000000..e38bd2831e6a --- /dev/null +++ b/sound/soc/intel/boards/sof_cs42l42.c @@ -0,0 +1,733 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2021 Intel Corporation. + +/* + * Intel SOF Machine Driver with Cirrus Logic CS42L42 Codec + * and speaker codec MAX98357A + */ +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/dmi.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/sof.h> +#include <sound/soc-acpi.h> +#include <dt-bindings/sound/cs42l42.h> +#include "../../codecs/hdac_hdmi.h" +#include "../common/soc-intel-quirks.h" +#include "hda_dsp_common.h" +#include "sof_maxim_common.h" + +#define NAME_SIZE 32 + +#define SOF_CS42L42_SSP_CODEC(quirk) ((quirk) & GENMASK(2, 0)) +#define SOF_CS42L42_SSP_CODEC_MASK (GENMASK(2, 0)) +#define SOF_SPEAKER_AMP_PRESENT BIT(3) +#define SOF_CS42L42_SSP_AMP_SHIFT 4 +#define SOF_CS42L42_SSP_AMP_MASK (GENMASK(6, 4)) +#define SOF_CS42L42_SSP_AMP(quirk) \ + (((quirk) << SOF_CS42L42_SSP_AMP_SHIFT) & SOF_CS42L42_SSP_AMP_MASK) +#define SOF_CS42L42_NUM_HDMIDEV_SHIFT 7 +#define SOF_CS42L42_NUM_HDMIDEV_MASK (GENMASK(9, 7)) +#define SOF_CS42L42_NUM_HDMIDEV(quirk) \ + (((quirk) << SOF_CS42L42_NUM_HDMIDEV_SHIFT) & SOF_CS42L42_NUM_HDMIDEV_MASK) +#define SOF_CS42L42_DAILINK_SHIFT 10 +#define SOF_CS42L42_DAILINK_MASK (GENMASK(24, 10)) +#define SOF_CS42L42_DAILINK(link1, link2, link3, link4, link5) \ + ((((link1) | ((link2) << 3) | ((link3) << 6) | ((link4) << 9) | ((link5) << 12)) << SOF_CS42L42_DAILINK_SHIFT) & SOF_CS42L42_DAILINK_MASK) +#define SOF_BT_OFFLOAD_PRESENT BIT(25) +#define SOF_CS42L42_SSP_BT_SHIFT 26 +#define SOF_CS42L42_SSP_BT_MASK (GENMASK(28, 26)) +#define SOF_CS42L42_SSP_BT(quirk) \ + (((quirk) << SOF_CS42L42_SSP_BT_SHIFT) & SOF_CS42L42_SSP_BT_MASK) +#define SOF_MAX98357A_SPEAKER_AMP_PRESENT BIT(29) +#define SOF_MAX98360A_SPEAKER_AMP_PRESENT BIT(30) + +enum { + LINK_NONE = 0, + LINK_HP = 1, + LINK_SPK = 2, + LINK_DMIC = 3, + LINK_HDMI = 4, + LINK_BT = 5, +}; + +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +/* Default: SSP2 */ +static unsigned long sof_cs42l42_quirk = SOF_CS42L42_SSP_CODEC(2); + +struct sof_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + struct snd_soc_jack hdmi_jack; + int device; +}; + +struct sof_card_private { + struct snd_soc_jack headset_jack; + struct list_head hdmi_pcm_list; + bool common_hdmi_codec_drv; +}; + +static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); + struct sof_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int sof_cs42l42_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_jack *jack = &ctx->headset_jack; + int ret; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + jack, + jack_pins, + ARRAY_SIZE(jack_pins)); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + ret = snd_soc_component_set_jack(component, jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return ret; +}; + +static void sof_cs42l42_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_set_jack(component, NULL, NULL); +} + +static int sof_cs42l42_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int clk_freq, ret; + + clk_freq = sof_dai_get_bclk(rtd); /* BCLK freq */ + + if (clk_freq <= 0) { + dev_err(rtd->dev, "get bclk freq failed: %d\n", clk_freq); + return -EINVAL; + } + + /* Configure sysclk for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + clk_freq, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops sof_cs42l42_ops = { + .hw_params = sof_cs42l42_hw_params, +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + /* name might be overridden during probe */ + .name = "0000:00:1f.3" + } +}; + +static int sof_card_late_probe(struct snd_soc_card *card) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = NULL; + char jack_name[NAME_SIZE]; + struct sof_hdmi_pcm *pcm; + int err; + + if (list_empty(&ctx->hdmi_pcm_list)) + return -EINVAL; + + if (ctx->common_hdmi_codec_drv) { + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm, + head); + component = pcm->codec_dai->component; + return hda_dsp_hdmi_build_controls(card, component); + } + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &pcm->hdmi_jack); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &pcm->hdmi_jack); + if (err < 0) + return err; + } + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +static const struct snd_kcontrol_new sof_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget sof_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_widget dmic_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route sof_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone Jack", NULL, "HP"}, + + /* other jacks */ + {"HS", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route dmic_map[] = { + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +static int dmic_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets, + ARRAY_SIZE(dmic_widgets)); + if (ret) { + dev_err(card->dev, "DMic widget addition failed: %d\n", ret); + /* Don't need to add routes if widget addition failed */ + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map, + ARRAY_SIZE(dmic_map)); + + if (ret) + dev_err(card->dev, "DMic map addition failed: %d\n", ret); + + return ret; +} + +/* sof audio machine driver for cs42l42 codec */ +static struct snd_soc_card sof_audio_card_cs42l42 = { + .name = "cs42l42", /* the sof- prefix is added by the core */ + .owner = THIS_MODULE, + .controls = sof_controls, + .num_controls = ARRAY_SIZE(sof_controls), + .dapm_widgets = sof_widgets, + .num_dapm_widgets = ARRAY_SIZE(sof_widgets), + .dapm_routes = sof_map, + .num_dapm_routes = ARRAY_SIZE(sof_map), + .fully_routed = true, + .late_probe = sof_card_late_probe, +}; + +static struct snd_soc_dai_link_component cs42l42_component[] = { + { + .name = "i2c-10134242:00", + .dai_name = "cs42l42", + } +}; + +static struct snd_soc_dai_link_component dmic_component[] = { + { + .name = "dmic-codec", + .dai_name = "dmic-hifi", + } +}; + +static struct snd_soc_dai_link_component dummy_component[] = { + { + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", + } +}; + +static int create_spk_amp_dai_links(struct device *dev, + struct snd_soc_dai_link *links, + struct snd_soc_dai_link_component *cpus, + int *id, int ssp_amp) +{ + int ret = 0; + + /* speaker amp */ + if (!(sof_cs42l42_quirk & SOF_SPEAKER_AMP_PRESENT)) + return 0; + + links[*id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", + ssp_amp); + if (!links[*id].name) { + ret = -ENOMEM; + goto devm_err; + } + + links[*id].id = *id; + + if (sof_cs42l42_quirk & SOF_MAX98357A_SPEAKER_AMP_PRESENT) { + max_98357a_dai_link(&links[*id]); + } else if (sof_cs42l42_quirk & SOF_MAX98360A_SPEAKER_AMP_PRESENT) { + max_98360a_dai_link(&links[*id]); + } else { + dev_err(dev, "no amp defined\n"); + ret = -EINVAL; + goto devm_err; + } + + links[*id].platforms = platform_component; + links[*id].num_platforms = ARRAY_SIZE(platform_component); + links[*id].dpcm_playback = 1; + links[*id].no_pcm = 1; + links[*id].cpus = &cpus[*id]; + links[*id].num_cpus = 1; + + links[*id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", ssp_amp); + if (!links[*id].cpus->dai_name) { + ret = -ENOMEM; + goto devm_err; + } + + (*id)++; + +devm_err: + return ret; +} + +static int create_hp_codec_dai_links(struct device *dev, + struct snd_soc_dai_link *links, + struct snd_soc_dai_link_component *cpus, + int *id, int ssp_codec) +{ + /* codec SSP */ + links[*id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", + ssp_codec); + if (!links[*id].name) + goto devm_err; + + links[*id].id = *id; + links[*id].codecs = cs42l42_component; + links[*id].num_codecs = ARRAY_SIZE(cs42l42_component); + links[*id].platforms = platform_component; + links[*id].num_platforms = ARRAY_SIZE(platform_component); + links[*id].init = sof_cs42l42_init; + links[*id].exit = sof_cs42l42_exit; + links[*id].ops = &sof_cs42l42_ops; + links[*id].dpcm_playback = 1; + links[*id].dpcm_capture = 1; + links[*id].no_pcm = 1; + links[*id].cpus = &cpus[*id]; + links[*id].num_cpus = 1; + + links[*id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_codec); + if (!links[*id].cpus->dai_name) + goto devm_err; + + (*id)++; + + return 0; + +devm_err: + return -ENOMEM; +} + +static int create_dmic_dai_links(struct device *dev, + struct snd_soc_dai_link *links, + struct snd_soc_dai_link_component *cpus, + int *id, int dmic_be_num) +{ + int i; + + /* dmic */ + if (dmic_be_num <= 0) + return 0; + + /* at least we have dmic01 */ + links[*id].name = "dmic01"; + links[*id].cpus = &cpus[*id]; + links[*id].cpus->dai_name = "DMIC01 Pin"; + links[*id].init = dmic_init; + if (dmic_be_num > 1) { + /* set up 2 BE links at most */ + links[*id + 1].name = "dmic16k"; + links[*id + 1].cpus = &cpus[*id + 1]; + links[*id + 1].cpus->dai_name = "DMIC16k Pin"; + dmic_be_num = 2; + } + + for (i = 0; i < dmic_be_num; i++) { + links[*id].id = *id; + links[*id].num_cpus = 1; + links[*id].codecs = dmic_component; + links[*id].num_codecs = ARRAY_SIZE(dmic_component); + links[*id].platforms = platform_component; + links[*id].num_platforms = ARRAY_SIZE(platform_component); + links[*id].ignore_suspend = 1; + links[*id].dpcm_capture = 1; + links[*id].no_pcm = 1; + + (*id)++; + } + + return 0; +} + +static int create_hdmi_dai_links(struct device *dev, + struct snd_soc_dai_link *links, + struct snd_soc_dai_link_component *cpus, + int *id, int hdmi_num) +{ + struct snd_soc_dai_link_component *idisp_components; + int i; + + /* HDMI */ + if (hdmi_num <= 0) + return 0; + + idisp_components = devm_kcalloc(dev, + hdmi_num, + sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); + if (!idisp_components) + goto devm_err; + + for (i = 1; i <= hdmi_num; i++) { + links[*id].name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d", i); + if (!links[*id].name) + goto devm_err; + + links[*id].id = *id; + links[*id].cpus = &cpus[*id]; + links[*id].num_cpus = 1; + links[*id].cpus->dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "iDisp%d Pin", + i); + if (!links[*id].cpus->dai_name) + goto devm_err; + + idisp_components[i - 1].name = "ehdaudio0D2"; + idisp_components[i - 1].dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "intel-hdmi-hifi%d", + i); + if (!idisp_components[i - 1].dai_name) + goto devm_err; + + links[*id].codecs = &idisp_components[i - 1]; + links[*id].num_codecs = 1; + links[*id].platforms = platform_component; + links[*id].num_platforms = ARRAY_SIZE(platform_component); + links[*id].init = sof_hdmi_init; + links[*id].dpcm_playback = 1; + links[*id].no_pcm = 1; + + (*id)++; + } + + return 0; + +devm_err: + return -ENOMEM; +} + +static int create_bt_offload_dai_links(struct device *dev, + struct snd_soc_dai_link *links, + struct snd_soc_dai_link_component *cpus, + int *id, int ssp_bt) +{ + /* bt offload */ + if (!(sof_cs42l42_quirk & SOF_BT_OFFLOAD_PRESENT)) + return 0; + + links[*id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", + ssp_bt); + if (!links[*id].name) + goto devm_err; + + links[*id].id = *id; + links[*id].codecs = dummy_component; + links[*id].num_codecs = ARRAY_SIZE(dummy_component); + links[*id].platforms = platform_component; + links[*id].num_platforms = ARRAY_SIZE(platform_component); + + links[*id].dpcm_playback = 1; + links[*id].dpcm_capture = 1; + links[*id].no_pcm = 1; + links[*id].cpus = &cpus[*id]; + links[*id].num_cpus = 1; + + links[*id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_bt); + if (!links[*id].cpus->dai_name) + goto devm_err; + + (*id)++; + + return 0; + +devm_err: + return -ENOMEM; +} + +static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, + int ssp_codec, + int ssp_amp, + int ssp_bt, + int dmic_be_num, + int hdmi_num) +{ + struct snd_soc_dai_link_component *cpus; + struct snd_soc_dai_link *links; + int ret, id = 0, link_seq; + + links = devm_kcalloc(dev, sof_audio_card_cs42l42.num_links, + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + cpus = devm_kcalloc(dev, sof_audio_card_cs42l42.num_links, + sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); + if (!links || !cpus) + goto devm_err; + + link_seq = (sof_cs42l42_quirk & SOF_CS42L42_DAILINK_MASK) >> SOF_CS42L42_DAILINK_SHIFT; + + while (link_seq) { + int link_type = link_seq & 0x07; + + switch (link_type) { + case LINK_HP: + ret = create_hp_codec_dai_links(dev, links, cpus, &id, ssp_codec); + if (ret < 0) { + dev_err(dev, "fail to create hp codec dai links, ret %d\n", + ret); + goto devm_err; + } + break; + case LINK_SPK: + ret = create_spk_amp_dai_links(dev, links, cpus, &id, ssp_amp); + if (ret < 0) { + dev_err(dev, "fail to create spk amp dai links, ret %d\n", + ret); + goto devm_err; + } + break; + case LINK_DMIC: + ret = create_dmic_dai_links(dev, links, cpus, &id, dmic_be_num); + if (ret < 0) { + dev_err(dev, "fail to create dmic dai links, ret %d\n", + ret); + goto devm_err; + } + break; + case LINK_HDMI: + ret = create_hdmi_dai_links(dev, links, cpus, &id, hdmi_num); + if (ret < 0) { + dev_err(dev, "fail to create hdmi dai links, ret %d\n", + ret); + goto devm_err; + } + break; + case LINK_BT: + ret = create_bt_offload_dai_links(dev, links, cpus, &id, ssp_bt); + if (ret < 0) { + dev_err(dev, "fail to create bt offload dai links, ret %d\n", + ret); + goto devm_err; + } + break; + case LINK_NONE: + /* caught here if it's not used as terminator in macro */ + default: + dev_err(dev, "invalid link type %d\n", link_type); + goto devm_err; + } + + link_seq >>= 3; + } + + return links; +devm_err: + return NULL; +} + +static int sof_audio_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_links; + struct snd_soc_acpi_mach *mach; + struct sof_card_private *ctx; + int dmic_be_num, hdmi_num; + int ret, ssp_bt, ssp_amp, ssp_codec; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (pdev->id_entry && pdev->id_entry->driver_data) + sof_cs42l42_quirk = (unsigned long)pdev->id_entry->driver_data; + + mach = pdev->dev.platform_data; + + if (soc_intel_is_glk()) { + dmic_be_num = 1; + hdmi_num = 3; + } else { + dmic_be_num = 2; + hdmi_num = (sof_cs42l42_quirk & SOF_CS42L42_NUM_HDMIDEV_MASK) >> + SOF_CS42L42_NUM_HDMIDEV_SHIFT; + /* default number of HDMI DAI's */ + if (!hdmi_num) + hdmi_num = 3; + } + + dev_dbg(&pdev->dev, "sof_cs42l42_quirk = %lx\n", sof_cs42l42_quirk); + + ssp_bt = (sof_cs42l42_quirk & SOF_CS42L42_SSP_BT_MASK) >> + SOF_CS42L42_SSP_BT_SHIFT; + + ssp_amp = (sof_cs42l42_quirk & SOF_CS42L42_SSP_AMP_MASK) >> + SOF_CS42L42_SSP_AMP_SHIFT; + + ssp_codec = sof_cs42l42_quirk & SOF_CS42L42_SSP_CODEC_MASK; + + /* compute number of dai links */ + sof_audio_card_cs42l42.num_links = 1 + dmic_be_num + hdmi_num; + + if (sof_cs42l42_quirk & SOF_SPEAKER_AMP_PRESENT) + sof_audio_card_cs42l42.num_links++; + if (sof_cs42l42_quirk & SOF_BT_OFFLOAD_PRESENT) + sof_audio_card_cs42l42.num_links++; + + dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec, ssp_amp, + ssp_bt, dmic_be_num, hdmi_num); + if (!dai_links) + return -ENOMEM; + + sof_audio_card_cs42l42.dai_link = dai_links; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + sof_audio_card_cs42l42.dev = &pdev->dev; + + /* set platform name for each dailink */ + ret = snd_soc_fixup_dai_links_platform_name(&sof_audio_card_cs42l42, + mach->mach_params.platform); + if (ret) + return ret; + + ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv; + + snd_soc_card_set_drvdata(&sof_audio_card_cs42l42, ctx); + + return devm_snd_soc_register_card(&pdev->dev, + &sof_audio_card_cs42l42); +} + +static const struct platform_device_id board_ids[] = { + { + .name = "glk_cs4242_mx98357a", + .driver_data = (kernel_ulong_t)(SOF_CS42L42_SSP_CODEC(2) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98357A_SPEAKER_AMP_PRESENT | + SOF_CS42L42_SSP_AMP(1)) | + SOF_CS42L42_DAILINK(LINK_SPK, LINK_HP, LINK_DMIC, LINK_HDMI, LINK_NONE), + }, + { + .name = "jsl_cs4242_mx98360a", + .driver_data = (kernel_ulong_t)(SOF_CS42L42_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_CS42L42_SSP_AMP(1)) | + SOF_CS42L42_DAILINK(LINK_HP, LINK_DMIC, LINK_HDMI, LINK_SPK, LINK_NONE), + }, + { + .name = "adl_mx98360a_cs4242", + .driver_data = (kernel_ulong_t)(SOF_CS42L42_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_CS42L42_SSP_AMP(1) | + SOF_CS42L42_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_PRESENT | + SOF_CS42L42_SSP_BT(2) | + SOF_CS42L42_DAILINK(LINK_HP, LINK_DMIC, LINK_HDMI, LINK_SPK, LINK_BT)), + }, + { } +}; +MODULE_DEVICE_TABLE(platform, board_ids); + +static struct platform_driver sof_audio = { + .probe = sof_audio_probe, + .driver = { + .name = "sof_cs42l42", + .pm = &snd_soc_pm_ops, + }, + .id_table = board_ids, +}; +module_platform_driver(sof_audio) + +/* Module information */ +MODULE_DESCRIPTION("SOF Audio Machine driver for CS42L42"); +MODULE_AUTHOR("Brent Lu <brent.lu@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); +MODULE_IMPORT_NS(SND_SOC_INTEL_SOF_MAXIM_COMMON); diff --git a/sound/soc/intel/boards/sof_da7219_max98373.c b/sound/soc/intel/boards/sof_da7219_max98373.c index 8f44f13d2848..e048e789e633 100644 --- a/sound/soc/intel/boards/sof_da7219_max98373.c +++ b/sound/soc/intel/boards/sof_da7219_max98373.c @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2019 Intel Corporation. /* - * Intel SOF Machine driver for DA7219 + MAX98373 codec + * Intel SOF Machine driver for DA7219 + MAX98373/MAX98360A codec */ #include <linux/input.h> @@ -69,35 +69,89 @@ static const struct snd_kcontrol_new controls[] = { SOC_DAPM_PIN_SWITCH("Right Spk"), }; +static const struct snd_kcontrol_new m98360a_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +/* For MAX98373 amp */ static const struct snd_soc_dapm_widget widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), SND_SOC_DAPM_SPK("Right Spk", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MIC("SoC DMIC", NULL), }; static const struct snd_soc_dapm_route audio_map[] = { { "Headphone Jack", NULL, "HPL" }, { "Headphone Jack", NULL, "HPR" }, + { "MIC", NULL, "Headset Mic" }, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, + { "Left Spk", NULL, "Left BE_OUT" }, { "Right Spk", NULL, "Right BE_OUT" }, + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +/* For MAX98360A amp */ +static const struct snd_soc_dapm_widget max98360a_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + + SND_SOC_DAPM_SPK("Spk", NULL), + + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route max98360a_map[] = { + { "Headphone Jack", NULL, "HPL" }, + { "Headphone Jack", NULL, "HPR" }, + { "MIC", NULL, "Headset Mic" }, { "Headphone Jack", NULL, "Platform Clock" }, { "Headset Mic", NULL, "Platform Clock" }, + + {"Spk", NULL, "Speaker"}, + + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, }; static struct snd_soc_jack headset; static int da7219_codec_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_component *component = rtd->codec_dai->component; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; struct snd_soc_jack *jack; int ret; @@ -113,11 +167,13 @@ static int da7219_codec_init(struct snd_soc_pcm_runtime *rtd) * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | - SND_JACK_BTN_1 | SND_JACK_BTN_2 | - SND_JACK_BTN_3 | SND_JACK_LINEOUT, - &headset, NULL, 0); + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3 | SND_JACK_LINEOUT, + &headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); return ret; @@ -136,11 +192,11 @@ static int da7219_codec_init(struct snd_soc_pcm_runtime *rtd) static int ssp1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *runtime = substream->private_data; + struct snd_soc_pcm_runtime *runtime = asoc_substream_to_rtd(substream); int ret, j; - for (j = 0; j < runtime->num_codecs; j++) { - struct snd_soc_dai *codec_dai = runtime->codec_dais[j]; + for (j = 0; j < runtime->dai_link->num_codecs; j++) { + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, j); if (!strcmp(codec_dai->component->name, MAXIM_DEV0_NAME)) { /* vmon_slot_no = 0 imon_slot_no = 1 for TX slots */ @@ -181,7 +237,7 @@ static struct snd_soc_codec_conf max98373_codec_conf[] = { static int hdmi_init(struct snd_soc_pcm_runtime *rtd) { struct card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -224,11 +280,17 @@ SND_SOC_DAILINK_DEF(ssp1_amps, /* Left */ COMP_CODEC(MAXIM_DEV0_NAME, MAX98373_CODEC_DAI), /* Right */ COMP_CODEC(MAXIM_DEV1_NAME, MAX98373_CODEC_DAI))); +SND_SOC_DAILINK_DEF(ssp1_m98360a, + DAILINK_COMP_ARRAY(COMP_CODEC("MX98360A:00", "HiFi"))); + SND_SOC_DAILINK_DEF(dmic_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin"))); SND_SOC_DAILINK_DEF(dmic_codec, DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); +SND_SOC_DAILINK_DEF(dmic16k_pin, + DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin"))); + SND_SOC_DAILINK_DEF(idisp1_pin, DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin"))); SND_SOC_DAILINK_DEF(idisp1_codec, @@ -301,6 +363,14 @@ static struct snd_soc_dai_link dais[] = { .no_pcm = 1, SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform), }, + { + .name = "dmic16k", + .id = 6, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(dmic16k_pin, dmic_codec, platform), + } }; static struct snd_soc_card card_da7219_m98373 = { @@ -320,6 +390,21 @@ static struct snd_soc_card card_da7219_m98373 = { .late_probe = card_late_probe, }; +static struct snd_soc_card card_da7219_m98360a = { + .name = "da7219max98360a", + .owner = THIS_MODULE, + .dai_link = dais, + .num_links = ARRAY_SIZE(dais), + .controls = m98360a_controls, + .num_controls = ARRAY_SIZE(m98360a_controls), + .dapm_widgets = max98360a_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98360a_widgets), + .dapm_routes = max98360a_map, + .num_dapm_routes = ARRAY_SIZE(max98360a_map), + .fully_routed = true, + .late_probe = card_late_probe, +}; + static int audio_probe(struct platform_device *pdev) { static struct snd_soc_card *card; @@ -327,15 +412,26 @@ static int audio_probe(struct platform_device *pdev) struct card_private *ctx; int ret; - ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; + /* By default dais[0] is configured for max98373 */ + if (!strcmp(pdev->name, "sof_da7219_mx98360a")) { + dais[0] = (struct snd_soc_dai_link) { + .name = "SSP1-Codec", + .id = 0, + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_pmdown_time = 1, + SND_SOC_DAILINK_REG(ssp1_pin, ssp1_m98360a, platform) }; + } + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); card = (struct snd_soc_card *)pdev->id_entry->driver_data; card->dev = &pdev->dev; - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; ret = snd_soc_fixup_dai_links_platform_name(card, mach->mach_params.platform); if (ret) @@ -348,16 +444,21 @@ static int audio_probe(struct platform_device *pdev) static const struct platform_device_id board_ids[] = { { - .name = "sof_da7219_max98373", + .name = "sof_da7219_mx98373", .driver_data = (kernel_ulong_t)&card_da7219_m98373, }, + { + .name = "sof_da7219_mx98360a", + .driver_data = (kernel_ulong_t)&card_da7219_m98360a, + }, { } }; +MODULE_DEVICE_TABLE(platform, board_ids); static struct platform_driver audio = { .probe = audio_probe, .driver = { - .name = "sof_da7219_max98373", + .name = "sof_da7219_max98_360a_373", .pm = &snd_soc_pm_ops, }, .id_table = board_ids, @@ -368,4 +469,4 @@ module_platform_driver(audio) MODULE_DESCRIPTION("ASoC Intel(R) SOF Machine driver"); MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:sof_da7219_max98373"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c new file mode 100644 index 000000000000..fbb42e54947a --- /dev/null +++ b/sound/soc/intel/boards/sof_es8336.c @@ -0,0 +1,793 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2021 Intel Corporation. + +/* + * Intel SOF Machine Driver with es8336 Codec + */ + +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/machine.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "hda_dsp_common.h" + +/* jd-inv + terminating entry */ +#define MAX_NO_PROPS 2 + +#define SOF_ES8336_SSP_CODEC(quirk) ((quirk) & GENMASK(3, 0)) +#define SOF_ES8336_SSP_CODEC_MASK (GENMASK(3, 0)) + +#define SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK BIT(4) + +/* HDMI capture*/ +#define SOF_SSP_HDMI_CAPTURE_PRESENT BIT(14) +#define SOF_NO_OF_HDMI_CAPTURE_SSP_SHIFT 15 +#define SOF_NO_OF_HDMI_CAPTURE_SSP_MASK (GENMASK(16, 15)) +#define SOF_NO_OF_HDMI_CAPTURE_SSP(quirk) \ + (((quirk) << SOF_NO_OF_HDMI_CAPTURE_SSP_SHIFT) & SOF_NO_OF_HDMI_CAPTURE_SSP_MASK) + +#define SOF_HDMI_CAPTURE_1_SSP_SHIFT 7 +#define SOF_HDMI_CAPTURE_1_SSP_MASK (GENMASK(9, 7)) +#define SOF_HDMI_CAPTURE_1_SSP(quirk) \ + (((quirk) << SOF_HDMI_CAPTURE_1_SSP_SHIFT) & SOF_HDMI_CAPTURE_1_SSP_MASK) + +#define SOF_HDMI_CAPTURE_2_SSP_SHIFT 10 +#define SOF_HDMI_CAPTURE_2_SSP_MASK (GENMASK(12, 10)) +#define SOF_HDMI_CAPTURE_2_SSP(quirk) \ + (((quirk) << SOF_HDMI_CAPTURE_2_SSP_SHIFT) & SOF_HDMI_CAPTURE_2_SSP_MASK) + +#define SOF_ES8336_ENABLE_DMIC BIT(5) +#define SOF_ES8336_JD_INVERTED BIT(6) +#define SOF_ES8336_HEADPHONE_GPIO BIT(7) +#define SOC_ES8336_HEADSET_MIC1 BIT(8) + +static unsigned long quirk; + +static int quirk_override = -1; +module_param_named(quirk, quirk_override, int, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); + +struct sof_es8336_private { + struct device *codec_dev; + struct gpio_desc *gpio_speakers, *gpio_headphone; + struct snd_soc_jack jack; + struct list_head hdmi_pcm_list; + bool speaker_en; +}; + +struct sof_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +static const struct acpi_gpio_params enable_gpio0 = { 0, 0, true }; +static const struct acpi_gpio_params enable_gpio1 = { 1, 0, true }; + +static const struct acpi_gpio_mapping acpi_speakers_enable_gpio0[] = { + { "speakers-enable-gpios", &enable_gpio0, 1, ACPI_GPIO_QUIRK_ONLY_GPIOIO }, + { } +}; + +static const struct acpi_gpio_mapping acpi_speakers_enable_gpio1[] = { + { "speakers-enable-gpios", &enable_gpio1, 1, ACPI_GPIO_QUIRK_ONLY_GPIOIO }, +}; + +static const struct acpi_gpio_mapping acpi_enable_both_gpios[] = { + { "speakers-enable-gpios", &enable_gpio0, 1, ACPI_GPIO_QUIRK_ONLY_GPIOIO }, + { "headphone-enable-gpios", &enable_gpio1, 1, ACPI_GPIO_QUIRK_ONLY_GPIOIO }, + { } +}; + +static const struct acpi_gpio_mapping acpi_enable_both_gpios_rev_order[] = { + { "speakers-enable-gpios", &enable_gpio1, 1, ACPI_GPIO_QUIRK_ONLY_GPIOIO }, + { "headphone-enable-gpios", &enable_gpio0, 1, ACPI_GPIO_QUIRK_ONLY_GPIOIO }, + { } +}; + +static void log_quirks(struct device *dev) +{ + dev_info(dev, "quirk mask %#lx\n", quirk); + dev_info(dev, "quirk SSP%ld\n", SOF_ES8336_SSP_CODEC(quirk)); + if (quirk & SOF_ES8336_ENABLE_DMIC) + dev_info(dev, "quirk DMIC enabled\n"); + if (quirk & SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK) + dev_info(dev, "Speakers GPIO1 quirk enabled\n"); + if (quirk & SOF_ES8336_HEADPHONE_GPIO) + dev_info(dev, "quirk headphone GPIO enabled\n"); + if (quirk & SOF_ES8336_JD_INVERTED) + dev_info(dev, "quirk JD inverted enabled\n"); + if (quirk & SOC_ES8336_HEADSET_MIC1) + dev_info(dev, "quirk headset at mic1 port enabled\n"); +} + +static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); + + if (priv->speaker_en == !SND_SOC_DAPM_EVENT_ON(event)) + return 0; + + priv->speaker_en = !SND_SOC_DAPM_EVENT_ON(event); + + if (SND_SOC_DAPM_EVENT_ON(event)) + msleep(70); + + gpiod_set_value_cansleep(priv->gpio_speakers, priv->speaker_en); + + if (!(quirk & SOF_ES8336_HEADPHONE_GPIO)) + return 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) + msleep(70); + + gpiod_set_value_cansleep(priv->gpio_headphone, priv->speaker_en); + + return 0; +} + +static const struct snd_soc_dapm_widget sof_es8316_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + + SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0, + sof_es8316_speaker_power_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +}; + +static const struct snd_soc_dapm_widget dmic_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route sof_es8316_audio_map[] = { + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + + /* + * There is no separate speaker output instead the speakers are muxed to + * the HP outputs. The mux is controlled Speaker and/or headphone switch. + */ + {"Speaker", NULL, "HPOL"}, + {"Speaker", NULL, "HPOR"}, + {"Speaker", NULL, "Speaker Power"}, +}; + +static const struct snd_soc_dapm_route sof_es8316_headset_mic2_map[] = { + {"MIC1", NULL, "Internal Mic"}, + {"MIC2", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route sof_es8316_headset_mic1_map[] = { + {"MIC2", NULL, "Internal Mic"}, + {"MIC1", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route dmic_map[] = { + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +static const struct snd_kcontrol_new sof_es8316_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), +}; + +static struct snd_soc_jack_pin sof_es8316_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int dmic_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets, + ARRAY_SIZE(dmic_widgets)); + if (ret) { + dev_err(card->dev, "DMic widget addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map, + ARRAY_SIZE(dmic_map)); + if (ret) + dev_err(card->dev, "DMic map addition failed: %d\n", ret); + + return ret; +} + +static int sof_hdmi_init(struct snd_soc_pcm_runtime *runtime) +{ + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(runtime->card); + struct snd_soc_dai *dai = asoc_rtd_to_codec(runtime, 0); + struct sof_hdmi_pcm *pcm; + + pcm = devm_kzalloc(runtime->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = runtime->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &priv->hdmi_pcm_list); + + return 0; +} + +static int sof_es8316_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *codec = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_card *card = runtime->card; + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); + const struct snd_soc_dapm_route *custom_map; + int num_routes; + int ret; + + card->dapm.idle_bias_off = true; + + if (quirk & SOC_ES8336_HEADSET_MIC1) { + custom_map = sof_es8316_headset_mic1_map; + num_routes = ARRAY_SIZE(sof_es8316_headset_mic1_map); + } else { + custom_map = sof_es8316_headset_mic2_map; + num_routes = ARRAY_SIZE(sof_es8316_headset_mic2_map); + } + + ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); + if (ret) + return ret; + + ret = snd_soc_card_jack_new_pins(card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &priv->jack, sof_es8316_jack_pins, + ARRAY_SIZE(sof_es8316_jack_pins)); + if (ret) { + dev_err(card->dev, "jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + + snd_soc_component_set_jack(codec, &priv->jack, NULL); + + return 0; +} + +static void sof_es8316_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_set_jack(component, NULL, NULL); +} + +static int sof_es8336_quirk_cb(const struct dmi_system_id *id) +{ + quirk = (unsigned long)id->driver_data; + + return 1; +} + +/* + * this table should only be used to add GPIO or jack-detection quirks + * that cannot be detected from ACPI tables. The SSP and DMIC + * information are providing by the platform driver and are aligned + * with the topology used. + * + * If the GPIO support is missing, the quirk parameter can be used to + * enable speakers. In that case it's recommended to keep the SSP and DMIC + * information consistent, overriding the SSP and DMIC can only be done + * if the topology file is modified as well. + */ +static const struct dmi_system_id sof_es8336_quirk_table[] = { + { + .callback = sof_es8336_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "IP3 tech"), + DMI_MATCH(DMI_BOARD_NAME, "WN1"), + }, + .driver_data = (void *)(SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK) + }, + { + .callback = sof_es8336_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), + DMI_MATCH(DMI_BOARD_NAME, "BOHB-WAX9-PCB-B2"), + }, + .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | + SOC_ES8336_HEADSET_MIC1) + }, + {} +}; + +static int sof_es8336_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + const int sysclk = 19200000; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 1, sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->dev, "%s, Failed to set ES8336 SYSCLK: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +/* machine stream operations */ +static struct snd_soc_ops sof_es8336_ops = { + .hw_params = sof_es8336_hw_params, +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + /* name might be overridden during probe */ + .name = "0000:00:1f.3" + } +}; + +SND_SOC_DAILINK_DEF(es8336_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8336:00", "ES8316 HiFi"))); + +static struct snd_soc_dai_link_component dmic_component[] = { + { + .name = "dmic-codec", + .dai_name = "dmic-hifi", + } +}; + +static struct snd_soc_dai_link_component dummy_component[] = { + { + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", + } +}; + +static int sof_es8336_late_probe(struct snd_soc_card *card) +{ + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); + struct sof_hdmi_pcm *pcm; + + if (list_empty(&priv->hdmi_pcm_list)) + return -ENOENT; + + pcm = list_first_entry(&priv->hdmi_pcm_list, struct sof_hdmi_pcm, head); + + return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component); +} + +/* SoC card */ +static struct snd_soc_card sof_es8336_card = { + .name = "essx8336", /* sof- prefix added automatically */ + .owner = THIS_MODULE, + .dapm_widgets = sof_es8316_widgets, + .num_dapm_widgets = ARRAY_SIZE(sof_es8316_widgets), + .dapm_routes = sof_es8316_audio_map, + .num_dapm_routes = ARRAY_SIZE(sof_es8316_audio_map), + .controls = sof_es8316_controls, + .num_controls = ARRAY_SIZE(sof_es8316_controls), + .fully_routed = true, + .late_probe = sof_es8336_late_probe, + .num_links = 1, +}; + +static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, + int ssp_codec, + int dmic_be_num, + int hdmi_num) +{ + struct snd_soc_dai_link_component *cpus; + struct snd_soc_dai_link *links; + struct snd_soc_dai_link_component *idisp_components; + int hdmi_id_offset = 0; + int id = 0; + int i; + + links = devm_kcalloc(dev, sof_es8336_card.num_links, + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + cpus = devm_kcalloc(dev, sof_es8336_card.num_links, + sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); + if (!links || !cpus) + goto devm_err; + + /* codec SSP */ + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d-Codec", ssp_codec); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].codecs = es8336_codec; + links[id].num_codecs = ARRAY_SIZE(es8336_codec); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_es8316_init; + links[id].exit = sof_es8316_exit; + links[id].ops = &sof_es8336_ops; + links[id].nonatomic = true; + links[id].dpcm_playback = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_codec); + if (!links[id].cpus->dai_name) + goto devm_err; + + id++; + + /* dmic */ + if (dmic_be_num > 0) { + /* at least we have dmic01 */ + links[id].name = "dmic01"; + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = "DMIC01 Pin"; + links[id].init = dmic_init; + if (dmic_be_num > 1) { + /* set up 2 BE links at most */ + links[id + 1].name = "dmic16k"; + links[id + 1].cpus = &cpus[id + 1]; + links[id + 1].cpus->dai_name = "DMIC16k Pin"; + dmic_be_num = 2; + } + } else { + /* HDMI dai link starts at 3 according to current topology settings */ + hdmi_id_offset = 2; + } + + for (i = 0; i < dmic_be_num; i++) { + links[id].id = id; + links[id].num_cpus = 1; + links[id].codecs = dmic_component; + links[id].num_codecs = ARRAY_SIZE(dmic_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].ignore_suspend = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + + id++; + } + + /* HDMI */ + if (hdmi_num > 0) { + idisp_components = devm_kcalloc(dev, + hdmi_num, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); + if (!idisp_components) + goto devm_err; + } + + for (i = 1; i <= hdmi_num; i++) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d", i); + if (!links[id].name) + goto devm_err; + + links[id].id = id + hdmi_id_offset; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d Pin", i); + if (!links[id].cpus->dai_name) + goto devm_err; + + idisp_components[i - 1].name = "ehdaudio0D2"; + idisp_components[i - 1].dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "intel-hdmi-hifi%d", + i); + if (!idisp_components[i - 1].dai_name) + goto devm_err; + + links[id].codecs = &idisp_components[i - 1]; + links[id].num_codecs = 1; + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_hdmi_init; + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + + id++; + } + + /* HDMI-In SSP */ + if (quirk & SOF_SSP_HDMI_CAPTURE_PRESENT) { + int num_of_hdmi_ssp = (quirk & SOF_NO_OF_HDMI_CAPTURE_SSP_MASK) >> + SOF_NO_OF_HDMI_CAPTURE_SSP_SHIFT; + + for (i = 1; i <= num_of_hdmi_ssp; i++) { + int port = (i == 1 ? (quirk & SOF_HDMI_CAPTURE_1_SSP_MASK) >> + SOF_HDMI_CAPTURE_1_SSP_SHIFT : + (quirk & SOF_HDMI_CAPTURE_2_SSP_MASK) >> + SOF_HDMI_CAPTURE_2_SSP_SHIFT); + + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", port); + if (!links[id].cpus->dai_name) + return NULL; + links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-HDMI", port); + if (!links[id].name) + return NULL; + links[id].id = id + hdmi_id_offset; + links[id].codecs = dummy_component; + links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].num_cpus = 1; + id++; + } + } + + return links; + +devm_err: + return NULL; +} + +static char soc_components[30]; + + /* i2c-<HID>:00 with HID being 8 chars */ +static char codec_name[SND_ACPI_I2C_ID_LEN]; + +static int sof_es8336_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + struct snd_soc_acpi_mach *mach = pdev->dev.platform_data; + struct property_entry props[MAX_NO_PROPS] = {}; + struct sof_es8336_private *priv; + struct fwnode_handle *fwnode; + struct acpi_device *adev; + struct snd_soc_dai_link *dai_links; + struct device *codec_dev; + const struct acpi_gpio_mapping *gpio_mapping; + unsigned int cnt = 0; + int dmic_be_num = 0; + int hdmi_num = 3; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + card = &sof_es8336_card; + card->dev = dev; + + if (pdev->id_entry && pdev->id_entry->driver_data) + quirk = (unsigned long)pdev->id_entry->driver_data; + + /* check GPIO DMI quirks */ + dmi_check_system(sof_es8336_quirk_table); + + /* Use NHLT configuration only for Non-HDMI capture use case. + * Because more than one SSP will be enabled for HDMI capture hence wrong codec + * SSP will be set. + */ + if (mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER) { + if (!mach->mach_params.i2s_link_mask) { + dev_warn(dev, "No I2S link information provided, using SSP0. This may need to be modified with the quirk module parameter\n"); + } else { + /* + * Set configuration based on platform NHLT. + * In this machine driver, we can only support one SSP for the + * ES8336 link. + * In some cases multiple SSPs can be reported by NHLT, starting MSB-first + * seems to pick the right connection. + */ + unsigned long ssp; + + /* fls returns 1-based results, SSPs indices are 0-based */ + ssp = fls(mach->mach_params.i2s_link_mask) - 1; + + quirk |= ssp; + } + } + + if (mach->mach_params.dmic_num) + quirk |= SOF_ES8336_ENABLE_DMIC; + + if (quirk_override != -1) { + dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n", + quirk, quirk_override); + quirk = quirk_override; + } + log_quirks(dev); + + if (quirk & SOF_ES8336_ENABLE_DMIC) + dmic_be_num = 2; + + /* compute number of dai links */ + sof_es8336_card.num_links = 1 + dmic_be_num + hdmi_num; + + if (quirk & SOF_SSP_HDMI_CAPTURE_PRESENT) + sof_es8336_card.num_links += (quirk & SOF_NO_OF_HDMI_CAPTURE_SSP_MASK) >> + SOF_NO_OF_HDMI_CAPTURE_SSP_SHIFT; + + dai_links = sof_card_dai_links_create(dev, + SOF_ES8336_SSP_CODEC(quirk), + dmic_be_num, hdmi_num); + if (!dai_links) + return -ENOMEM; + + sof_es8336_card.dai_link = dai_links; + + /* fixup codec name based on HID */ + adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); + if (adev) { + snprintf(codec_name, sizeof(codec_name), + "i2c-%s", acpi_dev_name(adev)); + put_device(&adev->dev); + dai_links[0].codecs->name = codec_name; + + /* also fixup codec dai name if relevant */ + if (!strncmp(mach->id, "ESSX8326", SND_ACPI_I2C_ID_LEN)) + dai_links[0].codecs->dai_name = "ES8326 HiFi"; + } else { + dev_err(dev, "Error cannot find '%s' dev\n", mach->id); + return -ENXIO; + } + + ret = snd_soc_fixup_dai_links_platform_name(&sof_es8336_card, + mach->mach_params.platform); + if (ret) + return ret; + + codec_dev = acpi_get_first_physical_node(adev); + if (!codec_dev) + return -EPROBE_DEFER; + priv->codec_dev = get_device(codec_dev); + + if (quirk & SOF_ES8336_JD_INVERTED) + props[cnt++] = PROPERTY_ENTRY_BOOL("everest,jack-detect-inverted"); + + if (cnt) { + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) { + put_device(codec_dev); + return PTR_ERR(fwnode); + } + + ret = device_add_software_node(codec_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + if (ret) { + put_device(codec_dev); + return ret; + } + } + + /* get speaker enable GPIO */ + if (quirk & SOF_ES8336_HEADPHONE_GPIO) { + if (quirk & SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK) + gpio_mapping = acpi_enable_both_gpios; + else + gpio_mapping = acpi_enable_both_gpios_rev_order; + } else if (quirk & SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK) { + gpio_mapping = acpi_speakers_enable_gpio1; + } else { + gpio_mapping = acpi_speakers_enable_gpio0; + } + + ret = devm_acpi_dev_add_driver_gpios(codec_dev, gpio_mapping); + if (ret) + dev_warn(codec_dev, "unable to add GPIO mapping table\n"); + + priv->gpio_speakers = gpiod_get_optional(codec_dev, "speakers-enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpio_speakers)) { + ret = dev_err_probe(dev, PTR_ERR(priv->gpio_speakers), + "could not get speakers-enable GPIO\n"); + goto err_put_codec; + } + + priv->gpio_headphone = gpiod_get_optional(codec_dev, "headphone-enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpio_headphone)) { + ret = dev_err_probe(dev, PTR_ERR(priv->gpio_headphone), + "could not get headphone-enable GPIO\n"); + goto err_put_codec; + } + + INIT_LIST_HEAD(&priv->hdmi_pcm_list); + + snd_soc_card_set_drvdata(card, priv); + + if (mach->mach_params.dmic_num > 0) { + snprintf(soc_components, sizeof(soc_components), + "cfg-dmics:%d", mach->mach_params.dmic_num); + card->components = soc_components; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) { + gpiod_put(priv->gpio_speakers); + dev_err(dev, "snd_soc_register_card failed: %d\n", ret); + goto err_put_codec; + } + platform_set_drvdata(pdev, &sof_es8336_card); + return 0; + +err_put_codec: + device_remove_software_node(priv->codec_dev); + put_device(codec_dev); + return ret; +} + +static int sof_es8336_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); + + gpiod_put(priv->gpio_speakers); + device_remove_software_node(priv->codec_dev); + put_device(priv->codec_dev); + + return 0; +} + +static const struct platform_device_id board_ids[] = { + { + .name = "sof-essx8336", /* default quirk == 0 */ + }, + { + .name = "adl_es83x6_c1_h02", + .driver_data = (kernel_ulong_t)(SOF_ES8336_SSP_CODEC(1) | + SOF_NO_OF_HDMI_CAPTURE_SSP(2) | + SOF_HDMI_CAPTURE_1_SSP(0) | + SOF_HDMI_CAPTURE_2_SSP(2) | + SOF_SSP_HDMI_CAPTURE_PRESENT | + SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK | + SOF_ES8336_JD_INVERTED), + }, + { } +}; +MODULE_DEVICE_TABLE(platform, board_ids); + +static struct platform_driver sof_es8336_driver = { + .driver = { + .name = "sof-essx8336", + .pm = &snd_soc_pm_ops, + }, + .probe = sof_es8336_probe, + .remove = sof_es8336_remove, + .id_table = board_ids, +}; +module_platform_driver(sof_es8336_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) SOF + ES8336 Machine driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/sof_maxim_common.c b/sound/soc/intel/boards/sof_maxim_common.c new file mode 100644 index 000000000000..112e89951da0 --- /dev/null +++ b/sound/soc/intel/boards/sof_maxim_common.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +#include <linux/module.h> +#include <linux/string.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dai.h> +#include <sound/soc-dapm.h> +#include <uapi/sound/asound.h> +#include "sof_maxim_common.h" + +#define MAX_98373_PIN_NAME 16 + +const struct snd_soc_dapm_route max_98373_dapm_routes[] = { + /* speaker */ + { "Left Spk", NULL, "Left BE_OUT" }, + { "Right Spk", NULL, "Right BE_OUT" }, +}; +EXPORT_SYMBOL_NS(max_98373_dapm_routes, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +static struct snd_soc_codec_conf max_98373_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF(MAX_98373_DEV0_NAME), + .name_prefix = "Right", + }, + { + .dlc = COMP_CODEC_CONF(MAX_98373_DEV1_NAME), + .name_prefix = "Left", + }, +}; + +struct snd_soc_dai_link_component max_98373_components[] = { + { /* For Right */ + .name = MAX_98373_DEV0_NAME, + .dai_name = MAX_98373_CODEC_DAI, + }, + { /* For Left */ + .name = MAX_98373_DEV1_NAME, + .dai_name = MAX_98373_CODEC_DAI, + }, +}; +EXPORT_SYMBOL_NS(max_98373_components, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +static int max_98373_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + int j; + + for_each_rtd_codec_dais(rtd, j, codec_dai) { + if (!strcmp(codec_dai->component->name, MAX_98373_DEV0_NAME)) { + /* DEV0 tdm slot configuration */ + snd_soc_dai_set_tdm_slot(codec_dai, 0x03, 3, 8, 32); + } + if (!strcmp(codec_dai->component->name, MAX_98373_DEV1_NAME)) { + /* DEV1 tdm slot configuration */ + snd_soc_dai_set_tdm_slot(codec_dai, 0x0C, 3, 8, 32); + } + } + return 0; +} + +int max_98373_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + struct snd_soc_dai *cpu_dai; + int j; + int ret = 0; + + /* set spk pin by playback only */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + cpu_dai = asoc_rtd_to_cpu(rtd, 0); + for_each_rtd_codec_dais(rtd, j, codec_dai) { + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(cpu_dai->component); + char pin_name[MAX_98373_PIN_NAME]; + + snprintf(pin_name, ARRAY_SIZE(pin_name), "%s Spk", + codec_dai->component->name_prefix); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = snd_soc_dapm_enable_pin(dapm, pin_name); + if (!ret) + snd_soc_dapm_sync(dapm); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = snd_soc_dapm_disable_pin(dapm, pin_name); + if (!ret) + snd_soc_dapm_sync(dapm); + break; + default: + break; + } + } + + return ret; +} +EXPORT_SYMBOL_NS(max_98373_trigger, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +struct snd_soc_ops max_98373_ops = { + .hw_params = max_98373_hw_params, + .trigger = max_98373_trigger, +}; +EXPORT_SYMBOL_NS(max_98373_ops, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +int max_98373_spk_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_add_routes(&card->dapm, max_98373_dapm_routes, + ARRAY_SIZE(max_98373_dapm_routes)); + if (ret) + dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret); + return ret; +} +EXPORT_SYMBOL_NS(max_98373_spk_codec_init, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +void max_98373_set_codec_conf(struct snd_soc_card *card) +{ + card->codec_conf = max_98373_codec_conf; + card->num_configs = ARRAY_SIZE(max_98373_codec_conf); +} +EXPORT_SYMBOL_NS(max_98373_set_codec_conf, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +/* + * Maxim MAX98390 + */ +static const struct snd_soc_dapm_route max_98390_dapm_routes[] = { + /* speaker */ + { "Left Spk", NULL, "Left BE_OUT" }, + { "Right Spk", NULL, "Right BE_OUT" }, +}; + +static const struct snd_kcontrol_new max_98390_tt_kcontrols[] = { + SOC_DAPM_PIN_SWITCH("TL Spk"), + SOC_DAPM_PIN_SWITCH("TR Spk"), +}; + +static const struct snd_soc_dapm_widget max_98390_tt_dapm_widgets[] = { + SND_SOC_DAPM_SPK("TL Spk", NULL), + SND_SOC_DAPM_SPK("TR Spk", NULL), +}; + +static const struct snd_soc_dapm_route max_98390_tt_dapm_routes[] = { + /* Tweeter speaker */ + { "TL Spk", NULL, "Tweeter Left BE_OUT" }, + { "TR Spk", NULL, "Tweeter Right BE_OUT" }, +}; + +static struct snd_soc_codec_conf max_98390_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF(MAX_98390_DEV0_NAME), + .name_prefix = "Right", + }, + { + .dlc = COMP_CODEC_CONF(MAX_98390_DEV1_NAME), + .name_prefix = "Left", + }, +}; + +static struct snd_soc_codec_conf max_98390_4spk_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF(MAX_98390_DEV0_NAME), + .name_prefix = "Right", + }, + { + .dlc = COMP_CODEC_CONF(MAX_98390_DEV1_NAME), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF(MAX_98390_DEV2_NAME), + .name_prefix = "Tweeter Right", + }, + { + .dlc = COMP_CODEC_CONF(MAX_98390_DEV3_NAME), + .name_prefix = "Tweeter Left", + }, +}; + +struct snd_soc_dai_link_component max_98390_components[] = { + { + .name = MAX_98390_DEV0_NAME, + .dai_name = MAX_98390_CODEC_DAI, + }, + { + .name = MAX_98390_DEV1_NAME, + .dai_name = MAX_98390_CODEC_DAI, + }, +}; +EXPORT_SYMBOL_NS(max_98390_components, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +struct snd_soc_dai_link_component max_98390_4spk_components[] = { + { + .name = MAX_98390_DEV0_NAME, + .dai_name = MAX_98390_CODEC_DAI, + }, + { + .name = MAX_98390_DEV1_NAME, + .dai_name = MAX_98390_CODEC_DAI, + }, + { + .name = MAX_98390_DEV2_NAME, + .dai_name = MAX_98390_CODEC_DAI, + }, + { + .name = MAX_98390_DEV3_NAME, + .dai_name = MAX_98390_CODEC_DAI, + }, +}; +EXPORT_SYMBOL_NS(max_98390_4spk_components, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +static int max_98390_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + int i; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + if (i >= ARRAY_SIZE(max_98390_4spk_components)) { + dev_err(codec_dai->dev, "invalid codec index %d\n", i); + return -ENODEV; + } + + if (!strcmp(codec_dai->component->name, MAX_98390_DEV0_NAME)) { + /* DEV0 tdm slot configuration Right */ + snd_soc_dai_set_tdm_slot(codec_dai, 0x01, 3, 4, 32); + } + if (!strcmp(codec_dai->component->name, MAX_98390_DEV1_NAME)) { + /* DEV1 tdm slot configuration Left */ + snd_soc_dai_set_tdm_slot(codec_dai, 0x02, 3, 4, 32); + } + + if (!strcmp(codec_dai->component->name, MAX_98390_DEV2_NAME)) { + /* DEVi2 tdm slot configuration Tweeter Right */ + snd_soc_dai_set_tdm_slot(codec_dai, 0x04, 3, 4, 32); + } + if (!strcmp(codec_dai->component->name, MAX_98390_DEV3_NAME)) { + /* DEV3 tdm slot configuration Tweeter Left */ + snd_soc_dai_set_tdm_slot(codec_dai, 0x08, 3, 4, 32); + } + } + return 0; +} + +int max_98390_spk_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + /* add regular speakers dapm route */ + ret = snd_soc_dapm_add_routes(&card->dapm, max_98390_dapm_routes, + ARRAY_SIZE(max_98390_dapm_routes)); + if (ret) { + dev_err(rtd->dev, "unable to add Left/Right Speaker dapm, ret %d\n", ret); + return ret; + } + + /* add widgets/controls/dapm for tweeter speakers */ + if (acpi_dev_present("MX98390", "3", -1)) { + ret = snd_soc_dapm_new_controls(&card->dapm, max_98390_tt_dapm_widgets, + ARRAY_SIZE(max_98390_tt_dapm_widgets)); + + if (ret) { + dev_err(rtd->dev, "unable to add tweeter dapm controls, ret %d\n", ret); + /* Don't need to add routes if widget addition failed */ + return ret; + } + + ret = snd_soc_add_card_controls(card, max_98390_tt_kcontrols, + ARRAY_SIZE(max_98390_tt_kcontrols)); + if (ret) { + dev_err(rtd->dev, "unable to add tweeter card controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, max_98390_tt_dapm_routes, + ARRAY_SIZE(max_98390_tt_dapm_routes)); + if (ret) + dev_err(rtd->dev, + "unable to add Tweeter Left/Right Speaker dapm, ret %d\n", ret); + } + return ret; +} +EXPORT_SYMBOL_NS(max_98390_spk_codec_init, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +const struct snd_soc_ops max_98390_ops = { + .hw_params = max_98390_hw_params, +}; +EXPORT_SYMBOL_NS(max_98390_ops, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +void max_98390_set_codec_conf(struct snd_soc_card *card, int ch) +{ + if (ch == ARRAY_SIZE(max_98390_4spk_codec_conf)) { + card->codec_conf = max_98390_4spk_codec_conf; + card->num_configs = ARRAY_SIZE(max_98390_4spk_codec_conf); + } else { + card->codec_conf = max_98390_codec_conf; + card->num_configs = ARRAY_SIZE(max_98390_codec_conf); + } +} +EXPORT_SYMBOL_NS(max_98390_set_codec_conf, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +/* + * Maxim MAX98357A/MAX98360A + */ +static const struct snd_kcontrol_new max_98357a_kcontrols[] = { + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget max_98357a_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Spk", NULL), +}; + +static const struct snd_soc_dapm_route max_98357a_dapm_routes[] = { + /* speaker */ + {"Spk", NULL, "Speaker"}, +}; + +static struct snd_soc_dai_link_component max_98357a_components[] = { + { + .name = MAX_98357A_DEV0_NAME, + .dai_name = MAX_98357A_CODEC_DAI, + } +}; + +static struct snd_soc_dai_link_component max_98360a_components[] = { + { + .name = MAX_98360A_DEV0_NAME, + .dai_name = MAX_98357A_CODEC_DAI, + } +}; + +static int max_98357a_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, max_98357a_dapm_widgets, + ARRAY_SIZE(max_98357a_dapm_widgets)); + if (ret) { + dev_err(rtd->dev, "unable to add dapm controls, ret %d\n", ret); + /* Don't need to add routes if widget addition failed */ + return ret; + } + + ret = snd_soc_add_card_controls(card, max_98357a_kcontrols, + ARRAY_SIZE(max_98357a_kcontrols)); + if (ret) { + dev_err(rtd->dev, "unable to add card controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, max_98357a_dapm_routes, + ARRAY_SIZE(max_98357a_dapm_routes)); + + if (ret) + dev_err(rtd->dev, "unable to add dapm routes, ret %d\n", ret); + + return ret; +} + +void max_98357a_dai_link(struct snd_soc_dai_link *link) +{ + link->codecs = max_98357a_components; + link->num_codecs = ARRAY_SIZE(max_98357a_components); + link->init = max_98357a_init; +} +EXPORT_SYMBOL_NS(max_98357a_dai_link, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +void max_98360a_dai_link(struct snd_soc_dai_link *link) +{ + link->codecs = max_98360a_components; + link->num_codecs = ARRAY_SIZE(max_98360a_components); + link->init = max_98357a_init; +} +EXPORT_SYMBOL_NS(max_98360a_dai_link, SND_SOC_INTEL_SOF_MAXIM_COMMON); + +MODULE_DESCRIPTION("ASoC Intel SOF Maxim helpers"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/boards/sof_maxim_common.h b/sound/soc/intel/boards/sof_maxim_common.h new file mode 100644 index 000000000000..7a8c53049e4d --- /dev/null +++ b/sound/soc/intel/boards/sof_maxim_common.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2020 Intel Corporation. + */ + +/* + * This file defines data structures used in Machine Driver for Intel + * platforms with Maxim Codecs. + */ +#ifndef __SOF_MAXIM_COMMON_H +#define __SOF_MAXIM_COMMON_H + +#include <sound/soc.h> + +#define MAX_98373_CODEC_DAI "max98373-aif1" +#define MAX_98373_DEV0_NAME "i2c-MX98373:00" +#define MAX_98373_DEV1_NAME "i2c-MX98373:01" + +extern struct snd_soc_dai_link_component max_98373_components[2]; +extern struct snd_soc_ops max_98373_ops; +extern const struct snd_soc_dapm_route max_98373_dapm_routes[]; + +int max_98373_spk_codec_init(struct snd_soc_pcm_runtime *rtd); +void max_98373_set_codec_conf(struct snd_soc_card *card); +int max_98373_trigger(struct snd_pcm_substream *substream, int cmd); + +/* + * Maxim MAX98390 + */ +#define MAX_98390_CODEC_DAI "max98390-aif1" +#define MAX_98390_DEV0_NAME "i2c-MX98390:00" +#define MAX_98390_DEV1_NAME "i2c-MX98390:01" +#define MAX_98390_DEV2_NAME "i2c-MX98390:02" +#define MAX_98390_DEV3_NAME "i2c-MX98390:03" + +extern struct snd_soc_dai_link_component max_98390_components[2]; +extern struct snd_soc_dai_link_component max_98390_4spk_components[4]; +extern const struct snd_soc_ops max_98390_ops; + +void max_98390_set_codec_conf(struct snd_soc_card *card, int ch); +int max_98390_spk_codec_init(struct snd_soc_pcm_runtime *rtd); + +/* + * Maxim MAX98357A/MAX98360A + */ +#define MAX_98357A_CODEC_DAI "HiFi" +#define MAX_98357A_DEV0_NAME "MX98357A:00" +#define MAX_98360A_DEV0_NAME "MX98360A:00" + +void max_98357a_dai_link(struct snd_soc_dai_link *link); +void max_98360a_dai_link(struct snd_soc_dai_link *link); + +#endif /* __SOF_MAXIM_COMMON_H */ diff --git a/sound/soc/intel/boards/sof_nau8825.c b/sound/soc/intel/boards/sof_nau8825.c new file mode 100644 index 000000000000..5585c217f78d --- /dev/null +++ b/sound/soc/intel/boards/sof_nau8825.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2021 Intel Corporation. +// Copyright(c) 2021 Nuvoton Corporation. + +/* + * Intel SOF Machine Driver with Nuvoton headphone codec NAU8825 + * and speaker codec RT1019P MAX98360a or MAX98373 + */ +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dmi.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/sof.h> +#include <sound/soc-acpi.h> +#include "../../codecs/nau8825.h" +#include "../common/soc-intel-quirks.h" +#include "hda_dsp_common.h" +#include "sof_realtek_common.h" +#include "sof_maxim_common.h" + +#define NAME_SIZE 32 + +#define SOF_NAU8825_SSP_CODEC(quirk) ((quirk) & GENMASK(2, 0)) +#define SOF_NAU8825_SSP_CODEC_MASK (GENMASK(2, 0)) +#define SOF_SPEAKER_AMP_PRESENT BIT(3) +#define SOF_NAU8825_SSP_AMP_SHIFT 4 +#define SOF_NAU8825_SSP_AMP_MASK (GENMASK(6, 4)) +#define SOF_NAU8825_SSP_AMP(quirk) \ + (((quirk) << SOF_NAU8825_SSP_AMP_SHIFT) & SOF_NAU8825_SSP_AMP_MASK) +#define SOF_NAU8825_NUM_HDMIDEV_SHIFT 7 +#define SOF_NAU8825_NUM_HDMIDEV_MASK (GENMASK(9, 7)) +#define SOF_NAU8825_NUM_HDMIDEV(quirk) \ + (((quirk) << SOF_NAU8825_NUM_HDMIDEV_SHIFT) & SOF_NAU8825_NUM_HDMIDEV_MASK) + +/* BT audio offload: reserve 3 bits for future */ +#define SOF_BT_OFFLOAD_SSP_SHIFT 10 +#define SOF_BT_OFFLOAD_SSP_MASK (GENMASK(12, 10)) +#define SOF_BT_OFFLOAD_SSP(quirk) \ + (((quirk) << SOF_BT_OFFLOAD_SSP_SHIFT) & SOF_BT_OFFLOAD_SSP_MASK) +#define SOF_SSP_BT_OFFLOAD_PRESENT BIT(13) +#define SOF_RT1019P_SPEAKER_AMP_PRESENT BIT(14) +#define SOF_MAX98373_SPEAKER_AMP_PRESENT BIT(15) +#define SOF_MAX98360A_SPEAKER_AMP_PRESENT BIT(16) + +static unsigned long sof_nau8825_quirk = SOF_NAU8825_SSP_CODEC(0); + +struct sof_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct sof_card_private { + struct clk *mclk; + struct snd_soc_jack sof_headset; + struct list_head hdmi_pcm_list; +}; + +static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); + struct sof_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int sof_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + + struct snd_soc_jack *jack; + int ret; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sof_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + jack = &ctx->sof_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return ret; +}; + +static void sof_nau8825_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_set_jack(component, NULL, NULL); +} + +static int sof_nau8825_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int clk_freq, ret; + + clk_freq = sof_dai_get_bclk(rtd); /* BCLK freq */ + + if (clk_freq <= 0) { + dev_err(rtd->dev, "get bclk freq failed: %d\n", clk_freq); + return -EINVAL; + } + + /* Configure clock for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_BLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set BCLK clock %d\n", ret); + return ret; + } + + /* Configure pll for codec */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, clk_freq, + params_rate(params) * 256); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set BCLK: %d\n", ret); + return ret; + } + + return ret; +} + +static struct snd_soc_ops sof_nau8825_ops = { + .hw_params = sof_nau8825_hw_params, +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + /* name might be overridden during probe */ + .name = "0000:00:1f.3" + } +}; + +static int sof_card_late_probe(struct snd_soc_card *card) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_dapm_context *dapm = &card->dapm; + struct sof_hdmi_pcm *pcm; + int err; + + if (sof_nau8825_quirk & SOF_MAX98373_SPEAKER_AMP_PRESENT) { + /* Disable Left and Right Spk pin after boot */ + snd_soc_dapm_disable_pin(dapm, "Left Spk"); + snd_soc_dapm_disable_pin(dapm, "Right Spk"); + err = snd_soc_dapm_sync(dapm); + if (err < 0) + return err; + } + + if (list_empty(&ctx->hdmi_pcm_list)) + return -EINVAL; + + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm, head); + + return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component); +} + +static const struct snd_kcontrol_new sof_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_kcontrol_new speaker_controls[] = { + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget sof_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_soc_dapm_widget speaker_widgets[] = { + SND_SOC_DAPM_SPK("Spk", NULL), +}; + +static const struct snd_soc_dapm_widget dmic_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route sof_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* other jacks */ + { "MIC", NULL, "Headset Mic" }, +}; + +static const struct snd_soc_dapm_route speaker_map[] = { + /* speaker */ + { "Spk", NULL, "Speaker" }, +}; + +static const struct snd_soc_dapm_route dmic_map[] = { + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +static int speaker_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, speaker_widgets, + ARRAY_SIZE(speaker_widgets)); + if (ret) { + dev_err(rtd->dev, "unable to add dapm controls, ret %d\n", ret); + /* Don't need to add routes if widget addition failed */ + return ret; + } + + ret = snd_soc_add_card_controls(card, speaker_controls, + ARRAY_SIZE(speaker_controls)); + if (ret) { + dev_err(rtd->dev, "unable to add card controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, speaker_map, + ARRAY_SIZE(speaker_map)); + + if (ret) + dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret); + return ret; +} + +static int dmic_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets, + ARRAY_SIZE(dmic_widgets)); + if (ret) { + dev_err(card->dev, "DMic widget addition failed: %d\n", ret); + /* Don't need to add routes if widget addition failed */ + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map, + ARRAY_SIZE(dmic_map)); + + if (ret) + dev_err(card->dev, "DMic map addition failed: %d\n", ret); + + return ret; +} + +/* sof audio machine driver for nau8825 codec */ +static struct snd_soc_card sof_audio_card_nau8825 = { + .name = "nau8825", /* the sof- prefix is added by the core */ + .owner = THIS_MODULE, + .controls = sof_controls, + .num_controls = ARRAY_SIZE(sof_controls), + .dapm_widgets = sof_widgets, + .num_dapm_widgets = ARRAY_SIZE(sof_widgets), + .dapm_routes = sof_map, + .num_dapm_routes = ARRAY_SIZE(sof_map), + .fully_routed = true, + .late_probe = sof_card_late_probe, +}; + +static struct snd_soc_dai_link_component nau8825_component[] = { + { + .name = "i2c-10508825:00", + .dai_name = "nau8825-hifi", + } +}; + +static struct snd_soc_dai_link_component dmic_component[] = { + { + .name = "dmic-codec", + .dai_name = "dmic-hifi", + } +}; + +static struct snd_soc_dai_link_component rt1019p_component[] = { + { + .name = "RTL1019:00", + .dai_name = "HiFi", + } +}; + +static struct snd_soc_dai_link_component dummy_component[] = { + { + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", + } +}; + +static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, + int ssp_codec, + int ssp_amp, + int dmic_be_num, + int hdmi_num) +{ + struct snd_soc_dai_link_component *idisp_components; + struct snd_soc_dai_link_component *cpus; + struct snd_soc_dai_link *links; + int i, id = 0; + + links = devm_kcalloc(dev, sof_audio_card_nau8825.num_links, + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + cpus = devm_kcalloc(dev, sof_audio_card_nau8825.num_links, + sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); + if (!links || !cpus) + goto devm_err; + + /* codec SSP */ + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d-Codec", ssp_codec); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].codecs = nau8825_component; + links[id].num_codecs = ARRAY_SIZE(nau8825_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_nau8825_codec_init; + links[id].exit = sof_nau8825_codec_exit; + links[id].ops = &sof_nau8825_ops; + links[id].dpcm_playback = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_codec); + if (!links[id].cpus->dai_name) + goto devm_err; + + id++; + + /* dmic */ + if (dmic_be_num > 0) { + /* at least we have dmic01 */ + links[id].name = "dmic01"; + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = "DMIC01 Pin"; + links[id].init = dmic_init; + if (dmic_be_num > 1) { + /* set up 2 BE links at most */ + links[id + 1].name = "dmic16k"; + links[id + 1].cpus = &cpus[id + 1]; + links[id + 1].cpus->dai_name = "DMIC16k Pin"; + dmic_be_num = 2; + } + } + + for (i = 0; i < dmic_be_num; i++) { + links[id].id = id; + links[id].num_cpus = 1; + links[id].codecs = dmic_component; + links[id].num_codecs = ARRAY_SIZE(dmic_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].ignore_suspend = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + id++; + } + + /* HDMI */ + if (hdmi_num > 0) { + idisp_components = devm_kcalloc(dev, + hdmi_num, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); + if (!idisp_components) + goto devm_err; + } + for (i = 1; i <= hdmi_num; i++) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d", i); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d Pin", i); + if (!links[id].cpus->dai_name) + goto devm_err; + + idisp_components[i - 1].name = "ehdaudio0D2"; + idisp_components[i - 1].dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "intel-hdmi-hifi%d", + i); + if (!idisp_components[i - 1].dai_name) + goto devm_err; + + links[id].codecs = &idisp_components[i - 1]; + links[id].num_codecs = 1; + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_hdmi_init; + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + id++; + } + + /* speaker amp */ + if (sof_nau8825_quirk & SOF_SPEAKER_AMP_PRESENT) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d-Codec", ssp_amp); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + if (sof_nau8825_quirk & SOF_RT1019P_SPEAKER_AMP_PRESENT) { + links[id].codecs = rt1019p_component; + links[id].num_codecs = ARRAY_SIZE(rt1019p_component); + links[id].init = speaker_codec_init; + } else if (sof_nau8825_quirk & + SOF_MAX98373_SPEAKER_AMP_PRESENT) { + links[id].codecs = max_98373_components; + links[id].num_codecs = ARRAY_SIZE(max_98373_components); + links[id].init = max_98373_spk_codec_init; + links[id].ops = &max_98373_ops; + /* feedback stream */ + links[id].dpcm_capture = 1; + } else if (sof_nau8825_quirk & + SOF_MAX98360A_SPEAKER_AMP_PRESENT) { + max_98360a_dai_link(&links[id]); + } else { + goto devm_err; + } + + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_amp); + if (!links[id].cpus->dai_name) + goto devm_err; + id++; + } + + /* BT audio offload */ + if (sof_nau8825_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) { + int port = (sof_nau8825_quirk & SOF_BT_OFFLOAD_SSP_MASK) >> + SOF_BT_OFFLOAD_SSP_SHIFT; + + links[id].id = id; + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", port); + if (!links[id].cpus->dai_name) + goto devm_err; + links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", port); + if (!links[id].name) + goto devm_err; + links[id].codecs = dummy_component; + links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].dpcm_playback = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].num_cpus = 1; + } + + return links; +devm_err: + return NULL; +} + +static int sof_audio_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_links; + struct snd_soc_acpi_mach *mach; + struct sof_card_private *ctx; + int dmic_be_num, hdmi_num; + int ret, ssp_amp, ssp_codec; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (pdev->id_entry && pdev->id_entry->driver_data) + sof_nau8825_quirk = (unsigned long)pdev->id_entry->driver_data; + + mach = pdev->dev.platform_data; + + /* A speaker amp might not be present when the quirk claims one is. + * Detect this via whether the machine driver match includes quirk_data. + */ + if ((sof_nau8825_quirk & SOF_SPEAKER_AMP_PRESENT) && !mach->quirk_data) + sof_nau8825_quirk &= ~SOF_SPEAKER_AMP_PRESENT; + + dev_dbg(&pdev->dev, "sof_nau8825_quirk = %lx\n", sof_nau8825_quirk); + + /* default number of DMIC DAI's */ + dmic_be_num = 2; + hdmi_num = (sof_nau8825_quirk & SOF_NAU8825_NUM_HDMIDEV_MASK) >> + SOF_NAU8825_NUM_HDMIDEV_SHIFT; + /* default number of HDMI DAI's */ + if (!hdmi_num) + hdmi_num = 3; + + ssp_amp = (sof_nau8825_quirk & SOF_NAU8825_SSP_AMP_MASK) >> + SOF_NAU8825_SSP_AMP_SHIFT; + + ssp_codec = sof_nau8825_quirk & SOF_NAU8825_SSP_CODEC_MASK; + + /* compute number of dai links */ + sof_audio_card_nau8825.num_links = 1 + dmic_be_num + hdmi_num; + + if (sof_nau8825_quirk & SOF_SPEAKER_AMP_PRESENT) + sof_audio_card_nau8825.num_links++; + + if (sof_nau8825_quirk & SOF_MAX98373_SPEAKER_AMP_PRESENT) + max_98373_set_codec_conf(&sof_audio_card_nau8825); + + if (sof_nau8825_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) + sof_audio_card_nau8825.num_links++; + + dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec, ssp_amp, + dmic_be_num, hdmi_num); + if (!dai_links) + return -ENOMEM; + + sof_audio_card_nau8825.dai_link = dai_links; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + sof_audio_card_nau8825.dev = &pdev->dev; + + /* set platform name for each dailink */ + ret = snd_soc_fixup_dai_links_platform_name(&sof_audio_card_nau8825, + mach->mach_params.platform); + if (ret) + return ret; + + snd_soc_card_set_drvdata(&sof_audio_card_nau8825, ctx); + + return devm_snd_soc_register_card(&pdev->dev, + &sof_audio_card_nau8825); +} + +static const struct platform_device_id board_ids[] = { + { + .name = "sof_nau8825", + .driver_data = (kernel_ulong_t)(SOF_NAU8825_SSP_CODEC(0) | + SOF_NAU8825_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + + }, + { + .name = "adl_rt1019p_nau8825", + .driver_data = (kernel_ulong_t)(SOF_NAU8825_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1019P_SPEAKER_AMP_PRESENT | + SOF_NAU8825_SSP_AMP(2) | + SOF_NAU8825_NUM_HDMIDEV(4)), + }, + { + .name = "adl_max98373_nau8825", + .driver_data = (kernel_ulong_t)(SOF_NAU8825_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98373_SPEAKER_AMP_PRESENT | + SOF_NAU8825_SSP_AMP(1) | + SOF_NAU8825_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + /* The limitation of length of char array, shorten the name */ + .name = "adl_mx98360a_nau8825", + .driver_data = (kernel_ulong_t)(SOF_NAU8825_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_NAU8825_SSP_AMP(1) | + SOF_NAU8825_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + + }, + { } +}; +MODULE_DEVICE_TABLE(platform, board_ids); + +static struct platform_driver sof_audio = { + .probe = sof_audio_probe, + .driver = { + .name = "sof_nau8825", + .pm = &snd_soc_pm_ops, + }, + .id_table = board_ids, +}; +module_platform_driver(sof_audio) + +/* Module information */ +MODULE_DESCRIPTION("SOF Audio Machine driver for NAU8825"); +MODULE_AUTHOR("David Lin <ctlin0@nuvoton.com>"); +MODULE_AUTHOR("Mac Chiang <mac.chiang@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); +MODULE_IMPORT_NS(SND_SOC_INTEL_SOF_MAXIM_COMMON); diff --git a/sound/soc/intel/boards/sof_pcm512x.c b/sound/soc/intel/boards/sof_pcm512x.c new file mode 100644 index 000000000000..d4c67d5340a9 --- /dev/null +++ b/sound/soc/intel/boards/sof_pcm512x.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2018-2020 Intel Corporation. + +/* + * Intel SOF Machine Driver for Intel platforms with TI PCM512x codec, + * e.g. Up or Up2 with Hifiberry DAC+ HAT + */ +#include <linux/clk.h> +#include <linux/dmi.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../codecs/pcm512x.h" +#include "../common/soc-intel-quirks.h" +#include "hda_dsp_common.h" + +#define NAME_SIZE 32 + +#define SOF_PCM512X_SSP_CODEC(quirk) ((quirk) & GENMASK(3, 0)) +#define SOF_PCM512X_SSP_CODEC_MASK (GENMASK(3, 0)) +#define SOF_PCM512X_ENABLE_SSP_CAPTURE BIT(4) +#define SOF_PCM512X_ENABLE_DMIC BIT(5) + +#define IDISP_CODEC_MASK 0x4 + +/* Default: SSP5 */ +static unsigned long sof_pcm512x_quirk = + SOF_PCM512X_SSP_CODEC(5) | + SOF_PCM512X_ENABLE_SSP_CAPTURE | + SOF_PCM512X_ENABLE_DMIC; + +static bool is_legacy_cpu; + +struct sof_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct sof_card_private { + struct list_head hdmi_pcm_list; + bool idisp_codec; +}; + +static int sof_pcm512x_quirk_cb(const struct dmi_system_id *id) +{ + sof_pcm512x_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id sof_pcm512x_quirk_table[] = { + { + .callback = sof_pcm512x_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), + DMI_MATCH(DMI_PRODUCT_NAME, "UP-CHT01"), + }, + .driver_data = (void *)(SOF_PCM512X_SSP_CODEC(2)), + }, + {} +}; + +static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); + struct sof_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int sof_pcm512x_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(codec, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(codec, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1, + 0x08, 0x08); + + return 0; +} + +static int aif1_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1, + 0x08, 0x08); + + return 0; +} + +static void aif1_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1, + 0x08, 0x00); +} + +static const struct snd_soc_ops sof_pcm512x_ops = { + .startup = aif1_startup, + .shutdown = aif1_shutdown, +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + /* name might be overridden during probe */ + .name = "0000:00:1f.3" + } +}; + +static int sof_card_late_probe(struct snd_soc_card *card) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(card); + struct sof_hdmi_pcm *pcm; + + /* HDMI is not supported by SOF on Baytrail/CherryTrail */ + if (is_legacy_cpu) + return 0; + + if (list_empty(&ctx->hdmi_pcm_list)) + return -EINVAL; + + if (!ctx->idisp_codec) + return 0; + + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm, head); + + return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component); +} + +static const struct snd_kcontrol_new sof_controls[] = { + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static const struct snd_soc_dapm_widget sof_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_widget dmic_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route sof_map[] = { + /* Speaker */ + {"Ext Spk", NULL, "OUTR"}, + {"Ext Spk", NULL, "OUTL"}, +}; + +static const struct snd_soc_dapm_route dmic_map[] = { + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +static int dmic_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets, + ARRAY_SIZE(dmic_widgets)); + if (ret) { + dev_err(card->dev, "DMic widget addition failed: %d\n", ret); + /* Don't need to add routes if widget addition failed */ + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map, + ARRAY_SIZE(dmic_map)); + + if (ret) + dev_err(card->dev, "DMic map addition failed: %d\n", ret); + + return ret; +} + +/* sof audio machine driver for pcm512x codec */ +static struct snd_soc_card sof_audio_card_pcm512x = { + .name = "pcm512x", + .owner = THIS_MODULE, + .controls = sof_controls, + .num_controls = ARRAY_SIZE(sof_controls), + .dapm_widgets = sof_widgets, + .num_dapm_widgets = ARRAY_SIZE(sof_widgets), + .dapm_routes = sof_map, + .num_dapm_routes = ARRAY_SIZE(sof_map), + .fully_routed = true, + .late_probe = sof_card_late_probe, +}; + +SND_SOC_DAILINK_DEF(pcm512x_component, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-104C5122:00", "pcm512x-hifi"))); +SND_SOC_DAILINK_DEF(dmic_component, + DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); + +static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, + int ssp_codec, + int dmic_be_num, + int hdmi_num, + bool idisp_codec) +{ + struct snd_soc_dai_link_component *idisp_components; + struct snd_soc_dai_link_component *cpus; + struct snd_soc_dai_link *links; + int i, id = 0; + + links = devm_kcalloc(dev, sof_audio_card_pcm512x.num_links, + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + cpus = devm_kcalloc(dev, sof_audio_card_pcm512x.num_links, + sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); + if (!links || !cpus) + goto devm_err; + + /* codec SSP */ + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d-Codec", ssp_codec); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].codecs = pcm512x_component; + links[id].num_codecs = ARRAY_SIZE(pcm512x_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_pcm512x_codec_init; + links[id].ops = &sof_pcm512x_ops; + links[id].dpcm_playback = 1; + /* + * capture only supported with specific versions of the Hifiberry DAC+ + */ + if (sof_pcm512x_quirk & SOF_PCM512X_ENABLE_SSP_CAPTURE) + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + if (is_legacy_cpu) { + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-port", + ssp_codec); + if (!links[id].cpus->dai_name) + goto devm_err; + } else { + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_codec); + if (!links[id].cpus->dai_name) + goto devm_err; + } + id++; + + /* dmic */ + if (dmic_be_num > 0) { + /* at least we have dmic01 */ + links[id].name = "dmic01"; + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = "DMIC01 Pin"; + links[id].init = dmic_init; + if (dmic_be_num > 1) { + /* set up 2 BE links at most */ + links[id + 1].name = "dmic16k"; + links[id + 1].cpus = &cpus[id + 1]; + links[id + 1].cpus->dai_name = "DMIC16k Pin"; + dmic_be_num = 2; + } + } + + for (i = 0; i < dmic_be_num; i++) { + links[id].id = id; + links[id].num_cpus = 1; + links[id].codecs = dmic_component; + links[id].num_codecs = ARRAY_SIZE(dmic_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].ignore_suspend = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + id++; + } + + /* HDMI */ + if (hdmi_num > 0) { + idisp_components = devm_kcalloc(dev, hdmi_num, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); + if (!idisp_components) + goto devm_err; + } + for (i = 1; i <= hdmi_num; i++) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d", i); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d Pin", i); + if (!links[id].cpus->dai_name) + goto devm_err; + + /* + * topology cannot be loaded if codec is missing, so + * use the dummy codec if needed + */ + if (idisp_codec) { + idisp_components[i - 1].name = "ehdaudio0D2"; + idisp_components[i - 1].dai_name = + devm_kasprintf(dev, GFP_KERNEL, + "intel-hdmi-hifi%d", i); + } else { + idisp_components[i - 1].name = "snd-soc-dummy"; + idisp_components[i - 1].dai_name = "snd-soc-dummy-dai"; + } + if (!idisp_components[i - 1].dai_name) + goto devm_err; + + links[id].codecs = &idisp_components[i - 1]; + links[id].num_codecs = 1; + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_hdmi_init; + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + id++; + } + + return links; +devm_err: + return NULL; +} + +static int sof_audio_probe(struct platform_device *pdev) +{ + struct snd_soc_acpi_mach *mach = pdev->dev.platform_data; + struct snd_soc_dai_link *dai_links; + struct sof_card_private *ctx; + int dmic_be_num, hdmi_num; + int ret, ssp_codec; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + hdmi_num = 0; + if (soc_intel_is_byt() || soc_intel_is_cht()) { + is_legacy_cpu = true; + dmic_be_num = 0; + /* default quirk for legacy cpu */ + sof_pcm512x_quirk = SOF_PCM512X_SSP_CODEC(2); + } else { + dmic_be_num = 2; + if (mach->mach_params.common_hdmi_codec_drv && + (mach->mach_params.codec_mask & IDISP_CODEC_MASK)) + ctx->idisp_codec = true; + + /* links are always present in topology */ + hdmi_num = 3; + } + + dmi_check_system(sof_pcm512x_quirk_table); + + dev_dbg(&pdev->dev, "sof_pcm512x_quirk = %lx\n", sof_pcm512x_quirk); + + ssp_codec = sof_pcm512x_quirk & SOF_PCM512X_SSP_CODEC_MASK; + + if (!(sof_pcm512x_quirk & SOF_PCM512X_ENABLE_DMIC)) + dmic_be_num = 0; + + /* compute number of dai links */ + sof_audio_card_pcm512x.num_links = 1 + dmic_be_num + hdmi_num; + + dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec, + dmic_be_num, hdmi_num, + ctx->idisp_codec); + if (!dai_links) + return -ENOMEM; + + sof_audio_card_pcm512x.dai_link = dai_links; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + sof_audio_card_pcm512x.dev = &pdev->dev; + + /* set platform name for each dailink */ + ret = snd_soc_fixup_dai_links_platform_name(&sof_audio_card_pcm512x, + mach->mach_params.platform); + if (ret) + return ret; + + snd_soc_card_set_drvdata(&sof_audio_card_pcm512x, ctx); + + return devm_snd_soc_register_card(&pdev->dev, + &sof_audio_card_pcm512x); +} + +static int sof_pcm512x_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct snd_soc_component *component; + + for_each_card_components(card, component) { + if (!strcmp(component->name, pcm512x_component[0].name)) { + snd_soc_component_set_jack(component, NULL, NULL); + break; + } + } + + return 0; +} + +static struct platform_driver sof_audio = { + .probe = sof_audio_probe, + .remove = sof_pcm512x_remove, + .driver = { + .name = "sof_pcm512x", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(sof_audio) + +MODULE_DESCRIPTION("ASoC Intel(R) SOF + PCM512x Machine driver"); +MODULE_AUTHOR("Pierre-Louis Bossart"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sof_pcm512x"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/sof_realtek_common.c b/sound/soc/intel/boards/sof_realtek_common.c new file mode 100644 index 000000000000..ff2851fc8930 --- /dev/null +++ b/sound/soc/intel/boards/sof_realtek_common.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. + +#include <linux/device.h> +#include <linux/kernel.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dai.h> +#include <sound/soc-dapm.h> +#include <sound/sof.h> +#include <uapi/sound/asound.h> +#include "../../codecs/rt1011.h" +#include "../../codecs/rt1015.h" +#include "../../codecs/rt1308.h" +#include "sof_realtek_common.h" + +/* + * Current only 2-amp configuration is supported for rt1011 + */ +static const struct snd_soc_dapm_route speaker_map_lr[] = { + /* speaker */ + { "Left Spk", NULL, "Left SPO" }, + { "Right Spk", NULL, "Right SPO" }, +}; + +/* + * Make sure device's Unique ID follows this configuration: + * + * Two speakers: + * 0: left, 1: right + * Four speakers: + * 0: Woofer left, 1: Woofer right + * 2: Tweeter left, 3: Tweeter right + */ +static struct snd_soc_codec_conf rt1011_codec_confs[] = { + { + .dlc = COMP_CODEC_CONF(RT1011_DEV0_NAME), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF(RT1011_DEV1_NAME), + .name_prefix = "Right", + }, +}; + +static struct snd_soc_dai_link_component rt1011_dai_link_components[] = { + { + .name = RT1011_DEV0_NAME, + .dai_name = RT1011_CODEC_DAI, + }, + { + .name = RT1011_DEV1_NAME, + .dai_name = RT1011_CODEC_DAI, + }, +}; + +static const struct { + unsigned int tx; + unsigned int rx; +} rt1011_tdm_mask[] = { + {.tx = 0x4, .rx = 0x1}, + {.tx = 0x8, .rx = 0x2}, +}; + +static int rt1011_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + int srate, i, ret = 0; + + srate = params_rate(params); + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + /* 100 Fs to drive 24 bit data */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT1011_PLL1_S_BCLK, + 100 * srate, 256 * srate); + if (ret < 0) { + dev_err(codec_dai->dev, "fail to set pll, ret %d\n", + ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT1011_FS_SYS_PRE_S_PLL1, + 256 * srate, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "fail to set sysclk, ret %d\n", + ret); + return ret; + } + + if (i >= ARRAY_SIZE(rt1011_tdm_mask)) { + dev_err(codec_dai->dev, "invalid codec index %d\n", + i); + return -ENODEV; + } + + ret = snd_soc_dai_set_tdm_slot(codec_dai, rt1011_tdm_mask[i].tx, + rt1011_tdm_mask[i].rx, 4, + params_width(params)); + if (ret < 0) { + dev_err(codec_dai->dev, "fail to set tdm slot, ret %d\n", + ret); + return ret; + } + } + + return 0; +} + +static const struct snd_soc_ops rt1011_ops = { + .hw_params = rt1011_hw_params, +}; + +static int rt1011_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_add_routes(&card->dapm, speaker_map_lr, + ARRAY_SIZE(speaker_map_lr)); + if (ret) + dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret); + return ret; +} + +void sof_rt1011_dai_link(struct snd_soc_dai_link *link) +{ + link->codecs = rt1011_dai_link_components; + link->num_codecs = ARRAY_SIZE(rt1011_dai_link_components); + link->init = rt1011_init; + link->ops = &rt1011_ops; +} +EXPORT_SYMBOL_NS(sof_rt1011_dai_link, SND_SOC_INTEL_SOF_REALTEK_COMMON); + +void sof_rt1011_codec_conf(struct snd_soc_card *card) +{ + card->codec_conf = rt1011_codec_confs; + card->num_configs = ARRAY_SIZE(rt1011_codec_confs); +} +EXPORT_SYMBOL_NS(sof_rt1011_codec_conf, SND_SOC_INTEL_SOF_REALTEK_COMMON); + +/* + * rt1015: i2c mode driver for ALC1015 and ALC1015Q + * rt1015p: auto-mode driver for ALC1015, ALC1015Q, and ALC1015Q-VB + * + * For stereo output, there are always two amplifiers on the board. + * However, the ACPI implements only one device instance (UID=0) if they + * are sharing the same enable pin. The code will detect the number of + * device instance and use corresponding DAPM structures for + * initialization. + */ +static const struct snd_soc_dapm_route rt1015p_1dev_dapm_routes[] = { + /* speaker */ + { "Left Spk", NULL, "Speaker" }, + { "Right Spk", NULL, "Speaker" }, +}; + +static const struct snd_soc_dapm_route rt1015p_2dev_dapm_routes[] = { + /* speaker */ + { "Left Spk", NULL, "Left Speaker" }, + { "Right Spk", NULL, "Right Speaker" }, +}; + +static struct snd_soc_codec_conf rt1015p_codec_confs[] = { + { + .dlc = COMP_CODEC_CONF(RT1015P_DEV0_NAME), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF(RT1015P_DEV1_NAME), + .name_prefix = "Right", + }, +}; + +static struct snd_soc_dai_link_component rt1015p_dai_link_components[] = { + { + .name = RT1015P_DEV0_NAME, + .dai_name = RT1015P_CODEC_DAI, + }, + { + .name = RT1015P_DEV1_NAME, + .dai_name = RT1015P_CODEC_DAI, + }, +}; + +static int rt1015p_get_num_codecs(void) +{ + static int dev_num; + + if (dev_num) + return dev_num; + + if (!acpi_dev_present("RTL1015", "1", -1)) + dev_num = 1; + else + dev_num = 2; + + return dev_num; +} + +static int rt1015p_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + /* reserved for debugging purpose */ + + return 0; +} + +static const struct snd_soc_ops rt1015p_ops = { + .hw_params = rt1015p_hw_params, +}; + +static int rt1015p_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + if (rt1015p_get_num_codecs() == 1) + ret = snd_soc_dapm_add_routes(&card->dapm, rt1015p_1dev_dapm_routes, + ARRAY_SIZE(rt1015p_1dev_dapm_routes)); + else + ret = snd_soc_dapm_add_routes(&card->dapm, rt1015p_2dev_dapm_routes, + ARRAY_SIZE(rt1015p_2dev_dapm_routes)); + if (ret) + dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret); + return ret; +} + +void sof_rt1015p_dai_link(struct snd_soc_dai_link *link) +{ + link->codecs = rt1015p_dai_link_components; + link->num_codecs = rt1015p_get_num_codecs(); + link->init = rt1015p_init; + link->ops = &rt1015p_ops; +} +EXPORT_SYMBOL_NS(sof_rt1015p_dai_link, SND_SOC_INTEL_SOF_REALTEK_COMMON); + +void sof_rt1015p_codec_conf(struct snd_soc_card *card) +{ + if (rt1015p_get_num_codecs() == 1) + return; + + card->codec_conf = rt1015p_codec_confs; + card->num_configs = ARRAY_SIZE(rt1015p_codec_confs); +} +EXPORT_SYMBOL_NS(sof_rt1015p_codec_conf, SND_SOC_INTEL_SOF_REALTEK_COMMON); + +/* + * RT1015 audio amplifier + */ + +static const struct { + unsigned int tx; + unsigned int rx; +} rt1015_tdm_mask[] = { + {.tx = 0x0, .rx = 0x1}, + {.tx = 0x0, .rx = 0x2}, +}; + +static int rt1015_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai_link *dai_link = rtd->dai_link; + struct snd_soc_dai *codec_dai; + int i, clk_freq, ret; + + clk_freq = sof_dai_get_bclk(rtd); + + if (clk_freq <= 0) { + dev_err(rtd->dev, "fail to get bclk freq, ret %d\n", clk_freq); + return -EINVAL; + } + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_pll(codec_dai, 0, RT1015_PLL_S_BCLK, + clk_freq, + params_rate(params) * 256); + if (ret) { + dev_err(codec_dai->dev, "fail to set pll, ret %d\n", + ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT1015_SCLK_S_PLL, + params_rate(params) * 256, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(codec_dai->dev, "fail to set sysclk, ret %d\n", + ret); + return ret; + } + + switch (dai_link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* 4-slot TDM */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, + rt1015_tdm_mask[i].tx, + rt1015_tdm_mask[i].rx, + 4, + params_width(params)); + if (ret < 0) { + dev_err(codec_dai->dev, "fail to set tdm slot, ret %d\n", + ret); + return ret; + } + break; + default: + dev_dbg(codec_dai->dev, "codec is in I2S mode\n"); + break; + } + } + + return ret; +} + +static struct snd_soc_ops rt1015_ops = { + .hw_params = rt1015_hw_params, +}; + +static struct snd_soc_codec_conf rt1015_amp_conf[] = { + { + .dlc = COMP_CODEC_CONF(RT1015_DEV0_NAME), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF(RT1015_DEV1_NAME), + .name_prefix = "Right", + }, +}; + +static struct snd_soc_dai_link_component rt1015_components[] = { + { + .name = RT1015_DEV0_NAME, + .dai_name = RT1015_CODEC_DAI, + }, + { + .name = RT1015_DEV1_NAME, + .dai_name = RT1015_CODEC_DAI, + }, +}; + +static int speaker_codec_init_lr(struct snd_soc_pcm_runtime *rtd) +{ + return snd_soc_dapm_add_routes(&rtd->card->dapm, speaker_map_lr, + ARRAY_SIZE(speaker_map_lr)); +} + +void sof_rt1015_codec_conf(struct snd_soc_card *card) +{ + card->codec_conf = rt1015_amp_conf; + card->num_configs = ARRAY_SIZE(rt1015_amp_conf); +} +EXPORT_SYMBOL_NS(sof_rt1015_codec_conf, SND_SOC_INTEL_SOF_REALTEK_COMMON); + +void sof_rt1015_dai_link(struct snd_soc_dai_link *link) +{ + link->codecs = rt1015_components; + link->num_codecs = ARRAY_SIZE(rt1015_components); + link->init = speaker_codec_init_lr; + link->ops = &rt1015_ops; +} +EXPORT_SYMBOL_NS(sof_rt1015_dai_link, SND_SOC_INTEL_SOF_REALTEK_COMMON); + +/* + * RT1308 audio amplifier + */ +static const struct snd_kcontrol_new rt1308_kcontrols[] = { + SOC_DAPM_PIN_SWITCH("Speakers"), +}; + +static const struct snd_soc_dapm_widget rt1308_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Speakers", NULL), +}; + +static const struct snd_soc_dapm_route rt1308_dapm_routes[] = { + /* speaker */ + {"Speakers", NULL, "SPOL"}, + {"Speakers", NULL, "SPOR"}, +}; + +static struct snd_soc_dai_link_component rt1308_components[] = { + { + .name = RT1308_DEV0_NAME, + .dai_name = RT1308_CODEC_DAI, + } +}; + +static int rt1308_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, rt1308_dapm_widgets, + ARRAY_SIZE(rt1308_dapm_widgets)); + if (ret) { + dev_err(rtd->dev, "fail to add dapm controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_add_card_controls(card, rt1308_kcontrols, + ARRAY_SIZE(rt1308_kcontrols)); + if (ret) { + dev_err(rtd->dev, "fail to add card controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, rt1308_dapm_routes, + ARRAY_SIZE(rt1308_dapm_routes)); + + if (ret) + dev_err(rtd->dev, "fail to add dapm routes, ret %d\n", ret); + + return ret; +} + +static int rt1308_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int clk_id, clk_freq, pll_out; + int ret; + + clk_id = RT1308_PLL_S_MCLK; + /* get the tplg configured mclk. */ + clk_freq = sof_dai_get_mclk(rtd); + + pll_out = params_rate(params) * 512; + + /* Set rt1308 pll */ + ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out); + if (ret < 0) { + dev_err(card->dev, "Failed to set RT1308 PLL: %d\n", ret); + return ret; + } + + /* Set rt1308 sysclk */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT1308_FS_SYS_S_PLL, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(card->dev, "Failed to set RT1308 SYSCLK: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops rt1308_ops = { + .hw_params = rt1308_hw_params, +}; + +void sof_rt1308_dai_link(struct snd_soc_dai_link *link) +{ + link->codecs = rt1308_components; + link->num_codecs = ARRAY_SIZE(rt1308_components); + link->init = rt1308_init; + link->ops = &rt1308_ops; +} +EXPORT_SYMBOL_NS(sof_rt1308_dai_link, SND_SOC_INTEL_SOF_REALTEK_COMMON); + +/* + * 2-amp Configuration for RT1019 + */ + +static const struct snd_soc_dapm_route rt1019p_dapm_routes[] = { + /* speaker */ + { "Left Spk", NULL, "Speaker" }, + { "Right Spk", NULL, "Speaker" }, +}; + +static struct snd_soc_dai_link_component rt1019p_components[] = { + { + .name = RT1019P_DEV0_NAME, + .dai_name = RT1019P_CODEC_DAI, + }, +}; + +static int rt1019p_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_add_routes(&card->dapm, rt1019p_dapm_routes, + ARRAY_SIZE(rt1019p_dapm_routes)); + if (ret) { + dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret); + return ret; + } + return ret; +} + +void sof_rt1019p_dai_link(struct snd_soc_dai_link *link) +{ + link->codecs = rt1019p_components; + link->num_codecs = ARRAY_SIZE(rt1019p_components); + link->init = rt1019p_init; +} +EXPORT_SYMBOL_NS(sof_rt1019p_dai_link, SND_SOC_INTEL_SOF_REALTEK_COMMON); + +MODULE_DESCRIPTION("ASoC Intel SOF Realtek helpers"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/boards/sof_realtek_common.h b/sound/soc/intel/boards/sof_realtek_common.h new file mode 100644 index 000000000000..3ae99d8239e0 --- /dev/null +++ b/sound/soc/intel/boards/sof_realtek_common.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2020 Intel Corporation. + */ + +/* + * This file defines data structures used in Machine Driver for Intel + * platforms with Realtek Codecs. + */ +#ifndef __SOF_REALTEK_COMMON_H +#define __SOF_REALTEK_COMMON_H + +#include <sound/soc.h> + +#define RT1011_CODEC_DAI "rt1011-aif" +#define RT1011_DEV0_NAME "i2c-10EC1011:00" +#define RT1011_DEV1_NAME "i2c-10EC1011:01" +#define RT1011_DEV2_NAME "i2c-10EC1011:02" +#define RT1011_DEV3_NAME "i2c-10EC1011:03" + +void sof_rt1011_dai_link(struct snd_soc_dai_link *link); +void sof_rt1011_codec_conf(struct snd_soc_card *card); + +#define RT1015P_CODEC_DAI "HiFi" +#define RT1015P_DEV0_NAME "RTL1015:00" +#define RT1015P_DEV1_NAME "RTL1015:01" + +void sof_rt1015p_dai_link(struct snd_soc_dai_link *link); +void sof_rt1015p_codec_conf(struct snd_soc_card *card); + +#define RT1015_CODEC_DAI "rt1015-aif" +#define RT1015_DEV0_NAME "i2c-10EC1015:00" +#define RT1015_DEV1_NAME "i2c-10EC1015:01" + +void sof_rt1015_dai_link(struct snd_soc_dai_link *link); +void sof_rt1015_codec_conf(struct snd_soc_card *card); + +#define RT1308_CODEC_DAI "rt1308-aif" +#define RT1308_DEV0_NAME "i2c-10EC1308:00" +void sof_rt1308_dai_link(struct snd_soc_dai_link *link); + +#define RT1019P_CODEC_DAI "HiFi" +#define RT1019P_DEV0_NAME "RTL1019:00" + +void sof_rt1019p_dai_link(struct snd_soc_dai_link *link); + +#endif /* __SOF_REALTEK_COMMON_H */ diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index 5d878873a8e0..2358be208c1f 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -1,9 +1,9 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright(c) 2019 Intel Corporation. +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2019-2020 Intel Corporation. /* * Intel SOF Machine Driver with Realtek rt5682 Codec - * and speaker codec MAX98357A + * and speaker codec MAX98357A or RT1015. */ #include <linux/i2c.h> #include <linux/input.h> @@ -16,12 +16,17 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/sof.h> #include <sound/rt5682.h> +#include <sound/rt5682s.h> #include <sound/soc-acpi.h> #include "../../codecs/rt5682.h" +#include "../../codecs/rt5682s.h" #include "../../codecs/hdac_hdmi.h" #include "../common/soc-intel-quirks.h" #include "hda_dsp_common.h" +#include "sof_maxim_common.h" +#include "sof_realtek_common.h" #define NAME_SIZE 32 @@ -39,6 +44,23 @@ #define SOF_RT5682_NUM_HDMIDEV_MASK (GENMASK(12, 10)) #define SOF_RT5682_NUM_HDMIDEV(quirk) \ ((quirk << SOF_RT5682_NUM_HDMIDEV_SHIFT) & SOF_RT5682_NUM_HDMIDEV_MASK) +#define SOF_RT1011_SPEAKER_AMP_PRESENT BIT(13) +#define SOF_RT1015_SPEAKER_AMP_PRESENT BIT(14) +#define SOF_RT1015P_SPEAKER_AMP_PRESENT BIT(16) +#define SOF_MAX98373_SPEAKER_AMP_PRESENT BIT(17) +#define SOF_MAX98360A_SPEAKER_AMP_PRESENT BIT(18) + +/* BT audio offload: reserve 3 bits for future */ +#define SOF_BT_OFFLOAD_SSP_SHIFT 19 +#define SOF_BT_OFFLOAD_SSP_MASK (GENMASK(21, 19)) +#define SOF_BT_OFFLOAD_SSP(quirk) \ + (((quirk) << SOF_BT_OFFLOAD_SSP_SHIFT) & SOF_BT_OFFLOAD_SSP_MASK) +#define SOF_SSP_BT_OFFLOAD_PRESENT BIT(22) +#define SOF_RT5682S_HEADPHONE_CODEC_PRESENT BIT(23) +#define SOF_MAX98390_SPEAKER_AMP_PRESENT BIT(24) +#define SOF_MAX98390_TWEETER_SPEAKER_PRESENT BIT(25) +#define SOF_RT1019_SPEAKER_AMP_PRESENT BIT(26) + /* Default: MCLK on, MCLK 19.2M, SSP0 */ static unsigned long sof_rt5682_quirk = SOF_RT5682_MCLK_EN | @@ -46,11 +68,10 @@ static unsigned long sof_rt5682_quirk = SOF_RT5682_MCLK_EN | static int is_legacy_cpu; -static struct snd_soc_jack sof_hdmi[3]; - struct sof_hdmi_pcm { struct list_head head; struct snd_soc_dai *codec_dai; + struct snd_soc_jack hdmi_jack; int device; }; @@ -59,6 +80,7 @@ struct sof_card_private { struct snd_soc_jack sof_headset; struct list_head hdmi_pcm_list; bool common_hdmi_codec_drv; + bool idisp_codec; }; static int sof_rt5682_quirk_cb(const struct dmi_system_id *id) @@ -95,6 +117,23 @@ static const struct dmi_system_id sof_rt5682_quirk_table[] = { SOF_RT5682_SSP_CODEC(1)), }, { + /* + * Dooly is hatch family but using rt1015 amp so it + * requires a quirk before "Google_Hatch". + */ + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "Dooly"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1015_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1)), + }, + { .callback = sof_rt5682_quirk_cb, .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Hatch"), @@ -114,13 +153,95 @@ static const struct dmi_system_id sof_rt5682_quirk_table[] = { .driver_data = (void *)(SOF_RT5682_MCLK_EN | SOF_RT5682_SSP_CODEC(0)), }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Volteer"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98373_ALC5682I_I2S_UP4"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98373_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(2) | + SOF_RT5682_NUM_HDMIDEV(4)), + }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alder Lake Client Platform"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-ADL_MAX98373_ALC5682I_I2S"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98373_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(2) | + SOF_RT5682_NUM_HDMIDEV(4)), + }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98390_ALC5682I_I2S"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98390_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(2) | + SOF_RT5682_NUM_HDMIDEV(4)), + }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98390_ALC5682I_I2S_4SPK"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98390_SPEAKER_AMP_PRESENT | + SOF_MAX98390_TWEETER_SPEAKER_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + + }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98360_ALC5682I_I2S_AMP_SSP2"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(2) | + SOF_RT5682_NUM_HDMIDEV(4)), + }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Rex"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(2) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(0) | + SOF_RT5682_NUM_HDMIDEV(4) + ), + }, {} }; static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) { struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); struct sof_hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); @@ -136,19 +257,37 @@ static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) return 0; } +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + static int sof_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) { struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; struct snd_soc_jack *jack; int ret; /* need to enable ASRC function for 24MHz mclk rate */ if ((sof_rt5682_quirk & SOF_RT5682_MCLK_EN) && (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ)) { - rt5682_sel_asrc_clk_src(component, RT5682_DA_STEREO1_FILTER | - RT5682_AD_STEREO1_FILTER, - RT5682_CLK_SEL_I2S1_ASRC); + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) + rt5682s_sel_asrc_clk_src(component, + RT5682S_DA_STEREO1_FILTER | + RT5682S_AD_STEREO1_FILTER, + RT5682S_CLK_SEL_I2S1_ASRC); + else + rt5682_sel_asrc_clk_src(component, + RT5682_DA_STEREO1_FILTER | + RT5682_AD_STEREO1_FILTER, + RT5682_CLK_SEL_I2S1_ASRC); } if (sof_rt5682_quirk & SOF_RT5682_MCLK_BYTCHT_EN) { @@ -176,11 +315,13 @@ static int sof_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | - SND_JACK_BTN_1 | SND_JACK_BTN_2 | - SND_JACK_BTN_3, - &ctx->sof_headset, NULL, 0); + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sof_headset, + jack_pins, + ARRAY_SIZE(jack_pins)); if (ret) { dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); return ret; @@ -202,13 +343,20 @@ static int sof_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; }; +static void sof_rt5682_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_set_jack(component, NULL, NULL); +} + static int sof_rt5682_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int clk_id, clk_freq, pll_out, ret; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int pll_id, pll_source, pll_in, pll_out, clk_id, ret; if (sof_rt5682_quirk & SOF_RT5682_MCLK_EN) { if (sof_rt5682_quirk & SOF_RT5682_MCLK_BYTCHT_EN) { @@ -220,24 +368,57 @@ static int sof_rt5682_hw_params(struct snd_pcm_substream *substream, } } - clk_id = RT5682_PLL1_S_MCLK; - if (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ) - clk_freq = 24000000; + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) + pll_source = RT5682S_PLL_S_MCLK; else - clk_freq = 19200000; + pll_source = RT5682_PLL1_S_MCLK; + + /* get the tplg configured mclk. */ + pll_in = sof_dai_get_mclk(rtd); + + /* mclk from the quirk is the first choice */ + if (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ) { + if (pll_in != 24000000) + dev_warn(rtd->dev, "configure wrong mclk in tplg, please use 24MHz.\n"); + pll_in = 24000000; + } else if (pll_in == 0) { + /* use default mclk if not specified correct in topology */ + pll_in = 19200000; + } else if (pll_in < 0) { + return pll_in; + } } else { - clk_id = RT5682_PLL1_S_BCLK1; - clk_freq = params_rate(params) * 50; + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) + pll_source = RT5682S_PLL_S_BCLK1; + else + pll_source = RT5682_PLL1_S_BCLK1; + + pll_in = params_rate(params) * 50; + } + + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) { + pll_id = RT5682S_PLL2; + clk_id = RT5682S_SCLK_S_PLL2; + } else { + pll_id = RT5682_PLL1; + clk_id = RT5682_SCLK_S_PLL1; } pll_out = params_rate(params) * 512; - ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out); - if (ret < 0) - dev_err(rtd->dev, "snd_soc_dai_set_pll err = %d\n", ret); + /* when MCLK is 512FS, no need to set PLL configuration additionally. */ + if (pll_in == pll_out) + clk_id = RT5682S_SCLK_S_MCLK; + else { + /* Configure pll for codec */ + ret = snd_soc_dai_set_pll(codec_dai, pll_id, pll_source, pll_in, + pll_out); + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_pll err = %d\n", ret); + } /* Configure sysclk for codec */ - ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, + ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, pll_out, SND_SOC_CLOCK_IN); if (ret < 0) dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); @@ -271,13 +452,22 @@ static int sof_card_late_probe(struct snd_soc_card *card) { struct sof_card_private *ctx = snd_soc_card_get_drvdata(card); struct snd_soc_component *component = NULL; + struct snd_soc_dapm_context *dapm = &card->dapm; char jack_name[NAME_SIZE]; struct sof_hdmi_pcm *pcm; int err; - int i = 0; + + if (sof_rt5682_quirk & SOF_MAX98373_SPEAKER_AMP_PRESENT) { + /* Disable Left and Right Spk pin after boot */ + snd_soc_dapm_disable_pin(dapm, "Left Spk"); + snd_soc_dapm_disable_pin(dapm, "Right Spk"); + err = snd_soc_dapm_sync(dapm); + if (err < 0) + return err; + } /* HDMI is not supported by SOF on Baytrail/CherryTrail */ - if (is_legacy_cpu) + if (is_legacy_cpu || !ctx->idisp_codec) return 0; if (list_empty(&ctx->hdmi_pcm_list)) @@ -295,18 +485,15 @@ static int sof_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &sof_hdmi[i], - NULL, 0); + SND_JACK_AVOUT, &pcm->hdmi_jack); if (err) return err; err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, - &sof_hdmi[i]); + &pcm->hdmi_jack); if (err < 0) return err; - - i++; } return hdac_hdmi_jack_port_init(component, &card->dapm); @@ -315,13 +502,16 @@ static int sof_card_late_probe(struct snd_soc_card *card) static const struct snd_kcontrol_new sof_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone Jack"), SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Spk"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), + }; static const struct snd_soc_dapm_widget sof_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), }; static const struct snd_soc_dapm_widget dmic_widgets[] = { @@ -337,29 +527,11 @@ static const struct snd_soc_dapm_route sof_map[] = { { "IN1P", NULL, "Headset Mic" }, }; -static const struct snd_soc_dapm_route speaker_map[] = { - /* speaker */ - { "Spk", NULL, "Speaker" }, -}; - static const struct snd_soc_dapm_route dmic_map[] = { /* digital mics */ {"DMic", NULL, "SoC DMIC"}, }; -static int speaker_codec_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_card *card = rtd->card; - int ret; - - ret = snd_soc_dapm_add_routes(&card->dapm, speaker_map, - ARRAY_SIZE(speaker_map)); - - if (ret) - dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret); - return ret; -} - static int dmic_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_card *card = rtd->card; @@ -403,6 +575,13 @@ static struct snd_soc_dai_link_component rt5682_component[] = { } }; +static struct snd_soc_dai_link_component rt5682s_component[] = { + { + .name = "i2c-RTL5682:00", + .dai_name = "rt5682s-aif1", + } +}; + static struct snd_soc_dai_link_component dmic_component[] = { { .name = "dmic-codec", @@ -410,28 +589,31 @@ static struct snd_soc_dai_link_component dmic_component[] = { } }; -static struct snd_soc_dai_link_component max98357a_component[] = { +static struct snd_soc_dai_link_component dummy_component[] = { { - .name = "MX98357A:00", - .dai_name = "HiFi", + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", } }; +#define IDISP_CODEC_MASK 0x4 + static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, int ssp_codec, int ssp_amp, int dmic_be_num, - int hdmi_num) + int hdmi_num, + bool idisp_codec) { struct snd_soc_dai_link_component *idisp_components; struct snd_soc_dai_link_component *cpus; struct snd_soc_dai_link *links; int i, id = 0; - links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * - sof_audio_card_rt5682.num_links, GFP_KERNEL); - cpus = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link_component) * - sof_audio_card_rt5682.num_links, GFP_KERNEL); + links = devm_kcalloc(dev, sof_audio_card_rt5682.num_links, + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + cpus = devm_kcalloc(dev, sof_audio_card_rt5682.num_links, + sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); if (!links || !cpus) goto devm_err; @@ -442,13 +624,18 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, goto devm_err; links[id].id = id; - links[id].codecs = rt5682_component; - links[id].num_codecs = ARRAY_SIZE(rt5682_component); + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) { + links[id].codecs = rt5682s_component; + links[id].num_codecs = ARRAY_SIZE(rt5682s_component); + } else { + links[id].codecs = rt5682_component; + links[id].num_codecs = ARRAY_SIZE(rt5682_component); + } links[id].platforms = platform_component; links[id].num_platforms = ARRAY_SIZE(platform_component); links[id].init = sof_rt5682_codec_init; + links[id].exit = sof_rt5682_codec_exit; links[id].ops = &sof_rt5682_ops; - links[id].nonatomic = true; links[id].dpcm_playback = 1; links[id].dpcm_capture = 1; links[id].no_pcm = 1; @@ -510,9 +697,10 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, /* HDMI */ if (hdmi_num > 0) { - idisp_components = devm_kzalloc(dev, - sizeof(struct snd_soc_dai_link_component) * - hdmi_num, GFP_KERNEL); + idisp_components = devm_kcalloc(dev, + hdmi_num, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); if (!idisp_components) goto devm_err; } @@ -530,13 +718,18 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, if (!links[id].cpus->dai_name) goto devm_err; - idisp_components[i - 1].name = "ehdaudio0D2"; - idisp_components[i - 1].dai_name = devm_kasprintf(dev, - GFP_KERNEL, - "intel-hdmi-hifi%d", - i); - if (!idisp_components[i - 1].dai_name) - goto devm_err; + if (idisp_codec) { + idisp_components[i - 1].name = "ehdaudio0D2"; + idisp_components[i - 1].dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "intel-hdmi-hifi%d", + i); + if (!idisp_components[i - 1].dai_name) + goto devm_err; + } else { + idisp_components[i - 1].name = "snd-soc-dummy"; + idisp_components[i - 1].dai_name = "snd-soc-dummy-dai"; + } links[id].codecs = &idisp_components[i - 1]; links[id].num_codecs = 1; @@ -556,12 +749,45 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, goto devm_err; links[id].id = id; - links[id].codecs = max98357a_component; - links[id].num_codecs = ARRAY_SIZE(max98357a_component); + if (sof_rt5682_quirk & SOF_RT1015_SPEAKER_AMP_PRESENT) { + sof_rt1015_dai_link(&links[id]); + } else if (sof_rt5682_quirk & SOF_RT1015P_SPEAKER_AMP_PRESENT) { + sof_rt1015p_dai_link(&links[id]); + } else if (sof_rt5682_quirk & SOF_RT1019_SPEAKER_AMP_PRESENT) { + sof_rt1019p_dai_link(&links[id]); + } else if (sof_rt5682_quirk & + SOF_MAX98373_SPEAKER_AMP_PRESENT) { + links[id].codecs = max_98373_components; + links[id].num_codecs = ARRAY_SIZE(max_98373_components); + links[id].init = max_98373_spk_codec_init; + links[id].ops = &max_98373_ops; + /* feedback stream */ + links[id].dpcm_capture = 1; + } else if (sof_rt5682_quirk & + SOF_MAX98360A_SPEAKER_AMP_PRESENT) { + max_98360a_dai_link(&links[id]); + } else if (sof_rt5682_quirk & + SOF_RT1011_SPEAKER_AMP_PRESENT) { + sof_rt1011_dai_link(&links[id]); + } else if (sof_rt5682_quirk & + SOF_MAX98390_SPEAKER_AMP_PRESENT) { + if (sof_rt5682_quirk & + SOF_MAX98390_TWEETER_SPEAKER_PRESENT) { + links[id].codecs = max_98390_4spk_components; + links[id].num_codecs = ARRAY_SIZE(max_98390_4spk_components); + } else { + links[id].codecs = max_98390_components; + links[id].num_codecs = ARRAY_SIZE(max_98390_components); + } + links[id].init = max_98390_spk_codec_init; + links[id].ops = &max_98390_ops; + links[id].dpcm_capture = 1; + + } else { + max_98357a_dai_link(&links[id]); + } links[id].platforms = platform_component; links[id].num_platforms = ARRAY_SIZE(platform_component); - links[id].init = speaker_codec_init, - links[id].nonatomic = true; links[id].dpcm_playback = 1; links[id].no_pcm = 1; links[id].cpus = &cpus[id]; @@ -580,6 +806,31 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, if (!links[id].cpus->dai_name) goto devm_err; } + id++; + } + + /* BT audio offload */ + if (sof_rt5682_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) { + int port = (sof_rt5682_quirk & SOF_BT_OFFLOAD_SSP_MASK) >> + SOF_BT_OFFLOAD_SSP_SHIFT; + + links[id].id = id; + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", port); + if (!links[id].cpus->dai_name) + goto devm_err; + links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", port); + if (!links[id].name) + goto devm_err; + links[id].codecs = dummy_component; + links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].dpcm_playback = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].num_cpus = 1; } return links; @@ -604,7 +855,7 @@ static int sof_audio_probe(struct platform_device *pdev) dmi_check_system(sof_rt5682_quirk_table); - mach = (&pdev->dev)->platform_data; + mach = pdev->dev.platform_data; /* A speaker amp might not be present when the quirk claims one is. * Detect this via whether the machine driver match includes quirk_data. @@ -612,6 +863,14 @@ static int sof_audio_probe(struct platform_device *pdev) if ((sof_rt5682_quirk & SOF_SPEAKER_AMP_PRESENT) && !mach->quirk_data) sof_rt5682_quirk &= ~SOF_SPEAKER_AMP_PRESENT; + /* Detect the headset codec variant */ + if (acpi_dev_present("RTL5682", NULL, -1)) + sof_rt5682_quirk |= SOF_RT5682S_HEADPHONE_CODEC_PRESENT; + + /* Detect the headset codec variant to support machines in DMI quirk */ + if (acpi_dev_present("RTL5682", NULL, -1)) + sof_rt5682_quirk |= SOF_RT5682S_HEADPHONE_CODEC_PRESENT; + if (soc_intel_is_byt() || soc_intel_is_cht()) { is_legacy_cpu = 1; dmic_be_num = 0; @@ -627,6 +886,9 @@ static int sof_audio_probe(struct platform_device *pdev) /* default number of HDMI DAI's */ if (!hdmi_num) hdmi_num = 3; + + if (mach->mach_params.codec_mask & IDISP_CODEC_MASK) + ctx->idisp_codec = true; } /* need to get main clock from pmc */ @@ -662,13 +924,34 @@ static int sof_audio_probe(struct platform_device *pdev) if (sof_rt5682_quirk & SOF_SPEAKER_AMP_PRESENT) sof_audio_card_rt5682.num_links++; + if (sof_rt5682_quirk & SOF_MAX98373_SPEAKER_AMP_PRESENT) + max_98373_set_codec_conf(&sof_audio_card_rt5682); + else if (sof_rt5682_quirk & SOF_RT1011_SPEAKER_AMP_PRESENT) + sof_rt1011_codec_conf(&sof_audio_card_rt5682); + else if (sof_rt5682_quirk & SOF_RT1015P_SPEAKER_AMP_PRESENT) + sof_rt1015p_codec_conf(&sof_audio_card_rt5682); + else if (sof_rt5682_quirk & SOF_MAX98390_SPEAKER_AMP_PRESENT) { + if (sof_rt5682_quirk & SOF_MAX98390_TWEETER_SPEAKER_PRESENT) + max_98390_set_codec_conf(&sof_audio_card_rt5682, + ARRAY_SIZE(max_98390_4spk_components)); + else + max_98390_set_codec_conf(&sof_audio_card_rt5682, + ARRAY_SIZE(max_98390_components)); + } + + if (sof_rt5682_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) + sof_audio_card_rt5682.num_links++; + dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec, ssp_amp, - dmic_be_num, hdmi_num); + dmic_be_num, hdmi_num, ctx->idisp_codec); if (!dai_links) return -ENOMEM; sof_audio_card_rt5682.dai_link = dai_links; + if (sof_rt5682_quirk & SOF_RT1015_SPEAKER_AMP_PRESENT) + sof_rt1015_codec_conf(&sof_audio_card_rt5682); + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); sof_audio_card_rt5682.dev = &pdev->dev; @@ -687,27 +970,139 @@ static int sof_audio_probe(struct platform_device *pdev) &sof_audio_card_rt5682); } -static int sof_rt5682_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - struct snd_soc_component *component = NULL; - - for_each_card_components(card, component) { - if (!strcmp(component->name, rt5682_component[0].name)) { - snd_soc_component_set_jack(component, NULL, NULL); - break; - } - } - - return 0; -} - static const struct platform_device_id board_ids[] = { { .name = "sof_rt5682", }, { - .name = "tgl_max98357a_rt5682", + .name = "tgl_mx98357_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .name = "jsl_rt5682_rt1015", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1015_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1)), + }, + { + .name = "tgl_mx98373_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98373_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .name = "jsl_rt5682_mx98360", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1)), + }, + { + .name = "cml_rt1015_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1015_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1)), + }, + { + .name = "tgl_rt1011_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1011_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .name = "jsl_rt5682_rt1015p", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1015P_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1)), + }, + { + .name = "adl_mx98373_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98373_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .name = "adl_mx98357_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(2) | + SOF_RT5682_NUM_HDMIDEV(4)), + }, + { + .name = "adl_max98390_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98390_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .name = "adl_mx98360_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .name = "adl_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .name = "adl_rt1019_rt5682s", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_RT5682S_HEADPHONE_CODEC_PRESENT | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1019_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4)), + }, + { + .name = "mtl_mx98357_rt5682", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | SOF_RT5682_SSP_CODEC(0) | SOF_SPEAKER_AMP_PRESENT | @@ -716,10 +1111,10 @@ static const struct platform_device_id board_ids[] = { }, { } }; +MODULE_DEVICE_TABLE(platform, board_ids); static struct platform_driver sof_audio = { .probe = sof_audio_probe, - .remove = sof_rt5682_remove, .driver = { .name = "sof_rt5682", .pm = &snd_soc_pm_ops, @@ -732,6 +1127,9 @@ module_platform_driver(sof_audio) MODULE_DESCRIPTION("SOF Audio Machine driver"); MODULE_AUTHOR("Bard Liao <bard.liao@intel.com>"); MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>"); +MODULE_AUTHOR("Brent Lu <brent.lu@intel.com>"); +MODULE_AUTHOR("Mac Chiang <mac.chiang@intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:sof_rt5682"); -MODULE_ALIAS("platform:tgl_max98357a_rt5682"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); +MODULE_IMPORT_NS(SND_SOC_INTEL_SOF_MAXIM_COMMON); +MODULE_IMPORT_NS(SND_SOC_INTEL_SOF_REALTEK_COMMON); diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c new file mode 100644 index 000000000000..ee9857dc3135 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw.c @@ -0,0 +1,1582 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw - ASOC Machine driver for Intel SoundWire platforms + */ + +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "sof_sdw_common.h" +#include "../../codecs/rt711.h" + +unsigned long sof_sdw_quirk = RT711_JD1; +static int quirk_override = -1; +module_param_named(quirk, quirk_override, int, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); + +#define INC_ID(BE, CPU, LINK) do { (BE)++; (CPU)++; (LINK)++; } while (0) + +static void log_quirks(struct device *dev) +{ + if (SOF_RT711_JDSRC(sof_sdw_quirk)) + dev_dbg(dev, "quirk realtek,jack-detect-source %ld\n", + SOF_RT711_JDSRC(sof_sdw_quirk)); + if (sof_sdw_quirk & SOF_SDW_FOUR_SPK) + dev_dbg(dev, "quirk SOF_SDW_FOUR_SPK enabled\n"); + if (sof_sdw_quirk & SOF_SDW_TGL_HDMI) + dev_dbg(dev, "quirk SOF_SDW_TGL_HDMI enabled\n"); + if (sof_sdw_quirk & SOF_SDW_PCH_DMIC) + dev_dbg(dev, "quirk SOF_SDW_PCH_DMIC enabled\n"); + if (SOF_SSP_GET_PORT(sof_sdw_quirk)) + dev_dbg(dev, "SSP port %ld\n", + SOF_SSP_GET_PORT(sof_sdw_quirk)); + if (sof_sdw_quirk & SOF_SDW_NO_AGGREGATION) + dev_dbg(dev, "quirk SOF_SDW_NO_AGGREGATION enabled\n"); +} + +static int sof_sdw_quirk_cb(const struct dmi_system_id *id) +{ + sof_sdw_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id sof_sdw_quirk_table[] = { + /* CometLake devices */ + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "CometLake Client"), + }, + .driver_data = (void *)SOF_SDW_PCH_DMIC, + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "09C6") + }, + .driver_data = (void *)RT711_JD2, + }, + { + /* early version of SKU 09C6 */ + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0983") + }, + .driver_data = (void *)RT711_JD2, + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "098F"), + }, + .driver_data = (void *)(RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0990"), + }, + .driver_data = (void *)(RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + /* IceLake devices */ + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Ice Lake Client"), + }, + .driver_data = (void *)SOF_SDW_PCH_DMIC, + }, + /* TigerLake devices */ + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, + "Tiger Lake Client Platform"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD1 | + SOF_SDW_PCH_DMIC | + SOF_SSP_PORT(SOF_I2S_SSP2)), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A3E") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2), + }, + { + /* another SKU of Dell Latitude 9520 */ + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A3F") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2), + }, + { + /* Dell XPS 9710 */ + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A5D") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A5E") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + DMI_MATCH(DMI_PRODUCT_NAME, "Volteer"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + SOF_SDW_PCH_DMIC | + SOF_SDW_FOUR_SPK | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + DMI_MATCH(DMI_PRODUCT_NAME, "Ripto"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + SOF_SDW_PCH_DMIC | + SOF_SDW_FOUR_SPK), + }, + { + /* + * this entry covers multiple HP SKUs. The family name + * does not seem robust enough, so we use a partial + * match that ignores the product name suffix + * (e.g. 15-eb1xxx, 14t-ea000 or 13-aw2xxx) + */ + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Conv"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + SOF_SDW_PCH_DMIC | + RT711_JD1), + }, + { + /* NUC15 'Bishop County' LAPBC510 and LAPBC710 skews */ + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), + DMI_MATCH(DMI_PRODUCT_NAME, "LAPBC"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + SOF_SDW_PCH_DMIC | + RT711_JD1), + }, + { + /* NUC15 LAPBC710 skews */ + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "LAPBC710"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + SOF_SDW_PCH_DMIC | + RT711_JD1), + }, + /* TigerLake-SDCA devices */ + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A32") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A45") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2), + }, + /* AlderLake devices */ + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alder Lake Client Platform"), + }, + .driver_data = (void *)(RT711_JD2_100K | + SOF_SDW_TGL_HDMI | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + DMI_MATCH(DMI_PRODUCT_NAME, "Brya"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + SOF_SDW_PCH_DMIC | + SOF_SDW_FOUR_SPK | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0AF0") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0AF3"), + }, + /* No Jack */ + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0AFF") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B00") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B01") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B11") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B12") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B13"), + }, + /* No Jack */ + .driver_data = (void *)SOF_SDW_TGL_HDMI, + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B29"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "OMEN by HP Gaming Laptop 16-k0xxx"), + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2), + }, + /* MeteorLake devices */ + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Intel_mtlrvp"), + }, + .driver_data = (void *)(RT711_JD1 | SOF_SDW_TGL_HDMI), + }, + {} +}; + +static struct snd_soc_dai_link_component dmic_component[] = { + { + .name = "dmic-codec", + .dai_name = "dmic-hifi", + } +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + /* name might be overridden during probe */ + .name = "0000:00:1f.3" + } +}; + +/* these wrappers are only needed to avoid typecast compilation errors */ +int sdw_startup(struct snd_pcm_substream *substream) +{ + return sdw_startup_stream(substream); +} + +int sdw_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sdw_stream_runtime *sdw_stream; + struct snd_soc_dai *dai; + + /* Find stream from first CPU DAI */ + dai = asoc_rtd_to_cpu(rtd, 0); + + sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); + + if (IS_ERR(sdw_stream)) { + dev_err(rtd->dev, "no stream found for DAI %s", dai->name); + return PTR_ERR(sdw_stream); + } + + return sdw_prepare_stream(sdw_stream); +} + +int sdw_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sdw_stream_runtime *sdw_stream; + struct snd_soc_dai *dai; + int ret; + + /* Find stream from first CPU DAI */ + dai = asoc_rtd_to_cpu(rtd, 0); + + sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); + + if (IS_ERR(sdw_stream)) { + dev_err(rtd->dev, "no stream found for DAI %s", dai->name); + return PTR_ERR(sdw_stream); + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + ret = sdw_enable_stream(sdw_stream); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + ret = sdw_disable_stream(sdw_stream); + break; + default: + ret = -EINVAL; + break; + } + + if (ret) + dev_err(rtd->dev, "%s trigger %d failed: %d", __func__, cmd, ret); + + return ret; +} + +int sdw_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sdw_stream_runtime *sdw_stream; + struct snd_soc_dai *dai; + + /* Find stream from first CPU DAI */ + dai = asoc_rtd_to_cpu(rtd, 0); + + sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); + + if (IS_ERR(sdw_stream)) { + dev_err(rtd->dev, "no stream found for DAI %s", dai->name); + return PTR_ERR(sdw_stream); + } + + return sdw_deprepare_stream(sdw_stream); +} + +void sdw_shutdown(struct snd_pcm_substream *substream) +{ + sdw_shutdown_stream(substream); +} + +static const struct snd_soc_ops sdw_ops = { + .startup = sdw_startup, + .prepare = sdw_prepare, + .trigger = sdw_trigger, + .hw_free = sdw_hw_free, + .shutdown = sdw_shutdown, +}; + +static struct sof_sdw_codec_info codec_info_list[] = { + { + .part_id = 0x700, + .direction = {true, true}, + .dai_name = "rt700-aif1", + .init = sof_sdw_rt700_init, + .codec_type = SOF_SDW_CODEC_TYPE_JACK, + }, + { + .part_id = 0x711, + .version_id = 3, + .direction = {true, true}, + .dai_name = "rt711-sdca-aif1", + .init = sof_sdw_rt711_sdca_init, + .exit = sof_sdw_rt711_sdca_exit, + .codec_type = SOF_SDW_CODEC_TYPE_JACK, + }, + { + .part_id = 0x711, + .version_id = 2, + .direction = {true, true}, + .dai_name = "rt711-aif1", + .init = sof_sdw_rt711_init, + .exit = sof_sdw_rt711_exit, + .codec_type = SOF_SDW_CODEC_TYPE_JACK, + }, + { + .part_id = 0x1308, + .acpi_id = "10EC1308", + .direction = {true, false}, + .dai_name = "rt1308-aif", + .ops = &sof_sdw_rt1308_i2s_ops, + .init = sof_sdw_rt1308_init, + .codec_type = SOF_SDW_CODEC_TYPE_AMP, + }, + { + .part_id = 0x1316, + .direction = {true, true}, + .dai_name = "rt1316-aif", + .init = sof_sdw_rt1316_init, + .codec_type = SOF_SDW_CODEC_TYPE_AMP, + }, + { + .part_id = 0x714, + .version_id = 3, + .direction = {false, true}, + .ignore_pch_dmic = true, + .dai_name = "rt715-aif2", + .init = sof_sdw_rt715_sdca_init, + .codec_type = SOF_SDW_CODEC_TYPE_MIC, + }, + { + .part_id = 0x715, + .version_id = 3, + .direction = {false, true}, + .ignore_pch_dmic = true, + .dai_name = "rt715-aif2", + .init = sof_sdw_rt715_sdca_init, + .codec_type = SOF_SDW_CODEC_TYPE_MIC, + }, + { + .part_id = 0x714, + .version_id = 2, + .direction = {false, true}, + .ignore_pch_dmic = true, + .dai_name = "rt715-aif2", + .init = sof_sdw_rt715_init, + .codec_type = SOF_SDW_CODEC_TYPE_MIC, + }, + { + .part_id = 0x715, + .version_id = 2, + .direction = {false, true}, + .ignore_pch_dmic = true, + .dai_name = "rt715-aif2", + .init = sof_sdw_rt715_init, + .codec_type = SOF_SDW_CODEC_TYPE_MIC, + }, + { + .part_id = 0x8373, + .direction = {true, true}, + .dai_name = "max98373-aif1", + .init = sof_sdw_mx8373_init, + .codec_card_late_probe = sof_sdw_mx8373_late_probe, + .codec_type = SOF_SDW_CODEC_TYPE_AMP, + }, + { + .part_id = 0x5682, + .direction = {true, true}, + .dai_name = "rt5682-sdw", + .init = sof_sdw_rt5682_init, + .codec_type = SOF_SDW_CODEC_TYPE_JACK, + }, + { + .part_id = 0xaaaa, /* generic codec mockup */ + .version_id = 0, + .direction = {true, true}, + .dai_name = "sdw-mockup-aif1", + .init = NULL, + .codec_type = SOF_SDW_CODEC_TYPE_JACK, + }, + { + .part_id = 0xaa55, /* headset codec mockup */ + .version_id = 0, + .direction = {true, true}, + .dai_name = "sdw-mockup-aif1", + .init = NULL, + .codec_type = SOF_SDW_CODEC_TYPE_JACK, + }, + { + .part_id = 0x55aa, /* amplifier mockup */ + .version_id = 0, + .direction = {true, false}, + .dai_name = "sdw-mockup-aif1", + .init = NULL, + .codec_type = SOF_SDW_CODEC_TYPE_AMP, + }, + { + .part_id = 0x5555, + .version_id = 0, + .direction = {false, true}, + .dai_name = "sdw-mockup-aif1", + .codec_type = SOF_SDW_CODEC_TYPE_MIC, + }, +}; + +static inline int find_codec_info_part(u64 adr) +{ + unsigned int part_id, sdw_version; + int i; + + part_id = SDW_PART_ID(adr); + sdw_version = SDW_VERSION(adr); + for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) + /* + * A codec info is for all sdw version with the part id if + * version_id is not specified in the codec info. + */ + if (part_id == codec_info_list[i].part_id && + (!codec_info_list[i].version_id || + sdw_version == codec_info_list[i].version_id)) + return i; + + return -EINVAL; + +} + +static inline int find_codec_info_acpi(const u8 *acpi_id) +{ + int i; + + if (!acpi_id[0]) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) + if (!memcmp(codec_info_list[i].acpi_id, acpi_id, + ACPI_ID_LEN)) + break; + + if (i == ARRAY_SIZE(codec_info_list)) + return -EINVAL; + + return i; +} + +/* + * get BE dailink number and CPU DAI number based on sdw link adr. + * Since some sdw slaves may be aggregated, the CPU DAI number + * may be larger than the number of BE dailinks. + */ +static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_link_adr *links, + int *sdw_be_num, int *sdw_cpu_dai_num) +{ + const struct snd_soc_acpi_link_adr *link; + int _codec_type = SOF_SDW_CODEC_TYPE_JACK; + bool group_visited[SDW_MAX_GROUPS]; + bool no_aggregation; + int i; + + no_aggregation = sof_sdw_quirk & SOF_SDW_NO_AGGREGATION; + *sdw_cpu_dai_num = 0; + *sdw_be_num = 0; + + if (!links) + return -EINVAL; + + for (i = 0; i < SDW_MAX_GROUPS; i++) + group_visited[i] = false; + + for (link = links; link->num_adr; link++) { + const struct snd_soc_acpi_endpoint *endpoint; + int codec_index; + int stream; + u64 adr; + + adr = link->adr_d->adr; + codec_index = find_codec_info_part(adr); + if (codec_index < 0) + return codec_index; + + if (codec_info_list[codec_index].codec_type < _codec_type) + dev_warn(dev, + "Unexpected address table ordering. Expected order: jack -> amp -> mic\n"); + + _codec_type = codec_info_list[codec_index].codec_type; + + endpoint = link->adr_d->endpoints; + + /* count DAI number for playback and capture */ + for_each_pcm_streams(stream) { + if (!codec_info_list[codec_index].direction[stream]) + continue; + + (*sdw_cpu_dai_num)++; + + /* count BE for each non-aggregated slave or group */ + if (!endpoint->aggregated || no_aggregation || + !group_visited[endpoint->group_id]) + (*sdw_be_num)++; + } + + if (endpoint->aggregated) + group_visited[endpoint->group_id] = true; + } + + return 0; +} + +static void init_dai_link(struct device *dev, struct snd_soc_dai_link *dai_links, + int be_id, char *name, int playback, int capture, + struct snd_soc_dai_link_component *cpus, int cpus_num, + struct snd_soc_dai_link_component *codecs, int codecs_num, + int (*init)(struct snd_soc_pcm_runtime *rtd), + const struct snd_soc_ops *ops) +{ + dev_dbg(dev, "create dai link %s, id %d\n", name, be_id); + dai_links->id = be_id; + dai_links->name = name; + dai_links->platforms = platform_component; + dai_links->num_platforms = ARRAY_SIZE(platform_component); + dai_links->no_pcm = 1; + dai_links->cpus = cpus; + dai_links->num_cpus = cpus_num; + dai_links->codecs = codecs; + dai_links->num_codecs = codecs_num; + dai_links->dpcm_playback = playback; + dai_links->dpcm_capture = capture; + dai_links->init = init; + dai_links->ops = ops; +} + +static bool is_unique_device(const struct snd_soc_acpi_link_adr *link, + unsigned int sdw_version, + unsigned int mfg_id, + unsigned int part_id, + unsigned int class_id, + int index_in_link + ) +{ + int i; + + for (i = 0; i < link->num_adr; i++) { + unsigned int sdw1_version, mfg1_id, part1_id, class1_id; + u64 adr; + + /* skip itself */ + if (i == index_in_link) + continue; + + adr = link->adr_d[i].adr; + + sdw1_version = SDW_VERSION(adr); + mfg1_id = SDW_MFG_ID(adr); + part1_id = SDW_PART_ID(adr); + class1_id = SDW_CLASS_ID(adr); + + if (sdw_version == sdw1_version && + mfg_id == mfg1_id && + part_id == part1_id && + class_id == class1_id) + return false; + } + + return true; +} + +static int create_codec_dai_name(struct device *dev, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link_component *codec, + int offset, + struct snd_soc_codec_conf *codec_conf, + int codec_count, + int *codec_conf_index) +{ + int i; + + /* sanity check */ + if (*codec_conf_index + link->num_adr > codec_count) { + dev_err(dev, "codec_conf: out-of-bounds access requested\n"); + return -EINVAL; + } + + for (i = 0; i < link->num_adr; i++) { + unsigned int sdw_version, unique_id, mfg_id; + unsigned int link_id, part_id, class_id; + int codec_index, comp_index; + char *codec_str; + u64 adr; + + adr = link->adr_d[i].adr; + + sdw_version = SDW_VERSION(adr); + link_id = SDW_DISCO_LINK_ID(adr); + unique_id = SDW_UNIQUE_ID(adr); + mfg_id = SDW_MFG_ID(adr); + part_id = SDW_PART_ID(adr); + class_id = SDW_CLASS_ID(adr); + + comp_index = i + offset; + if (is_unique_device(link, sdw_version, mfg_id, part_id, + class_id, i)) { + codec_str = "sdw:%01x:%04x:%04x:%02x"; + codec[comp_index].name = + devm_kasprintf(dev, GFP_KERNEL, codec_str, + link_id, mfg_id, part_id, + class_id); + } else { + codec_str = "sdw:%01x:%04x:%04x:%02x:%01x"; + codec[comp_index].name = + devm_kasprintf(dev, GFP_KERNEL, codec_str, + link_id, mfg_id, part_id, + class_id, unique_id); + } + + if (!codec[comp_index].name) + return -ENOMEM; + + codec_index = find_codec_info_part(adr); + if (codec_index < 0) + return codec_index; + + codec[comp_index].dai_name = + codec_info_list[codec_index].dai_name; + + codec_conf[*codec_conf_index].dlc = codec[comp_index]; + codec_conf[*codec_conf_index].name_prefix = link->adr_d[i].name_prefix; + + ++*codec_conf_index; + } + + return 0; +} + +static int set_codec_init_func(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + bool playback, int group_id) +{ + int i; + + do { + /* + * Initialize the codec. If codec is part of an aggregated + * group (group_id>0), initialize all codecs belonging to + * same group. + */ + for (i = 0; i < link->num_adr; i++) { + int codec_index; + + codec_index = find_codec_info_part(link->adr_d[i].adr); + + if (codec_index < 0) + return codec_index; + /* The group_id is > 0 iff the codec is aggregated */ + if (link->adr_d[i].endpoints->group_id != group_id) + continue; + if (codec_info_list[codec_index].init) + codec_info_list[codec_index].init(card, + link, + dai_links, + &codec_info_list[codec_index], + playback); + } + link++; + } while (link->mask && group_id); + + return 0; +} + +/* + * check endpoint status in slaves and gather link ID for all slaves in + * the same group to generate different CPU DAI. Now only support + * one sdw link with all slaves set with only single group id. + * + * one slave on one sdw link with aggregated = 0 + * one sdw BE DAI <---> one-cpu DAI <---> one-codec DAI + * + * two or more slaves on one sdw link with aggregated = 0 + * one sdw BE DAI <---> one-cpu DAI <---> multi-codec DAIs + * + * multiple links with multiple slaves with aggregated = 1 + * one sdw BE DAI <---> 1 .. N CPU DAIs <----> 1 .. N codec DAIs + */ +static int get_slave_info(const struct snd_soc_acpi_link_adr *adr_link, + struct device *dev, int *cpu_dai_id, int *cpu_dai_num, + int *codec_num, unsigned int *group_id, + bool *group_generated) +{ + const struct snd_soc_acpi_adr_device *adr_d; + const struct snd_soc_acpi_link_adr *adr_next; + bool no_aggregation; + int index = 0; + + no_aggregation = sof_sdw_quirk & SOF_SDW_NO_AGGREGATION; + *codec_num = adr_link->num_adr; + adr_d = adr_link->adr_d; + + /* make sure the link mask has a single bit set */ + if (!is_power_of_2(adr_link->mask)) + return -EINVAL; + + cpu_dai_id[index++] = ffs(adr_link->mask) - 1; + if (!adr_d->endpoints->aggregated || no_aggregation) { + *cpu_dai_num = 1; + *group_id = 0; + return 0; + } + + *group_id = adr_d->endpoints->group_id; + + /* gather other link ID of slaves in the same group */ + for (adr_next = adr_link + 1; adr_next && adr_next->num_adr; + adr_next++) { + const struct snd_soc_acpi_endpoint *endpoint; + + endpoint = adr_next->adr_d->endpoints; + if (!endpoint->aggregated || + endpoint->group_id != *group_id) + continue; + + /* make sure the link mask has a single bit set */ + if (!is_power_of_2(adr_next->mask)) + return -EINVAL; + + if (index >= SDW_MAX_CPU_DAIS) { + dev_err(dev, " cpu_dai_id array overflows"); + return -EINVAL; + } + + cpu_dai_id[index++] = ffs(adr_next->mask) - 1; + *codec_num += adr_next->num_adr; + } + + /* + * indicate CPU DAIs for this group have been generated + * to avoid generating CPU DAIs for this group again. + */ + group_generated[*group_id] = true; + *cpu_dai_num = index; + + return 0; +} + +static int create_sdw_dailink(struct snd_soc_card *card, + struct device *dev, int *link_index, + struct snd_soc_dai_link *dai_links, + int sdw_be_num, int sdw_cpu_dai_num, + struct snd_soc_dai_link_component *cpus, + const struct snd_soc_acpi_link_adr *link, + int *cpu_id, bool *group_generated, + struct snd_soc_codec_conf *codec_conf, + int codec_count, int *link_id, + int *codec_conf_index, + bool *ignore_pch_dmic) +{ + const struct snd_soc_acpi_link_adr *link_next; + struct snd_soc_dai_link_component *codecs; + int cpu_dai_id[SDW_MAX_CPU_DAIS]; + int cpu_dai_num, cpu_dai_index; + unsigned int group_id; + int codec_idx = 0; + int i = 0, j = 0; + int codec_index; + int codec_num; + int stream; + int ret; + int k; + + ret = get_slave_info(link, dev, cpu_dai_id, &cpu_dai_num, &codec_num, + &group_id, group_generated); + if (ret) + return ret; + + codecs = devm_kcalloc(dev, codec_num, sizeof(*codecs), GFP_KERNEL); + if (!codecs) + return -ENOMEM; + + /* generate codec name on different links in the same group */ + for (link_next = link; link_next && link_next->num_adr && + i < cpu_dai_num; link_next++) { + const struct snd_soc_acpi_endpoint *endpoints; + + endpoints = link_next->adr_d->endpoints; + if (group_id && (!endpoints->aggregated || + endpoints->group_id != group_id)) + continue; + + /* skip the link excluded by this processed group */ + if (cpu_dai_id[i] != ffs(link_next->mask) - 1) + continue; + + ret = create_codec_dai_name(dev, link_next, codecs, codec_idx, + codec_conf, codec_count, codec_conf_index); + if (ret < 0) + return ret; + + /* check next link to create codec dai in the processed group */ + i++; + codec_idx += link_next->num_adr; + } + + /* find codec info to create BE DAI */ + codec_index = find_codec_info_part(link->adr_d[0].adr); + if (codec_index < 0) + return codec_index; + + if (codec_info_list[codec_index].ignore_pch_dmic) + *ignore_pch_dmic = true; + + /* Shift the first amplifier's *link_id to SDW_AMP_DAI_ID */ + if (codec_info_list[codec_index].codec_type == SOF_SDW_CODEC_TYPE_AMP && + *link_id < SDW_AMP_DAI_ID) + *link_id = SDW_AMP_DAI_ID; + + /* + * DAI ID is fixed at SDW_DMIC_DAI_ID for MICs to + * keep sdw DMIC and HDMI setting static in UCM + */ + if (codec_info_list[codec_index].codec_type == SOF_SDW_CODEC_TYPE_MIC && + *link_id < SDW_DMIC_DAI_ID) + *link_id = SDW_DMIC_DAI_ID; + + cpu_dai_index = *cpu_id; + for_each_pcm_streams(stream) { + char *name, *cpu_name; + int playback, capture; + static const char * const sdw_stream_name[] = { + "SDW%d-Playback", + "SDW%d-Capture", + }; + + if (!codec_info_list[codec_index].direction[stream]) + continue; + + /* create stream name according to first link id */ + name = devm_kasprintf(dev, GFP_KERNEL, + sdw_stream_name[stream], cpu_dai_id[0]); + if (!name) + return -ENOMEM; + + /* + * generate CPU DAI name base on the sdw link ID and + * PIN ID with offset of 2 according to sdw dai driver. + */ + for (k = 0; k < cpu_dai_num; k++) { + cpu_name = devm_kasprintf(dev, GFP_KERNEL, + "SDW%d Pin%d", cpu_dai_id[k], + j + SDW_INTEL_BIDIR_PDI_BASE); + if (!cpu_name) + return -ENOMEM; + + if (cpu_dai_index >= sdw_cpu_dai_num) { + dev_err(dev, "invalid cpu dai index %d", + cpu_dai_index); + return -EINVAL; + } + + cpus[cpu_dai_index++].dai_name = cpu_name; + } + + /* + * We create sdw dai links at first stage, so link index should + * not be larger than sdw_be_num + */ + if (*link_index >= sdw_be_num) { + dev_err(dev, "invalid dai link index %d", *link_index); + return -EINVAL; + } + + if (*cpu_id >= sdw_cpu_dai_num) { + dev_err(dev, " invalid cpu dai index %d", *cpu_id); + return -EINVAL; + } + + playback = (stream == SNDRV_PCM_STREAM_PLAYBACK); + capture = (stream == SNDRV_PCM_STREAM_CAPTURE); + init_dai_link(dev, dai_links + *link_index, (*link_id)++, name, + playback, capture, + cpus + *cpu_id, cpu_dai_num, + codecs, codec_num, + NULL, &sdw_ops); + + /* + * SoundWire DAILINKs use 'stream' functions and Bank Switch operations + * based on wait_for_completion(), tag them as 'nonatomic'. + */ + dai_links[*link_index].nonatomic = true; + + ret = set_codec_init_func(card, link, dai_links + (*link_index)++, + playback, group_id); + if (ret < 0) { + dev_err(dev, "failed to init codec %d", codec_index); + return ret; + } + + *cpu_id += cpu_dai_num; + j++; + } + + return 0; +} + +#define IDISP_CODEC_MASK 0x4 + +static int sof_card_codec_conf_alloc(struct device *dev, + struct snd_soc_acpi_mach_params *mach_params, + struct snd_soc_codec_conf **codec_conf, + int *codec_conf_count) +{ + const struct snd_soc_acpi_link_adr *adr_link; + struct snd_soc_codec_conf *c_conf; + int num_codecs = 0; + int i; + + adr_link = mach_params->links; + if (!adr_link) + return -EINVAL; + + /* generate DAI links by each sdw link */ + for (; adr_link->num_adr; adr_link++) { + for (i = 0; i < adr_link->num_adr; i++) { + if (!adr_link->adr_d[i].name_prefix) { + dev_err(dev, "codec 0x%llx does not have a name prefix\n", + adr_link->adr_d[i].adr); + return -EINVAL; + } + } + num_codecs += adr_link->num_adr; + } + + c_conf = devm_kzalloc(dev, num_codecs * sizeof(*c_conf), GFP_KERNEL); + if (!c_conf) + return -ENOMEM; + + *codec_conf = c_conf; + *codec_conf_count = num_codecs; + + return 0; +} + +static int sof_card_dai_links_create(struct device *dev, + struct snd_soc_acpi_mach *mach, + struct snd_soc_card *card) +{ + int ssp_num, sdw_be_num = 0, hdmi_num = 0, dmic_num; + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link_component *idisp_components; + struct snd_soc_dai_link_component *ssp_components; + struct snd_soc_acpi_mach_params *mach_params; + const struct snd_soc_acpi_link_adr *adr_link; + struct snd_soc_dai_link_component *cpus; + struct snd_soc_codec_conf *codec_conf; + bool ignore_pch_dmic = false; + int codec_conf_count; + int codec_conf_index = 0; + bool group_generated[SDW_MAX_GROUPS]; + int ssp_codec_index, ssp_mask; + struct snd_soc_dai_link *links; + int num_links, link_index = 0; + char *name, *cpu_name; + int total_cpu_dai_num; + int sdw_cpu_dai_num; + int i, j, be_id = 0; + int cpu_id = 0; + int comp_num; + int ret; + + mach_params = &mach->mach_params; + + /* allocate codec conf, will be populated when dailinks are created */ + ret = sof_card_codec_conf_alloc(dev, mach_params, &codec_conf, &codec_conf_count); + if (ret < 0) + return ret; + + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ + for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) + codec_info_list[i].amp_num = 0; + + if (mach_params->codec_mask & IDISP_CODEC_MASK) { + ctx->idisp_codec = true; + + if (sof_sdw_quirk & SOF_SDW_TGL_HDMI) + hdmi_num = SOF_TGL_HDMI_COUNT; + else + hdmi_num = SOF_PRE_TGL_HDMI_COUNT; + } + + ssp_mask = SOF_SSP_GET_PORT(sof_sdw_quirk); + /* + * on generic tgl platform, I2S or sdw mode is supported + * based on board rework. A ACPI device is registered in + * system only when I2S mode is supported, not sdw mode. + * Here check ACPI ID to confirm I2S is supported. + */ + ssp_codec_index = find_codec_info_acpi(mach->id); + ssp_num = ssp_codec_index >= 0 ? hweight_long(ssp_mask) : 0; + comp_num = hdmi_num + ssp_num; + + ret = get_sdw_dailink_info(dev, mach_params->links, + &sdw_be_num, &sdw_cpu_dai_num); + if (ret < 0) { + dev_err(dev, "failed to get sdw link info %d", ret); + return ret; + } + + /* enable dmic01 & dmic16k */ + dmic_num = (sof_sdw_quirk & SOF_SDW_PCH_DMIC || mach_params->dmic_num) ? 2 : 0; + comp_num += dmic_num; + + if (sof_sdw_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) + comp_num++; + + dev_dbg(dev, "sdw %d, ssp %d, dmic %d, hdmi %d", sdw_be_num, ssp_num, + dmic_num, ctx->idisp_codec ? hdmi_num : 0); + + /* allocate BE dailinks */ + num_links = comp_num + sdw_be_num; + links = devm_kcalloc(dev, num_links, sizeof(*links), GFP_KERNEL); + + /* allocated CPU DAIs */ + total_cpu_dai_num = comp_num + sdw_cpu_dai_num; + cpus = devm_kcalloc(dev, total_cpu_dai_num, sizeof(*cpus), + GFP_KERNEL); + + if (!links || !cpus) + return -ENOMEM; + + /* SDW */ + if (!sdw_be_num) + goto SSP; + + adr_link = mach_params->links; + if (!adr_link) + return -EINVAL; + + /* + * SoundWire Slaves aggregated in the same group may be + * located on different hardware links. Clear array to indicate + * CPU DAIs for this group have not been generated. + */ + for (i = 0; i < SDW_MAX_GROUPS; i++) + group_generated[i] = false; + + /* generate DAI links by each sdw link */ + for (; adr_link->num_adr; adr_link++) { + const struct snd_soc_acpi_endpoint *endpoint; + + endpoint = adr_link->adr_d->endpoints; + if (endpoint->aggregated && !endpoint->group_id) { + dev_err(dev, "invalid group id on link %x", + adr_link->mask); + continue; + } + + /* this group has been generated */ + if (endpoint->aggregated && + group_generated[endpoint->group_id]) + continue; + + ret = create_sdw_dailink(card, dev, &link_index, links, sdw_be_num, + sdw_cpu_dai_num, cpus, adr_link, + &cpu_id, group_generated, + codec_conf, codec_conf_count, + &be_id, &codec_conf_index, + &ignore_pch_dmic); + if (ret < 0) { + dev_err(dev, "failed to create dai link %d", link_index); + return ret; + } + } + +SSP: + /* SSP */ + if (!ssp_num) + goto DMIC; + + for (i = 0, j = 0; ssp_mask; i++, ssp_mask >>= 1) { + struct sof_sdw_codec_info *info; + int playback, capture; + char *codec_name; + + if (!(ssp_mask & 0x1)) + continue; + + name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d-Codec", i); + if (!name) + return -ENOMEM; + + cpu_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", i); + if (!cpu_name) + return -ENOMEM; + + ssp_components = devm_kzalloc(dev, sizeof(*ssp_components), + GFP_KERNEL); + if (!ssp_components) + return -ENOMEM; + + info = &codec_info_list[ssp_codec_index]; + codec_name = devm_kasprintf(dev, GFP_KERNEL, "i2c-%s:0%d", + info->acpi_id, j++); + if (!codec_name) + return -ENOMEM; + + ssp_components->name = codec_name; + ssp_components->dai_name = info->dai_name; + cpus[cpu_id].dai_name = cpu_name; + + playback = info->direction[SNDRV_PCM_STREAM_PLAYBACK]; + capture = info->direction[SNDRV_PCM_STREAM_CAPTURE]; + init_dai_link(dev, links + link_index, be_id, name, + playback, capture, + cpus + cpu_id, 1, + ssp_components, 1, + NULL, info->ops); + + ret = info->init(card, NULL, links + link_index, info, 0); + if (ret < 0) + return ret; + + INC_ID(be_id, cpu_id, link_index); + } + +DMIC: + /* dmic */ + if (dmic_num > 0) { + if (ignore_pch_dmic) { + dev_warn(dev, "Ignoring PCH DMIC\n"); + goto HDMI; + } + cpus[cpu_id].dai_name = "DMIC01 Pin"; + init_dai_link(dev, links + link_index, be_id, "dmic01", + 0, 1, // DMIC only supports capture + cpus + cpu_id, 1, + dmic_component, 1, + sof_sdw_dmic_init, NULL); + INC_ID(be_id, cpu_id, link_index); + + cpus[cpu_id].dai_name = "DMIC16k Pin"; + init_dai_link(dev, links + link_index, be_id, "dmic16k", + 0, 1, // DMIC only supports capture + cpus + cpu_id, 1, + dmic_component, 1, + /* don't call sof_sdw_dmic_init() twice */ + NULL, NULL); + INC_ID(be_id, cpu_id, link_index); + } + +HDMI: + /* HDMI */ + if (hdmi_num > 0) { + idisp_components = devm_kcalloc(dev, hdmi_num, + sizeof(*idisp_components), + GFP_KERNEL); + if (!idisp_components) + return -ENOMEM; + } + + for (i = 0; i < hdmi_num; i++) { + name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d", i + 1); + if (!name) + return -ENOMEM; + + if (ctx->idisp_codec) { + idisp_components[i].name = "ehdaudio0D2"; + idisp_components[i].dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "intel-hdmi-hifi%d", + i + 1); + if (!idisp_components[i].dai_name) + return -ENOMEM; + } else { + idisp_components[i].name = "snd-soc-dummy"; + idisp_components[i].dai_name = "snd-soc-dummy-dai"; + } + + cpu_name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d Pin", i + 1); + if (!cpu_name) + return -ENOMEM; + + cpus[cpu_id].dai_name = cpu_name; + init_dai_link(dev, links + link_index, be_id, name, + 1, 0, // HDMI only supports playback + cpus + cpu_id, 1, + idisp_components + i, 1, + sof_sdw_hdmi_init, NULL); + INC_ID(be_id, cpu_id, link_index); + } + + if (sof_sdw_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) { + int port = (sof_sdw_quirk & SOF_BT_OFFLOAD_SSP_MASK) >> + SOF_BT_OFFLOAD_SSP_SHIFT; + + name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", port); + if (!name) + return -ENOMEM; + + ssp_components = devm_kzalloc(dev, sizeof(*ssp_components), + GFP_KERNEL); + if (!ssp_components) + return -ENOMEM; + + ssp_components->name = "snd-soc-dummy"; + ssp_components->dai_name = "snd-soc-dummy-dai"; + + cpu_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", port); + if (!cpu_name) + return -ENOMEM; + + cpus[cpu_id].dai_name = cpu_name; + init_dai_link(dev, links + link_index, be_id, name, 1, 1, + cpus + cpu_id, 1, ssp_components, 1, NULL, NULL); + } + + card->dai_link = links; + card->num_links = num_links; + + card->codec_conf = codec_conf; + card->num_configs = codec_conf_count; + + return 0; +} + +static int sof_sdw_card_late_probe(struct snd_soc_card *card) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + int ret = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) { + if (!codec_info_list[i].late_probe) + continue; + + ret = codec_info_list[i].codec_card_late_probe(card); + if (ret < 0) + return ret; + } + + if (ctx->idisp_codec) + ret = sof_sdw_hdmi_card_late_probe(card); + + return ret; +} + +/* SoC card */ +static const char sdw_card_long_name[] = "Intel Soundwire SOF"; + +static struct snd_soc_card card_sof_sdw = { + .name = "soundwire", + .owner = THIS_MODULE, + .late_probe = sof_sdw_card_late_probe, +}; + +static void mc_dailink_exit_loop(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link; + int ret; + int i, j; + + for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) { + if (!codec_info_list[i].exit) + continue; + /* + * We don't need to call .exit function if there is no matched + * dai link found. + */ + for_each_card_prelinks(card, j, link) { + if (!strcmp(link->codecs[0].dai_name, + codec_info_list[i].dai_name)) { + ret = codec_info_list[i].exit(card, link); + if (ret) + dev_warn(card->dev, + "codec exit failed %d\n", + ret); + break; + } + } + } +} + +static int mc_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &card_sof_sdw; + struct snd_soc_acpi_mach *mach; + struct mc_private *ctx; + int amp_num = 0, i; + int ret; + + dev_dbg(&pdev->dev, "Entry\n"); + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { + dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + log_quirks(&pdev->dev); + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, ctx); + + mach = pdev->dev.platform_data; + ret = sof_card_dai_links_create(&pdev->dev, mach, + card); + if (ret < 0) + return ret; + + /* + * the default amp_num is zero for each codec and + * amp_num will only be increased for active amp + * codecs on used platform + */ + for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) + amp_num += codec_info_list[i].amp_num; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "cfg-spk:%d cfg-amp:%d", + (sof_sdw_quirk & SOF_SDW_FOUR_SPK) + ? 4 : 2, amp_num); + if (!card->components) + return -ENOMEM; + + if (mach->mach_params.dmic_num) { + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s mic:dmic cfg-mics:%d", + card->components, + mach->mach_params.dmic_num); + if (!card->components) + return -ENOMEM; + } + + card->long_name = sdw_card_long_name; + + /* Register the card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(card->dev, "snd_soc_register_card failed %d\n", ret); + mc_dailink_exit_loop(card); + return ret; + } + + platform_set_drvdata(pdev, card); + + return ret; +} + +static int mc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + mc_dailink_exit_loop(card); + + return 0; +} + +static struct platform_driver sof_sdw_driver = { + .driver = { + .name = "sof_sdw", + .pm = &snd_soc_pm_ops, + }, + .probe = mc_probe, + .remove = mc_remove, +}; + +module_platform_driver(sof_sdw_driver); + +MODULE_DESCRIPTION("ASoC SoundWire Generic Machine driver"); +MODULE_AUTHOR("Bard Liao <yung-chuan.liao@linux.intel.com>"); +MODULE_AUTHOR("Rander Wang <rander.wang@linux.intel.com>"); +MODULE_AUTHOR("Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sof_sdw"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); +MODULE_IMPORT_NS(SND_SOC_INTEL_SOF_MAXIM_COMMON); diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h new file mode 100644 index 000000000000..e2457738a332 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2020 Intel Corporation + */ + +/* + * sof_sdw_common.h - prototypes for common helpers + */ + +#ifndef SND_SOC_SOF_SDW_COMMON_H +#define SND_SOC_SOF_SDW_COMMON_H + +#include <linux/bits.h> +#include <linux/types.h> +#include <sound/soc.h> + +#define MAX_NO_PROPS 2 +#define MAX_HDMI_NUM 4 +#define SDW_AMP_DAI_ID 2 +#define SDW_DMIC_DAI_ID 4 +#define SDW_MAX_CPU_DAIS 16 +#define SDW_INTEL_BIDIR_PDI_BASE 2 + +/* 8 combinations with 4 links + unused group 0 */ +#define SDW_MAX_GROUPS 9 + +enum { + SOF_PRE_TGL_HDMI_COUNT = 3, + SOF_TGL_HDMI_COUNT = 4, +}; + +enum { + SOF_I2S_SSP0 = BIT(0), + SOF_I2S_SSP1 = BIT(1), + SOF_I2S_SSP2 = BIT(2), + SOF_I2S_SSP3 = BIT(3), + SOF_I2S_SSP4 = BIT(4), + SOF_I2S_SSP5 = BIT(5), +}; + +#define SOF_RT711_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) +#define SOF_SDW_FOUR_SPK BIT(4) +#define SOF_SDW_TGL_HDMI BIT(5) +#define SOF_SDW_PCH_DMIC BIT(6) +#define SOF_SSP_PORT(x) (((x) & GENMASK(5, 0)) << 7) +#define SOF_SSP_GET_PORT(quirk) (((quirk) >> 7) & GENMASK(5, 0)) +#define SOF_SDW_NO_AGGREGATION BIT(14) + +/* BT audio offload: reserve 3 bits for future */ +#define SOF_BT_OFFLOAD_SSP_SHIFT 15 +#define SOF_BT_OFFLOAD_SSP_MASK (GENMASK(17, 15)) +#define SOF_BT_OFFLOAD_SSP(quirk) \ + (((quirk) << SOF_BT_OFFLOAD_SSP_SHIFT) & SOF_BT_OFFLOAD_SSP_MASK) +#define SOF_SSP_BT_OFFLOAD_PRESENT BIT(18) + +#define SOF_SDW_CODEC_TYPE_JACK 0 +#define SOF_SDW_CODEC_TYPE_AMP 1 +#define SOF_SDW_CODEC_TYPE_MIC 2 + +struct sof_sdw_codec_info { + const int part_id; + const int version_id; + const int codec_type; + int amp_num; + const u8 acpi_id[ACPI_ID_LEN]; + const bool direction[2]; // playback & capture support + const bool ignore_pch_dmic; + const char *dai_name; + const struct snd_soc_ops *ops; + + int (*init)(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + + int (*exit)(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); + bool late_probe; + int (*codec_card_late_probe)(struct snd_soc_card *card); +}; + +struct mc_private { + struct list_head hdmi_pcm_list; + bool idisp_codec; + struct snd_soc_jack sdw_headset; + struct device *headset_codec_dev; /* only one headset per card */ +}; + +extern unsigned long sof_sdw_quirk; + +int sdw_startup(struct snd_pcm_substream *substream); +int sdw_prepare(struct snd_pcm_substream *substream); +int sdw_trigger(struct snd_pcm_substream *substream, int cmd); +int sdw_hw_free(struct snd_pcm_substream *substream); +void sdw_shutdown(struct snd_pcm_substream *substream); + +/* generic HDMI support */ +int sof_sdw_hdmi_init(struct snd_soc_pcm_runtime *rtd); + +int sof_sdw_hdmi_card_late_probe(struct snd_soc_card *card); + +/* DMIC support */ +int sof_sdw_dmic_init(struct snd_soc_pcm_runtime *rtd); + +/* RT711 support */ +int sof_sdw_rt711_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); +int sof_sdw_rt711_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); + +/* RT711-SDCA support */ +int sof_sdw_rt711_sdca_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); +int sof_sdw_rt711_sdca_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); + +/* RT700 support */ +int sof_sdw_rt700_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + +/* RT1308 support */ +extern struct snd_soc_ops sof_sdw_rt1308_i2s_ops; + +int sof_sdw_rt1308_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + +/* RT1316 support */ +int sof_sdw_rt1316_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + +/* RT715 support */ +int sof_sdw_rt715_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + +/* RT715-SDCA support */ +int sof_sdw_rt715_sdca_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + +/* MAX98373 support */ +int sof_sdw_mx8373_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + +int sof_sdw_mx8373_late_probe(struct snd_soc_card *card); + +/* RT5682 support */ +int sof_sdw_rt5682_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + +#endif diff --git a/sound/soc/intel/boards/sof_sdw_dmic.c b/sound/soc/intel/boards/sof_sdw_dmic.c new file mode 100644 index 000000000000..19df0f7a1d85 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_dmic.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_dmic - Helpers to handle dmic from generic machine driver + */ + +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include "sof_sdw_common.h" + +static const struct snd_soc_dapm_widget dmic_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route dmic_map[] = { + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +int sof_sdw_dmic_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets, + ARRAY_SIZE(dmic_widgets)); + if (ret) { + dev_err(card->dev, "DMic widget addition failed: %d\n", ret); + /* Don't need to add routes if widget addition failed */ + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map, + ARRAY_SIZE(dmic_map)); + + if (ret) + dev_err(card->dev, "DMic map addition failed: %d\n", ret); + + return ret; +} + diff --git a/sound/soc/intel/boards/sof_sdw_hdmi.c b/sound/soc/intel/boards/sof_sdw_hdmi.c new file mode 100644 index 000000000000..d47d8bf528c1 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_hdmi.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_hdmi - Helpers to handle HDMI from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/jack.h> +#include "sof_sdw_common.h" +#include "hda_dsp_common.h" + +struct hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +int sof_sdw_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); + struct hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +#define NAME_SIZE 32 +int sof_sdw_hdmi_card_late_probe(struct snd_soc_card *card) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + + if (!ctx->idisp_codec) + return 0; + + if (list_empty(&ctx->hdmi_pcm_list)) + return -EINVAL; + + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct hdmi_pcm, + head); + component = pcm->codec_dai->component; + + return hda_dsp_hdmi_build_controls(card, component); +} diff --git a/sound/soc/intel/boards/sof_sdw_max98373.c b/sound/soc/intel/boards/sof_sdw_max98373.c new file mode 100644 index 000000000000..77a3f32db11e --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_max98373.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation +// +// sof_sdw_max98373 - Helpers to handle 2x MAX98373 +// codec devices from generic machine driver + +#include <linux/device.h> +#include <linux/errno.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include "sof_sdw_common.h" +#include "sof_maxim_common.h" + +static const struct snd_soc_dapm_widget mx8373_widgets[] = { + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_kcontrol_new mx8373_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static int spk_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s spk:mx8373", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, mx8373_controls, + ARRAY_SIZE(mx8373_controls)); + if (ret) { + dev_err(card->dev, "mx8373 ctrls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, mx8373_widgets, + ARRAY_SIZE(mx8373_widgets)); + if (ret) { + dev_err(card->dev, "mx8373 widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, max_98373_dapm_routes, 2); + if (ret) + dev_err(rtd->dev, "failed to add first SPK map: %d\n", ret); + + return ret; +} + +static int mx8373_enable_spk_pin(struct snd_pcm_substream *substream, bool enable) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + struct snd_soc_dai *cpu_dai; + int ret; + int j; + + /* set spk pin by playback only */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + cpu_dai = asoc_rtd_to_cpu(rtd, 0); + for_each_rtd_codec_dais(rtd, j, codec_dai) { + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(cpu_dai->component); + char pin_name[16]; + + snprintf(pin_name, ARRAY_SIZE(pin_name), "%s Spk", + codec_dai->component->name_prefix); + + if (enable) + ret = snd_soc_dapm_enable_pin(dapm, pin_name); + else + ret = snd_soc_dapm_disable_pin(dapm, pin_name); + + if (!ret) + snd_soc_dapm_sync(dapm); + } + + return 0; +} + +static int mx8373_sdw_prepare(struct snd_pcm_substream *substream) +{ + int ret; + + /* according to soc_pcm_prepare dai link prepare is called first */ + ret = sdw_prepare(substream); + if (ret < 0) + return ret; + + return mx8373_enable_spk_pin(substream, true); +} + +static int mx8373_sdw_hw_free(struct snd_pcm_substream *substream) +{ + int ret; + + /* according to soc_pcm_hw_free dai link free is called first */ + ret = sdw_hw_free(substream); + if (ret < 0) + return ret; + + return mx8373_enable_spk_pin(substream, false); +} + +static const struct snd_soc_ops max_98373_sdw_ops = { + .startup = sdw_startup, + .prepare = mx8373_sdw_prepare, + .trigger = sdw_trigger, + .hw_free = mx8373_sdw_hw_free, + .shutdown = sdw_shutdown, +}; + +int sof_sdw_mx8373_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + info->amp_num++; + if (info->amp_num == 2) + dai_links->init = spk_init; + + info->late_probe = true; + + dai_links->ops = &max_98373_sdw_ops; + + return 0; +} + +int sof_sdw_mx8373_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_dapm_context *dapm = &card->dapm; + + /* Disable Left and Right Spk pin after boot */ + snd_soc_dapm_disable_pin(dapm, "Left Spk"); + snd_soc_dapm_disable_pin(dapm, "Right Spk"); + return snd_soc_dapm_sync(dapm); +} diff --git a/sound/soc/intel/boards/sof_sdw_rt1308.c b/sound/soc/intel/boards/sof_sdw_rt1308.c new file mode 100644 index 000000000000..f078fb1aad02 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt1308.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt1308 - Helpers to handle RT1308 from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include "sof_sdw_common.h" +#include "../../codecs/rt1308.h" + +static const struct snd_soc_dapm_widget rt1308_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +/* + * dapm routes for rt1308 will be registered dynamically according + * to the number of rt1308 used. The first two entries will be registered + * for one codec case, and the last two entries are also registered + * if two 1308s are used. + */ +static const struct snd_soc_dapm_route rt1308_map[] = { + { "Speaker", NULL, "rt1308-1 SPOL" }, + { "Speaker", NULL, "rt1308-1 SPOR" }, + { "Speaker", NULL, "rt1308-2 SPOL" }, + { "Speaker", NULL, "rt1308-2 SPOR" }, +}; + +static const struct snd_kcontrol_new rt1308_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int first_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s spk:rt1308", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, rt1308_controls, + ARRAY_SIZE(rt1308_controls)); + if (ret) { + dev_err(card->dev, "rt1308 controls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, rt1308_widgets, + ARRAY_SIZE(rt1308_widgets)); + if (ret) { + dev_err(card->dev, "rt1308 widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, rt1308_map, 2); + if (ret) + dev_err(rtd->dev, "failed to add first SPK map: %d\n", ret); + + return ret; +} + +static int second_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_add_routes(&card->dapm, rt1308_map + 2, 2); + if (ret) + dev_err(rtd->dev, "failed to add second SPK map: %d\n", ret); + + return ret; +} + +static int all_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + + ret = first_spk_init(rtd); + if (ret) + return ret; + + return second_spk_init(rtd); +} + +static int rt1308_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int clk_id, clk_freq, pll_out; + int err; + + clk_id = RT1308_PLL_S_MCLK; + clk_freq = 38400000; + + pll_out = params_rate(params) * 512; + + /* Set rt1308 pll */ + err = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out); + if (err < 0) { + dev_err(card->dev, "Failed to set RT1308 PLL: %d\n", err); + return err; + } + + /* Set rt1308 sysclk */ + err = snd_soc_dai_set_sysclk(codec_dai, RT1308_FS_SYS_S_PLL, pll_out, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "Failed to set RT1308 SYSCLK: %d\n", err); + return err; + } + + return 0; +} + +/* machine stream operations */ +struct snd_soc_ops sof_sdw_rt1308_i2s_ops = { + .hw_params = rt1308_i2s_hw_params, +}; + +int sof_sdw_rt1308_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + /* Count amp number and do init on playback link only. */ + if (!playback) + return 0; + + info->amp_num++; + if (info->amp_num == 1) + dai_links->init = first_spk_init; + + if (info->amp_num == 2) { + /* + * if two 1308s are in one dai link, the init function + * in this dai link will be first set for the first speaker, + * and it should be reset to initialize all speakers when + * the second speaker is found. + */ + if (dai_links->init) + dai_links->init = all_spk_init; + else + dai_links->init = second_spk_init; + } + + return 0; +} diff --git a/sound/soc/intel/boards/sof_sdw_rt1316.c b/sound/soc/intel/boards/sof_sdw_rt1316.c new file mode 100644 index 000000000000..58194b380232 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt1316.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt1316 - Helpers to handle RT1316 from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include "sof_sdw_common.h" + +static const struct snd_soc_dapm_widget rt1316_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +/* + * dapm routes for rt1316 will be registered dynamically according + * to the number of rt1316 used. The first two entries will be registered + * for one codec case, and the last two entries are also registered + * if two 1316s are used. + */ +static const struct snd_soc_dapm_route rt1316_map[] = { + { "Speaker", NULL, "rt1316-1 SPOL" }, + { "Speaker", NULL, "rt1316-1 SPOR" }, + { "Speaker", NULL, "rt1316-2 SPOL" }, + { "Speaker", NULL, "rt1316-2 SPOR" }, +}; + +static const struct snd_kcontrol_new rt1316_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int first_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s spk:rt1316", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, rt1316_controls, + ARRAY_SIZE(rt1316_controls)); + if (ret) { + dev_err(card->dev, "rt1316 controls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, rt1316_widgets, + ARRAY_SIZE(rt1316_widgets)); + if (ret) { + dev_err(card->dev, "rt1316 widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, rt1316_map, 2); + if (ret) + dev_err(rtd->dev, "failed to add first SPK map: %d\n", ret); + + return ret; +} + +static int second_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_add_routes(&card->dapm, rt1316_map + 2, 2); + if (ret) + dev_err(rtd->dev, "failed to add second SPK map: %d\n", ret); + + return ret; +} + +static int all_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + + ret = first_spk_init(rtd); + if (ret) + return ret; + + return second_spk_init(rtd); +} + +int sof_sdw_rt1316_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + /* Count amp number and do init on playback link only. */ + if (!playback) + return 0; + + info->amp_num++; + if (info->amp_num == 1) + dai_links->init = first_spk_init; + + if (info->amp_num == 2) { + /* + * if two 1316s are in one dai link, the init function + * in this dai link will be first set for the first speaker, + * and it should be reset to initialize all speakers when + * the second speaker is found. + */ + if (dai_links->init) + dai_links->init = all_spk_init; + else + dai_links->init = second_spk_init; + } + + return 0; +} diff --git a/sound/soc/intel/boards/sof_sdw_rt5682.c b/sound/soc/intel/boards/sof_sdw_rt5682.c new file mode 100644 index 000000000000..3a9be8211586 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt5682.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt5682 - Helpers to handle RT5682 from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include "sof_sdw_common.h" + +static const struct snd_soc_dapm_widget rt5682_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route rt5682_map[] = { + /*Headphones*/ + { "Headphone", NULL, "rt5682 HPOL" }, + { "Headphone", NULL, "rt5682 HPOR" }, + { "rt5682 IN1P", NULL, "Headset Mic" }, +}; + +static const struct snd_kcontrol_new rt5682_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static struct snd_soc_jack_pin rt5682_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int rt5682_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s hs:rt5682", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, rt5682_controls, + ARRAY_SIZE(rt5682_controls)); + if (ret) { + dev_err(card->dev, "rt5682 control addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, rt5682_widgets, + ARRAY_SIZE(rt5682_widgets)); + if (ret) { + dev_err(card->dev, "rt5682 widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, rt5682_map, + ARRAY_SIZE(rt5682_map)); + + if (ret) { + dev_err(card->dev, "rt5682 map addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + rt5682_jack_pins, + ARRAY_SIZE(rt5682_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +int sof_sdw_rt5682_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + /* + * headset should be initialized once. + * Do it with dai link for playback. + */ + if (!playback) + return 0; + + dai_links->init = rt5682_rtd_init; + + return 0; +} diff --git a/sound/soc/intel/boards/sof_sdw_rt700.c b/sound/soc/intel/boards/sof_sdw_rt700.c new file mode 100644 index 000000000000..c93b1f5b9440 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt700.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt700 - Helpers to handle RT700 from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include "sof_sdw_common.h" + +static const struct snd_soc_dapm_widget rt700_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route rt700_map[] = { + /* Headphones */ + { "Headphones", NULL, "rt700 HP" }, + { "Speaker", NULL, "rt700 SPK" }, + { "rt700 MIC2", NULL, "AMIC" }, +}; + +static const struct snd_kcontrol_new rt700_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("AMIC"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static struct snd_soc_jack_pin rt700_jack_pins[] = { + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "AMIC", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int rt700_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s hs:rt700", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, rt700_controls, + ARRAY_SIZE(rt700_controls)); + if (ret) { + dev_err(card->dev, "rt700 controls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, rt700_widgets, + ARRAY_SIZE(rt700_widgets)); + if (ret) { + dev_err(card->dev, "rt700 widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, rt700_map, + ARRAY_SIZE(rt700_map)); + + if (ret) { + dev_err(card->dev, "rt700 map addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + rt700_jack_pins, + ARRAY_SIZE(rt700_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +int sof_sdw_rt700_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + /* + * headset should be initialized once. + * Do it with dai link for playback. + */ + if (!playback) + return 0; + + dai_links->init = rt700_rtd_init; + + return 0; +} diff --git a/sound/soc/intel/boards/sof_sdw_rt711.c b/sound/soc/intel/boards/sof_sdw_rt711.c new file mode 100644 index 000000000000..8291967f23f3 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt711.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt711 - Helpers to handle RT711 from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include "sof_sdw_common.h" + +/* + * Note this MUST be called before snd_soc_register_card(), so that the props + * are in place before the codec component driver's probe function parses them. + */ +static int rt711_add_codec_device_props(struct device *sdw_dev) +{ + struct property_entry props[MAX_NO_PROPS] = {}; + struct fwnode_handle *fwnode; + int ret; + + if (!SOF_RT711_JDSRC(sof_sdw_quirk)) + return 0; + props[0] = PROPERTY_ENTRY_U32("realtek,jd-src", SOF_RT711_JDSRC(sof_sdw_quirk)); + + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); + + ret = device_add_software_node(sdw_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + return ret; +} + +static const struct snd_soc_dapm_widget rt711_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route rt711_map[] = { + /* Headphones */ + { "Headphone", NULL, "rt711 HP" }, + { "rt711 MIC2", NULL, "Headset Mic" }, +}; + +static const struct snd_kcontrol_new rt711_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static struct snd_soc_jack_pin rt711_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int rt711_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s hs:rt711", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, rt711_controls, + ARRAY_SIZE(rt711_controls)); + if (ret) { + dev_err(card->dev, "rt711 controls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, rt711_widgets, + ARRAY_SIZE(rt711_widgets)); + if (ret) { + dev_err(card->dev, "rt711 widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, rt711_map, + ARRAY_SIZE(rt711_map)); + + if (ret) { + dev_err(card->dev, "rt711 map addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + rt711_jack_pins, + ARRAY_SIZE(rt711_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +int sof_sdw_rt711_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + + if (!ctx->headset_codec_dev) + return 0; + + device_remove_software_node(ctx->headset_codec_dev); + put_device(ctx->headset_codec_dev); + + return 0; +} + +int sof_sdw_rt711_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct device *sdw_dev; + int ret; + + /* + * headset should be initialized once. + * Do it with dai link for playback. + */ + if (!playback) + return 0; + + sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name); + if (!sdw_dev) + return -EPROBE_DEFER; + + ret = rt711_add_codec_device_props(sdw_dev); + if (ret < 0) { + put_device(sdw_dev); + return ret; + } + ctx->headset_codec_dev = sdw_dev; + + dai_links->init = rt711_rtd_init; + + return 0; +} diff --git a/sound/soc/intel/boards/sof_sdw_rt711_sdca.c b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c new file mode 100644 index 000000000000..7f16304d025b --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt711_sdca - Helpers to handle RT711-SDCA from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include "sof_sdw_common.h" + +/* + * Note this MUST be called before snd_soc_register_card(), so that the props + * are in place before the codec component driver's probe function parses them. + */ +static int rt711_sdca_add_codec_device_props(struct device *sdw_dev) +{ + struct property_entry props[MAX_NO_PROPS] = {}; + struct fwnode_handle *fwnode; + int ret; + + if (!SOF_RT711_JDSRC(sof_sdw_quirk)) + return 0; + + props[0] = PROPERTY_ENTRY_U32("realtek,jd-src", SOF_RT711_JDSRC(sof_sdw_quirk)); + + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); + + ret = device_add_software_node(sdw_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + return ret; +} + +static const struct snd_soc_dapm_widget rt711_sdca_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route rt711_sdca_map[] = { + /* Headphones */ + { "Headphone", NULL, "rt711 HP" }, + { "rt711 MIC2", NULL, "Headset Mic" }, +}; + +static const struct snd_kcontrol_new rt711_sdca_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static struct snd_soc_jack_pin rt711_sdca_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int rt711_sdca_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s hs:rt711-sdca", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, rt711_sdca_controls, + ARRAY_SIZE(rt711_sdca_controls)); + if (ret) { + dev_err(card->dev, "rt711-sdca controls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, rt711_sdca_widgets, + ARRAY_SIZE(rt711_sdca_widgets)); + if (ret) { + dev_err(card->dev, "rt711-sdca widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, rt711_sdca_map, + ARRAY_SIZE(rt711_sdca_map)); + + if (ret) { + dev_err(card->dev, "rt711-sdca map addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + rt711_sdca_jack_pins, + ARRAY_SIZE(rt711_sdca_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +int sof_sdw_rt711_sdca_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + + if (!ctx->headset_codec_dev) + return 0; + + device_remove_software_node(ctx->headset_codec_dev); + put_device(ctx->headset_codec_dev); + + return 0; +} + +int sof_sdw_rt711_sdca_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct device *sdw_dev; + int ret; + + /* + * headset should be initialized once. + * Do it with dai link for playback. + */ + if (!playback) + return 0; + + sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name); + if (!sdw_dev) + return -EPROBE_DEFER; + + ret = rt711_sdca_add_codec_device_props(sdw_dev); + if (ret < 0) { + put_device(sdw_dev); + return ret; + } + ctx->headset_codec_dev = sdw_dev; + + dai_links->init = rt711_sdca_rtd_init; + + return 0; +} diff --git a/sound/soc/intel/boards/sof_sdw_rt715.c b/sound/soc/intel/boards/sof_sdw_rt715.c new file mode 100644 index 000000000000..7c068dc6b9cf --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt715.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt715 - Helpers to handle RT715 from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "sof_sdw_common.h" + +static int rt715_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s mic:rt715", + card->components); + if (!card->components) + return -ENOMEM; + + return 0; +} + +int sof_sdw_rt715_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + dai_links->init = rt715_rtd_init; + + return 0; +} diff --git a/sound/soc/intel/boards/sof_sdw_rt715_sdca.c b/sound/soc/intel/boards/sof_sdw_rt715_sdca.c new file mode 100644 index 000000000000..ca0cf3db2e4d --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt715_sdca.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt715_sdca - Helpers to handle RT715-SDCA from generic machine driver + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "sof_sdw_common.h" + +static int rt715_sdca_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s mic:rt715-sdca", + card->components); + if (!card->components) + return -ENOMEM; + + return 0; +} + +int sof_sdw_rt715_sdca_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + dai_links->init = rt715_sdca_rtd_init; + + return 0; +} diff --git a/sound/soc/intel/boards/sof_ssp_amp.c b/sound/soc/intel/boards/sof_ssp_amp.c new file mode 100644 index 000000000000..94d25aeb6e7c --- /dev/null +++ b/sound/soc/intel/boards/sof_ssp_amp.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. + +/* + * sof_ssp_amp.c - ASoc Machine driver for Intel platforms + * with RT1308/CS35L41 codec. + */ + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/sof.h> +#include "../../codecs/hdac_hdmi.h" +#include "hda_dsp_common.h" +#include "sof_realtek_common.h" +#include "sof_cirrus_common.h" + +#define NAME_SIZE 32 + +/* SSP port ID for speaker amplifier */ +#define SOF_AMPLIFIER_SSP(quirk) ((quirk) & GENMASK(3, 0)) +#define SOF_AMPLIFIER_SSP_MASK (GENMASK(3, 0)) + +/* HDMI capture*/ +#define SOF_SSP_HDMI_CAPTURE_PRESENT BIT(4) +#define SOF_NO_OF_HDMI_CAPTURE_SSP_SHIFT 5 +#define SOF_NO_OF_HDMI_CAPTURE_SSP_MASK (GENMASK(6, 5)) +#define SOF_NO_OF_HDMI_CAPTURE_SSP(quirk) \ + (((quirk) << SOF_NO_OF_HDMI_CAPTURE_SSP_SHIFT) & SOF_NO_OF_HDMI_CAPTURE_SSP_MASK) + +#define SOF_HDMI_CAPTURE_1_SSP_SHIFT 7 +#define SOF_HDMI_CAPTURE_1_SSP_MASK (GENMASK(9, 7)) +#define SOF_HDMI_CAPTURE_1_SSP(quirk) \ + (((quirk) << SOF_HDMI_CAPTURE_1_SSP_SHIFT) & SOF_HDMI_CAPTURE_1_SSP_MASK) + +#define SOF_HDMI_CAPTURE_2_SSP_SHIFT 10 +#define SOF_HDMI_CAPTURE_2_SSP_MASK (GENMASK(12, 10)) +#define SOF_HDMI_CAPTURE_2_SSP(quirk) \ + (((quirk) << SOF_HDMI_CAPTURE_2_SSP_SHIFT) & SOF_HDMI_CAPTURE_2_SSP_MASK) + +/* HDMI playback */ +#define SOF_HDMI_PLAYBACK_PRESENT BIT(13) +#define SOF_NO_OF_HDMI_PLAYBACK_SHIFT 14 +#define SOF_NO_OF_HDMI_PLAYBACK_MASK (GENMASK(16, 14)) +#define SOF_NO_OF_HDMI_PLAYBACK(quirk) \ + (((quirk) << SOF_NO_OF_HDMI_PLAYBACK_SHIFT) & SOF_NO_OF_HDMI_PLAYBACK_MASK) + +/* BT audio offload */ +#define SOF_SSP_BT_OFFLOAD_PRESENT BIT(17) +#define SOF_BT_OFFLOAD_SSP_SHIFT 18 +#define SOF_BT_OFFLOAD_SSP_MASK (GENMASK(20, 18)) +#define SOF_BT_OFFLOAD_SSP(quirk) \ + (((quirk) << SOF_BT_OFFLOAD_SSP_SHIFT) & SOF_BT_OFFLOAD_SSP_MASK) + +/* Speaker amplifiers */ +#define SOF_RT1308_SPEAKER_AMP_PRESENT BIT(21) +#define SOF_CS35L41_SPEAKER_AMP_PRESENT BIT(22) + +/* Default: SSP2 */ +static unsigned long sof_ssp_amp_quirk = SOF_AMPLIFIER_SSP(2); + +struct sof_hdmi_pcm { + struct list_head head; + struct snd_soc_jack sof_hdmi; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct sof_card_private { + struct list_head hdmi_pcm_list; + bool common_hdmi_codec_drv; + bool idisp_codec; +}; + +static const struct dmi_system_id chromebook_platforms[] = { + { + .ident = "Google Chromebooks", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + } + }, + {}, +}; + +static const struct snd_soc_dapm_widget sof_ssp_amp_dapm_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route sof_ssp_amp_dapm_routes[] = { + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +static int sof_card_late_probe(struct snd_soc_card *card) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = NULL; + char jack_name[NAME_SIZE]; + struct sof_hdmi_pcm *pcm; + int err; + int i; + + if (!(sof_ssp_amp_quirk & SOF_HDMI_PLAYBACK_PRESENT)) + return 0; + + /* HDMI is not supported by SOF on Baytrail/CherryTrail */ + if (!ctx->idisp_codec) + return 0; + + if (list_empty(&ctx->hdmi_pcm_list)) + return -EINVAL; + + if (ctx->common_hdmi_codec_drv) { + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm, + head); + component = pcm->codec_dai->component; + return hda_dsp_hdmi_build_controls(card, component); + } + + i = 0; + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &pcm->sof_hdmi); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &pcm->sof_hdmi); + if (err < 0) + return err; + + i++; + } + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +static struct snd_soc_card sof_ssp_amp_card = { + .name = "ssp_amp", + .owner = THIS_MODULE, + .dapm_widgets = sof_ssp_amp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sof_ssp_amp_dapm_widgets), + .dapm_routes = sof_ssp_amp_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sof_ssp_amp_dapm_routes), + .fully_routed = true, + .late_probe = sof_card_late_probe, +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + /* name might be overridden during probe */ + .name = "0000:00:1f.3" + } +}; + +static struct snd_soc_dai_link_component dmic_component[] = { + { + .name = "dmic-codec", + .dai_name = "dmic-hifi", + } +}; + +static struct snd_soc_dai_link_component dummy_component[] = { + { + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", + } +}; + +static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); + struct sof_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +#define IDISP_CODEC_MASK 0x4 + +static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, + int ssp_codec, + int dmic_be_num, + int hdmi_num, + bool idisp_codec) +{ + struct snd_soc_dai_link_component *idisp_components; + struct snd_soc_dai_link_component *cpus; + struct snd_soc_dai_link *links; + int i, id = 0; + + links = devm_kcalloc(dev, sof_ssp_amp_card.num_links, + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + cpus = devm_kcalloc(dev, sof_ssp_amp_card.num_links, + sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); + if (!links || !cpus) + return NULL; + + /* HDMI-In SSP */ + if (sof_ssp_amp_quirk & SOF_SSP_HDMI_CAPTURE_PRESENT) { + int num_of_hdmi_ssp = (sof_ssp_amp_quirk & SOF_NO_OF_HDMI_CAPTURE_SSP_MASK) >> + SOF_NO_OF_HDMI_CAPTURE_SSP_SHIFT; + + for (i = 1; i <= num_of_hdmi_ssp; i++) { + int port = (i == 1 ? (sof_ssp_amp_quirk & SOF_HDMI_CAPTURE_1_SSP_MASK) >> + SOF_HDMI_CAPTURE_1_SSP_SHIFT : + (sof_ssp_amp_quirk & SOF_HDMI_CAPTURE_2_SSP_MASK) >> + SOF_HDMI_CAPTURE_2_SSP_SHIFT); + + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", port); + if (!links[id].cpus->dai_name) + return NULL; + links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-HDMI", port); + if (!links[id].name) + return NULL; + links[id].id = id; + links[id].codecs = dummy_component; + links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].num_cpus = 1; + id++; + } + } + + /* codec SSP */ + links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_codec); + if (!links[id].name) + return NULL; + + links[id].id = id; + if (sof_ssp_amp_quirk & SOF_RT1308_SPEAKER_AMP_PRESENT) { + sof_rt1308_dai_link(&links[id]); + } else if (sof_ssp_amp_quirk & SOF_CS35L41_SPEAKER_AMP_PRESENT) { + cs35l41_set_dai_link(&links[id]); + + /* feedback from amplifier */ + links[id].dpcm_capture = 1; + } + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_codec); + if (!links[id].cpus->dai_name) + return NULL; + + id++; + + /* dmic */ + if (dmic_be_num > 0) { + /* at least we have dmic01 */ + links[id].name = "dmic01"; + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = "DMIC01 Pin"; + if (dmic_be_num > 1) { + /* set up 2 BE links at most */ + links[id + 1].name = "dmic16k"; + links[id + 1].cpus = &cpus[id + 1]; + links[id + 1].cpus->dai_name = "DMIC16k Pin"; + dmic_be_num = 2; + } + } + + for (i = 0; i < dmic_be_num; i++) { + links[id].id = id; + links[id].num_cpus = 1; + links[id].codecs = dmic_component; + links[id].num_codecs = ARRAY_SIZE(dmic_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].ignore_suspend = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + id++; + } + + /* HDMI playback */ + if (sof_ssp_amp_quirk & SOF_HDMI_PLAYBACK_PRESENT) { + /* HDMI */ + if (hdmi_num > 0) { + idisp_components = devm_kcalloc(dev, + hdmi_num, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); + if (!idisp_components) + goto devm_err; + } + for (i = 1; i <= hdmi_num; i++) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d", i); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d Pin", i); + if (!links[id].cpus->dai_name) + goto devm_err; + + if (idisp_codec) { + idisp_components[i - 1].name = "ehdaudio0D2"; + idisp_components[i - 1].dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "intel-hdmi-hifi%d", + i); + if (!idisp_components[i - 1].dai_name) + goto devm_err; + } else { + idisp_components[i - 1].name = "snd-soc-dummy"; + idisp_components[i - 1].dai_name = "snd-soc-dummy-dai"; + } + + links[id].codecs = &idisp_components[i - 1]; + links[id].num_codecs = 1; + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_hdmi_init; + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + id++; + } + } + + /* BT audio offload */ + if (sof_ssp_amp_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) { + int port = (sof_ssp_amp_quirk & SOF_BT_OFFLOAD_SSP_MASK) >> + SOF_BT_OFFLOAD_SSP_SHIFT; + + links[id].id = id; + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", port); + if (!links[id].cpus->dai_name) + goto devm_err; + links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", port); + if (!links[id].name) + goto devm_err; + links[id].codecs = dummy_component; + links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].dpcm_playback = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].num_cpus = 1; + id++; + } + + return links; +devm_err: + return NULL; +} + +static int sof_ssp_amp_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_links; + struct snd_soc_acpi_mach *mach; + struct sof_card_private *ctx; + int dmic_be_num = 0, hdmi_num = 0; + int ret, ssp_codec; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (pdev->id_entry && pdev->id_entry->driver_data) + sof_ssp_amp_quirk = (unsigned long)pdev->id_entry->driver_data; + + mach = pdev->dev.platform_data; + + if (dmi_check_system(chromebook_platforms) || mach->mach_params.dmic_num > 0) + dmic_be_num = 2; + + ssp_codec = sof_ssp_amp_quirk & SOF_AMPLIFIER_SSP_MASK; + + /* set number of dai links */ + sof_ssp_amp_card.num_links = 1 + dmic_be_num; + + if (sof_ssp_amp_quirk & SOF_SSP_HDMI_CAPTURE_PRESENT) + sof_ssp_amp_card.num_links += (sof_ssp_amp_quirk & SOF_NO_OF_HDMI_CAPTURE_SSP_MASK) >> + SOF_NO_OF_HDMI_CAPTURE_SSP_SHIFT; + + if (sof_ssp_amp_quirk & SOF_HDMI_PLAYBACK_PRESENT) { + hdmi_num = (sof_ssp_amp_quirk & SOF_NO_OF_HDMI_PLAYBACK_MASK) >> + SOF_NO_OF_HDMI_PLAYBACK_SHIFT; + /* default number of HDMI DAI's */ + if (!hdmi_num) + hdmi_num = 3; + + if (mach->mach_params.codec_mask & IDISP_CODEC_MASK) + ctx->idisp_codec = true; + + sof_ssp_amp_card.num_links += hdmi_num; + } + + if (sof_ssp_amp_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) + sof_ssp_amp_card.num_links++; + + dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec, dmic_be_num, hdmi_num, ctx->idisp_codec); + if (!dai_links) + return -ENOMEM; + + sof_ssp_amp_card.dai_link = dai_links; + + /* update codec_conf */ + if (sof_ssp_amp_quirk & SOF_CS35L41_SPEAKER_AMP_PRESENT) { + cs35l41_set_codec_conf(&sof_ssp_amp_card); + } + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + sof_ssp_amp_card.dev = &pdev->dev; + + /* set platform name for each dailink */ + ret = snd_soc_fixup_dai_links_platform_name(&sof_ssp_amp_card, + mach->mach_params.platform); + if (ret) + return ret; + + ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv; + + snd_soc_card_set_drvdata(&sof_ssp_amp_card, ctx); + + return devm_snd_soc_register_card(&pdev->dev, &sof_ssp_amp_card); +} + +static const struct platform_device_id board_ids[] = { + { + .name = "sof_ssp_amp", + }, + { + .name = "tgl_rt1308_hdmi_ssp", + .driver_data = (kernel_ulong_t)(SOF_AMPLIFIER_SSP(2) | + SOF_NO_OF_HDMI_CAPTURE_SSP(2) | + SOF_HDMI_CAPTURE_1_SSP(1) | + SOF_HDMI_CAPTURE_2_SSP(5) | + SOF_SSP_HDMI_CAPTURE_PRESENT | + SOF_RT1308_SPEAKER_AMP_PRESENT), + }, + { + .name = "adl_cs35l41", + .driver_data = (kernel_ulong_t)(SOF_AMPLIFIER_SSP(1) | + SOF_NO_OF_HDMI_PLAYBACK(4) | + SOF_HDMI_PLAYBACK_PRESENT | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT | + SOF_CS35L41_SPEAKER_AMP_PRESENT), + }, + { } +}; +MODULE_DEVICE_TABLE(platform, board_ids); + +static struct platform_driver sof_ssp_amp_driver = { + .probe = sof_ssp_amp_probe, + .driver = { + .name = "sof_ssp_amp", + .pm = &snd_soc_pm_ops, + }, + .id_table = board_ids, +}; +module_platform_driver(sof_ssp_amp_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) SOF Amplifier Machine driver"); +MODULE_AUTHOR("balamurugan.c <balamurugan.c@intel.com>"); +MODULE_AUTHOR("Brent Lu <brent.lu@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); +MODULE_IMPORT_NS(SND_SOC_INTEL_SOF_REALTEK_COMMON); +MODULE_IMPORT_NS(SND_SOC_INTEL_SOF_CIRRUS_COMMON); diff --git a/sound/soc/intel/boards/sof_wm8804.c b/sound/soc/intel/boards/sof_wm8804.c new file mode 100644 index 000000000000..54395e2ededc --- /dev/null +++ b/sound/soc/intel/boards/sof_wm8804.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2018-2020, Intel Corporation +// +// sof-wm8804.c - ASoC machine driver for Up and Up2 board +// based on WM8804/Hifiberry Digi+ + + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/machine.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../codecs/wm8804.h" + +struct sof_card_private { + struct gpio_desc *gpio_44; + struct gpio_desc *gpio_48; + int sample_rate; +}; + +#define SOF_WM8804_UP2_QUIRK BIT(0) + +static unsigned long sof_wm8804_quirk; + +static int sof_wm8804_quirk_cb(const struct dmi_system_id *id) +{ + sof_wm8804_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id sof_wm8804_quirk_table[] = { + { + .callback = sof_wm8804_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), + DMI_MATCH(DMI_PRODUCT_NAME, "UP-APL01"), + }, + .driver_data = (void *)SOF_WM8804_UP2_QUIRK, + }, + {} +}; + +static int sof_wm8804_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *codec = codec_dai->component; + const int sysclk = 27000000; /* This is fixed on this board */ + int samplerate; + long mclk_freq; + int mclk_div; + int sampling_freq; + bool clk_44; + int ret; + + samplerate = params_rate(params); + if (samplerate == ctx->sample_rate) + return 0; + + ctx->sample_rate = 0; + + if (samplerate <= 96000) { + mclk_freq = samplerate * 256; + mclk_div = WM8804_MCLKDIV_256FS; + } else { + mclk_freq = samplerate * 128; + mclk_div = WM8804_MCLKDIV_128FS; + } + + switch (samplerate) { + case 32000: + sampling_freq = 0x03; + break; + case 44100: + sampling_freq = 0x00; + break; + case 48000: + sampling_freq = 0x02; + break; + case 88200: + sampling_freq = 0x08; + break; + case 96000: + sampling_freq = 0x0a; + break; + case 176400: + sampling_freq = 0x0c; + break; + case 192000: + sampling_freq = 0x0e; + break; + default: + dev_err(rtd->card->dev, + "unsupported samplerate %d\n", samplerate); + return -EINVAL; + } + + if (samplerate % 16000) + clk_44 = true; /* use 44.1 kHz root frequency */ + else + clk_44 = false; + + if (!(IS_ERR_OR_NULL(ctx->gpio_44) || + IS_ERR_OR_NULL(ctx->gpio_48))) { + /* + * ensure both GPIOs are LOW first, then drive the + * relevant one to HIGH + */ + if (clk_44) { + gpiod_set_value_cansleep(ctx->gpio_48, !clk_44); + gpiod_set_value_cansleep(ctx->gpio_44, clk_44); + } else { + gpiod_set_value_cansleep(ctx->gpio_44, clk_44); + gpiod_set_value_cansleep(ctx->gpio_48, !clk_44); + } + } + + snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div); + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set WM8804 PLL\n"); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, + sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK: %d\n", ret); + return ret; + } + + /* set sampling frequency status bits */ + snd_soc_component_update_bits(codec, WM8804_SPDTX4, 0x0f, + sampling_freq); + + ctx->sample_rate = samplerate; + + return 0; +} + +/* machine stream operations */ +static struct snd_soc_ops sof_wm8804_ops = { + .hw_params = sof_wm8804_hw_params, +}; + +SND_SOC_DAILINK_DEF(ssp5_pin, + DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin"))); + +SND_SOC_DAILINK_DEF(ssp5_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-1AEC8804:00", "wm8804-spdif"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0"))); + +static struct snd_soc_dai_link dailink[] = { + /* back ends */ + { + .name = "SSP5-Codec", + .id = 0, + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &sof_wm8804_ops, + SND_SOC_DAILINK_REG(ssp5_pin, ssp5_codec, platform), + }, +}; + +/* SoC card */ +static struct snd_soc_card sof_wm8804_card = { + .name = "wm8804", /* sof- prefix added automatically */ + .owner = THIS_MODULE, + .dai_link = dailink, + .num_links = ARRAY_SIZE(dailink), +}; + + /* i2c-<HID>:00 with HID being 8 chars */ +static char codec_name[SND_ACPI_I2C_ID_LEN]; + +/* + * to control the HifiBerry Digi+ PRO, it's required to toggle GPIO to + * select the clock source. On the Up2 board, this means + * Pin29/BCM5/Linux GPIO 430 and Pin 31/BCM6/ Linux GPIO 404. + * + * Using the ACPI device name is not very nice, but since we only use + * the value for the Up2 board there is no risk of conflict with other + * platforms. + */ + +static struct gpiod_lookup_table up2_gpios_table = { + /* .dev_id is set during probe */ + .table = { + GPIO_LOOKUP("INT3452:01", 73, "BCM-GPIO5", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT3452:01", 74, "BCM-GPIO6", GPIO_ACTIVE_HIGH), + { }, + }, +}; + +static int sof_wm8804_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct snd_soc_acpi_mach *mach; + struct sof_card_private *ctx; + struct acpi_device *adev; + int dai_index = 0; + int ret; + int i; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mach = pdev->dev.platform_data; + card = &sof_wm8804_card; + card->dev = &pdev->dev; + + dmi_check_system(sof_wm8804_quirk_table); + + if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) { + up2_gpios_table.dev_id = dev_name(&pdev->dev); + gpiod_add_lookup_table(&up2_gpios_table); + + /* + * The gpios are required for specific boards with + * local oscillators, and optional in other cases. + * Since we can't identify when they are needed, use + * the GPIO as non-optional + */ + + ctx->gpio_44 = devm_gpiod_get(&pdev->dev, "BCM-GPIO5", + GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpio_44)) { + ret = PTR_ERR(ctx->gpio_44); + dev_err(&pdev->dev, + "could not get BCM-GPIO5: %d\n", + ret); + return ret; + } + + ctx->gpio_48 = devm_gpiod_get(&pdev->dev, "BCM-GPIO6", + GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpio_48)) { + ret = PTR_ERR(ctx->gpio_48); + dev_err(&pdev->dev, + "could not get BCM-GPIO6: %d\n", + ret); + return ret; + } + } + + /* fix index of codec dai */ + for (i = 0; i < ARRAY_SIZE(dailink); i++) { + if (!strcmp(dailink[i].codecs->name, "i2c-1AEC8804:00")) { + dai_index = i; + break; + } + } + + /* fixup codec name based on HID */ + adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); + if (adev) { + snprintf(codec_name, sizeof(codec_name), + "%s%s", "i2c-", acpi_dev_name(adev)); + put_device(&adev->dev); + dailink[dai_index].codecs->name = codec_name; + } + + snd_soc_card_set_drvdata(card, ctx); + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static int sof_wm8804_remove(struct platform_device *pdev) +{ + if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) + gpiod_remove_lookup_table(&up2_gpios_table); + return 0; +} + +static struct platform_driver sof_wm8804_driver = { + .driver = { + .name = "sof-wm8804", + .pm = &snd_soc_pm_ops, + }, + .probe = sof_wm8804_probe, + .remove = sof_wm8804_remove, +}; +module_platform_driver(sof_wm8804_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) SOF + WM8804 Machine driver"); +MODULE_AUTHOR("Pierre-Louis Bossart"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sof-wm8804"); diff --git a/sound/soc/intel/catpt/Makefile b/sound/soc/intel/catpt/Makefile new file mode 100644 index 000000000000..c393a45795da --- /dev/null +++ b/sound/soc/intel/catpt/Makefile @@ -0,0 +1,6 @@ +snd-soc-catpt-objs := device.o dsp.o loader.o ipc.o messages.o pcm.o sysfs.o + +# tell define_trace.h where to find the trace header +CFLAGS_device.o := -I$(src) + +obj-$(CONFIG_SND_SOC_INTEL_CATPT) += snd-soc-catpt.o diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h new file mode 100644 index 000000000000..a64a0a77dcb7 --- /dev/null +++ b/sound/soc/intel/catpt/core.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SND_SOC_INTEL_CATPT_CORE_H +#define __SND_SOC_INTEL_CATPT_CORE_H + +#include <linux/dma/dw.h> +#include <linux/irqreturn.h> +#include "messages.h" +#include "registers.h" + +struct catpt_dev; + +extern const struct attribute_group *catpt_attr_groups[]; + +void catpt_sram_init(struct resource *sram, u32 start, u32 size); +void catpt_sram_free(struct resource *sram); +struct resource * +catpt_request_region(struct resource *root, resource_size_t size); + +struct catpt_ipc_msg { + union { + u32 header; + union catpt_global_msg rsp; + }; + void *data; + size_t size; +}; + +struct catpt_ipc { + struct device *dev; + + struct catpt_ipc_msg rx; + struct catpt_fw_ready config; + u32 default_timeout; + bool ready; + + spinlock_t lock; + struct mutex mutex; + struct completion done_completion; + struct completion busy_completion; +}; + +void catpt_ipc_init(struct catpt_ipc *ipc, struct device *dev); + +struct catpt_module_type { + bool loaded; + u32 entry_point; + u32 persistent_size; + u32 scratch_size; + /* DRAM, initial module state */ + u32 state_offset; + u32 state_size; + + struct list_head node; +}; + +struct catpt_spec { + struct snd_soc_acpi_mach *machines; + u8 core_id; + u32 host_dram_offset; + u32 host_iram_offset; + u32 host_shim_offset; + u32 host_dma_offset[CATPT_DMA_COUNT]; + u32 host_ssp_offset[CATPT_SSP_COUNT]; + u32 dram_mask; + u32 iram_mask; + u32 d3srampgd_bit; + u32 d3pgd_bit; + void (*pll_shutdown)(struct catpt_dev *cdev, bool enable); +}; + +struct catpt_dev { + struct device *dev; + struct dw_dma_chip *dmac; + struct catpt_ipc ipc; + + void __iomem *pci_ba; + void __iomem *lpe_ba; + u32 lpe_base; + int irq; + + const struct catpt_spec *spec; + struct completion fw_ready; + + struct resource dram; + struct resource iram; + struct resource *scratch; + + struct catpt_mixer_stream_info mixer; + struct catpt_module_type modules[CATPT_MODULE_COUNT]; + struct catpt_ssp_device_format devfmt[CATPT_SSP_COUNT]; + struct list_head stream_list; + spinlock_t list_lock; + struct mutex clk_mutex; + + struct catpt_dx_context dx_ctx; + void *dxbuf_vaddr; + dma_addr_t dxbuf_paddr; +}; + +int catpt_dmac_probe(struct catpt_dev *cdev); +void catpt_dmac_remove(struct catpt_dev *cdev); +struct dma_chan *catpt_dma_request_config_chan(struct catpt_dev *cdev); +int catpt_dma_memcpy_todsp(struct catpt_dev *cdev, struct dma_chan *chan, + dma_addr_t dst_addr, dma_addr_t src_addr, + size_t size); +int catpt_dma_memcpy_fromdsp(struct catpt_dev *cdev, struct dma_chan *chan, + dma_addr_t dst_addr, dma_addr_t src_addr, + size_t size); + +void lpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable); +void wpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable); +int catpt_dsp_power_up(struct catpt_dev *cdev); +int catpt_dsp_power_down(struct catpt_dev *cdev); +int catpt_dsp_stall(struct catpt_dev *cdev, bool stall); +void catpt_dsp_update_srampge(struct catpt_dev *cdev, struct resource *sram, + unsigned long mask); +int catpt_dsp_update_lpclock(struct catpt_dev *cdev); +irqreturn_t catpt_dsp_irq_handler(int irq, void *dev_id); +irqreturn_t catpt_dsp_irq_thread(int irq, void *dev_id); + +/* + * IPC handlers may return positive values which denote successful + * HOST <-> DSP communication yet failure to process specific request. + * Use below macro to convert returned non-zero values appropriately + */ +#define CATPT_IPC_ERROR(err) (((err) < 0) ? (err) : -EREMOTEIO) + +int catpt_dsp_send_msg_timeout(struct catpt_dev *cdev, + struct catpt_ipc_msg request, + struct catpt_ipc_msg *reply, int timeout); +int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request, + struct catpt_ipc_msg *reply); + +int catpt_first_boot_firmware(struct catpt_dev *cdev); +int catpt_boot_firmware(struct catpt_dev *cdev, bool restore); +int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan); +int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan); +int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan); +int catpt_coredump(struct catpt_dev *cdev); + +#include <sound/memalloc.h> +#include <uapi/sound/asound.h> + +struct snd_pcm_substream; +struct catpt_stream_template; + +struct catpt_stream_runtime { + struct snd_pcm_substream *substream; + + struct catpt_stream_template *template; + struct catpt_stream_info info; + struct resource *persistent; + struct snd_dma_buffer pgtbl; + + bool allocated; + bool prepared; + + struct list_head node; +}; + +int catpt_register_plat_component(struct catpt_dev *cdev); +void catpt_stream_update_position(struct catpt_dev *cdev, + struct catpt_stream_runtime *stream, + struct catpt_notify_position *pos); +struct catpt_stream_runtime * +catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id); +int catpt_arm_stream_templates(struct catpt_dev *cdev); + +#endif diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c new file mode 100644 index 000000000000..d5d08bd766c7 --- /dev/null +++ b/sound/soc/intel/catpt/device.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// +// Special thanks to: +// Marcin Barlik <marcin.barlik@intel.com> +// Piotr Papierkowski <piotr.papierkowski@intel.com> +// +// for sharing LPT-LP and WTP-LP AudioDSP architecture expertise and +// helping backtrack its historical background +// + +#include <linux/acpi.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <sound/intel-dsp-config.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "core.h" +#include "registers.h" + +#define CREATE_TRACE_POINTS +#include "trace.h" + +static int __maybe_unused catpt_suspend(struct device *dev) +{ + struct catpt_dev *cdev = dev_get_drvdata(dev); + struct dma_chan *chan; + int ret; + + chan = catpt_dma_request_config_chan(cdev); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx)); + ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx); + if (ret) { + ret = CATPT_IPC_ERROR(ret); + goto release_dma_chan; + } + + ret = catpt_dsp_stall(cdev, true); + if (ret) + goto release_dma_chan; + + ret = catpt_store_memdumps(cdev, chan); + if (ret) { + dev_err(cdev->dev, "store memdumps failed: %d\n", ret); + goto release_dma_chan; + } + + ret = catpt_store_module_states(cdev, chan); + if (ret) { + dev_err(cdev->dev, "store module states failed: %d\n", ret); + goto release_dma_chan; + } + + ret = catpt_store_streams_context(cdev, chan); + if (ret) + dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); + +release_dma_chan: + dma_release_channel(chan); + if (ret) + return ret; + return catpt_dsp_power_down(cdev); +} + +static int __maybe_unused catpt_resume(struct device *dev) +{ + struct catpt_dev *cdev = dev_get_drvdata(dev); + int ret, i; + + ret = catpt_dsp_power_up(cdev); + if (ret) + return ret; + + if (!try_module_get(dev->driver->owner)) { + dev_info(dev, "module unloading, skipping fw boot\n"); + return 0; + } + module_put(dev->driver->owner); + + ret = catpt_boot_firmware(cdev, true); + if (ret) { + dev_err(cdev->dev, "boot firmware failed: %d\n", ret); + return ret; + } + + /* reconfigure SSP devices after Dx transition */ + for (i = 0; i < CATPT_SSP_COUNT; i++) { + if (cdev->devfmt[i].iface == UINT_MAX) + continue; + + ret = catpt_ipc_set_device_format(cdev, &cdev->devfmt[i]); + if (ret) + return CATPT_IPC_ERROR(ret); + } + + return 0; +} + +static int __maybe_unused catpt_runtime_suspend(struct device *dev) +{ + if (!try_module_get(dev->driver->owner)) { + dev_info(dev, "module unloading, skipping suspend\n"); + return 0; + } + module_put(dev->driver->owner); + + return catpt_suspend(dev); +} + +static int __maybe_unused catpt_runtime_resume(struct device *dev) +{ + return catpt_resume(dev); +} + +static const struct dev_pm_ops catpt_dev_pm = { + SET_SYSTEM_SLEEP_PM_OPS(catpt_suspend, catpt_resume) + SET_RUNTIME_PM_OPS(catpt_runtime_suspend, catpt_runtime_resume, NULL) +}; + +/* machine board owned by CATPT is removed with this hook */ +static void board_pdev_unregister(void *data) +{ + platform_device_unregister(data); +} + +static int catpt_register_board(struct catpt_dev *cdev) +{ + const struct catpt_spec *spec = cdev->spec; + struct snd_soc_acpi_mach *mach; + struct platform_device *board; + + mach = snd_soc_acpi_find_machine(spec->machines); + if (!mach) { + dev_info(cdev->dev, "no machines present\n"); + return 0; + } + + mach->mach_params.platform = "catpt-platform"; + board = platform_device_register_data(NULL, mach->drv_name, + PLATFORM_DEVID_NONE, + (const void *)mach, sizeof(*mach)); + if (IS_ERR(board)) { + dev_err(cdev->dev, "board register failed\n"); + return PTR_ERR(board); + } + + return devm_add_action_or_reset(cdev->dev, board_pdev_unregister, + board); +} + +static int catpt_probe_components(struct catpt_dev *cdev) +{ + int ret; + + ret = catpt_dsp_power_up(cdev); + if (ret) + return ret; + + ret = catpt_dmac_probe(cdev); + if (ret) { + dev_err(cdev->dev, "DMAC probe failed: %d\n", ret); + goto err_dmac_probe; + } + + ret = catpt_first_boot_firmware(cdev); + if (ret) { + dev_err(cdev->dev, "first fw boot failed: %d\n", ret); + goto err_boot_fw; + } + + ret = catpt_register_plat_component(cdev); + if (ret) { + dev_err(cdev->dev, "register plat comp failed: %d\n", ret); + goto err_boot_fw; + } + + ret = catpt_register_board(cdev); + if (ret) { + dev_err(cdev->dev, "register board failed: %d\n", ret); + goto err_reg_board; + } + + /* reflect actual ADSP state in pm_runtime */ + pm_runtime_set_active(cdev->dev); + + pm_runtime_set_autosuspend_delay(cdev->dev, 2000); + pm_runtime_use_autosuspend(cdev->dev); + pm_runtime_mark_last_busy(cdev->dev); + pm_runtime_enable(cdev->dev); + return 0; + +err_reg_board: + snd_soc_unregister_component(cdev->dev); +err_boot_fw: + catpt_dmac_remove(cdev); +err_dmac_probe: + catpt_dsp_power_down(cdev); + + return ret; +} + +static void catpt_dev_init(struct catpt_dev *cdev, struct device *dev, + const struct catpt_spec *spec) +{ + cdev->dev = dev; + cdev->spec = spec; + init_completion(&cdev->fw_ready); + INIT_LIST_HEAD(&cdev->stream_list); + spin_lock_init(&cdev->list_lock); + mutex_init(&cdev->clk_mutex); + + /* + * Mark both device formats as uninitialized. Once corresponding + * cpu_dai's pcm is created, proper values are assigned. + */ + cdev->devfmt[CATPT_SSP_IFACE_0].iface = UINT_MAX; + cdev->devfmt[CATPT_SSP_IFACE_1].iface = UINT_MAX; + + catpt_ipc_init(&cdev->ipc, dev); + + catpt_sram_init(&cdev->dram, spec->host_dram_offset, + catpt_dram_size(cdev)); + catpt_sram_init(&cdev->iram, spec->host_iram_offset, + catpt_iram_size(cdev)); +} + +static int catpt_acpi_probe(struct platform_device *pdev) +{ + const struct catpt_spec *spec; + struct catpt_dev *cdev; + struct device *dev = &pdev->dev; + const struct acpi_device_id *id; + struct resource *res; + int ret; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return -ENODEV; + + ret = snd_intel_acpi_dsp_driver_probe(dev, id->id); + if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SST) { + dev_dbg(dev, "CATPT ACPI driver not selected, aborting probe\n"); + return -ENODEV; + } + + cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + spec = (const struct catpt_spec *)id->driver_data; + catpt_dev_init(cdev, dev, spec); + + /* map DSP bar address */ + cdev->lpe_ba = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(cdev->lpe_ba)) + return PTR_ERR(cdev->lpe_ba); + cdev->lpe_base = res->start; + + /* map PCI bar address */ + cdev->pci_ba = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(cdev->pci_ba)) + return PTR_ERR(cdev->pci_ba); + + /* alloc buffer for storing DRAM context during dx transitions */ + cdev->dxbuf_vaddr = dmam_alloc_coherent(dev, catpt_dram_size(cdev), + &cdev->dxbuf_paddr, GFP_KERNEL); + if (!cdev->dxbuf_vaddr) + return -ENOMEM; + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + cdev->irq = ret; + + platform_set_drvdata(pdev, cdev); + + ret = devm_request_threaded_irq(dev, cdev->irq, catpt_dsp_irq_handler, + catpt_dsp_irq_thread, + IRQF_SHARED, "AudioDSP", cdev); + if (ret) + return ret; + + return catpt_probe_components(cdev); +} + +static int catpt_acpi_remove(struct platform_device *pdev) +{ + struct catpt_dev *cdev = platform_get_drvdata(pdev); + + pm_runtime_disable(cdev->dev); + + snd_soc_unregister_component(cdev->dev); + catpt_dmac_remove(cdev); + catpt_dsp_power_down(cdev); + + catpt_sram_free(&cdev->iram); + catpt_sram_free(&cdev->dram); + + return 0; +} + +static struct snd_soc_acpi_mach lpt_machines[] = { + { + .id = "INT33CA", + .drv_name = "hsw_rt5640", + }, + {} +}; + +static struct snd_soc_acpi_mach wpt_machines[] = { + { + .id = "INT33CA", + .drv_name = "hsw_rt5640", + }, + { + .id = "INT343A", + .drv_name = "bdw_rt286", + }, + { + .id = "10EC5650", + .drv_name = "bdw-rt5650", + }, + { + .id = "RT5677CE", + .drv_name = "bdw-rt5677", + }, + {} +}; + +static struct catpt_spec lpt_desc = { + .machines = lpt_machines, + .core_id = 0x01, + .host_dram_offset = 0x000000, + .host_iram_offset = 0x080000, + .host_shim_offset = 0x0E7000, + .host_dma_offset = { 0x0F0000, 0x0F8000 }, + .host_ssp_offset = { 0x0E8000, 0x0E9000 }, + .dram_mask = LPT_VDRTCTL0_DSRAMPGE_MASK, + .iram_mask = LPT_VDRTCTL0_ISRAMPGE_MASK, + .d3srampgd_bit = LPT_VDRTCTL0_D3SRAMPGD, + .d3pgd_bit = LPT_VDRTCTL0_D3PGD, + .pll_shutdown = lpt_dsp_pll_shutdown, +}; + +static struct catpt_spec wpt_desc = { + .machines = wpt_machines, + .core_id = 0x02, + .host_dram_offset = 0x000000, + .host_iram_offset = 0x0A0000, + .host_shim_offset = 0x0FB000, + .host_dma_offset = { 0x0FE000, 0x0FF000 }, + .host_ssp_offset = { 0x0FC000, 0x0FD000 }, + .dram_mask = WPT_VDRTCTL0_DSRAMPGE_MASK, + .iram_mask = WPT_VDRTCTL0_ISRAMPGE_MASK, + .d3srampgd_bit = WPT_VDRTCTL0_D3SRAMPGD, + .d3pgd_bit = WPT_VDRTCTL0_D3PGD, + .pll_shutdown = wpt_dsp_pll_shutdown, +}; + +static const struct acpi_device_id catpt_ids[] = { + { "INT33C8", (unsigned long)&lpt_desc }, + { "INT3438", (unsigned long)&wpt_desc }, + { } +}; +MODULE_DEVICE_TABLE(acpi, catpt_ids); + +static struct platform_driver catpt_acpi_driver = { + .probe = catpt_acpi_probe, + .remove = catpt_acpi_remove, + .driver = { + .name = "intel_catpt", + .acpi_match_table = catpt_ids, + .pm = &catpt_dev_pm, + .dev_groups = catpt_attr_groups, + }, +}; +module_platform_driver(catpt_acpi_driver); + +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_DESCRIPTION("Intel LPT/WPT AudioDSP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/catpt/dsp.c b/sound/soc/intel/catpt/dsp.c new file mode 100644 index 000000000000..346bec000306 --- /dev/null +++ b/sound/soc/intel/catpt/dsp.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/devcoredump.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/pci.h> +#include <linux/pxa2xx_ssp.h> +#include "core.h" +#include "messages.h" +#include "registers.h" + +static bool catpt_dma_filter(struct dma_chan *chan, void *param) +{ + return param == chan->device->dev; +} + +/* + * Either engine 0 or 1 can be used for image loading. + * Align with Windows driver equivalent and stick to engine 1. + */ +#define CATPT_DMA_DEVID 1 +#define CATPT_DMA_DSP_ADDR_MASK GENMASK(31, 20) + +struct dma_chan *catpt_dma_request_config_chan(struct catpt_dev *cdev) +{ + struct dma_slave_config config; + struct dma_chan *chan; + dma_cap_mask_t mask; + int ret; + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + + chan = dma_request_channel(mask, catpt_dma_filter, cdev->dev); + if (!chan) { + dev_err(cdev->dev, "request channel failed\n"); + return ERR_PTR(-ENODEV); + } + + memset(&config, 0, sizeof(config)); + config.direction = DMA_MEM_TO_DEV; + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.src_maxburst = 16; + config.dst_maxburst = 16; + + ret = dmaengine_slave_config(chan, &config); + if (ret) { + dev_err(cdev->dev, "slave config failed: %d\n", ret); + dma_release_channel(chan); + return ERR_PTR(ret); + } + + return chan; +} + +static int catpt_dma_memcpy(struct catpt_dev *cdev, struct dma_chan *chan, + dma_addr_t dst_addr, dma_addr_t src_addr, + size_t size) +{ + struct dma_async_tx_descriptor *desc; + enum dma_status status; + int ret; + + desc = dmaengine_prep_dma_memcpy(chan, dst_addr, src_addr, size, + DMA_CTRL_ACK); + if (!desc) { + dev_err(cdev->dev, "prep dma memcpy failed\n"); + return -EIO; + } + + /* enable demand mode for dma channel */ + catpt_updatel_shim(cdev, HMDC, + CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id), + CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id)); + + ret = dma_submit_error(dmaengine_submit(desc)); + if (ret) { + dev_err(cdev->dev, "submit tx failed: %d\n", ret); + goto clear_hdda; + } + + status = dma_wait_for_async_tx(desc); + ret = (status == DMA_COMPLETE) ? 0 : -EPROTO; + +clear_hdda: + /* regardless of status, disable access to HOST memory in demand mode */ + catpt_updatel_shim(cdev, HMDC, + CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id), 0); + + return ret; +} + +int catpt_dma_memcpy_todsp(struct catpt_dev *cdev, struct dma_chan *chan, + dma_addr_t dst_addr, dma_addr_t src_addr, + size_t size) +{ + return catpt_dma_memcpy(cdev, chan, dst_addr | CATPT_DMA_DSP_ADDR_MASK, + src_addr, size); +} + +int catpt_dma_memcpy_fromdsp(struct catpt_dev *cdev, struct dma_chan *chan, + dma_addr_t dst_addr, dma_addr_t src_addr, + size_t size) +{ + return catpt_dma_memcpy(cdev, chan, dst_addr, + src_addr | CATPT_DMA_DSP_ADDR_MASK, size); +} + +int catpt_dmac_probe(struct catpt_dev *cdev) +{ + struct dw_dma_chip *dmac; + int ret; + + dmac = devm_kzalloc(cdev->dev, sizeof(*dmac), GFP_KERNEL); + if (!dmac) + return -ENOMEM; + + dmac->regs = cdev->lpe_ba + cdev->spec->host_dma_offset[CATPT_DMA_DEVID]; + dmac->dev = cdev->dev; + dmac->irq = cdev->irq; + + ret = dma_coerce_mask_and_coherent(cdev->dev, DMA_BIT_MASK(31)); + if (ret) + return ret; + /* + * Caller is responsible for putting device in D0 to allow + * for I/O and memory access before probing DW. + */ + ret = dw_dma_probe(dmac); + if (ret) + return ret; + + cdev->dmac = dmac; + return 0; +} + +void catpt_dmac_remove(struct catpt_dev *cdev) +{ + /* + * As do_dma_remove() juggles with pm_runtime_get_xxx() and + * pm_runtime_put_xxx() while both ADSP and DW 'devices' are part of + * the same module, caller makes sure pm_runtime_disable() is invoked + * before removing DW to prevent postmortem resume and suspend. + */ + dw_dma_remove(cdev->dmac); +} + +static void catpt_dsp_set_srampge(struct catpt_dev *cdev, struct resource *sram, + unsigned long mask, unsigned long new) +{ + unsigned long old; + u32 off = sram->start; + u32 b = __ffs(mask); + + old = catpt_readl_pci(cdev, VDRTCTL0) & mask; + dev_dbg(cdev->dev, "SRAMPGE [0x%08lx] 0x%08lx -> 0x%08lx", + mask, old, new); + + if (old == new) + return; + + catpt_updatel_pci(cdev, VDRTCTL0, mask, new); + /* wait for SRAM power gating to propagate */ + udelay(60); + + /* + * Dummy read as the very first access after block enable + * to prevent byte loss in future operations. + */ + for_each_clear_bit_from(b, &new, fls_long(mask)) { + u8 buf[4]; + + /* newly enabled: new bit=0 while old bit=1 */ + if (test_bit(b, &old)) { + dev_dbg(cdev->dev, "sanitize block %ld: off 0x%08x\n", + b - __ffs(mask), off); + memcpy_fromio(buf, cdev->lpe_ba + off, sizeof(buf)); + } + off += CATPT_MEMBLOCK_SIZE; + } +} + +void catpt_dsp_update_srampge(struct catpt_dev *cdev, struct resource *sram, + unsigned long mask) +{ + struct resource *res; + unsigned long new = 0; + + /* flag all busy blocks */ + for (res = sram->child; res; res = res->sibling) { + u32 h, l; + + h = (res->end - sram->start) / CATPT_MEMBLOCK_SIZE; + l = (res->start - sram->start) / CATPT_MEMBLOCK_SIZE; + new |= GENMASK(h, l); + } + + /* offset value given mask's start and invert it as ON=b0 */ + new = ~(new << __ffs(mask)) & mask; + + /* disable core clock gating */ + catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0); + + catpt_dsp_set_srampge(cdev, sram, mask, new); + + /* enable core clock gating */ + catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, + CATPT_VDRTCTL2_DCLCGE); +} + +int catpt_dsp_stall(struct catpt_dev *cdev, bool stall) +{ + u32 reg, val; + + val = stall ? CATPT_CS_STALL : 0; + catpt_updatel_shim(cdev, CS1, CATPT_CS_STALL, val); + + return catpt_readl_poll_shim(cdev, CS1, + reg, (reg & CATPT_CS_STALL) == val, + 500, 10000); +} + +static int catpt_dsp_reset(struct catpt_dev *cdev, bool reset) +{ + u32 reg, val; + + val = reset ? CATPT_CS_RST : 0; + catpt_updatel_shim(cdev, CS1, CATPT_CS_RST, val); + + return catpt_readl_poll_shim(cdev, CS1, + reg, (reg & CATPT_CS_RST) == val, + 500, 10000); +} + +void lpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable) +{ + u32 val; + + val = enable ? LPT_VDRTCTL0_APLLSE : 0; + catpt_updatel_pci(cdev, VDRTCTL0, LPT_VDRTCTL0_APLLSE, val); +} + +void wpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable) +{ + u32 val; + + val = enable ? WPT_VDRTCTL2_APLLSE : 0; + catpt_updatel_pci(cdev, VDRTCTL2, WPT_VDRTCTL2_APLLSE, val); +} + +static int catpt_dsp_select_lpclock(struct catpt_dev *cdev, bool lp, bool waiti) +{ + u32 mask, reg, val; + int ret; + + mutex_lock(&cdev->clk_mutex); + + val = lp ? CATPT_CS_LPCS : 0; + reg = catpt_readl_shim(cdev, CS1) & CATPT_CS_LPCS; + dev_dbg(cdev->dev, "LPCS [0x%08lx] 0x%08x -> 0x%08x", + CATPT_CS_LPCS, reg, val); + + if (reg == val) { + mutex_unlock(&cdev->clk_mutex); + return 0; + } + + if (waiti) { + /* wait for DSP to signal WAIT state */ + ret = catpt_readl_poll_shim(cdev, ISD, + reg, (reg & CATPT_ISD_DCPWM), + 500, 10000); + if (ret) { + dev_warn(cdev->dev, "await WAITI timeout\n"); + /* no signal - only high clock selection allowed */ + if (lp) { + mutex_unlock(&cdev->clk_mutex); + return 0; + } + } + } + + ret = catpt_readl_poll_shim(cdev, CLKCTL, + reg, !(reg & CATPT_CLKCTL_CFCIP), + 500, 10000); + if (ret) + dev_warn(cdev->dev, "clock change still in progress\n"); + + /* default to DSP core & audio fabric high clock */ + val |= CATPT_CS_DCS_HIGH; + mask = CATPT_CS_LPCS | CATPT_CS_DCS; + catpt_updatel_shim(cdev, CS1, mask, val); + + ret = catpt_readl_poll_shim(cdev, CLKCTL, + reg, !(reg & CATPT_CLKCTL_CFCIP), + 500, 10000); + if (ret) + dev_warn(cdev->dev, "clock change still in progress\n"); + + /* update PLL accordingly */ + cdev->spec->pll_shutdown(cdev, lp); + + mutex_unlock(&cdev->clk_mutex); + return 0; +} + +int catpt_dsp_update_lpclock(struct catpt_dev *cdev) +{ + struct catpt_stream_runtime *stream; + + list_for_each_entry(stream, &cdev->stream_list, node) + if (stream->prepared) + return catpt_dsp_select_lpclock(cdev, false, true); + + return catpt_dsp_select_lpclock(cdev, true, true); +} + +/* bring registers to their defaults as HW won't reset itself */ +static void catpt_dsp_set_regs_defaults(struct catpt_dev *cdev) +{ + int i; + + catpt_writel_shim(cdev, CS1, CATPT_CS_DEFAULT); + catpt_writel_shim(cdev, ISC, CATPT_ISC_DEFAULT); + catpt_writel_shim(cdev, ISD, CATPT_ISD_DEFAULT); + catpt_writel_shim(cdev, IMC, CATPT_IMC_DEFAULT); + catpt_writel_shim(cdev, IMD, CATPT_IMD_DEFAULT); + catpt_writel_shim(cdev, IPCC, CATPT_IPCC_DEFAULT); + catpt_writel_shim(cdev, IPCD, CATPT_IPCD_DEFAULT); + catpt_writel_shim(cdev, CLKCTL, CATPT_CLKCTL_DEFAULT); + catpt_writel_shim(cdev, CS2, CATPT_CS2_DEFAULT); + catpt_writel_shim(cdev, LTRC, CATPT_LTRC_DEFAULT); + catpt_writel_shim(cdev, HMDC, CATPT_HMDC_DEFAULT); + + for (i = 0; i < CATPT_SSP_COUNT; i++) { + catpt_writel_ssp(cdev, i, SSCR0, CATPT_SSC0_DEFAULT); + catpt_writel_ssp(cdev, i, SSCR1, CATPT_SSC1_DEFAULT); + catpt_writel_ssp(cdev, i, SSSR, CATPT_SSS_DEFAULT); + catpt_writel_ssp(cdev, i, SSITR, CATPT_SSIT_DEFAULT); + catpt_writel_ssp(cdev, i, SSDR, CATPT_SSD_DEFAULT); + catpt_writel_ssp(cdev, i, SSTO, CATPT_SSTO_DEFAULT); + catpt_writel_ssp(cdev, i, SSPSP, CATPT_SSPSP_DEFAULT); + catpt_writel_ssp(cdev, i, SSTSA, CATPT_SSTSA_DEFAULT); + catpt_writel_ssp(cdev, i, SSRSA, CATPT_SSRSA_DEFAULT); + catpt_writel_ssp(cdev, i, SSTSS, CATPT_SSTSS_DEFAULT); + catpt_writel_ssp(cdev, i, SSCR2, CATPT_SSCR2_DEFAULT); + catpt_writel_ssp(cdev, i, SSPSP2, CATPT_SSPSP2_DEFAULT); + } +} + +int catpt_dsp_power_down(struct catpt_dev *cdev) +{ + u32 mask, val; + + /* disable core clock gating */ + catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0); + + catpt_dsp_reset(cdev, true); + /* set 24Mhz clock for both SSPs */ + catpt_updatel_shim(cdev, CS1, CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1), + CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1)); + catpt_dsp_select_lpclock(cdev, true, false); + /* disable MCLK */ + catpt_updatel_shim(cdev, CLKCTL, CATPT_CLKCTL_SMOS, 0); + + catpt_dsp_set_regs_defaults(cdev); + + /* switch clock gating */ + mask = CATPT_VDRTCTL2_CGEALL & (~CATPT_VDRTCTL2_DCLCGE); + val = mask & (~CATPT_VDRTCTL2_DTCGE); + catpt_updatel_pci(cdev, VDRTCTL2, mask, val); + /* enable DTCGE separatelly */ + catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DTCGE, + CATPT_VDRTCTL2_DTCGE); + + /* SRAM power gating all */ + catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask, + cdev->spec->dram_mask); + catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask, + cdev->spec->iram_mask); + mask = cdev->spec->d3srampgd_bit | cdev->spec->d3pgd_bit; + catpt_updatel_pci(cdev, VDRTCTL0, mask, cdev->spec->d3pgd_bit); + + catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D3hot); + /* give hw time to drop off */ + udelay(50); + + /* enable core clock gating */ + catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, + CATPT_VDRTCTL2_DCLCGE); + udelay(50); + + return 0; +} + +int catpt_dsp_power_up(struct catpt_dev *cdev) +{ + u32 mask, val; + + /* disable core clock gating */ + catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0); + + /* switch clock gating */ + mask = CATPT_VDRTCTL2_CGEALL & (~CATPT_VDRTCTL2_DCLCGE); + val = mask & (~CATPT_VDRTCTL2_DTCGE); + catpt_updatel_pci(cdev, VDRTCTL2, mask, val); + + catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D0); + + /* SRAM power gating none */ + mask = cdev->spec->d3srampgd_bit | cdev->spec->d3pgd_bit; + catpt_updatel_pci(cdev, VDRTCTL0, mask, mask); + catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask, 0); + catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask, 0); + + catpt_dsp_set_regs_defaults(cdev); + + /* restore MCLK */ + catpt_updatel_shim(cdev, CLKCTL, CATPT_CLKCTL_SMOS, CATPT_CLKCTL_SMOS); + catpt_dsp_select_lpclock(cdev, false, false); + /* set 24Mhz clock for both SSPs */ + catpt_updatel_shim(cdev, CS1, CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1), + CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1)); + catpt_dsp_reset(cdev, false); + + /* enable core clock gating */ + catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, + CATPT_VDRTCTL2_DCLCGE); + + /* generate int deassert msg to fix inversed int logic */ + catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB | CATPT_IMC_IPCCD, 0); + + return 0; +} + +#define CATPT_DUMP_MAGIC 0xcd42 +#define CATPT_DUMP_SECTION_ID_FILE 0x00 +#define CATPT_DUMP_SECTION_ID_IRAM 0x01 +#define CATPT_DUMP_SECTION_ID_DRAM 0x02 +#define CATPT_DUMP_SECTION_ID_REGS 0x03 +#define CATPT_DUMP_HASH_SIZE 20 + +struct catpt_dump_section_hdr { + u16 magic; + u8 core_id; + u8 section_id; + u32 size; +}; + +int catpt_coredump(struct catpt_dev *cdev) +{ + struct catpt_dump_section_hdr *hdr; + size_t dump_size, regs_size; + u8 *dump, *pos; + const char *eof; + char *info; + int i; + + regs_size = CATPT_SHIM_REGS_SIZE; + regs_size += CATPT_DMA_COUNT * CATPT_DMA_REGS_SIZE; + regs_size += CATPT_SSP_COUNT * CATPT_SSP_REGS_SIZE; + dump_size = resource_size(&cdev->dram); + dump_size += resource_size(&cdev->iram); + dump_size += regs_size; + /* account for header of each section and hash chunk */ + dump_size += 4 * sizeof(*hdr) + CATPT_DUMP_HASH_SIZE; + + dump = vzalloc(dump_size); + if (!dump) + return -ENOMEM; + + pos = dump; + + hdr = (struct catpt_dump_section_hdr *)pos; + hdr->magic = CATPT_DUMP_MAGIC; + hdr->core_id = cdev->spec->core_id; + hdr->section_id = CATPT_DUMP_SECTION_ID_FILE; + hdr->size = dump_size - sizeof(*hdr); + pos += sizeof(*hdr); + + info = cdev->ipc.config.fw_info; + eof = info + FW_INFO_SIZE_MAX; + /* navigate to fifth info segment (fw hash) */ + for (i = 0; i < 4 && info < eof; i++, info++) { + /* info segments are separated by space each */ + info = strnchr(info, eof - info, ' '); + if (!info) + break; + } + + if (i == 4 && info) + memcpy(pos, info, min_t(u32, eof - info, CATPT_DUMP_HASH_SIZE)); + pos += CATPT_DUMP_HASH_SIZE; + + hdr = (struct catpt_dump_section_hdr *)pos; + hdr->magic = CATPT_DUMP_MAGIC; + hdr->core_id = cdev->spec->core_id; + hdr->section_id = CATPT_DUMP_SECTION_ID_IRAM; + hdr->size = resource_size(&cdev->iram); + pos += sizeof(*hdr); + + memcpy_fromio(pos, cdev->lpe_ba + cdev->iram.start, hdr->size); + pos += hdr->size; + + hdr = (struct catpt_dump_section_hdr *)pos; + hdr->magic = CATPT_DUMP_MAGIC; + hdr->core_id = cdev->spec->core_id; + hdr->section_id = CATPT_DUMP_SECTION_ID_DRAM; + hdr->size = resource_size(&cdev->dram); + pos += sizeof(*hdr); + + memcpy_fromio(pos, cdev->lpe_ba + cdev->dram.start, hdr->size); + pos += hdr->size; + + hdr = (struct catpt_dump_section_hdr *)pos; + hdr->magic = CATPT_DUMP_MAGIC; + hdr->core_id = cdev->spec->core_id; + hdr->section_id = CATPT_DUMP_SECTION_ID_REGS; + hdr->size = regs_size; + pos += sizeof(*hdr); + + memcpy_fromio(pos, catpt_shim_addr(cdev), CATPT_SHIM_REGS_SIZE); + pos += CATPT_SHIM_REGS_SIZE; + + for (i = 0; i < CATPT_SSP_COUNT; i++) { + memcpy_fromio(pos, catpt_ssp_addr(cdev, i), + CATPT_SSP_REGS_SIZE); + pos += CATPT_SSP_REGS_SIZE; + } + for (i = 0; i < CATPT_DMA_COUNT; i++) { + memcpy_fromio(pos, catpt_dma_addr(cdev, i), + CATPT_DMA_REGS_SIZE); + pos += CATPT_DMA_REGS_SIZE; + } + + dev_coredumpv(cdev->dev, dump, dump_size, GFP_KERNEL); + + return 0; +} diff --git a/sound/soc/intel/catpt/ipc.c b/sound/soc/intel/catpt/ipc.c new file mode 100644 index 000000000000..5b718a846fda --- /dev/null +++ b/sound/soc/intel/catpt/ipc.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/irqreturn.h> +#include "core.h" +#include "messages.h" +#include "registers.h" +#include "trace.h" + +#define CATPT_IPC_TIMEOUT_MS 300 + +void catpt_ipc_init(struct catpt_ipc *ipc, struct device *dev) +{ + ipc->dev = dev; + ipc->ready = false; + ipc->default_timeout = CATPT_IPC_TIMEOUT_MS; + init_completion(&ipc->done_completion); + init_completion(&ipc->busy_completion); + spin_lock_init(&ipc->lock); + mutex_init(&ipc->mutex); +} + +static int catpt_ipc_arm(struct catpt_ipc *ipc, struct catpt_fw_ready *config) +{ + /* + * Both tx and rx are put into and received from outbox. Inbox is + * only used for notifications where payload size is known upfront, + * thus no separate buffer is allocated for it. + */ + ipc->rx.data = devm_kzalloc(ipc->dev, config->outbox_size, GFP_KERNEL); + if (!ipc->rx.data) + return -ENOMEM; + + memcpy(&ipc->config, config, sizeof(*config)); + ipc->ready = true; + + return 0; +} + +static void catpt_ipc_msg_init(struct catpt_ipc *ipc, + struct catpt_ipc_msg *reply) +{ + lockdep_assert_held(&ipc->lock); + + ipc->rx.header = 0; + ipc->rx.size = reply ? reply->size : 0; + reinit_completion(&ipc->done_completion); + reinit_completion(&ipc->busy_completion); +} + +static void catpt_dsp_send_tx(struct catpt_dev *cdev, + const struct catpt_ipc_msg *tx) +{ + u32 header = tx->header | CATPT_IPCC_BUSY; + + trace_catpt_ipc_request(header); + trace_catpt_ipc_payload(tx->data, tx->size); + + memcpy_toio(catpt_outbox_addr(cdev), tx->data, tx->size); + catpt_writel_shim(cdev, IPCC, header); +} + +static int catpt_wait_msg_completion(struct catpt_dev *cdev, int timeout) +{ + struct catpt_ipc *ipc = &cdev->ipc; + int ret; + + ret = wait_for_completion_timeout(&ipc->done_completion, + msecs_to_jiffies(timeout)); + if (!ret) + return -ETIMEDOUT; + if (ipc->rx.rsp.status != CATPT_REPLY_PENDING) + return 0; + + /* wait for delayed reply */ + ret = wait_for_completion_timeout(&ipc->busy_completion, + msecs_to_jiffies(timeout)); + return ret ? 0 : -ETIMEDOUT; +} + +static int catpt_dsp_do_send_msg(struct catpt_dev *cdev, + struct catpt_ipc_msg request, + struct catpt_ipc_msg *reply, int timeout) +{ + struct catpt_ipc *ipc = &cdev->ipc; + unsigned long flags; + int ret; + + if (!ipc->ready) + return -EPERM; + if (request.size > ipc->config.outbox_size || + (reply && reply->size > ipc->config.outbox_size)) + return -EINVAL; + + spin_lock_irqsave(&ipc->lock, flags); + catpt_ipc_msg_init(ipc, reply); + catpt_dsp_send_tx(cdev, &request); + spin_unlock_irqrestore(&ipc->lock, flags); + + ret = catpt_wait_msg_completion(cdev, timeout); + if (ret) { + dev_crit(cdev->dev, "communication severed: %d, rebooting dsp..\n", + ret); + ipc->ready = false; + /* TODO: attempt recovery */ + return ret; + } + + ret = ipc->rx.rsp.status; + if (reply) { + reply->header = ipc->rx.header; + + if (!ret && reply->data) + memcpy(reply->data, ipc->rx.data, reply->size); + } + + return ret; +} + +int catpt_dsp_send_msg_timeout(struct catpt_dev *cdev, + struct catpt_ipc_msg request, + struct catpt_ipc_msg *reply, int timeout) +{ + struct catpt_ipc *ipc = &cdev->ipc; + int ret; + + mutex_lock(&ipc->mutex); + ret = catpt_dsp_do_send_msg(cdev, request, reply, timeout); + mutex_unlock(&ipc->mutex); + + return ret; +} + +int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request, + struct catpt_ipc_msg *reply) +{ + return catpt_dsp_send_msg_timeout(cdev, request, reply, + cdev->ipc.default_timeout); +} + +static void +catpt_dsp_notify_stream(struct catpt_dev *cdev, union catpt_notify_msg msg) +{ + struct catpt_stream_runtime *stream; + struct catpt_notify_position pos; + struct catpt_notify_glitch glitch; + + stream = catpt_stream_find(cdev, msg.stream_hw_id); + if (!stream) { + dev_warn(cdev->dev, "notify %d for non-existent stream %d\n", + msg.notify_reason, msg.stream_hw_id); + return; + } + + switch (msg.notify_reason) { + case CATPT_NOTIFY_POSITION_CHANGED: + memcpy_fromio(&pos, catpt_inbox_addr(cdev), sizeof(pos)); + trace_catpt_ipc_payload((u8 *)&pos, sizeof(pos)); + + catpt_stream_update_position(cdev, stream, &pos); + break; + + case CATPT_NOTIFY_GLITCH_OCCURRED: + memcpy_fromio(&glitch, catpt_inbox_addr(cdev), sizeof(glitch)); + trace_catpt_ipc_payload((u8 *)&glitch, sizeof(glitch)); + + dev_warn(cdev->dev, "glitch %d at pos: 0x%08llx, wp: 0x%08x\n", + glitch.type, glitch.presentation_pos, + glitch.write_pos); + break; + + default: + dev_warn(cdev->dev, "unknown notification: %d received\n", + msg.notify_reason); + break; + } +} + +static void catpt_dsp_copy_rx(struct catpt_dev *cdev, u32 header) +{ + struct catpt_ipc *ipc = &cdev->ipc; + + ipc->rx.header = header; + if (ipc->rx.rsp.status != CATPT_REPLY_SUCCESS) + return; + + memcpy_fromio(ipc->rx.data, catpt_outbox_addr(cdev), ipc->rx.size); + trace_catpt_ipc_payload(ipc->rx.data, ipc->rx.size); +} + +static void catpt_dsp_process_response(struct catpt_dev *cdev, u32 header) +{ + union catpt_notify_msg msg = CATPT_MSG(header); + struct catpt_ipc *ipc = &cdev->ipc; + + if (msg.fw_ready) { + struct catpt_fw_ready config; + /* to fit 32b header original address is shifted right by 3 */ + u32 off = msg.mailbox_address << 3; + + memcpy_fromio(&config, cdev->lpe_ba + off, sizeof(config)); + trace_catpt_ipc_payload((u8 *)&config, sizeof(config)); + + catpt_ipc_arm(ipc, &config); + complete(&cdev->fw_ready); + return; + } + + switch (msg.global_msg_type) { + case CATPT_GLB_REQUEST_CORE_DUMP: + dev_err(cdev->dev, "ADSP device coredump received\n"); + ipc->ready = false; + catpt_coredump(cdev); + /* TODO: attempt recovery */ + break; + + case CATPT_GLB_STREAM_MESSAGE: + switch (msg.stream_msg_type) { + case CATPT_STRM_NOTIFICATION: + catpt_dsp_notify_stream(cdev, msg); + break; + default: + catpt_dsp_copy_rx(cdev, header); + /* signal completion of delayed reply */ + complete(&ipc->busy_completion); + break; + } + break; + + default: + dev_warn(cdev->dev, "unknown response: %d received\n", + msg.global_msg_type); + break; + } +} + +irqreturn_t catpt_dsp_irq_thread(int irq, void *dev_id) +{ + struct catpt_dev *cdev = dev_id; + u32 ipcd; + + ipcd = catpt_readl_shim(cdev, IPCD); + trace_catpt_ipc_notify(ipcd); + + /* ensure there is delayed reply or notification to process */ + if (!(ipcd & CATPT_IPCD_BUSY)) + return IRQ_NONE; + + catpt_dsp_process_response(cdev, ipcd); + + /* tell DSP processing is completed */ + catpt_updatel_shim(cdev, IPCD, CATPT_IPCD_BUSY | CATPT_IPCD_DONE, + CATPT_IPCD_DONE); + /* unmask dsp BUSY interrupt */ + catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB, 0); + + return IRQ_HANDLED; +} + +irqreturn_t catpt_dsp_irq_handler(int irq, void *dev_id) +{ + struct catpt_dev *cdev = dev_id; + irqreturn_t ret = IRQ_NONE; + u32 isc, ipcc; + + isc = catpt_readl_shim(cdev, ISC); + trace_catpt_irq(isc); + + /* immediate reply */ + if (isc & CATPT_ISC_IPCCD) { + /* mask host DONE interrupt */ + catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCCD, CATPT_IMC_IPCCD); + + ipcc = catpt_readl_shim(cdev, IPCC); + trace_catpt_ipc_reply(ipcc); + catpt_dsp_copy_rx(cdev, ipcc); + complete(&cdev->ipc.done_completion); + + /* tell DSP processing is completed */ + catpt_updatel_shim(cdev, IPCC, CATPT_IPCC_DONE, 0); + /* unmask host DONE interrupt */ + catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCCD, 0); + ret = IRQ_HANDLED; + } + + /* delayed reply or notification */ + if (isc & CATPT_ISC_IPCDB) { + /* mask dsp BUSY interrupt */ + catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB, CATPT_IMC_IPCDB); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c new file mode 100644 index 000000000000..ff7b8f0d34ac --- /dev/null +++ b/sound/soc/intel/catpt/loader.c @@ -0,0 +1,671 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/slab.h> +#include "core.h" +#include "registers.h" + +/* FW load (200ms) plus operational delays */ +#define FW_READY_TIMEOUT_MS 250 + +#define FW_SIGNATURE "$SST" +#define FW_SIGNATURE_SIZE 4 + +struct catpt_fw_hdr { + char signature[FW_SIGNATURE_SIZE]; + u32 file_size; + u32 modules; + u32 file_format; + u32 reserved[4]; +} __packed; + +struct catpt_fw_mod_hdr { + char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; + u16 slot; + u16 module_id; + u32 entry_point; + u32 persistent_size; + u32 scratch_size; +} __packed; + +enum catpt_ram_type { + CATPT_RAM_TYPE_IRAM = 1, + CATPT_RAM_TYPE_DRAM = 2, + /* DRAM with module's initial state */ + CATPT_RAM_TYPE_INSTANCE = 3, +}; + +struct catpt_fw_block_hdr { + u32 ram_type; + u32 size; + u32 ram_offset; + u32 rsvd; +} __packed; + +void catpt_sram_init(struct resource *sram, u32 start, u32 size) +{ + sram->start = start; + sram->end = start + size - 1; +} + +void catpt_sram_free(struct resource *sram) +{ + struct resource *res, *save; + + for (res = sram->child; res;) { + save = res->sibling; + release_resource(res); + kfree(res); + res = save; + } +} + +struct resource * +catpt_request_region(struct resource *root, resource_size_t size) +{ + struct resource *res = root->child; + resource_size_t addr = root->start; + + for (;;) { + if (res->start - addr >= size) + break; + addr = res->end + 1; + res = res->sibling; + if (!res) + return NULL; + } + + return __request_region(root, addr, size, NULL, 0); +} + +int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) +{ + struct catpt_stream_runtime *stream; + + list_for_each_entry(stream, &cdev->stream_list, node) { + u32 off, size; + int ret; + + off = stream->persistent->start; + size = resource_size(stream->persistent); + dev_dbg(cdev->dev, "storing stream %d ctx: off 0x%08x size %d\n", + stream->info.stream_hw_id, off, size); + + ret = catpt_dma_memcpy_fromdsp(cdev, chan, + cdev->dxbuf_paddr + off, + cdev->lpe_base + off, + ALIGN(size, 4)); + if (ret) { + dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cdev->modules); i++) { + struct catpt_module_type *type; + u32 off; + int ret; + + type = &cdev->modules[i]; + if (!type->loaded || !type->state_size) + continue; + + off = type->state_offset; + dev_dbg(cdev->dev, "storing mod %d state: off 0x%08x size %d\n", + i, off, type->state_size); + + ret = catpt_dma_memcpy_fromdsp(cdev, chan, + cdev->dxbuf_paddr + off, + cdev->lpe_base + off, + ALIGN(type->state_size, 4)); + if (ret) { + dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) +{ + int i; + + for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) { + struct catpt_save_meminfo *info; + u32 off; + int ret; + + info = &cdev->dx_ctx.meminfo[i]; + if (info->source != CATPT_DX_TYPE_MEMORY_DUMP) + continue; + + off = catpt_to_host_offset(info->offset); + if (off < cdev->dram.start || off > cdev->dram.end) + continue; + + dev_dbg(cdev->dev, "storing memdump: off 0x%08x size %d\n", + off, info->size); + + ret = catpt_dma_memcpy_fromdsp(cdev, chan, + cdev->dxbuf_paddr + off, + cdev->lpe_base + off, + ALIGN(info->size, 4)); + if (ret) { + dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int +catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) +{ + struct catpt_stream_runtime *stream; + + list_for_each_entry(stream, &cdev->stream_list, node) { + u32 off, size; + int ret; + + off = stream->persistent->start; + size = resource_size(stream->persistent); + dev_dbg(cdev->dev, "restoring stream %d ctx: off 0x%08x size %d\n", + stream->info.stream_hw_id, off, size); + + ret = catpt_dma_memcpy_todsp(cdev, chan, + cdev->lpe_base + off, + cdev->dxbuf_paddr + off, + ALIGN(size, 4)); + if (ret) { + dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) +{ + int i; + + for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) { + struct catpt_save_meminfo *info; + u32 off; + int ret; + + info = &cdev->dx_ctx.meminfo[i]; + if (info->source != CATPT_DX_TYPE_MEMORY_DUMP) + continue; + + off = catpt_to_host_offset(info->offset); + if (off < cdev->dram.start || off > cdev->dram.end) + continue; + + dev_dbg(cdev->dev, "restoring memdump: off 0x%08x size %d\n", + off, info->size); + + ret = catpt_dma_memcpy_todsp(cdev, chan, + cdev->lpe_base + off, + cdev->dxbuf_paddr + off, + ALIGN(info->size, 4)); + if (ret) { + dev_err(cdev->dev, "restore block failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int catpt_restore_fwimage(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_block_hdr *blk) +{ + struct resource r1, r2, common; + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + blk, sizeof(*blk), false); + + r1.start = cdev->dram.start + blk->ram_offset; + r1.end = r1.start + blk->size - 1; + /* advance to data area */ + paddr += sizeof(*blk); + + for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) { + struct catpt_save_meminfo *info; + u32 off; + int ret; + + info = &cdev->dx_ctx.meminfo[i]; + + if (info->source != CATPT_DX_TYPE_FW_IMAGE) + continue; + + off = catpt_to_host_offset(info->offset); + if (off < cdev->dram.start || off > cdev->dram.end) + continue; + + r2.start = off; + r2.end = r2.start + info->size - 1; + + if (!resource_intersection(&r2, &r1, &common)) + continue; + /* calculate start offset of common data area */ + off = common.start - r1.start; + + dev_dbg(cdev->dev, "restoring fwimage: %pr\n", &common); + + ret = catpt_dma_memcpy_todsp(cdev, chan, common.start, + paddr + off, + resource_size(&common)); + if (ret) { + dev_err(cdev->dev, "memcpy todsp failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int catpt_load_block(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_block_hdr *blk, bool alloc) +{ + struct resource *sram, *res; + dma_addr_t dst_addr; + int ret; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + blk, sizeof(*blk), false); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_IRAM: + sram = &cdev->iram; + break; + default: + sram = &cdev->dram; + break; + } + + dst_addr = sram->start + blk->ram_offset; + if (alloc) { + res = __request_region(sram, dst_addr, blk->size, NULL, 0); + if (!res) + return -EBUSY; + } + + /* advance to data area */ + paddr += sizeof(*blk); + + ret = catpt_dma_memcpy_todsp(cdev, chan, dst_addr, paddr, blk->size); + if (ret) { + dev_err(cdev->dev, "memcpy error: %d\n", ret); + __release_region(sram, dst_addr, blk->size); + } + + return ret; +} + +static int catpt_restore_basefw(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_mod_hdr *basefw) +{ + u32 offset = sizeof(*basefw); + int ret, i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + basefw, sizeof(*basefw), false); + + /* restore basefw image */ + for (i = 0; i < basefw->blocks; i++) { + struct catpt_fw_block_hdr *blk; + + blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_IRAM: + ret = catpt_load_block(cdev, chan, paddr + offset, + blk, false); + break; + default: + ret = catpt_restore_fwimage(cdev, chan, paddr + offset, + blk); + break; + } + + if (ret) { + dev_err(cdev->dev, "restore block failed: %d\n", ret); + return ret; + } + + offset += sizeof(*blk) + blk->size; + } + + /* then proceed with memory dumps */ + ret = catpt_restore_memdumps(cdev, chan); + if (ret) + dev_err(cdev->dev, "restore memdumps failed: %d\n", ret); + + return ret; +} + +static int catpt_restore_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_mod_hdr *mod) +{ + u32 offset = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + mod, sizeof(*mod), false); + + for (i = 0; i < mod->blocks; i++) { + struct catpt_fw_block_hdr *blk; + int ret; + + blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_INSTANCE: + /* restore module state */ + ret = catpt_dma_memcpy_todsp(cdev, chan, + cdev->lpe_base + blk->ram_offset, + cdev->dxbuf_paddr + blk->ram_offset, + ALIGN(blk->size, 4)); + break; + default: + ret = catpt_load_block(cdev, chan, paddr + offset, + blk, false); + break; + } + + if (ret) { + dev_err(cdev->dev, "restore block failed: %d\n", ret); + return ret; + } + + offset += sizeof(*blk) + blk->size; + } + + return 0; +} + +static int catpt_load_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_mod_hdr *mod) +{ + struct catpt_module_type *type; + u32 offset = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + mod, sizeof(*mod), false); + + type = &cdev->modules[mod->module_id]; + + for (i = 0; i < mod->blocks; i++) { + struct catpt_fw_block_hdr *blk; + int ret; + + blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); + + ret = catpt_load_block(cdev, chan, paddr + offset, blk, true); + if (ret) { + dev_err(cdev->dev, "load block failed: %d\n", ret); + return ret; + } + + /* + * Save state window coordinates - these will be + * used to capture module state on D0 exit. + */ + if (blk->ram_type == CATPT_RAM_TYPE_INSTANCE) { + type->state_offset = blk->ram_offset; + type->state_size = blk->size; + } + + offset += sizeof(*blk) + blk->size; + } + + /* init module type static info */ + type->loaded = true; + /* DSP expects address from module header substracted by 4 */ + type->entry_point = mod->entry_point - 4; + type->persistent_size = mod->persistent_size; + type->scratch_size = mod->scratch_size; + + return 0; +} + +static int catpt_restore_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) +{ + u32 offset = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { + struct catpt_fw_mod_hdr *mod; + int ret; + + mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); + return -EINVAL; + } + + if (mod->module_id > CATPT_MODID_LAST) + return -EINVAL; + + switch (mod->module_id) { + case CATPT_MODID_BASE_FW: + ret = catpt_restore_basefw(cdev, chan, paddr + offset, + mod); + break; + default: + ret = catpt_restore_module(cdev, chan, paddr + offset, + mod); + break; + } + + if (ret) { + dev_err(cdev->dev, "restore module failed: %d\n", ret); + return ret; + } + + offset += sizeof(*mod) + mod->mod_size; + } + + return 0; +} + +static int catpt_load_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) +{ + u32 offset = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { + struct catpt_fw_mod_hdr *mod; + int ret; + + mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); + return -EINVAL; + } + + if (mod->module_id > CATPT_MODID_LAST) + return -EINVAL; + + ret = catpt_load_module(cdev, chan, paddr + offset, mod); + if (ret) { + dev_err(cdev->dev, "load module failed: %d\n", ret); + return ret; + } + + offset += sizeof(*mod) + mod->mod_size; + } + + return 0; +} + +static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, + const char *name, const char *signature, + bool restore) +{ + struct catpt_fw_hdr *fw; + struct firmware *img; + dma_addr_t paddr; + void *vaddr; + int ret; + + ret = request_firmware((const struct firmware **)&img, name, cdev->dev); + if (ret) + return ret; + + fw = (struct catpt_fw_hdr *)img->data; + if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "firmware signature mismatch\n"); + ret = -EINVAL; + goto release_fw; + } + + vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL); + if (!vaddr) { + ret = -ENOMEM; + goto release_fw; + } + + memcpy(vaddr, img->data, img->size); + fw = (struct catpt_fw_hdr *)vaddr; + if (restore) + ret = catpt_restore_firmware(cdev, chan, paddr, fw); + else + ret = catpt_load_firmware(cdev, chan, paddr, fw); + + dma_free_coherent(cdev->dev, img->size, vaddr, paddr); +release_fw: + release_firmware(img); + return ret; +} + +static int catpt_load_images(struct catpt_dev *cdev, bool restore) +{ + static const char *const names[] = { + "intel/IntcSST1.bin", + "intel/IntcSST2.bin", + }; + struct dma_chan *chan; + int ret; + + chan = catpt_dma_request_config_chan(cdev); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + ret = catpt_load_image(cdev, chan, names[cdev->spec->core_id - 1], + FW_SIGNATURE, restore); + if (ret) + goto release_dma_chan; + + if (!restore) + goto release_dma_chan; + ret = catpt_restore_streams_context(cdev, chan); + if (ret) + dev_err(cdev->dev, "restore streams ctx failed: %d\n", ret); +release_dma_chan: + dma_release_channel(chan); + return ret; +} + +int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) +{ + int ret; + + catpt_dsp_stall(cdev, true); + + ret = catpt_load_images(cdev, restore); + if (ret) { + dev_err(cdev->dev, "load binaries failed: %d\n", ret); + return ret; + } + + reinit_completion(&cdev->fw_ready); + catpt_dsp_stall(cdev, false); + + ret = wait_for_completion_timeout(&cdev->fw_ready, + msecs_to_jiffies(FW_READY_TIMEOUT_MS)); + if (!ret) { + dev_err(cdev->dev, "firmware ready timeout\n"); + return -ETIMEDOUT; + } + + /* update sram pg & clock once done booting */ + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); + catpt_dsp_update_srampge(cdev, &cdev->iram, cdev->spec->iram_mask); + + return catpt_dsp_update_lpclock(cdev); +} + +int catpt_first_boot_firmware(struct catpt_dev *cdev) +{ + struct resource *res; + int ret; + + ret = catpt_boot_firmware(cdev, false); + if (ret) { + dev_err(cdev->dev, "basefw boot failed: %d\n", ret); + return ret; + } + + /* restrict FW Core dump area */ + __request_region(&cdev->dram, 0, 0x200, NULL, 0); + /* restrict entire area following BASE_FW - highest offset in DRAM */ + for (res = cdev->dram.child; res->sibling; res = res->sibling) + ; + __request_region(&cdev->dram, res->end + 1, + cdev->dram.end - res->end, NULL, 0); + + ret = catpt_ipc_get_mixer_stream_info(cdev, &cdev->mixer); + if (ret) + return CATPT_IPC_ERROR(ret); + + ret = catpt_arm_stream_templates(cdev); + if (ret) { + dev_err(cdev->dev, "arm templates failed: %d\n", ret); + return ret; + } + + /* update dram pg for scratch and restricted regions */ + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); + + return 0; +} diff --git a/sound/soc/intel/catpt/messages.c b/sound/soc/intel/catpt/messages.c new file mode 100644 index 000000000000..a793d114afa4 --- /dev/null +++ b/sound/soc/intel/catpt/messages.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/slab.h> +#include "core.h" +#include "messages.h" +#include "registers.h" + +int catpt_ipc_get_fw_version(struct catpt_dev *cdev, + struct catpt_fw_version *version) +{ + union catpt_global_msg msg = CATPT_GLOBAL_MSG(GET_FW_VERSION); + struct catpt_ipc_msg request = {{0}}, reply; + int ret; + + request.header = msg.val; + reply.size = sizeof(*version); + reply.data = version; + + ret = catpt_dsp_send_msg(cdev, request, &reply); + if (ret) + dev_err(cdev->dev, "get fw version failed: %d\n", ret); + + return ret; +} + +struct catpt_alloc_stream_input { + enum catpt_path_id path_id:8; + enum catpt_stream_type stream_type:8; + enum catpt_format_id format_id:8; + u8 reserved; + struct catpt_audio_format input_format; + struct catpt_ring_info ring_info; + u8 num_entries; + /* flex array with entries here */ + struct catpt_memory_info persistent_mem; + struct catpt_memory_info scratch_mem; + u32 num_notifications; /* obsolete */ +} __packed; + +int catpt_ipc_alloc_stream(struct catpt_dev *cdev, + enum catpt_path_id path_id, + enum catpt_stream_type type, + struct catpt_audio_format *afmt, + struct catpt_ring_info *rinfo, + u8 num_modules, + struct catpt_module_entry *modules, + struct resource *persistent, + struct resource *scratch, + struct catpt_stream_info *sinfo) +{ + union catpt_global_msg msg = CATPT_GLOBAL_MSG(ALLOCATE_STREAM); + struct catpt_alloc_stream_input input; + struct catpt_ipc_msg request, reply; + size_t size, arrsz; + u8 *payload; + off_t off; + int ret; + + off = offsetof(struct catpt_alloc_stream_input, persistent_mem); + arrsz = sizeof(*modules) * num_modules; + size = sizeof(input) + arrsz; + + payload = kzalloc(size, GFP_KERNEL); + if (!payload) + return -ENOMEM; + + memset(&input, 0, sizeof(input)); + input.path_id = path_id; + input.stream_type = type; + input.format_id = CATPT_FORMAT_PCM; + input.input_format = *afmt; + input.ring_info = *rinfo; + input.num_entries = num_modules; + input.persistent_mem.offset = catpt_to_dsp_offset(persistent->start); + input.persistent_mem.size = resource_size(persistent); + if (scratch) { + input.scratch_mem.offset = catpt_to_dsp_offset(scratch->start); + input.scratch_mem.size = resource_size(scratch); + } + + /* re-arrange the input: account for flex array 'entries' */ + memcpy(payload, &input, sizeof(input)); + memmove(payload + off + arrsz, payload + off, sizeof(input) - off); + memcpy(payload + off, modules, arrsz); + + request.header = msg.val; + request.size = size; + request.data = payload; + reply.size = sizeof(*sinfo); + reply.data = sinfo; + + ret = catpt_dsp_send_msg(cdev, request, &reply); + if (ret) + dev_err(cdev->dev, "alloc stream type %d failed: %d\n", + type, ret); + + kfree(payload); + return ret; +} + +int catpt_ipc_free_stream(struct catpt_dev *cdev, u8 stream_hw_id) +{ + union catpt_global_msg msg = CATPT_GLOBAL_MSG(FREE_STREAM); + struct catpt_ipc_msg request; + int ret; + + request.header = msg.val; + request.size = sizeof(stream_hw_id); + request.data = &stream_hw_id; + + ret = catpt_dsp_send_msg(cdev, request, NULL); + if (ret) + dev_err(cdev->dev, "free stream %d failed: %d\n", + stream_hw_id, ret); + + return ret; +} + +int catpt_ipc_set_device_format(struct catpt_dev *cdev, + struct catpt_ssp_device_format *devfmt) +{ + union catpt_global_msg msg = CATPT_GLOBAL_MSG(SET_DEVICE_FORMATS); + struct catpt_ipc_msg request; + int ret; + + request.header = msg.val; + request.size = sizeof(*devfmt); + request.data = devfmt; + + ret = catpt_dsp_send_msg(cdev, request, NULL); + if (ret) + dev_err(cdev->dev, "set device format failed: %d\n", ret); + + return ret; +} + +int catpt_ipc_enter_dxstate(struct catpt_dev *cdev, enum catpt_dx_state state, + struct catpt_dx_context *context) +{ + union catpt_global_msg msg = CATPT_GLOBAL_MSG(ENTER_DX_STATE); + struct catpt_ipc_msg request, reply; + int ret; + + request.header = msg.val; + request.size = sizeof(state); + request.data = &state; + reply.size = sizeof(*context); + reply.data = context; + + ret = catpt_dsp_send_msg(cdev, request, &reply); + if (ret) + dev_err(cdev->dev, "enter dx state failed: %d\n", ret); + + return ret; +} + +int catpt_ipc_get_mixer_stream_info(struct catpt_dev *cdev, + struct catpt_mixer_stream_info *info) +{ + union catpt_global_msg msg = CATPT_GLOBAL_MSG(GET_MIXER_STREAM_INFO); + struct catpt_ipc_msg request = {{0}}, reply; + int ret; + + request.header = msg.val; + reply.size = sizeof(*info); + reply.data = info; + + ret = catpt_dsp_send_msg(cdev, request, &reply); + if (ret) + dev_err(cdev->dev, "get mixer info failed: %d\n", ret); + + return ret; +} + +int catpt_ipc_reset_stream(struct catpt_dev *cdev, u8 stream_hw_id) +{ + union catpt_stream_msg msg = CATPT_STREAM_MSG(RESET_STREAM); + struct catpt_ipc_msg request = {{0}}; + int ret; + + msg.stream_hw_id = stream_hw_id; + request.header = msg.val; + + ret = catpt_dsp_send_msg(cdev, request, NULL); + if (ret) + dev_err(cdev->dev, "reset stream %d failed: %d\n", + stream_hw_id, ret); + + return ret; +} + +int catpt_ipc_pause_stream(struct catpt_dev *cdev, u8 stream_hw_id) +{ + union catpt_stream_msg msg = CATPT_STREAM_MSG(PAUSE_STREAM); + struct catpt_ipc_msg request = {{0}}; + int ret; + + msg.stream_hw_id = stream_hw_id; + request.header = msg.val; + + ret = catpt_dsp_send_msg(cdev, request, NULL); + if (ret) + dev_err(cdev->dev, "pause stream %d failed: %d\n", + stream_hw_id, ret); + + return ret; +} + +int catpt_ipc_resume_stream(struct catpt_dev *cdev, u8 stream_hw_id) +{ + union catpt_stream_msg msg = CATPT_STREAM_MSG(RESUME_STREAM); + struct catpt_ipc_msg request = {{0}}; + int ret; + + msg.stream_hw_id = stream_hw_id; + request.header = msg.val; + + ret = catpt_dsp_send_msg(cdev, request, NULL); + if (ret) + dev_err(cdev->dev, "resume stream %d failed: %d\n", + stream_hw_id, ret); + + return ret; +} + +struct catpt_set_volume_input { + u32 channel; + u32 target_volume; + u64 curve_duration; + u32 curve_type; +} __packed; + +int catpt_ipc_set_volume(struct catpt_dev *cdev, u8 stream_hw_id, + u32 channel, u32 volume, + u32 curve_duration, + enum catpt_audio_curve_type curve_type) +{ + union catpt_stream_msg msg = CATPT_STAGE_MSG(SET_VOLUME); + struct catpt_ipc_msg request; + struct catpt_set_volume_input input; + int ret; + + msg.stream_hw_id = stream_hw_id; + input.channel = channel; + input.target_volume = volume; + input.curve_duration = curve_duration; + input.curve_type = curve_type; + + request.header = msg.val; + request.size = sizeof(input); + request.data = &input; + + ret = catpt_dsp_send_msg(cdev, request, NULL); + if (ret) + dev_err(cdev->dev, "set stream %d volume failed: %d\n", + stream_hw_id, ret); + + return ret; +} + +struct catpt_set_write_pos_input { + u32 new_write_pos; + bool end_of_buffer; + bool low_latency; +} __packed; + +int catpt_ipc_set_write_pos(struct catpt_dev *cdev, u8 stream_hw_id, + u32 pos, bool eob, bool ll) +{ + union catpt_stream_msg msg = CATPT_STAGE_MSG(SET_WRITE_POSITION); + struct catpt_ipc_msg request; + struct catpt_set_write_pos_input input; + int ret; + + msg.stream_hw_id = stream_hw_id; + input.new_write_pos = pos; + input.end_of_buffer = eob; + input.low_latency = ll; + + request.header = msg.val; + request.size = sizeof(input); + request.data = &input; + + ret = catpt_dsp_send_msg(cdev, request, NULL); + if (ret) + dev_err(cdev->dev, "set stream %d write pos failed: %d\n", + stream_hw_id, ret); + + return ret; +} + +int catpt_ipc_mute_loopback(struct catpt_dev *cdev, u8 stream_hw_id, bool mute) +{ + union catpt_stream_msg msg = CATPT_STAGE_MSG(MUTE_LOOPBACK); + struct catpt_ipc_msg request; + int ret; + + msg.stream_hw_id = stream_hw_id; + request.header = msg.val; + request.size = sizeof(mute); + request.data = &mute; + + ret = catpt_dsp_send_msg(cdev, request, NULL); + if (ret) + dev_err(cdev->dev, "mute loopback failed: %d\n", ret); + + return ret; +} diff --git a/sound/soc/intel/catpt/messages.h b/sound/soc/intel/catpt/messages.h new file mode 100644 index 000000000000..c17e948d9f52 --- /dev/null +++ b/sound/soc/intel/catpt/messages.h @@ -0,0 +1,399 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SND_SOC_INTEL_CATPT_MSG_H +#define __SND_SOC_INTEL_CATPT_MSG_H + +struct catpt_dev; + +/* IPC messages base types */ + +enum catpt_reply_status { + CATPT_REPLY_SUCCESS = 0, + CATPT_REPLY_ERROR_INVALID_PARAM = 1, + CATPT_REPLY_UNKNOWN_MESSAGE_TYPE = 2, + CATPT_REPLY_OUT_OF_RESOURCES = 3, + CATPT_REPLY_BUSY = 4, + CATPT_REPLY_PENDING = 5, + CATPT_REPLY_FAILURE = 6, + CATPT_REPLY_INVALID_REQUEST = 7, + CATPT_REPLY_UNINITIALIZED = 8, + CATPT_REPLY_NOT_FOUND = 9, + CATPT_REPLY_SOURCE_NOT_STARTED = 10, +}; + +/* GLOBAL messages */ + +enum catpt_global_msg_type { + CATPT_GLB_GET_FW_VERSION = 0, + CATPT_GLB_ALLOCATE_STREAM = 3, + CATPT_GLB_FREE_STREAM = 4, + CATPT_GLB_STREAM_MESSAGE = 6, + CATPT_GLB_REQUEST_CORE_DUMP = 7, + CATPT_GLB_SET_DEVICE_FORMATS = 10, + CATPT_GLB_ENTER_DX_STATE = 12, + CATPT_GLB_GET_MIXER_STREAM_INFO = 13, +}; + +union catpt_global_msg { + u32 val; + struct { + u32 status:5; + u32 context:19; /* stream or module specific */ + u32 global_msg_type:5; + u32 fw_ready:1; + u32 done:1; + u32 busy:1; + }; +} __packed; + +#define CATPT_MSG(hdr) { .val = hdr } +#define CATPT_GLOBAL_MSG(msg_type) \ + { .global_msg_type = CATPT_GLB_##msg_type } + +#define BUILD_HASH_SIZE 40 + +struct catpt_fw_version { + u8 build; + u8 minor; + u8 major; + u8 type; + u8 build_hash[BUILD_HASH_SIZE]; + u32 log_providers_hash; +} __packed; + +int catpt_ipc_get_fw_version(struct catpt_dev *cdev, + struct catpt_fw_version *version); + +enum catpt_pin_id { + CATPT_PIN_ID_SYSTEM = 0, + CATPT_PIN_ID_REFERENCE = 1, + CATPT_PIN_ID_CAPTURE1 = 2, + CATPT_PIN_ID_CAPTURE2 = 3, + CATPT_PIN_ID_OFFLOAD1 = 4, + CATPT_PIN_ID_OFFLOAD2 = 5, + CATPT_PIN_ID_MIXER = 7, + CATPT_PIN_ID_BLUETOOTH_CAPTURE = 8, + CATPT_PIN_ID_BLUETOOTH_RENDER = 9, +}; + +enum catpt_path_id { + CATPT_PATH_SSP0_OUT = 0, + CATPT_PATH_SSP0_IN = 1, + CATPT_PATH_SSP1_OUT = 2, + CATPT_PATH_SSP1_IN = 3, + /* duplicated audio in capture path */ + CATPT_PATH_SSP0_IN_DUP = 4, +}; + +enum catpt_stream_type { + CATPT_STRM_TYPE_RENDER = 0, /* offload */ + CATPT_STRM_TYPE_SYSTEM = 1, + CATPT_STRM_TYPE_CAPTURE = 2, + CATPT_STRM_TYPE_LOOPBACK = 3, + CATPT_STRM_TYPE_BLUETOOTH_RENDER = 4, + CATPT_STRM_TYPE_BLUETOOTH_CAPTURE = 5, +}; + +enum catpt_format_id { + CATPT_FORMAT_PCM = 0, + CATPT_FORMAT_MP3 = 1, + CATPT_FORMAT_AAC = 2, + CATPT_FORMAT_WMA = 3, +}; + +enum catpt_channel_index { + CATPT_CHANNEL_LEFT = 0x0, + CATPT_CHANNEL_CENTER = 0x1, + CATPT_CHANNEL_RIGHT = 0x2, + CATPT_CHANNEL_LEFT_SURROUND = 0x3, + CATPT_CHANNEL_CENTER_SURROUND = 0x3, + CATPT_CHANNEL_RIGHT_SURROUND = 0x4, + CATPT_CHANNEL_LFE = 0x7, + CATPT_CHANNEL_INVALID = 0xF, +}; + +enum catpt_channel_config { + CATPT_CHANNEL_CONFIG_MONO = 0, /* One channel only */ + CATPT_CHANNEL_CONFIG_STEREO = 1, /* L & R */ + CATPT_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only */ + CATPT_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only */ + CATPT_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only */ + CATPT_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only */ + CATPT_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only */ + CATPT_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs */ + CATPT_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE */ + CATPT_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two */ + CATPT_CHANNEL_CONFIG_INVALID = 10, +}; + +enum catpt_interleaving_style { + CATPT_INTERLEAVING_PER_CHANNEL = 0, + CATPT_INTERLEAVING_PER_SAMPLE = 1, +}; + +struct catpt_audio_format { + u32 sample_rate; + u32 bit_depth; + u32 channel_map; + u32 channel_config; + u32 interleaving; + u8 num_channels; + u8 valid_bit_depth; + u8 reserved[2]; +} __packed; + +struct catpt_ring_info { + u32 page_table_addr; + u32 num_pages; + u32 size; + u32 offset; + u32 ring_first_page_pfn; +} __packed; + +#define CATPT_MODULE_COUNT (CATPT_MODID_LAST + 1) + +enum catpt_module_id { + CATPT_MODID_BASE_FW = 0x0, + CATPT_MODID_MP3 = 0x1, + CATPT_MODID_AAC_5_1 = 0x2, + CATPT_MODID_AAC_2_0 = 0x3, + CATPT_MODID_SRC = 0x4, + CATPT_MODID_WAVES = 0x5, + CATPT_MODID_DOLBY = 0x6, + CATPT_MODID_BOOST = 0x7, + CATPT_MODID_LPAL = 0x8, + CATPT_MODID_DTS = 0x9, + CATPT_MODID_PCM_CAPTURE = 0xA, + CATPT_MODID_PCM_SYSTEM = 0xB, + CATPT_MODID_PCM_REFERENCE = 0xC, + CATPT_MODID_PCM = 0xD, /* offload */ + CATPT_MODID_BLUETOOTH_RENDER = 0xE, + CATPT_MODID_BLUETOOTH_CAPTURE = 0xF, + CATPT_MODID_LAST = CATPT_MODID_BLUETOOTH_CAPTURE, +}; + +struct catpt_module_entry { + u32 module_id; + u32 entry_point; +} __packed; + +struct catpt_module_map { + u8 num_entries; + struct catpt_module_entry entries[]; +} __packed; + +struct catpt_memory_info { + u32 offset; + u32 size; +} __packed; + +#define CATPT_CHANNELS_MAX 4 +#define CATPT_ALL_CHANNELS_MASK UINT_MAX + +struct catpt_stream_info { + u32 stream_hw_id; + u32 reserved; + u32 read_pos_regaddr; + u32 pres_pos_regaddr; + u32 peak_meter_regaddr[CATPT_CHANNELS_MAX]; + u32 volume_regaddr[CATPT_CHANNELS_MAX]; +} __packed; + +int catpt_ipc_alloc_stream(struct catpt_dev *cdev, + enum catpt_path_id path_id, + enum catpt_stream_type type, + struct catpt_audio_format *afmt, + struct catpt_ring_info *rinfo, + u8 num_modules, + struct catpt_module_entry *modules, + struct resource *persistent, + struct resource *scratch, + struct catpt_stream_info *sinfo); +int catpt_ipc_free_stream(struct catpt_dev *cdev, u8 stream_hw_id); + +enum catpt_ssp_iface { + CATPT_SSP_IFACE_0 = 0, + CATPT_SSP_IFACE_1 = 1, + CATPT_SSP_COUNT, +}; + +enum catpt_mclk_frequency { + CATPT_MCLK_OFF = 0, + CATPT_MCLK_FREQ_6_MHZ = 1, + CATPT_MCLK_FREQ_21_MHZ = 2, + CATPT_MCLK_FREQ_24_MHZ = 3, +}; + +enum catpt_ssp_mode { + CATPT_SSP_MODE_I2S_CONSUMER = 0, + CATPT_SSP_MODE_I2S_PROVIDER = 1, + CATPT_SSP_MODE_TDM_PROVIDER = 2, +}; + +struct catpt_ssp_device_format { + u32 iface; + u32 mclk; + u32 mode; + u16 clock_divider; + u8 channels; +} __packed; + +int catpt_ipc_set_device_format(struct catpt_dev *cdev, + struct catpt_ssp_device_format *devfmt); + +enum catpt_dx_state { + CATPT_DX_STATE_D3 = 3, +}; + +enum catpt_dx_type { + CATPT_DX_TYPE_FW_IMAGE = 0, + CATPT_DX_TYPE_MEMORY_DUMP = 1, +}; + +struct catpt_save_meminfo { + u32 offset; + u32 size; + u32 source; +} __packed; + +#define SAVE_MEMINFO_MAX 14 + +struct catpt_dx_context { + u32 num_meminfo; + struct catpt_save_meminfo meminfo[SAVE_MEMINFO_MAX]; +} __packed; + +int catpt_ipc_enter_dxstate(struct catpt_dev *cdev, enum catpt_dx_state state, + struct catpt_dx_context *context); + +struct catpt_mixer_stream_info { + u32 mixer_hw_id; + u32 peak_meter_regaddr[CATPT_CHANNELS_MAX]; + u32 volume_regaddr[CATPT_CHANNELS_MAX]; +} __packed; + +int catpt_ipc_get_mixer_stream_info(struct catpt_dev *cdev, + struct catpt_mixer_stream_info *info); + +/* STREAM messages */ + +enum catpt_stream_msg_type { + CATPT_STRM_RESET_STREAM = 0, + CATPT_STRM_PAUSE_STREAM = 1, + CATPT_STRM_RESUME_STREAM = 2, + CATPT_STRM_STAGE_MESSAGE = 3, + CATPT_STRM_NOTIFICATION = 4, +}; + +enum catpt_stage_action { + CATPT_STG_SET_VOLUME = 1, + CATPT_STG_SET_WRITE_POSITION = 2, + CATPT_STG_MUTE_LOOPBACK = 3, +}; + +union catpt_stream_msg { + u32 val; + struct { + u32 status:5; + u32 reserved:7; + u32 stage_action:4; + u32 stream_hw_id:4; + u32 stream_msg_type:4; + u32 global_msg_type:5; + u32 fw_ready:1; + u32 done:1; + u32 busy:1; + }; +} __packed; + +#define CATPT_STREAM_MSG(msg_type) \ +{ \ + .stream_msg_type = CATPT_STRM_##msg_type, \ + .global_msg_type = CATPT_GLB_STREAM_MESSAGE } +#define CATPT_STAGE_MSG(msg_type) \ +{ \ + .stage_action = CATPT_STG_##msg_type, \ + .stream_msg_type = CATPT_STRM_STAGE_MESSAGE, \ + .global_msg_type = CATPT_GLB_STREAM_MESSAGE } + +int catpt_ipc_reset_stream(struct catpt_dev *cdev, u8 stream_hw_id); +int catpt_ipc_pause_stream(struct catpt_dev *cdev, u8 stream_hw_id); +int catpt_ipc_resume_stream(struct catpt_dev *cdev, u8 stream_hw_id); + +/* STREAM messages - STAGE subtype */ + +enum catpt_audio_curve_type { + CATPT_AUDIO_CURVE_NONE = 0, + CATPT_AUDIO_CURVE_WINDOWS_FADE = 1, +}; + +int catpt_ipc_set_volume(struct catpt_dev *cdev, u8 stream_hw_id, + u32 channel, u32 volume, + u32 curve_duration, + enum catpt_audio_curve_type curve_type); + +int catpt_ipc_set_write_pos(struct catpt_dev *cdev, u8 stream_hw_id, + u32 pos, bool eob, bool ll); + +int catpt_ipc_mute_loopback(struct catpt_dev *cdev, u8 stream_hw_id, bool mute); + +/* NOTIFICATION messages */ + +enum catpt_notify_reason { + CATPT_NOTIFY_POSITION_CHANGED = 0, + CATPT_NOTIFY_GLITCH_OCCURRED = 1, +}; + +union catpt_notify_msg { + u32 val; + struct { + u32 mailbox_address:29; + u32 fw_ready:1; + u32 done:1; + u32 busy:1; + }; + struct { + u32 status:5; + u32 reserved:7; + u32 notify_reason:4; + u32 stream_hw_id:4; + u32 stream_msg_type:4; + u32 global_msg_type:5; + u32 hdr:3; /* fw_ready, done, busy */ + }; +} __packed; + +#define FW_INFO_SIZE_MAX 100 + +struct catpt_fw_ready { + u32 inbox_offset; + u32 outbox_offset; + u32 inbox_size; + u32 outbox_size; + u32 fw_info_size; + char fw_info[FW_INFO_SIZE_MAX]; +} __packed; + +struct catpt_notify_position { + u32 stream_position; + u32 fw_cycle_count; +} __packed; + +enum catpt_glitch_type { + CATPT_GLITCH_UNDERRUN = 1, + CATPT_GLITCH_DECODER_ERROR = 2, + CATPT_GLITCH_DOUBLED_WRITE_POS = 3, +}; + +struct catpt_notify_glitch { + u32 type; + u64 presentation_pos; + u32 write_pos; +} __packed; + +#endif diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c new file mode 100644 index 000000000000..30ca5416c9a3 --- /dev/null +++ b/sound/soc/intel/catpt/pcm.c @@ -0,0 +1,1195 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <uapi/sound/tlv.h> +#include "core.h" +#include "messages.h" + +struct catpt_stream_template { + enum catpt_path_id path_id; + enum catpt_stream_type type; + u32 persistent_size; + u8 num_entries; + struct catpt_module_entry entries[]; +}; + +static struct catpt_stream_template system_pb = { + .path_id = CATPT_PATH_SSP0_OUT, + .type = CATPT_STRM_TYPE_SYSTEM, + .num_entries = 1, + .entries = {{ CATPT_MODID_PCM_SYSTEM, 0 }}, +}; + +static struct catpt_stream_template system_cp = { + .path_id = CATPT_PATH_SSP0_IN, + .type = CATPT_STRM_TYPE_CAPTURE, + .num_entries = 1, + .entries = {{ CATPT_MODID_PCM_CAPTURE, 0 }}, +}; + +static struct catpt_stream_template offload_pb = { + .path_id = CATPT_PATH_SSP0_OUT, + .type = CATPT_STRM_TYPE_RENDER, + .num_entries = 1, + .entries = {{ CATPT_MODID_PCM, 0 }}, +}; + +static struct catpt_stream_template loopback_cp = { + .path_id = CATPT_PATH_SSP0_OUT, + .type = CATPT_STRM_TYPE_LOOPBACK, + .num_entries = 1, + .entries = {{ CATPT_MODID_PCM_REFERENCE, 0 }}, +}; + +static struct catpt_stream_template bluetooth_pb = { + .path_id = CATPT_PATH_SSP1_OUT, + .type = CATPT_STRM_TYPE_BLUETOOTH_RENDER, + .num_entries = 1, + .entries = {{ CATPT_MODID_BLUETOOTH_RENDER, 0 }}, +}; + +static struct catpt_stream_template bluetooth_cp = { + .path_id = CATPT_PATH_SSP1_IN, + .type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE, + .num_entries = 1, + .entries = {{ CATPT_MODID_BLUETOOTH_CAPTURE, 0 }}, +}; + +static struct catpt_stream_template *catpt_topology[] = { + [CATPT_STRM_TYPE_RENDER] = &offload_pb, + [CATPT_STRM_TYPE_SYSTEM] = &system_pb, + [CATPT_STRM_TYPE_CAPTURE] = &system_cp, + [CATPT_STRM_TYPE_LOOPBACK] = &loopback_cp, + [CATPT_STRM_TYPE_BLUETOOTH_RENDER] = &bluetooth_pb, + [CATPT_STRM_TYPE_BLUETOOTH_CAPTURE] = &bluetooth_cp, +}; + +static struct catpt_stream_template * +catpt_get_stream_template(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtm = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0); + enum catpt_stream_type type; + + type = cpu_dai->driver->id; + + /* account for capture in bidirectional dais */ + switch (type) { + case CATPT_STRM_TYPE_SYSTEM: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + type = CATPT_STRM_TYPE_CAPTURE; + break; + case CATPT_STRM_TYPE_BLUETOOTH_RENDER: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE; + break; + default: + break; + } + + return catpt_topology[type]; +} + +struct catpt_stream_runtime * +catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id) +{ + struct catpt_stream_runtime *pos, *result = NULL; + + spin_lock(&cdev->list_lock); + list_for_each_entry(pos, &cdev->stream_list, node) { + if (pos->info.stream_hw_id == stream_hw_id) { + result = pos; + break; + } + } + + spin_unlock(&cdev->list_lock); + return result; +} + +static u32 catpt_stream_read_position(struct catpt_dev *cdev, + struct catpt_stream_runtime *stream) +{ + u32 pos; + + memcpy_fromio(&pos, cdev->lpe_ba + stream->info.read_pos_regaddr, + sizeof(pos)); + return pos; +} + +static u32 catpt_stream_volume(struct catpt_dev *cdev, + struct catpt_stream_runtime *stream, u32 channel) +{ + u32 volume, offset; + + if (channel >= CATPT_CHANNELS_MAX) + channel = 0; + + offset = stream->info.volume_regaddr[channel]; + memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume)); + return volume; +} + +static u32 catpt_mixer_volume(struct catpt_dev *cdev, + struct catpt_mixer_stream_info *info, u32 channel) +{ + u32 volume, offset; + + if (channel >= CATPT_CHANNELS_MAX) + channel = 0; + + offset = info->volume_regaddr[channel]; + memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume)); + return volume; +} + +static void catpt_arrange_page_table(struct snd_pcm_substream *substream, + struct snd_dma_buffer *pgtbl) +{ + struct snd_pcm_runtime *rtm = substream->runtime; + struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream); + int i, pages; + + pages = snd_sgbuf_aligned_pages(rtm->dma_bytes); + + for (i = 0; i < pages; i++) { + u32 pfn, offset; + u32 *page_table; + + pfn = PFN_DOWN(snd_sgbuf_get_addr(databuf, i * PAGE_SIZE)); + /* incrementing by 2 on even and 3 on odd */ + offset = ((i << 2) + i) >> 1; + page_table = (u32 *)(pgtbl->area + offset); + + if (i & 1) + *page_table |= (pfn << 4); + else + *page_table |= pfn; + } +} + +static u32 catpt_get_channel_map(enum catpt_channel_config config) +{ + switch (config) { + case CATPT_CHANNEL_CONFIG_MONO: + return GENMASK(31, 4) | CATPT_CHANNEL_CENTER; + + case CATPT_CHANNEL_CONFIG_STEREO: + return GENMASK(31, 8) | CATPT_CHANNEL_LEFT + | (CATPT_CHANNEL_RIGHT << 4); + + case CATPT_CHANNEL_CONFIG_2_POINT_1: + return GENMASK(31, 12) | CATPT_CHANNEL_LEFT + | (CATPT_CHANNEL_RIGHT << 4) + | (CATPT_CHANNEL_LFE << 8); + + case CATPT_CHANNEL_CONFIG_3_POINT_0: + return GENMASK(31, 12) | CATPT_CHANNEL_LEFT + | (CATPT_CHANNEL_CENTER << 4) + | (CATPT_CHANNEL_RIGHT << 8); + + case CATPT_CHANNEL_CONFIG_3_POINT_1: + return GENMASK(31, 16) | CATPT_CHANNEL_LEFT + | (CATPT_CHANNEL_CENTER << 4) + | (CATPT_CHANNEL_RIGHT << 8) + | (CATPT_CHANNEL_LFE << 12); + + case CATPT_CHANNEL_CONFIG_QUATRO: + return GENMASK(31, 16) | CATPT_CHANNEL_LEFT + | (CATPT_CHANNEL_RIGHT << 4) + | (CATPT_CHANNEL_LEFT_SURROUND << 8) + | (CATPT_CHANNEL_RIGHT_SURROUND << 12); + + case CATPT_CHANNEL_CONFIG_4_POINT_0: + return GENMASK(31, 16) | CATPT_CHANNEL_LEFT + | (CATPT_CHANNEL_CENTER << 4) + | (CATPT_CHANNEL_RIGHT << 8) + | (CATPT_CHANNEL_CENTER_SURROUND << 12); + + case CATPT_CHANNEL_CONFIG_5_POINT_0: + return GENMASK(31, 20) | CATPT_CHANNEL_LEFT + | (CATPT_CHANNEL_CENTER << 4) + | (CATPT_CHANNEL_RIGHT << 8) + | (CATPT_CHANNEL_LEFT_SURROUND << 12) + | (CATPT_CHANNEL_RIGHT_SURROUND << 16); + + case CATPT_CHANNEL_CONFIG_5_POINT_1: + return GENMASK(31, 24) | CATPT_CHANNEL_CENTER + | (CATPT_CHANNEL_LEFT << 4) + | (CATPT_CHANNEL_RIGHT << 8) + | (CATPT_CHANNEL_LEFT_SURROUND << 12) + | (CATPT_CHANNEL_RIGHT_SURROUND << 16) + | (CATPT_CHANNEL_LFE << 20); + + case CATPT_CHANNEL_CONFIG_DUAL_MONO: + return GENMASK(31, 8) | CATPT_CHANNEL_LEFT + | (CATPT_CHANNEL_LEFT << 4); + + default: + return U32_MAX; + } +} + +static enum catpt_channel_config catpt_get_channel_config(u32 num_channels) +{ + switch (num_channels) { + case 6: + return CATPT_CHANNEL_CONFIG_5_POINT_1; + case 5: + return CATPT_CHANNEL_CONFIG_5_POINT_0; + case 4: + return CATPT_CHANNEL_CONFIG_QUATRO; + case 3: + return CATPT_CHANNEL_CONFIG_2_POINT_1; + case 1: + return CATPT_CHANNEL_CONFIG_MONO; + case 2: + default: + return CATPT_CHANNEL_CONFIG_STEREO; + } +} + +static int catpt_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct catpt_stream_template *template; + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + struct resource *res; + int ret; + + template = catpt_get_stream_template(substream); + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, cdev->dev, PAGE_SIZE, + &stream->pgtbl); + if (ret) + goto err_pgtbl; + + res = catpt_request_region(&cdev->dram, template->persistent_size); + if (!res) { + ret = -EBUSY; + goto err_request; + } + + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); + + stream->template = template; + stream->persistent = res; + stream->substream = substream; + INIT_LIST_HEAD(&stream->node); + snd_soc_dai_set_dma_data(dai, substream, stream); + + spin_lock(&cdev->list_lock); + list_add_tail(&stream->node, &cdev->stream_list); + spin_unlock(&cdev->list_lock); + + return 0; + +err_request: + snd_dma_free_pages(&stream->pgtbl); +err_pgtbl: + kfree(stream); + return ret; +} + +static void catpt_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + + stream = snd_soc_dai_get_dma_data(dai, substream); + + spin_lock(&cdev->list_lock); + list_del(&stream->node); + spin_unlock(&cdev->list_lock); + + release_resource(stream->persistent); + kfree(stream->persistent); + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); + + snd_dma_free_pages(&stream->pgtbl); + kfree(stream); + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol); + +static int catpt_dai_apply_usettings(struct snd_soc_dai *dai, + struct catpt_stream_runtime *stream) +{ + struct snd_soc_component *component = dai->component; + struct snd_kcontrol *pos; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + const char *name; + int ret; + u32 id = stream->info.stream_hw_id; + + /* only selected streams have individual controls */ + switch (id) { + case CATPT_PIN_ID_OFFLOAD1: + name = "Media0 Playback Volume"; + break; + case CATPT_PIN_ID_OFFLOAD2: + name = "Media1 Playback Volume"; + break; + case CATPT_PIN_ID_CAPTURE1: + name = "Mic Capture Volume"; + break; + case CATPT_PIN_ID_REFERENCE: + name = "Loopback Mute"; + break; + default: + return 0; + } + + list_for_each_entry(pos, &component->card->snd_card->controls, list) { + if (pos->private_data == component && + !strncmp(name, pos->id.name, sizeof(pos->id.name))) + break; + } + if (list_entry_is_head(pos, &component->card->snd_card->controls, list)) + return -ENOENT; + + if (stream->template->type != CATPT_STRM_TYPE_LOOPBACK) + return catpt_set_dspvol(cdev, id, (long *)pos->private_value); + ret = catpt_ipc_mute_loopback(cdev, id, *(bool *)pos->private_value); + if (ret) + return CATPT_IPC_ERROR(ret); + return 0; +} + +static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *rtm = substream->runtime; + struct snd_dma_buffer *dmab; + struct catpt_stream_runtime *stream; + struct catpt_audio_format afmt; + struct catpt_ring_info rinfo; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + int ret; + + stream = snd_soc_dai_get_dma_data(dai, substream); + if (stream->allocated) + return 0; + + memset(&afmt, 0, sizeof(afmt)); + afmt.sample_rate = params_rate(params); + afmt.bit_depth = params_physical_width(params); + afmt.valid_bit_depth = params_width(params); + afmt.num_channels = params_channels(params); + afmt.channel_config = catpt_get_channel_config(afmt.num_channels); + afmt.channel_map = catpt_get_channel_map(afmt.channel_config); + afmt.interleaving = CATPT_INTERLEAVING_PER_CHANNEL; + + dmab = snd_pcm_get_dma_buf(substream); + catpt_arrange_page_table(substream, &stream->pgtbl); + + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.page_table_addr = stream->pgtbl.addr; + rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE); + rinfo.size = rtm->dma_bytes; + rinfo.offset = 0; + rinfo.ring_first_page_pfn = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0)); + + ret = catpt_ipc_alloc_stream(cdev, stream->template->path_id, + stream->template->type, + &afmt, &rinfo, + stream->template->num_entries, + stream->template->entries, + stream->persistent, + cdev->scratch, + &stream->info); + if (ret) + return CATPT_IPC_ERROR(ret); + + ret = catpt_dai_apply_usettings(dai, stream); + if (ret) + return ret; + + stream->allocated = true; + return 0; +} + +static int catpt_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + + stream = snd_soc_dai_get_dma_data(dai, substream); + if (!stream->allocated) + return 0; + + catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id); + catpt_ipc_free_stream(cdev, stream->info.stream_hw_id); + + stream->allocated = false; + return 0; +} + +static int catpt_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + int ret; + + stream = snd_soc_dai_get_dma_data(dai, substream); + if (stream->prepared) + return 0; + + ret = catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id); + if (ret) + return CATPT_IPC_ERROR(ret); + + ret = catpt_ipc_pause_stream(cdev, stream->info.stream_hw_id); + if (ret) + return CATPT_IPC_ERROR(ret); + + stream->prepared = true; + return 0; +} + +static int catpt_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + snd_pcm_uframes_t pos; + int ret; + + stream = snd_soc_dai_get_dma_data(dai, substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* only offload is set_write_pos driven */ + if (stream->template->type != CATPT_STRM_TYPE_RENDER) + goto resume_stream; + + pos = frames_to_bytes(runtime, runtime->start_threshold); + /* + * Dsp operates on buffer halves, thus max 2x set_write_pos + * (entire buffer filled) prior to stream start. + */ + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id, + pos, false, false); + if (ret) + return CATPT_IPC_ERROR(ret); + fallthrough; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + resume_stream: + catpt_dsp_update_lpclock(cdev); + ret = catpt_ipc_resume_stream(cdev, stream->info.stream_hw_id); + if (ret) + return CATPT_IPC_ERROR(ret); + break; + + case SNDRV_PCM_TRIGGER_STOP: + stream->prepared = false; + fallthrough; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = catpt_ipc_pause_stream(cdev, stream->info.stream_hw_id); + catpt_dsp_update_lpclock(cdev); + if (ret) + return CATPT_IPC_ERROR(ret); + break; + + default: + break; + } + + return 0; +} + +void catpt_stream_update_position(struct catpt_dev *cdev, + struct catpt_stream_runtime *stream, + struct catpt_notify_position *pos) +{ + struct snd_pcm_substream *substream = stream->substream; + struct snd_pcm_runtime *r = substream->runtime; + snd_pcm_uframes_t dsppos, newpos; + int ret; + + dsppos = bytes_to_frames(r, pos->stream_position); + + if (!stream->prepared) + goto exit; + /* only offload is set_write_pos driven */ + if (stream->template->type != CATPT_STRM_TYPE_RENDER) + goto exit; + + if (dsppos >= r->buffer_size / 2) + newpos = r->buffer_size / 2; + else + newpos = 0; + /* + * Dsp operates on buffer halves, thus on every notify position + * (buffer half consumed) update wp to allow stream progression. + */ + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id, + frames_to_bytes(r, newpos), + false, false); + if (ret) { + dev_err(cdev->dev, "update position for stream %d failed: %d\n", + stream->info.stream_hw_id, ret); + return; + } +exit: + snd_pcm_period_elapsed(substream); +} + +/* 200 ms for 2 32-bit channels at 48kHz (native format) */ +#define CATPT_BUFFER_MAX_SIZE 76800 +#define CATPT_PCM_PERIODS_MAX 4 +#define CATPT_PCM_PERIODS_MIN 2 + +static const struct snd_pcm_hardware catpt_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = CATPT_BUFFER_MAX_SIZE / CATPT_PCM_PERIODS_MIN, + .periods_min = CATPT_PCM_PERIODS_MIN, + .periods_max = CATPT_PCM_PERIODS_MAX, + .buffer_bytes_max = CATPT_BUFFER_MAX_SIZE, +}; + +static int catpt_component_pcm_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtm) +{ + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + + snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG, + cdev->dev, + catpt_pcm_hardware.buffer_bytes_max, + catpt_pcm_hardware.buffer_bytes_max); + + return 0; +} + +static int catpt_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtm = asoc_substream_to_rtd(substream); + + if (!rtm->dai_link->no_pcm) + snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware); + return 0; +} + +static snd_pcm_uframes_t +catpt_component_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtm = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + u32 pos; + + if (rtm->dai_link->no_pcm) + return 0; + + stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + pos = catpt_stream_read_position(cdev, stream); + + return bytes_to_frames(substream->runtime, pos); +} + +static const struct snd_soc_dai_ops catpt_fe_dai_ops = { + .startup = catpt_dai_startup, + .shutdown = catpt_dai_shutdown, + .hw_params = catpt_dai_hw_params, + .hw_free = catpt_dai_hw_free, + .prepare = catpt_dai_prepare, + .trigger = catpt_dai_trigger, +}; + +static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm, + struct snd_soc_dai *dai) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtm, 0); + struct catpt_ssp_device_format devfmt; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + int ret; + + devfmt.iface = dai->driver->id; + devfmt.channels = codec_dai->driver->capture.channels_max; + + switch (devfmt.iface) { + case CATPT_SSP_IFACE_0: + devfmt.mclk = CATPT_MCLK_FREQ_24_MHZ; + + switch (devfmt.channels) { + case 4: + devfmt.mode = CATPT_SSP_MODE_TDM_PROVIDER; + devfmt.clock_divider = 4; + break; + case 2: + default: + devfmt.mode = CATPT_SSP_MODE_I2S_PROVIDER; + devfmt.clock_divider = 9; + break; + } + break; + + case CATPT_SSP_IFACE_1: + devfmt.mclk = CATPT_MCLK_OFF; + devfmt.mode = CATPT_SSP_MODE_I2S_CONSUMER; + devfmt.clock_divider = 0; + break; + } + + /* see if this is a new configuration */ + if (!memcmp(&cdev->devfmt[devfmt.iface], &devfmt, sizeof(devfmt))) + return 0; + + ret = pm_runtime_resume_and_get(cdev->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + ret = catpt_ipc_set_device_format(cdev, &devfmt); + + pm_runtime_mark_last_busy(cdev->dev); + pm_runtime_put_autosuspend(cdev->dev); + + if (ret) + return CATPT_IPC_ERROR(ret); + + /* store device format set for given SSP */ + memcpy(&cdev->devfmt[devfmt.iface], &devfmt, sizeof(devfmt)); + return 0; +} + +static struct snd_soc_dai_driver dai_drivers[] = { +/* FE DAIs */ +{ + .name = "System Pin", + .id = CATPT_STRM_TYPE_SYSTEM, + .ops = &catpt_fe_dai_ops, + .playback = { + .stream_name = "System Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .stream_name = "Analog Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Offload0 Pin", + .id = CATPT_STRM_TYPE_RENDER, + .ops = &catpt_fe_dai_ops, + .playback = { + .stream_name = "Offload0 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Offload1 Pin", + .id = CATPT_STRM_TYPE_RENDER, + .ops = &catpt_fe_dai_ops, + .playback = { + .stream_name = "Offload1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Loopback Pin", + .id = CATPT_STRM_TYPE_LOOPBACK, + .ops = &catpt_fe_dai_ops, + .capture = { + .stream_name = "Loopback Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Bluetooth Pin", + .id = CATPT_STRM_TYPE_BLUETOOTH_RENDER, + .ops = &catpt_fe_dai_ops, + .playback = { + .stream_name = "Bluetooth Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Bluetooth Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +/* BE DAIs */ +{ + .name = "ssp0-port", + .id = CATPT_SSP_IFACE_0, + .pcm_new = catpt_dai_pcm_new, + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "ssp1-port", + .id = CATPT_SSP_IFACE_1, + .pcm_new = catpt_dai_pcm_new, + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +}; + +#define DSP_VOLUME_MAX S32_MAX /* 0db */ +#define DSP_VOLUME_STEP_MAX 30 + +static u32 ctlvol_to_dspvol(u32 value) +{ + if (value > DSP_VOLUME_STEP_MAX) + value = 0; + return DSP_VOLUME_MAX >> (DSP_VOLUME_STEP_MAX - value); +} + +static u32 dspvol_to_ctlvol(u32 volume) +{ + if (volume > DSP_VOLUME_MAX) + return DSP_VOLUME_STEP_MAX; + return volume ? __fls(volume) : 0; +} + +static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol) +{ + u32 dspvol; + int ret, i; + + for (i = 1; i < CATPT_CHANNELS_MAX; i++) + if (ctlvol[i] != ctlvol[0]) + break; + + if (i == CATPT_CHANNELS_MAX) { + dspvol = ctlvol_to_dspvol(ctlvol[0]); + + ret = catpt_ipc_set_volume(cdev, stream_id, + CATPT_ALL_CHANNELS_MASK, dspvol, + 0, CATPT_AUDIO_CURVE_NONE); + } else { + for (i = 0; i < CATPT_CHANNELS_MAX; i++) { + dspvol = ctlvol_to_dspvol(ctlvol[i]); + + ret = catpt_ipc_set_volume(cdev, stream_id, + i, dspvol, + 0, CATPT_AUDIO_CURVE_NONE); + if (ret) + break; + } + } + + if (ret) + return CATPT_IPC_ERROR(ret); + return 0; +} + +static int catpt_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = CATPT_CHANNELS_MAX; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = DSP_VOLUME_STEP_MAX; + return 0; +} + +static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + u32 dspvol; + int ret; + int i; + + ret = pm_runtime_resume_and_get(cdev->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + for (i = 0; i < CATPT_CHANNELS_MAX; i++) { + dspvol = catpt_mixer_volume(cdev, &cdev->mixer, i); + ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol); + } + + pm_runtime_mark_last_busy(cdev->dev); + pm_runtime_put_autosuspend(cdev->dev); + + return 0; +} + +static int catpt_mixer_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + int ret; + + ret = pm_runtime_resume_and_get(cdev->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + ret = catpt_set_dspvol(cdev, cdev->mixer.mixer_hw_id, + ucontrol->value.integer.value); + + pm_runtime_mark_last_busy(cdev->dev); + pm_runtime_put_autosuspend(cdev->dev); + + return ret; +} + +static int catpt_stream_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + enum catpt_pin_id pin_id) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + long *ctlvol = (long *)kcontrol->private_value; + u32 dspvol; + int ret; + int i; + + stream = catpt_stream_find(cdev, pin_id); + if (!stream) { + for (i = 0; i < CATPT_CHANNELS_MAX; i++) + ucontrol->value.integer.value[i] = ctlvol[i]; + return 0; + } + + ret = pm_runtime_resume_and_get(cdev->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + for (i = 0; i < CATPT_CHANNELS_MAX; i++) { + dspvol = catpt_stream_volume(cdev, stream, i); + ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol); + } + + pm_runtime_mark_last_busy(cdev->dev); + pm_runtime_put_autosuspend(cdev->dev); + + return 0; +} + +static int catpt_stream_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + enum catpt_pin_id pin_id) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + long *ctlvol = (long *)kcontrol->private_value; + int ret, i; + + stream = catpt_stream_find(cdev, pin_id); + if (!stream) { + for (i = 0; i < CATPT_CHANNELS_MAX; i++) + ctlvol[i] = ucontrol->value.integer.value[i]; + return 0; + } + + ret = pm_runtime_resume_and_get(cdev->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + ret = catpt_set_dspvol(cdev, stream->info.stream_hw_id, + ucontrol->value.integer.value); + + pm_runtime_mark_last_busy(cdev->dev); + pm_runtime_put_autosuspend(cdev->dev); + + if (ret) + return ret; + + for (i = 0; i < CATPT_CHANNELS_MAX; i++) + ctlvol[i] = ucontrol->value.integer.value[i]; + return 0; +} + +static int catpt_offload1_volume_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD1); +} + +static int catpt_offload1_volume_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD1); +} + +static int catpt_offload2_volume_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD2); +} + +static int catpt_offload2_volume_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD2); +} + +static int catpt_capture_volume_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_CAPTURE1); +} + +static int catpt_capture_volume_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_CAPTURE1); +} + +static int catpt_loopback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = *(bool *)kcontrol->private_value; + return 0; +} + +static int catpt_loopback_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + bool mute; + int ret; + + mute = (bool)ucontrol->value.integer.value[0]; + stream = catpt_stream_find(cdev, CATPT_PIN_ID_REFERENCE); + if (!stream) { + *(bool *)kcontrol->private_value = mute; + return 0; + } + + ret = pm_runtime_resume_and_get(cdev->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + ret = catpt_ipc_mute_loopback(cdev, stream->info.stream_hw_id, mute); + + pm_runtime_mark_last_busy(cdev->dev); + pm_runtime_put_autosuspend(cdev->dev); + + if (ret) + return CATPT_IPC_ERROR(ret); + + *(bool *)kcontrol->private_value = mute; + return 0; +} + +static int catpt_waves_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int catpt_waves_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int catpt_waves_param_get(struct snd_kcontrol *kcontrol, + unsigned int __user *bytes, + unsigned int size) +{ + return 0; +} + +static int catpt_waves_param_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *bytes, + unsigned int size) +{ + return 0; +} + +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1); + +#define CATPT_VOLUME_CTL(kname, sname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = (kname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = catpt_volume_info, \ + .get = catpt_##sname##_volume_get, \ + .put = catpt_##sname##_volume_put, \ + .tlv.p = catpt_volume_tlv, \ + .private_value = (unsigned long) \ + &(long[CATPT_CHANNELS_MAX]) {0} } + +static const struct snd_kcontrol_new component_kcontrols[] = { +/* Master volume (mixer stream) */ +CATPT_VOLUME_CTL("Master Playback Volume", mixer), +/* Individual volume controls for offload and capture */ +CATPT_VOLUME_CTL("Media0 Playback Volume", offload1), +CATPT_VOLUME_CTL("Media1 Playback Volume", offload2), +CATPT_VOLUME_CTL("Mic Capture Volume", capture), +SOC_SINGLE_BOOL_EXT("Loopback Mute", (unsigned long)&(bool[1]) {0}, + catpt_loopback_switch_get, catpt_loopback_switch_put), +/* Enable or disable WAVES module */ +SOC_SINGLE_BOOL_EXT("Waves Switch", 0, + catpt_waves_switch_get, catpt_waves_switch_put), +/* WAVES module parameter control */ +SND_SOC_BYTES_TLV("Waves Set Param", 128, + catpt_waves_param_get, catpt_waves_param_put), +}; + +static const struct snd_soc_dapm_widget component_widgets[] = { + SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route component_routes[] = { + {"Playback VMixer", NULL, "System Playback"}, + {"Playback VMixer", NULL, "Offload0 Playback"}, + {"Playback VMixer", NULL, "Offload1 Playback"}, + + {"SSP0 CODEC OUT", NULL, "Playback VMixer"}, + + {"Analog Capture", NULL, "SSP0 CODEC IN"}, + {"Loopback Capture", NULL, "SSP0 CODEC IN"}, + + {"SSP1 BT OUT", NULL, "Bluetooth Playback"}, + {"Bluetooth Capture", NULL, "SSP1 BT IN"}, +}; + +static const struct snd_soc_component_driver catpt_comp_driver = { + .name = "catpt-platform", + + .pcm_construct = catpt_component_pcm_construct, + .open = catpt_component_open, + .pointer = catpt_component_pointer, + + .controls = component_kcontrols, + .num_controls = ARRAY_SIZE(component_kcontrols), + .dapm_widgets = component_widgets, + .num_dapm_widgets = ARRAY_SIZE(component_widgets), + .dapm_routes = component_routes, + .num_dapm_routes = ARRAY_SIZE(component_routes), +}; + +int catpt_arm_stream_templates(struct catpt_dev *cdev) +{ + struct resource *res; + u32 scratch_size = 0; + int i, j; + + for (i = 0; i < ARRAY_SIZE(catpt_topology); i++) { + struct catpt_stream_template *template; + struct catpt_module_entry *entry; + struct catpt_module_type *type; + + template = catpt_topology[i]; + template->persistent_size = 0; + + for (j = 0; j < template->num_entries; j++) { + entry = &template->entries[j]; + type = &cdev->modules[entry->module_id]; + + if (!type->loaded) + return -ENOENT; + + entry->entry_point = type->entry_point; + template->persistent_size += type->persistent_size; + if (type->scratch_size > scratch_size) + scratch_size = type->scratch_size; + } + } + + if (scratch_size) { + /* allocate single scratch area for all modules */ + res = catpt_request_region(&cdev->dram, scratch_size); + if (!res) + return -EBUSY; + cdev->scratch = res; + } + + return 0; +} + +int catpt_register_plat_component(struct catpt_dev *cdev) +{ + struct snd_soc_component *component; + int ret; + + component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL); + if (!component) + return -ENOMEM; + + ret = snd_soc_component_initialize(component, &catpt_comp_driver, + cdev->dev); + if (ret) + return ret; + + component->name = catpt_comp_driver.name; + return snd_soc_add_component(component, dai_drivers, + ARRAY_SIZE(dai_drivers)); +} diff --git a/sound/soc/intel/catpt/registers.h b/sound/soc/intel/catpt/registers.h new file mode 100644 index 000000000000..47280d82842e --- /dev/null +++ b/sound/soc/intel/catpt/registers.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SND_SOC_INTEL_CATPT_REGS_H +#define __SND_SOC_INTEL_CATPT_REGS_H + +#include <linux/bitops.h> +#include <linux/iopoll.h> +#include <uapi/linux/pci_regs.h> + +#define CATPT_SHIM_REGS_SIZE 4096 +#define CATPT_DMA_REGS_SIZE 1024 +#define CATPT_DMA_COUNT 2 +#define CATPT_SSP_REGS_SIZE 512 + +/* DSP Shim registers */ + +#define CATPT_SHIM_CS1 0x00 +#define CATPT_SHIM_ISC 0x18 +#define CATPT_SHIM_ISD 0x20 +#define CATPT_SHIM_IMC 0x28 +#define CATPT_SHIM_IMD 0x30 +#define CATPT_SHIM_IPCC 0x38 +#define CATPT_SHIM_IPCD 0x40 +#define CATPT_SHIM_CLKCTL 0x78 +#define CATPT_SHIM_CS2 0x80 +#define CATPT_SHIM_LTRC 0xE0 +#define CATPT_SHIM_HMDC 0xE8 + +#define CATPT_CS_LPCS BIT(31) +#define CATPT_CS_SFCR(ssp) BIT(27 + (ssp)) +#define CATPT_CS_S1IOCS BIT(23) +#define CATPT_CS_S0IOCS BIT(21) +#define CATPT_CS_PCE BIT(15) +#define CATPT_CS_SDPM(ssp) BIT(11 + (ssp)) +#define CATPT_CS_STALL BIT(10) +#define CATPT_CS_DCS GENMASK(6, 4) +/* b100 DSP core & audio fabric high clock */ +#define CATPT_CS_DCS_HIGH (0x4 << 4) +#define CATPT_CS_SBCS(ssp) BIT(2 + (ssp)) +#define CATPT_CS_RST BIT(1) + +#define CATPT_ISC_IPCDB BIT(1) +#define CATPT_ISC_IPCCD BIT(0) +#define CATPT_ISD_DCPWM BIT(31) +#define CATPT_ISD_IPCCB BIT(1) +#define CATPT_ISD_IPCDD BIT(0) + +#define CATPT_IMC_IPCDB BIT(1) +#define CATPT_IMC_IPCCD BIT(0) +#define CATPT_IMD_IPCCB BIT(1) +#define CATPT_IMD_IPCDD BIT(0) + +#define CATPT_IPCC_BUSY BIT(31) +#define CATPT_IPCC_DONE BIT(30) +#define CATPT_IPCD_BUSY BIT(31) +#define CATPT_IPCD_DONE BIT(30) + +#define CATPT_CLKCTL_CFCIP BIT(31) +#define CATPT_CLKCTL_SMOS GENMASK(25, 24) + +#define CATPT_HMDC_HDDA(e, ch) BIT(8 * (e) + (ch)) + +/* defaults to reset SHIM registers to after each power cycle */ +#define CATPT_CS_DEFAULT 0x8480040E +#define CATPT_ISC_DEFAULT 0x0 +#define CATPT_ISD_DEFAULT 0x0 +#define CATPT_IMC_DEFAULT 0x7FFF0003 +#define CATPT_IMD_DEFAULT 0x7FFF0003 +#define CATPT_IPCC_DEFAULT 0x0 +#define CATPT_IPCD_DEFAULT 0x0 +#define CATPT_CLKCTL_DEFAULT 0x7FF +#define CATPT_CS2_DEFAULT 0x0 +#define CATPT_LTRC_DEFAULT 0x0 +#define CATPT_HMDC_DEFAULT 0x0 + +/* PCI Configuration registers */ + +#define CATPT_PCI_PMCAPID 0x80 +#define CATPT_PCI_PMCS (CATPT_PCI_PMCAPID + PCI_PM_CTRL) +#define CATPT_PCI_VDRTCTL0 0xA0 +#define CATPT_PCI_VDRTCTL2 0xA8 + +#define CATPT_VDRTCTL2_DTCGE BIT(10) +#define CATPT_VDRTCTL2_DCLCGE BIT(1) +#define CATPT_VDRTCTL2_CGEALL 0xF7F + +/* LPT PCI Configuration bits */ + +#define LPT_VDRTCTL0_DSRAMPGE(b) BIT(16 + (b)) +#define LPT_VDRTCTL0_DSRAMPGE_MASK GENMASK(31, 16) +#define LPT_VDRTCTL0_ISRAMPGE(b) BIT(6 + (b)) +#define LPT_VDRTCTL0_ISRAMPGE_MASK GENMASK(15, 6) +#define LPT_VDRTCTL0_D3SRAMPGD BIT(2) +#define LPT_VDRTCTL0_D3PGD BIT(1) +#define LPT_VDRTCTL0_APLLSE BIT(0) + +/* WPT PCI Configuration bits */ + +#define WPT_VDRTCTL0_DSRAMPGE(b) BIT(12 + (b)) +#define WPT_VDRTCTL0_DSRAMPGE_MASK GENMASK(31, 12) +#define WPT_VDRTCTL0_ISRAMPGE(b) BIT(2 + (b)) +#define WPT_VDRTCTL0_ISRAMPGE_MASK GENMASK(11, 2) +#define WPT_VDRTCTL0_D3SRAMPGD BIT(1) +#define WPT_VDRTCTL0_D3PGD BIT(0) + +#define WPT_VDRTCTL2_APLLSE BIT(31) + +/* defaults to reset SSP registers to after each power cycle */ +#define CATPT_SSC0_DEFAULT 0x0 +#define CATPT_SSC1_DEFAULT 0x0 +#define CATPT_SSS_DEFAULT 0xF004 +#define CATPT_SSIT_DEFAULT 0x0 +#define CATPT_SSD_DEFAULT 0xC43893A3 +#define CATPT_SSTO_DEFAULT 0x0 +#define CATPT_SSPSP_DEFAULT 0x0 +#define CATPT_SSTSA_DEFAULT 0x0 +#define CATPT_SSRSA_DEFAULT 0x0 +#define CATPT_SSTSS_DEFAULT 0x0 +#define CATPT_SSCR2_DEFAULT 0x0 +#define CATPT_SSPSP2_DEFAULT 0x0 + +/* Physically the same block, access address differs between host and dsp */ +#define CATPT_DSP_DRAM_OFFSET 0x400000 +#define catpt_to_host_offset(offset) ((offset) & ~(CATPT_DSP_DRAM_OFFSET)) +#define catpt_to_dsp_offset(offset) ((offset) | CATPT_DSP_DRAM_OFFSET) + +#define CATPT_MEMBLOCK_SIZE 0x8000 +#define catpt_num_dram(cdev) (hweight_long((cdev)->spec->dram_mask)) +#define catpt_num_iram(cdev) (hweight_long((cdev)->spec->iram_mask)) +#define catpt_dram_size(cdev) (catpt_num_dram(cdev) * CATPT_MEMBLOCK_SIZE) +#define catpt_iram_size(cdev) (catpt_num_iram(cdev) * CATPT_MEMBLOCK_SIZE) + +/* registry I/O helpers */ + +#define catpt_shim_addr(cdev) \ + ((cdev)->lpe_ba + (cdev)->spec->host_shim_offset) +#define catpt_dma_addr(cdev, dma) \ + ((cdev)->lpe_ba + (cdev)->spec->host_dma_offset[dma]) +#define catpt_ssp_addr(cdev, ssp) \ + ((cdev)->lpe_ba + (cdev)->spec->host_ssp_offset[ssp]) +#define catpt_inbox_addr(cdev) \ + ((cdev)->lpe_ba + (cdev)->ipc.config.inbox_offset) +#define catpt_outbox_addr(cdev) \ + ((cdev)->lpe_ba + (cdev)->ipc.config.outbox_offset) + +#define catpt_writel_ssp(cdev, ssp, reg, val) \ + writel(val, catpt_ssp_addr(cdev, ssp) + (reg)) + +#define catpt_readl_shim(cdev, reg) \ + readl(catpt_shim_addr(cdev) + CATPT_SHIM_##reg) +#define catpt_writel_shim(cdev, reg, val) \ + writel(val, catpt_shim_addr(cdev) + CATPT_SHIM_##reg) +#define catpt_updatel_shim(cdev, reg, mask, val) \ + catpt_writel_shim(cdev, reg, \ + (catpt_readl_shim(cdev, reg) & ~(mask)) | (val)) + +#define catpt_readl_poll_shim(cdev, reg, val, cond, delay_us, timeout_us) \ + readl_poll_timeout(catpt_shim_addr(cdev) + CATPT_SHIM_##reg, \ + val, cond, delay_us, timeout_us) + +#define catpt_readl_pci(cdev, reg) \ + readl(cdev->pci_ba + CATPT_PCI_##reg) +#define catpt_writel_pci(cdev, reg, val) \ + writel(val, cdev->pci_ba + CATPT_PCI_##reg) +#define catpt_updatel_pci(cdev, reg, mask, val) \ + catpt_writel_pci(cdev, reg, \ + (catpt_readl_pci(cdev, reg) & ~(mask)) | (val)) + +#define catpt_readl_poll_pci(cdev, reg, val, cond, delay_us, timeout_us) \ + readl_poll_timeout((cdev)->pci_ba + CATPT_PCI_##reg, \ + val, cond, delay_us, timeout_us) + +#endif diff --git a/sound/soc/intel/catpt/sysfs.c b/sound/soc/intel/catpt/sysfs.c new file mode 100644 index 000000000000..9b6d2d93a2e7 --- /dev/null +++ b/sound/soc/intel/catpt/sysfs.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/pm_runtime.h> +#include "core.h" + +static ssize_t fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct catpt_dev *cdev = dev_get_drvdata(dev); + struct catpt_fw_version version; + int ret; + + ret = pm_runtime_resume_and_get(cdev->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + ret = catpt_ipc_get_fw_version(cdev, &version); + + pm_runtime_mark_last_busy(cdev->dev); + pm_runtime_put_autosuspend(cdev->dev); + + if (ret) + return CATPT_IPC_ERROR(ret); + + return sysfs_emit(buf, "%d.%d.%d.%d\n", version.type, version.major, + version.minor, version.build); +} +static DEVICE_ATTR_RO(fw_version); + +static ssize_t fw_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct catpt_dev *cdev = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", cdev->ipc.config.fw_info); +} +static DEVICE_ATTR_RO(fw_info); + +static struct attribute *catpt_attrs[] = { + &dev_attr_fw_version.attr, + &dev_attr_fw_info.attr, + NULL +}; + +static const struct attribute_group catpt_attr_group = { + .attrs = catpt_attrs, +}; + +const struct attribute_group *catpt_attr_groups[] = { + &catpt_attr_group, + NULL +}; diff --git a/sound/soc/intel/catpt/trace.h b/sound/soc/intel/catpt/trace.h new file mode 100644 index 000000000000..bb3d627dbeaf --- /dev/null +++ b/sound/soc/intel/catpt/trace.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM intel_catpt + +#if !defined(__SND_SOC_INTEL_CATPT_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __SND_SOC_INTEL_CATPT_TRACE_H + +#include <linux/types.h> +#include <linux/tracepoint.h> + +DECLARE_EVENT_CLASS(catpt_ipc_msg, + + TP_PROTO(u32 header), + + TP_ARGS(header), + + TP_STRUCT__entry( + __field(u32, header) + ), + + TP_fast_assign( + __entry->header = header; + ), + + TP_printk("0x%08x", __entry->header) +); + +DEFINE_EVENT(catpt_ipc_msg, catpt_irq, + TP_PROTO(u32 header), + TP_ARGS(header) +); + +DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_request, + TP_PROTO(u32 header), + TP_ARGS(header) +); + +DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_reply, + TP_PROTO(u32 header), + TP_ARGS(header) +); + +DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_notify, + TP_PROTO(u32 header), + TP_ARGS(header) +); + +TRACE_EVENT_CONDITION(catpt_ipc_payload, + + TP_PROTO(const u8 *data, size_t size), + + TP_ARGS(data, size), + + TP_CONDITION(data && size), + + TP_STRUCT__entry( + __dynamic_array(u8, buf, size) + ), + + TP_fast_assign( + memcpy(__get_dynamic_array(buf), data, size); + ), + + TP_printk("%u byte(s)%s", + __get_dynamic_array_len(buf), + __print_hex_dump("", DUMP_PREFIX_NONE, 16, 4, + __get_dynamic_array(buf), + __get_dynamic_array_len(buf), false)) +); + +#endif /* __SND_SOC_INTEL_CATPT_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE trace +#include <trace/define_trace.h> diff --git a/sound/soc/intel/common/Makefile b/sound/soc/intel/common/Makefile index bd352878f89a..41054cf09ec9 100644 --- a/sound/soc/intel/common/Makefile +++ b/sound/soc/intel/common/Makefile @@ -1,8 +1,6 @@ -# SPDX-License-Identifier: GPL-2.0 +# SPDX-License-Identifier: GPL-2.0-only snd-soc-sst-dsp-objs := sst-dsp.o -snd-soc-sst-acpi-objs := sst-acpi.o snd-soc-sst-ipc-objs := sst-ipc.o -snd-soc-sst-firmware-objs := sst-firmware.o snd-soc-acpi-intel-match-objs := soc-acpi-intel-byt-match.o soc-acpi-intel-cht-match.o \ soc-acpi-intel-hsw-bdw-match.o \ soc-acpi-intel-skl-match.o soc-acpi-intel-kbl-match.o \ @@ -10,10 +8,10 @@ snd-soc-acpi-intel-match-objs := soc-acpi-intel-byt-match.o soc-acpi-intel-cht-m soc-acpi-intel-cnl-match.o soc-acpi-intel-cfl-match.o \ soc-acpi-intel-cml-match.o soc-acpi-intel-icl-match.o \ soc-acpi-intel-tgl-match.o soc-acpi-intel-ehl-match.o \ - soc-acpi-intel-jsl-match.o \ - soc-acpi-intel-hda-match.o + soc-acpi-intel-jsl-match.o soc-acpi-intel-adl-match.o \ + soc-acpi-intel-rpl-match.o soc-acpi-intel-mtl-match.o \ + soc-acpi-intel-hda-match.o \ + soc-acpi-intel-sdw-mockup-match.o obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o snd-soc-sst-ipc.o -obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o -obj-$(CONFIG_SND_SOC_INTEL_SST_FIRMWARE) += snd-soc-sst-firmware.o obj-$(CONFIG_SND_SOC_ACPI_INTEL_MATCH) += snd-soc-acpi-intel-match.o diff --git a/sound/soc/intel/common/soc-acpi-intel-adl-match.c b/sound/soc/intel/common/soc-acpi-intel-adl-match.c new file mode 100644 index 000000000000..9990d5502d26 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-adl-match.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * soc-apci-intel-adl-match.c - tables and support for ADL ACPI enumeration. + * + * Copyright (c) 2020, Intel Corporation. + */ + +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> + +static const struct snd_soc_acpi_codecs essx_83x6 = { + .num_codecs = 3, + .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"}, +}; + +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_endpoint spk_l_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 0, + .group_id = 1, +}; + +static const struct snd_soc_acpi_endpoint spk_r_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 1, + .group_id = 1, +}; + +static const struct snd_soc_acpi_adr_device rt711_0_adr[] = { + { + .adr = 0x000020025D071100ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_1_group1_adr[] = { + { + .adr = 0x000120025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1308-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_2_group1_adr[] = { + { + .adr = 0x000220025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1308-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt715_3_adr[] = { + { + .adr = 0x000320025D071500ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt715" + } +}; + +static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { + { + .adr = 0x000030025D071101ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_adr_device rt711_sdca_2_adr[] = { + { + .adr = 0x000230025D071101ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_1_group1_adr[] = { + { + .adr = 0x000131025D131601ull, /* unique ID is set for some reason */ + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1316-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_2_group1_adr[] = { + { + .adr = 0x000230025D131601ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1316-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_3_group1_adr[] = { + { + .adr = 0x000330025D131601ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1316-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_0_group2_adr[] = { + { + .adr = 0x000031025D131601ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1316-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_1_group2_adr[] = { + { + .adr = 0x000130025D131601ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1316-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_2_single_adr[] = { + { + .adr = 0x000230025D131601ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1316-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_3_single_adr[] = { + { + .adr = 0x000330025D131601ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1316-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt714_0_adr[] = { + { + .adr = 0x000030025D071401ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt714" + } +}; + +static const struct snd_soc_acpi_adr_device rt714_2_adr[] = { + { + .adr = 0x000230025D071401ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt714" + } +}; + +static const struct snd_soc_acpi_adr_device rt714_3_adr[] = { + { + .adr = 0x000330025D071401ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt714" + } +}; + +static const struct snd_soc_acpi_link_adr adl_default[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_0_adr), + .adr_d = rt711_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1308_1_group1_adr), + .adr_d = rt1308_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1308_2_group1_adr), + .adr_d = rt1308_2_group1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt715_3_adr), + .adr_d = rt715_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_sdca_default[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group1_adr), + .adr_d = rt1316_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1316_2_group1_adr), + .adr_d = rt1316_2_group1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt714_3_adr), + .adr_d = rt714_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_sdca_3_in_1[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group1_adr), + .adr_d = rt1316_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt714_2_adr), + .adr_d = rt714_2_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt1316_3_group1_adr), + .adr_d = rt1316_3_group1_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_sdw_rt711_link2_rt1316_link01_rt714_link3[] = { + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt711_sdca_2_adr), + .adr_d = rt711_sdca_2_adr, + }, + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt1316_0_group2_adr), + .adr_d = rt1316_0_group2_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group2_adr), + .adr_d = rt1316_1_group2_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt714_3_adr), + .adr_d = rt714_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_sdw_rt711_link2_rt1316_link01[] = { + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt711_sdca_2_adr), + .adr_d = rt711_sdca_2_adr, + }, + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt1316_0_group2_adr), + .adr_d = rt1316_0_group2_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group2_adr), + .adr_d = rt1316_1_group2_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_sdw_rt1316_link12_rt714_link0[] = { + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group1_adr), + .adr_d = rt1316_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1316_2_group1_adr), + .adr_d = rt1316_2_group1_adr, + }, + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt714_0_adr), + .adr_d = rt714_0_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_sdw_rt1316_link2_rt714_link3[] = { + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1316_2_single_adr), + .adr_d = rt1316_2_single_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt714_3_adr), + .adr_d = rt714_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_sdw_rt1316_link2_rt714_link0[] = { + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1316_2_single_adr), + .adr_d = rt1316_2_single_adr, + }, + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt714_0_adr), + .adr_d = rt714_0_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_sdw_rt711_link0_rt1316_link3[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt1316_3_single_adr), + .adr_d = rt1316_3_single_adr, + }, + {} +}; + +static const struct snd_soc_acpi_adr_device mx8373_2_adr[] = { + { + .adr = 0x000223019F837300ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "Left" + }, + { + .adr = 0x000227019F837300ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "Right" + } +}; + +static const struct snd_soc_acpi_adr_device rt5682_0_adr[] = { + { + .adr = 0x000021025D568200ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt5682" + } +}; + +static const struct snd_soc_acpi_link_adr adl_rvp[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_0_adr), + .adr_d = rt711_0_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adlps_rvp[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr adl_chromebook_base[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt5682_0_adr), + .adr_d = rt5682_0_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(mx8373_2_adr), + .adr_d = mx8373_2_adr, + }, + {} +}; + +static const struct snd_soc_acpi_codecs adl_max98373_amp = { + .num_codecs = 1, + .codecs = {"MX98373"} +}; + +static const struct snd_soc_acpi_codecs adl_max98357a_amp = { + .num_codecs = 1, + .codecs = {"MX98357A"} +}; + +static const struct snd_soc_acpi_codecs adl_max98360a_amp = { + .num_codecs = 1, + .codecs = {"MX98360A"} +}; + +static const struct snd_soc_acpi_codecs adl_rt5682_rt5682s_hp = { + .num_codecs = 2, + .codecs = {"10EC5682", "RTL5682"}, +}; + +static const struct snd_soc_acpi_codecs adl_rt1019p_amp = { + .num_codecs = 1, + .codecs = {"RTL1019"} +}; + +static const struct snd_soc_acpi_codecs adl_max98390_amp = { + .num_codecs = 1, + .codecs = {"MX98390"} +}; + +static const struct snd_soc_acpi_codecs adl_lt6911_hdmi = { + .num_codecs = 1, + .codecs = {"INTC10B0"} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_machines[] = { + { + .comp_ids = &adl_rt5682_rt5682s_hp, + .drv_name = "adl_mx98373_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_max98373_amp, + .sof_tplg_filename = "sof-adl-max98373-rt5682.tplg", + }, + { + .comp_ids = &adl_rt5682_rt5682s_hp, + .drv_name = "adl_mx98357_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_max98357a_amp, + .sof_tplg_filename = "sof-adl-max98357a-rt5682.tplg", + }, + { + .comp_ids = &adl_rt5682_rt5682s_hp, + .drv_name = "adl_mx98360_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_max98360a_amp, + .sof_tplg_filename = "sof-adl-max98360a-rt5682.tplg", + }, + { + .id = "10508825", + .drv_name = "adl_rt1019p_nau8825", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_rt1019p_amp, + .sof_tplg_filename = "sof-adl-rt1019-nau8825.tplg", + }, + { + .id = "10508825", + .drv_name = "adl_max98373_nau8825", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_max98373_amp, + .sof_tplg_filename = "sof-adl-max98373-nau8825.tplg", + }, + { + .id = "10508825", + .drv_name = "adl_mx98360a_nau8825", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_max98360a_amp, + .sof_tplg_filename = "sof-adl-max98360a-nau8825.tplg", + }, + { + .id = "RTL5682", + .drv_name = "adl_rt1019_rt5682s", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_rt1019p_amp, + .sof_tplg_filename = "sof-adl-rt1019-rt5682.tplg", + }, + { + .id = "10508825", + .drv_name = "sof_nau8825", + .sof_tplg_filename = "sof-adl-nau8825.tplg", + }, + { + .comp_ids = &adl_rt5682_rt5682s_hp, + .drv_name = "adl_max98390_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_max98390_amp, + .sof_tplg_filename = "sof-adl-max98390-rt5682.tplg", + }, + { + .comp_ids = &adl_rt5682_rt5682s_hp, + .drv_name = "adl_rt5682", + .sof_tplg_filename = "sof-adl-rt5682.tplg", + }, + { + .id = "10134242", + .drv_name = "adl_mx98360a_cs4242", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_max98360a_amp, + .sof_tplg_filename = "sof-adl-max98360a-cs42l42.tplg", + }, + /* place amp-only boards in the end of table */ + { + .id = "CSC3541", + .drv_name = "adl_cs35l41", + .sof_tplg_filename = "sof-adl-cs35l41.tplg", + }, + { + .comp_ids = &essx_83x6, + .drv_name = "adl_es83x6_c1_h02", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_lt6911_hdmi, + .sof_tplg_filename = "sof-adl-es83x6-ssp1-hdmi-ssp02.tplg", + }, + { + .comp_ids = &essx_83x6, + .drv_name = "sof-essx8336", + .sof_tplg_filename = "sof-adl-es83x6", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | + SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | + SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_adl_machines); + +/* this table is used when there is no I2S codec present */ +struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_sdw_machines[] = { + { + .link_mask = 0xF, /* 4 active links required */ + .links = adl_default, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt711-l0-rt1308-l12-rt715-l3.tplg", + }, + { + .link_mask = 0xF, /* 4 active links required */ + .links = adl_sdca_default, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt711-l0-rt1316-l12-rt714-l3.tplg", + }, + { + .link_mask = 0xF, /* 4 active links required */ + .links = adl_sdca_3_in_1, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt711-l0-rt1316-l13-rt714-l2.tplg", + }, + { + .link_mask = 0xF, /* 4 active links required */ + .links = adl_sdw_rt711_link2_rt1316_link01_rt714_link3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt711-l2-rt1316-l01-rt714-l3.tplg", + }, + { + .link_mask = 0x7, /* rt1316 on link0 and link1 & rt711 on link2*/ + .links = adl_sdw_rt711_link2_rt1316_link01, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt711-l2-rt1316-l01.tplg", + }, + { + .link_mask = 0xC, /* rt1316 on link2 & rt714 on link3 */ + .links = adl_sdw_rt1316_link2_rt714_link3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt1316-l2-mono-rt714-l3.tplg", + }, + { + .link_mask = 0x7, /* rt714 on link0 & two rt1316s on link1 and link2 */ + .links = adl_sdw_rt1316_link12_rt714_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt1316-l12-rt714-l0.tplg", + }, + { + .link_mask = 0x5, /* 2 active links required */ + .links = adl_sdw_rt1316_link2_rt714_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt1316-l2-mono-rt714-l0.tplg", + }, + { + .link_mask = 0x9, /* 2 active links required */ + .links = adl_sdw_rt711_link0_rt1316_link3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt711-l0-rt1316-l3.tplg", + }, + { + .link_mask = 0x1, /* link0 required */ + .links = adl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt711.tplg", + }, + { + .link_mask = 0x1, /* link0 required */ + .links = adlps_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt711.tplg", + }, + { + .link_mask = 0x5, /* rt5682 on link0 & 2xmax98373 on link 2 */ + .links = adl_chromebook_base, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-sdw-max98373-rt5682.tplg", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_adl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-bxt-match.c b/sound/soc/intel/common/soc-acpi-intel-bxt-match.c index 4a5adae1d785..f99cf6c794dc 100644 --- a/sound/soc/intel/common/soc-acpi-intel-bxt-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-bxt-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-acpi-intel-bxt-match.c - tables and support for BXT ACPI enumeration. * @@ -41,7 +41,12 @@ static struct snd_soc_acpi_mach *apl_quirk(void *arg) return mach; } -static struct snd_soc_acpi_codecs bxt_codecs = { +static const struct snd_soc_acpi_codecs essx_83x6 = { + .num_codecs = 3, + .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"}, +}; + +static const struct snd_soc_acpi_codecs bxt_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; @@ -51,40 +56,40 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_bxt_machines[] = { .id = "INT343A", .drv_name = "bxt_alc298s_i2s", .fw_filename = "intel/dsp_fw_bxtn.bin", - .sof_fw_filename = "sof-apl.ri", .sof_tplg_filename = "sof-apl-rt298.tplg", }, { .id = "DLGS7219", - .drv_name = "bxt_da7219_max98357a", + .drv_name = "bxt_da7219_mx98357a", .fw_filename = "intel/dsp_fw_bxtn.bin", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &bxt_codecs, - .sof_fw_filename = "sof-apl.ri", .sof_tplg_filename = "sof-apl-da7219.tplg", }, { .id = "104C5122", - .drv_name = "bxt-pcm512x", - .sof_fw_filename = "sof-apl.ri", + .drv_name = "sof_pcm512x", .sof_tplg_filename = "sof-apl-pcm512x.tplg", }, { .id = "1AEC8804", - .drv_name = "bxt-wm8804", - .sof_fw_filename = "sof-apl.ri", + .drv_name = "sof-wm8804", .sof_tplg_filename = "sof-apl-wm8804.tplg", }, { .id = "INT34C3", .drv_name = "bxt_tdf8532", .machine_quirk = apl_quirk, - .sof_fw_filename = "sof-apl.ri", .sof_tplg_filename = "sof-apl-tdf8532.tplg", }, + { + .comp_ids = &essx_83x6, + .drv_name = "sof-essx8336", + .sof_tplg_filename = "sof-apl-es8336", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | + SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | + SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_bxt_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-byt-match.c b/sound/soc/intel/common/soc-acpi-intel-byt-match.c index 1cc801ba92eb..db5a92b9875a 100644 --- a/sound/soc/intel/common/soc-acpi-intel-byt-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-byt-match.c @@ -11,13 +11,12 @@ static unsigned long byt_machine_id; -#define BYT_THINKPAD_10 1 +#define BYT_RT5672 1 #define BYT_POV_P1006W 2 -#define BYT_AEGEX_10 3 -static int byt_thinkpad10_quirk_cb(const struct dmi_system_id *id) +static int byt_rt5672_quirk_cb(const struct dmi_system_id *id) { - byt_machine_id = BYT_THINKPAD_10; + byt_machine_id = BYT_RT5672; return 1; } @@ -27,36 +26,30 @@ static int byt_pov_p1006w_quirk_cb(const struct dmi_system_id *id) return 1; } -static int byt_aegex10_quirk_cb(const struct dmi_system_id *id) -{ - byt_machine_id = BYT_AEGEX_10; - return 1; -} - static const struct dmi_system_id byt_table[] = { { - .callback = byt_thinkpad10_quirk_cb, + .callback = byt_rt5672_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 8"), }, }, { - .callback = byt_thinkpad10_quirk_cb, + .callback = byt_rt5672_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 10"), }, }, { - .callback = byt_thinkpad10_quirk_cb, + .callback = byt_rt5672_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Tablet B"), }, }, { - .callback = byt_thinkpad10_quirk_cb, + .callback = byt_rt5672_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Miix 2 10"), @@ -75,22 +68,29 @@ static const struct dmi_system_id byt_table[] = { }, { /* Aegex 10 tablet (RU2) */ - .callback = byt_aegex10_quirk_cb, + .callback = byt_rt5672_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "AEGEX"), DMI_MATCH(DMI_PRODUCT_VERSION, "RU2"), }, }, + { + /* Dell Venue 10 Pro 5055 */ + .callback = byt_rt5672_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Venue 10 Pro 5055"), + }, + }, { } }; -/* The Thinkapd 10 and Aegex 10 tablets have the same ID problem */ -static struct snd_soc_acpi_mach byt_thinkpad_10 = { +/* Various devices use an ACPI id of 10EC5640 while using a rt5672 codec */ +static struct snd_soc_acpi_mach byt_rt5672 = { .id = "10EC5640", .drv_name = "cht-bsw-rt5672", .fw_filename = "intel/fw_sst_0f28.bin", .board = "cht-bsw", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-rt5670.tplg", }; @@ -99,7 +99,6 @@ static struct snd_soc_acpi_mach byt_pov_p1006w = { .drv_name = "bytcr_rt5651", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcr_rt5651", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-rt5651.tplg", }; @@ -110,9 +109,8 @@ static struct snd_soc_acpi_mach *byt_quirk(void *arg) dmi_check_system(byt_table); switch (byt_machine_id) { - case BYT_THINKPAD_10: - case BYT_AEGEX_10: - return &byt_thinkpad_10; + case BYT_RT5672: + return &byt_rt5672; case BYT_POV_P1006W: return &byt_pov_p1006w; default: @@ -120,45 +118,33 @@ static struct snd_soc_acpi_mach *byt_quirk(void *arg) } } -struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_legacy_machines[] = { - { - .id = "10EC5640", - .drv_name = "byt-rt5640", - .fw_filename = "intel/fw_sst_0f28.bin-48kHz_i2s_master", - }, - { - .id = "193C9890", - .drv_name = "byt-max98090", - .fw_filename = "intel/fw_sst_0f28.bin-48kHz_i2s_master", - }, - {} +static const struct snd_soc_acpi_codecs rt5640_comp_ids = { + .num_codecs = 3, + .codecs = { "10EC5640", "10EC5642", "INTCCFFD"}, +}; + +static const struct snd_soc_acpi_codecs wm5102_comp_ids = { + .num_codecs = 2, + .codecs = { "10WM5102", "WM510204", "WM510205"}, +}; + +static const struct snd_soc_acpi_codecs da7213_comp_ids = { + .num_codecs = 2, + .codecs = { "DGLS7212", "DGLS7213"}, +}; + +static const struct snd_soc_acpi_codecs rt5645_comp_ids = { + .num_codecs = 2, + .codecs = { "10EC5645", "10EC5648"}, }; -EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_baytrail_legacy_machines); struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { { - .id = "10EC5640", + .comp_ids = &rt5640_comp_ids, .drv_name = "bytcr_rt5640", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcr_rt5640", .machine_quirk = byt_quirk, - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-rt5640.tplg", - }, - { - .id = "10EC5642", - .drv_name = "bytcr_rt5640", - .fw_filename = "intel/fw_sst_0f28.bin", - .board = "bytcr_rt5640", - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-rt5640.tplg", - }, - { - .id = "INTCCFFD", - .drv_name = "bytcr_rt5640", - .fw_filename = "intel/fw_sst_0f28.bin", - .board = "bytcr_rt5640", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-rt5640.tplg", }, { @@ -166,23 +152,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { .drv_name = "bytcr_rt5651", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcr_rt5651", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-rt5651.tplg", }, { - .id = "DLGS7212", - .drv_name = "bytcht_da7213", + .comp_ids = &wm5102_comp_ids, + .drv_name = "bytcr_wm5102", .fw_filename = "intel/fw_sst_0f28.bin", - .board = "bytcht_da7213", - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-da7213.tplg", + .board = "bytcr_wm5102", + .sof_tplg_filename = "sof-byt-wm5102.tplg", }, { - .id = "DLGS7213", + .comp_ids = &da7213_comp_ids, .drv_name = "bytcht_da7213", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcht_da7213", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-da7213.tplg", }, { @@ -190,30 +173,19 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { .drv_name = "bytcht_es8316", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcht_es8316", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-es8316.tplg", }, { .id = "10EC5682", .drv_name = "sof_rt5682", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-rt5682.tplg", }, /* some Baytrail platforms rely on RT5645, use CHT machine driver */ { - .id = "10EC5645", - .drv_name = "cht-bsw-rt5645", - .fw_filename = "intel/fw_sst_0f28.bin", - .board = "cht-bsw", - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-rt5645.tplg", - }, - { - .id = "10EC5648", + .comp_ids = &rt5645_comp_ids, .drv_name = "cht-bsw-rt5645", .fw_filename = "intel/fw_sst_0f28.bin", .board = "cht-bsw", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-rt5645.tplg", }, /* use CHT driver to Baytrail Chromebooks */ @@ -222,7 +194,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { .drv_name = "cht-bsw-max98090", .fw_filename = "intel/fw_sst_0f28.bin", .board = "cht-bsw", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-max98090.tplg", }, { @@ -230,7 +201,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { .drv_name = "bytcht_cx2072x", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcht_cx2072x", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-cx2072x.tplg", }, #if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) @@ -248,6 +218,3 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_baytrail_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cfl-match.c b/sound/soc/intel/common/soc-acpi-intel-cfl-match.c index ff9d6938b9f6..1733dfb23e79 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cfl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cfl-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-apci-intel-cfl-match.c - tables and support for CFL ACPI enumeration. * @@ -18,6 +18,3 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cfl_sdw_machines[] = { {} }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cfl_sdw_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index d0fb43c2b9f6..6beb00858c33 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -35,7 +35,6 @@ static struct snd_soc_acpi_mach cht_surface_mach = { .drv_name = "cht-bsw-rt5645", .fw_filename = "intel/fw_sst_22a8.bin", .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-rt5645.tplg", }; @@ -51,46 +50,41 @@ static struct snd_soc_acpi_mach *cht_quirk(void *arg) return mach; } +static const struct snd_soc_acpi_codecs rt5640_comp_ids = { + .num_codecs = 2, + .codecs = { "10EC5640", "10EC3276" }, +}; + +static const struct snd_soc_acpi_codecs rt5670_comp_ids = { + .num_codecs = 2, + .codecs = { "10EC5670", "10EC5672" }, +}; + +static const struct snd_soc_acpi_codecs rt5645_comp_ids = { + .num_codecs = 3, + .codecs = { "10EC5645", "10EC5650", "10EC3270" }, +}; + +static const struct snd_soc_acpi_codecs da7213_comp_ids = { + .num_codecs = 2, + .codecs = { "DGLS7212", "DGLS7213"}, + +}; + /* Cherryview-based platforms: CherryTrail and Braswell */ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { { - .id = "10EC5670", - .drv_name = "cht-bsw-rt5672", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-rt5670.tplg", - }, - { - .id = "10EC5672", + .comp_ids = &rt5670_comp_ids, .drv_name = "cht-bsw-rt5672", .fw_filename = "intel/fw_sst_22a8.bin", .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-rt5670.tplg", }, { - .id = "10EC5645", + .comp_ids = &rt5645_comp_ids, .drv_name = "cht-bsw-rt5645", .fw_filename = "intel/fw_sst_22a8.bin", .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-rt5645.tplg", - }, - { - .id = "10EC5650", - .drv_name = "cht-bsw-rt5645", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-rt5645.tplg", - }, - { - .id = "10EC3270", - .drv_name = "cht-bsw-rt5645", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-rt5645.tplg", }, { @@ -98,7 +92,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .drv_name = "cht-bsw-max98090", .fw_filename = "intel/fw_sst_22a8.bin", .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-max98090.tplg", }, { @@ -106,23 +99,13 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .drv_name = "cht-bsw-nau8824", .fw_filename = "intel/fw_sst_22a8.bin", .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-nau8824.tplg", }, { - .id = "DLGS7212", - .drv_name = "bytcht_da7213", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "bytcht_da7213", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-da7213.tplg", - }, - { - .id = "DLGS7213", + .comp_ids = &da7213_comp_ids, .drv_name = "bytcht_da7213", .fw_filename = "intel/fw_sst_22a8.bin", .board = "bytcht_da7213", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-da7213.tplg", }, { @@ -130,31 +113,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .drv_name = "bytcht_es8316", .fw_filename = "intel/fw_sst_22a8.bin", .board = "bytcht_es8316", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-es8316.tplg", }, /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */ { - .id = "10EC5640", + .comp_ids = &rt5640_comp_ids, .drv_name = "bytcr_rt5640", .fw_filename = "intel/fw_sst_22a8.bin", .board = "bytcr_rt5640", .machine_quirk = cht_quirk, - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-rt5640.tplg", - }, - { - .id = "10EC3276", - .drv_name = "bytcr_rt5640", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "bytcr_rt5640", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-rt5640.tplg", }, { .id = "10EC5682", .drv_name = "sof_rt5682", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-rt5682.tplg", }, /* some CHT-T platforms rely on RT5651, use Baytrail machine driver */ @@ -163,7 +135,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .drv_name = "bytcr_rt5651", .fw_filename = "intel/fw_sst_22a8.bin", .board = "bytcr_rt5651", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-rt5651.tplg", }, { @@ -171,9 +142,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .drv_name = "bytcht_cx2072x", .fw_filename = "intel/fw_sst_22a8.bin", .board = "bytcht_cx2072x", - .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-cx2072x.tplg", }, + { + .id = "104C5122", + .drv_name = "sof_pcm512x", + .sof_tplg_filename = "sof-cht-src-50khz-pcm512x.tplg", + }, + #if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) /* * This is always last in the table so that it is selected only when @@ -189,6 +165,3 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cherrytrail_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cml-match.c b/sound/soc/intel/common/soc-acpi-intel-cml-match.c index f55634c4c2e8..5eab17820532 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cml-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cml-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-acpi-intel-cml-match.c - tables and support for CML ACPI enumeration. * @@ -9,16 +9,31 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> -static struct snd_soc_acpi_codecs rt1011_spk_codecs = { +static const struct snd_soc_acpi_codecs essx_83x6 = { + .num_codecs = 3, + .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"}, +}; + +static const struct snd_soc_acpi_codecs rt1011_spk_codecs = { .num_codecs = 1, .codecs = {"10EC1011"} }; -static struct snd_soc_acpi_codecs max98357a_spk_codecs = { +static const struct snd_soc_acpi_codecs rt1015_spk_codecs = { + .num_codecs = 1, + .codecs = {"10EC1015"} +}; + +static const struct snd_soc_acpi_codecs max98357a_spk_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; +static const struct snd_soc_acpi_codecs max98390_spk_codecs = { + .num_codecs = 1, + .codecs = {"MX98390"} +}; + /* * The order of the three entries with .id = "10EC5682" matters * here, because DSDT tables expose an ACPI HID for the MAX98357A @@ -30,7 +45,13 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_machines[] = { .drv_name = "cml_rt1011_rt5682", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &rt1011_spk_codecs, - .sof_fw_filename = "sof-cml.ri", + .sof_tplg_filename = "sof-cml-rt1011-rt5682.tplg", + }, + { + .id = "10EC5682", + .drv_name = "cml_rt1015_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &rt1015_spk_codecs, .sof_tplg_filename = "sof-cml-rt1011-rt5682.tplg", }, { @@ -38,63 +59,179 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_machines[] = { .drv_name = "sof_rt5682", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &max98357a_spk_codecs, - .sof_fw_filename = "sof-cml.ri", .sof_tplg_filename = "sof-cml-rt5682-max98357a.tplg", }, { .id = "10EC5682", .drv_name = "sof_rt5682", - .sof_fw_filename = "sof-cml.ri", .sof_tplg_filename = "sof-cml-rt5682.tplg", }, { .id = "DLGS7219", - .drv_name = "cml_da7219_max98357a", + .drv_name = "cml_da7219_mx98357a", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &max98357a_spk_codecs, - .sof_fw_filename = "sof-cml.ri", .sof_tplg_filename = "sof-cml-da7219-max98357a.tplg", }, + { + .id = "DLGS7219", + .drv_name = "cml_da7219_mx98357a", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &max98390_spk_codecs, + .sof_tplg_filename = "sof-cml-da7219-max98390.tplg", + }, + { + .comp_ids = &essx_83x6, + .drv_name = "sof-essx8336", + .sof_tplg_filename = "sof-cml-es8336", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | + SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | + SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cml_machines); -static const u64 rt711_0_adr[] = { - 0x000010025D071100 +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_endpoint spk_l_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 0, + .group_id = 1, +}; + +static const struct snd_soc_acpi_endpoint spk_r_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 1, + .group_id = 1, +}; + +static const struct snd_soc_acpi_adr_device rt700_1_adr[] = { + { + .adr = 0x000110025D070000ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt700" + } +}; + +static const struct snd_soc_acpi_link_adr cml_rvp[] = { + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt700_1_adr), + .adr_d = rt700_1_adr, + }, + {} +}; + +static const struct snd_soc_acpi_adr_device rt711_0_adr[] = { + { + .adr = 0x000020025D071100ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_1_single_adr[] = { + { + .adr = 0x000120025D130800ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1308-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_1_group1_adr[] = { + { + .adr = 0x000120025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1308-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_2_group1_adr[] = { + { + .adr = 0x000220025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1308-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt715_3_adr[] = { + { + .adr = 0x000320025D071500ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt715" + } }; -static const u64 rt1308_1_adr[] = { - 0x000110025D130800 +static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { + { + .adr = 0x000030025D071101ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } }; -static const u64 rt1308_2_adr[] = { - 0x000210025D130800 +static const struct snd_soc_acpi_adr_device rt1316_1_group1_adr[] = { + { + .adr = 0x000131025D131601ull, /* unique ID is set for some reason */ + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1316-1" + } }; -static const u64 rt715_3_adr[] = { - 0x000310025D071500 +static const struct snd_soc_acpi_adr_device rt1316_2_group1_adr[] = { + { + .adr = 0x000230025D131601ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1316-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt714_3_adr[] = { + { + .adr = 0x000330025D071401ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt714" + } }; static const struct snd_soc_acpi_link_adr cml_3_in_1_default[] = { { .mask = BIT(0), .num_adr = ARRAY_SIZE(rt711_0_adr), - .adr = rt711_0_adr, + .adr_d = rt711_0_adr, }, { .mask = BIT(1), - .num_adr = ARRAY_SIZE(rt1308_1_adr), - .adr = rt1308_1_adr, + .num_adr = ARRAY_SIZE(rt1308_1_group1_adr), + .adr_d = rt1308_1_group1_adr, }, { .mask = BIT(2), - .num_adr = ARRAY_SIZE(rt1308_2_adr), - .adr = rt1308_2_adr, + .num_adr = ARRAY_SIZE(rt1308_2_group1_adr), + .adr_d = rt1308_2_group1_adr, }, { .mask = BIT(3), .num_adr = ARRAY_SIZE(rt715_3_adr), - .adr = rt715_3_adr, + .adr_d = rt715_3_adr, }, {} }; @@ -103,17 +240,41 @@ static const struct snd_soc_acpi_link_adr cml_3_in_1_mono_amp[] = { { .mask = BIT(0), .num_adr = ARRAY_SIZE(rt711_0_adr), - .adr = rt711_0_adr, + .adr_d = rt711_0_adr, }, { .mask = BIT(1), - .num_adr = ARRAY_SIZE(rt1308_1_adr), - .adr = rt1308_1_adr, + .num_adr = ARRAY_SIZE(rt1308_1_single_adr), + .adr_d = rt1308_1_single_adr, }, { .mask = BIT(3), .num_adr = ARRAY_SIZE(rt715_3_adr), - .adr = rt715_3_adr, + .adr_d = rt715_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr cml_3_in_1_sdca[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group1_adr), + .adr_d = rt1316_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1316_2_group1_adr), + .adr_d = rt1316_2_group1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt714_3_adr), + .adr_d = rt714_3_adr, }, {} }; @@ -122,11 +283,16 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_sdw_machines[] = { { .link_mask = 0xF, /* 4 active links required */ .links = cml_3_in_1_default, - .drv_name = "sdw_rt711_rt1308_rt715", - .sof_fw_filename = "sof-cml.ri", + .drv_name = "sof_sdw", .sof_tplg_filename = "sof-cml-rt711-rt1308-rt715.tplg", }, { + .link_mask = 0xF, /* 4 active links required */ + .links = cml_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-cml-rt711-rt1316-rt714.tplg", + }, + { /* * link_mask should be 0xB, but all links are enabled by BIOS. * This entry will be selected if there is no rt1308 exposed @@ -134,19 +300,15 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_sdw_machines[] = { */ .link_mask = 0xF, .links = cml_3_in_1_mono_amp, - .drv_name = "sdw_rt711_rt1308_rt715", - .sof_fw_filename = "sof-cml.ri", + .drv_name = "sof_sdw", .sof_tplg_filename = "sof-cml-rt711-rt1308-mono-rt715.tplg", }, { .link_mask = 0x2, /* RT700 connected on Link1 */ - .drv_name = "sdw_rt700", - .sof_fw_filename = "sof-cml.ri", + .links = cml_rvp, + .drv_name = "sof_sdw", .sof_tplg_filename = "sof-cml-rt700.tplg", }, {} }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cml_sdw_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cnl-match.c b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c index 828980d5630d..3df89e4511da 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cnl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-acpi-intel-cnl-match.c - tables and support for CNL ACPI enumeration. * @@ -9,6 +9,12 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> #include "../skylake/skl.h" +#include "soc-acpi-intel-sdw-mockup-match.h" + +static const struct snd_soc_acpi_codecs essx_83x6 = { + .num_codecs = 3, + .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"}, +}; static struct skl_machine_pdata cnl_pdata = { .use_tplg_pcm = true, @@ -20,17 +26,65 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_machines[] = { .drv_name = "cnl_rt274", .fw_filename = "intel/dsp_fw_cnl.bin", .pdata = &cnl_pdata, - .sof_fw_filename = "sof-cnl.ri", .sof_tplg_filename = "sof-cnl-rt274.tplg", }, + { + .comp_ids = &essx_83x6, + .drv_name = "sof-essx8336", + /* cnl and cml are identical */ + .sof_tplg_filename = "sof-cml-es8336", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | + SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | + SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cnl_machines); +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_adr_device rt5682_2_adr[] = { + { + .adr = 0x000220025D568200ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt5682" + } +}; + +static const struct snd_soc_acpi_link_adr up_extreme_rt5682_2[] = { + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt5682_2_adr), + .adr_d = rt5682_2_adr, + }, + {} +}; + struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_sdw_machines[] = { - {}, + { + .link_mask = BIT(2), + .links = up_extreme_rt5682_2, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-cnl-rt5682-sdw2.tplg" + }, + { + .link_mask = GENMASK(3, 0), + .links = sdw_mockup_headset_2amps_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-cml-rt711-rt1308-rt715.tplg", + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = sdw_mockup_headset_1amp_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-cml-rt711-rt1308-mono-rt715.tplg", + }, + {} }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cnl_sdw_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-ehl-match.c b/sound/soc/intel/common/soc-acpi-intel-ehl-match.c index a1290c3fa99f..84639c41a268 100644 --- a/sound/soc/intel/common/soc-acpi-intel-ehl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-ehl-match.c @@ -1,6 +1,6 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* - * soc-apci-intel-ehl-match.c - tables and support for EHL ACPI enumeration. + * soc-acpi-intel-ehl-match.c - tables and support for EHL ACPI enumeration. * * Copyright (c) 2019, Intel Corporation. * @@ -8,11 +8,14 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> +#include "../skylake/skl.h" struct snd_soc_acpi_mach snd_soc_acpi_intel_ehl_machines[] = { + { + .id = "10EC5660", + .drv_name = "ehl_rt5660", + .sof_tplg_filename = "sof-ehl-rt5660.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_ehl_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-glk-match.c b/sound/soc/intel/common/soc-acpi-intel-glk-match.c index 60dea358fa04..387e73100884 100644 --- a/sound/soc/intel/common/soc-acpi-intel-glk-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-glk-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-acpi-intel-glk-match.c - tables and support for GLK ACPI enumeration. * @@ -9,7 +9,12 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> -static struct snd_soc_acpi_codecs glk_codecs = { +static const struct snd_soc_acpi_codecs essx_83x6 = { + .num_codecs = 3, + .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"}, +}; + +static const struct snd_soc_acpi_codecs glk_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; @@ -19,30 +24,47 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_glk_machines[] = { .id = "INT343A", .drv_name = "glk_alc298s_i2s", .fw_filename = "intel/dsp_fw_glk.bin", - .sof_fw_filename = "sof-glk.ri", .sof_tplg_filename = "sof-glk-alc298.tplg", }, { .id = "DLGS7219", - .drv_name = "glk_da7219_max98357a", + .drv_name = "glk_da7219_mx98357a", .fw_filename = "intel/dsp_fw_glk.bin", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &glk_codecs, - .sof_fw_filename = "sof-glk.ri", .sof_tplg_filename = "sof-glk-da7219.tplg", }, { .id = "10EC5682", - .drv_name = "glk_rt5682_max98357a", + .drv_name = "glk_rt5682_mx98357a", .fw_filename = "intel/dsp_fw_glk.bin", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &glk_codecs, - .sof_fw_filename = "sof-glk.ri", .sof_tplg_filename = "sof-glk-rt5682.tplg", }, + { + .id = "RTL5682", + .drv_name = "glk_rt5682_max98357a", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &glk_codecs, + .sof_tplg_filename = "sof-glk-rt5682.tplg", + }, + { + .id = "10134242", + .drv_name = "glk_cs4242_mx98357a", + .fw_filename = "intel/dsp_fw_glk.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &glk_codecs, + .sof_tplg_filename = "sof-glk-cs42l42.tplg", + }, + { + .comp_ids = &essx_83x6, + .drv_name = "sof-essx8336", + .sof_tplg_filename = "sof-glk-es8336", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | + SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | + SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_glk_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-hda-match.c b/sound/soc/intel/common/soc-acpi-intel-hda-match.c index cc972d2ac691..2017fd0d676f 100644 --- a/sound/soc/intel/common/soc-acpi-intel-hda-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-hda-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright (c) 2018, Intel Corporation. /* @@ -21,8 +21,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_hda_machines[] = { /* .fw_filename is dynamically set in skylake driver */ - /* .sof_fw_filename is dynamically set in sof/intel driver */ - .sof_tplg_filename = "sof-hda-generic.tplg", /* diff --git a/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c b/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c index 35958553652e..6daf60b1edf1 100644 --- a/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c @@ -9,50 +9,27 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> -struct snd_soc_acpi_mach snd_soc_acpi_intel_haswell_machines[] = { - { - .id = "INT33CA", - .drv_name = "haswell-audio", - .fw_filename = "intel/IntcSST1.bin", - .sof_fw_filename = "sof-hsw.ri", - .sof_tplg_filename = "sof-hsw.tplg", - }, - {} -}; -EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_haswell_machines); - struct snd_soc_acpi_mach snd_soc_acpi_intel_broadwell_machines[] = { { .id = "INT343A", - .drv_name = "broadwell-audio", - .fw_filename = "intel/IntcSST2.bin", - .sof_fw_filename = "sof-bdw.ri", + .drv_name = "bdw_rt286", .sof_tplg_filename = "sof-bdw-rt286.tplg", }, { .id = "10EC5650", .drv_name = "bdw-rt5650", - .fw_filename = "intel/IntcSST2.bin", - .sof_fw_filename = "sof-bdw.ri", .sof_tplg_filename = "sof-bdw-rt5650.tplg", }, { .id = "RT5677CE", .drv_name = "bdw-rt5677", - .fw_filename = "intel/IntcSST2.bin", - .sof_fw_filename = "sof-bdw.ri", .sof_tplg_filename = "sof-bdw-rt5677.tplg", }, { .id = "INT33CA", - .drv_name = "haswell-audio", - .fw_filename = "intel/IntcSST2.bin", - .sof_fw_filename = "sof-bdw.ri", + .drv_name = "hsw_rt5640", .sof_tplg_filename = "sof-bdw-rt5640.tplg", }, {} }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_broadwell_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-icl-match.c b/sound/soc/intel/common/soc-acpi-intel-icl-match.c index 752733013d54..b032bc07de8b 100644 --- a/sound/soc/intel/common/soc-acpi-intel-icl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-icl-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-acpi-intel-icl-match.c - tables and support for ICL ACPI enumeration. * @@ -20,68 +20,121 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_machines[] = { .drv_name = "icl_rt274", .fw_filename = "intel/dsp_fw_icl.bin", .pdata = &icl_pdata, - .sof_fw_filename = "sof-icl.ri", .sof_tplg_filename = "sof-icl-rt274.tplg", }, { .id = "10EC5682", .drv_name = "sof_rt5682", - .sof_fw_filename = "sof-icl.ri", .sof_tplg_filename = "sof-icl-rt5682.tplg", }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_icl_machines); -static const u64 rt700_0_adr[] = { - 0x000010025D070000 +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_endpoint spk_l_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 0, + .group_id = 1, +}; + +static const struct snd_soc_acpi_endpoint spk_r_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 1, + .group_id = 1, +}; + +static const struct snd_soc_acpi_adr_device rt700_0_adr[] = { + { + .adr = 0x000010025D070000ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt700" + } }; static const struct snd_soc_acpi_link_adr icl_rvp[] = { { .mask = BIT(0), .num_adr = ARRAY_SIZE(rt700_0_adr), - .adr = rt700_0_adr, + .adr_d = rt700_0_adr, }, {} }; -static const u64 rt711_0_adr[] = { - 0x000010025D071100 +static const struct snd_soc_acpi_adr_device rt711_0_adr[] = { + { + .adr = 0x000020025D071100ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_1_adr[] = { + { + .adr = 0x000120025D130800ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1308-1" + } }; -static const u64 rt1308_1_adr[] = { - 0x000110025D130800 +static const struct snd_soc_acpi_adr_device rt1308_1_group1_adr[] = { + { + .adr = 0x000120025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1308-1" + } }; -static const u64 rt1308_2_adr[] = { - 0x000210025D130800 +static const struct snd_soc_acpi_adr_device rt1308_2_group1_adr[] = { + { + .adr = 0x000220025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1308-2" + } }; -static const u64 rt715_3_adr[] = { - 0x000310025D071500 +static const struct snd_soc_acpi_adr_device rt715_3_adr[] = { + { + .adr = 0x000320025D071500ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt715" + } }; static const struct snd_soc_acpi_link_adr icl_3_in_1_default[] = { { .mask = BIT(0), .num_adr = ARRAY_SIZE(rt711_0_adr), - .adr = rt711_0_adr, + .adr_d = rt711_0_adr, }, { .mask = BIT(1), - .num_adr = ARRAY_SIZE(rt1308_1_adr), - .adr = rt1308_1_adr, + .num_adr = ARRAY_SIZE(rt1308_1_group1_adr), + .adr_d = rt1308_1_group1_adr, }, { .mask = BIT(2), - .num_adr = ARRAY_SIZE(rt1308_2_adr), - .adr = rt1308_2_adr, + .num_adr = ARRAY_SIZE(rt1308_2_group1_adr), + .adr_d = rt1308_2_group1_adr, }, { .mask = BIT(3), .num_adr = ARRAY_SIZE(rt715_3_adr), - .adr = rt715_3_adr, + .adr_d = rt715_3_adr, }, {} }; @@ -90,17 +143,17 @@ static const struct snd_soc_acpi_link_adr icl_3_in_1_mono_amp[] = { { .mask = BIT(0), .num_adr = ARRAY_SIZE(rt711_0_adr), - .adr = rt711_0_adr, + .adr_d = rt711_0_adr, }, { .mask = BIT(1), .num_adr = ARRAY_SIZE(rt1308_1_adr), - .adr = rt1308_1_adr, + .adr_d = rt1308_1_adr, }, { .mask = BIT(3), .num_adr = ARRAY_SIZE(rt715_3_adr), - .adr = rt715_3_adr, + .adr_d = rt715_3_adr, }, {} }; @@ -109,27 +162,21 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_sdw_machines[] = { { .link_mask = 0xF, /* 4 active links required */ .links = icl_3_in_1_default, - .drv_name = "sdw_rt711_rt1308_rt715", - .sof_fw_filename = "sof-icl.ri", + .drv_name = "sof_sdw", .sof_tplg_filename = "sof-icl-rt711-rt1308-rt715.tplg", }, { .link_mask = 0xB, /* 3 active links required */ .links = icl_3_in_1_mono_amp, - .drv_name = "sdw_rt711_rt1308_rt715", - .sof_fw_filename = "sof-icl.ri", + .drv_name = "sof_sdw", .sof_tplg_filename = "sof-icl-rt711-rt1308-rt715-mono.tplg", }, { .link_mask = 0x1, /* rt700 connected on link0 */ .links = icl_rvp, - .drv_name = "sdw_rt700", - .sof_fw_filename = "sof-icl.ri", + .drv_name = "sof_sdw", .sof_tplg_filename = "sof-icl-rt700.tplg", }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_icl_sdw_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-jsl-match.c b/sound/soc/intel/common/soc-acpi-intel-jsl-match.c index ed2b125f6a11..b95c4b2cda94 100644 --- a/sound/soc/intel/common/soc-acpi-intel-jsl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-jsl-match.c @@ -1,25 +1,98 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-apci-intel-jsl-match.c - tables and support for JSL ACPI enumeration. * - * Copyright (c) 2019, Intel Corporation. + * Copyright (c) 2019-2020, Intel Corporation. * */ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> +static const struct snd_soc_acpi_codecs essx_83x6 = { + .num_codecs = 3, + .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"}, +}; + +static const struct snd_soc_acpi_codecs jsl_7219_98373_codecs = { + .num_codecs = 1, + .codecs = {"MX98373"} +}; + +static const struct snd_soc_acpi_codecs rt1015_spk = { + .num_codecs = 1, + .codecs = {"10EC1015"} +}; + +static const struct snd_soc_acpi_codecs rt1015p_spk = { + .num_codecs = 1, + .codecs = {"RTL1015"} +}; + +static const struct snd_soc_acpi_codecs mx98360a_spk = { + .num_codecs = 1, + .codecs = {"MX98360A"} +}; + +static const struct snd_soc_acpi_codecs rt5682_rt5682s_hp = { + .num_codecs = 2, + .codecs = {"10EC5682", "RTL5682"}, +}; + +/* + * When adding new entry to the snd_soc_acpi_intel_jsl_machines array, + * use .quirk_data member to distinguish different machine driver, + * and keep ACPI .id field unchanged for the common codec. + */ struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[] = { { .id = "DLGS7219", - .drv_name = "sof_da7219_max98373", - .machine_quirk = snd_soc_acpi_codec_list, - .sof_fw_filename = "sof-jsl.ri", + .drv_name = "sof_da7219_mx98373", .sof_tplg_filename = "sof-jsl-da7219.tplg", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &jsl_7219_98373_codecs, + }, + { + .id = "DLGS7219", + .drv_name = "sof_da7219_mx98360a", + .sof_tplg_filename = "sof-jsl-da7219-mx98360a.tplg", + }, + { + .comp_ids = &rt5682_rt5682s_hp, + .drv_name = "jsl_rt5682_rt1015", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &rt1015_spk, + .sof_tplg_filename = "sof-jsl-rt5682-rt1015.tplg", + }, + { + .comp_ids = &rt5682_rt5682s_hp, + .drv_name = "jsl_rt5682_rt1015p", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &rt1015p_spk, + .sof_tplg_filename = "sof-jsl-rt5682-rt1015.tplg", + }, + { + .comp_ids = &rt5682_rt5682s_hp, + .drv_name = "jsl_rt5682_mx98360", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &mx98360a_spk, + .sof_tplg_filename = "sof-jsl-rt5682-mx98360a.tplg", + }, + { + .id = "10134242", + .drv_name = "jsl_cs4242_mx98360a", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &mx98360a_spk, + .sof_tplg_filename = "sof-jsl-cs42l42-mx98360a.tplg", + }, + { + .comp_ids = &essx_83x6, + .drv_name = "sof-essx8336", + .sof_tplg_filename = "sof-jsl-es8336", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | + SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | + SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_jsl_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-kbl-match.c b/sound/soc/intel/common/soc-acpi-intel-kbl-match.c index e200baa11011..4e817f559d38 100644 --- a/sound/soc/intel/common/soc-acpi-intel-kbl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-kbl-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-acpi-intel-kbl-match.c - tables and support for KBL ACPI enumeration. * @@ -12,32 +12,32 @@ static struct skl_machine_pdata skl_dmic_data; -static struct snd_soc_acpi_codecs kbl_codecs = { +static const struct snd_soc_acpi_codecs kbl_codecs = { .num_codecs = 1, .codecs = {"10508825"} }; -static struct snd_soc_acpi_codecs kbl_poppy_codecs = { +static const struct snd_soc_acpi_codecs kbl_poppy_codecs = { .num_codecs = 1, .codecs = {"10EC5663"} }; -static struct snd_soc_acpi_codecs kbl_5663_5514_codecs = { +static const struct snd_soc_acpi_codecs kbl_5663_5514_codecs = { .num_codecs = 2, .codecs = {"10EC5663", "10EC5514"} }; -static struct snd_soc_acpi_codecs kbl_7219_98357_codecs = { +static const struct snd_soc_acpi_codecs kbl_7219_98357_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; -static struct snd_soc_acpi_codecs kbl_7219_98927_codecs = { +static const struct snd_soc_acpi_codecs kbl_7219_98927_codecs = { .num_codecs = 1, .codecs = {"MX98927"} }; -static struct snd_soc_acpi_codecs kbl_7219_98373_codecs = { +static const struct snd_soc_acpi_codecs kbl_7219_98373_codecs = { .num_codecs = 1, .codecs = {"MX98373"} }; @@ -87,7 +87,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_kbl_machines[] = { }, { .id = "DLGS7219", - .drv_name = "kbl_da7219_max98357a", + .drv_name = "kbl_da7219_mx98357a", .fw_filename = "intel/dsp_fw_kbl.bin", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &kbl_7219_98357_codecs, @@ -113,7 +113,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_kbl_machines[] = { }, { .id = "DLGS7219", - .drv_name = "kbl_da7219_max98373", + .drv_name = "kbl_da7219_mx98373", .fw_filename = "intel/dsp_fw_kbl.bin", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &kbl_7219_98373_codecs, @@ -128,6 +128,3 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_kbl_machines[] = { {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_kbl_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c new file mode 100644 index 000000000000..36c361fb28a4 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * soc-acpi-intel-mtl-match.c - tables and support for MTL ACPI enumeration. + * + * Copyright (c) 2022, Intel Corporation. + * + */ + +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include "soc-acpi-intel-sdw-mockup-match.h" + +static const struct snd_soc_acpi_codecs mtl_max98357a_amp = { + .num_codecs = 1, + .codecs = {"MX98357A"} +}; + +static const struct snd_soc_acpi_codecs mtl_rt5682_rt5682s_hp = { + .num_codecs = 2, + .codecs = {"10EC5682", "RTL5682"}, +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_machines[] = { + { + .comp_ids = &mtl_rt5682_rt5682s_hp, + .drv_name = "mtl_mx98357_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &mtl_max98357a_amp, + .sof_tplg_filename = "sof-mtl-max98357a-rt5682.tplg", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_mtl_machines); + +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { + { + .adr = 0x000030025D071101ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_link_adr mtl_rvp[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + {} +}; + +/* this table is used when there is no I2S codec present */ +struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + /* mockup tests need to be first */ + { + .link_mask = GENMASK(3, 0), + .links = sdw_mockup_headset_2amps_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-rt1308-rt715.tplg", + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = sdw_mockup_headset_1amp_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-rt1308-mono-rt715.tplg", + }, + { + .link_mask = GENMASK(2, 0), + .links = sdw_mockup_mic_headset_1amp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt715-rt711-rt1308-mono.tplg", + }, + { + .link_mask = BIT(0), + .links = mtl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711.tplg", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_mtl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-rpl-match.c b/sound/soc/intel/common/soc-acpi-intel-rpl-match.c new file mode 100644 index 000000000000..9ccf7370157b --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-rpl-match.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * soc-apci-intel-rpl-match.c - tables and support for RPL ACPI enumeration. + * + * Copyright (c) 2022 Intel Corporation. + */ + +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> + +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_endpoint spk_l_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 0, + .group_id = 1, +}; + +static const struct snd_soc_acpi_endpoint spk_r_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 1, + .group_id = 1, +}; + +static const struct snd_soc_acpi_adr_device rt711_0_adr[] = { + { + .adr = 0x000020025D071100ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_link_adr rpl_rvp[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_0_adr), + .adr_d = rt711_0_adr, + }, + {} +}; + +static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { + { + .adr = 0x000030025D071101ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_1_group1_adr[] = { + { + .adr = 0x000131025D131601ull, /* unique ID is set for some reason */ + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1316-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_3_group1_adr[] = { + { + .adr = 0x000330025D131601ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1316-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt714_2_adr[] = { + { + .adr = 0x000230025D071401ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt714" + } +}; + +static const struct snd_soc_acpi_link_adr rpl_sdca_3_in_1[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group1_adr), + .adr_d = rt1316_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt714_2_adr), + .adr_d = rt714_2_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt1316_3_group1_adr), + .adr_d = rt1316_3_group1_adr, + }, + {} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_rpl_machines[] = { + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_rpl_machines); + +/* this table is used when there is no I2S codec present */ +struct snd_soc_acpi_mach snd_soc_acpi_intel_rpl_sdw_machines[] = { + { + .link_mask = 0xF, /* 4 active links required */ + .links = rpl_sdca_3_in_1, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-rpl-rt711-l0-rt1316-l13-rt714-l2.tplg", + }, + { + .link_mask = 0x1, /* link0 required */ + .links = rpl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-rpl-rt711.tplg", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_rpl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-sdw-mockup-match.c b/sound/soc/intel/common/soc-acpi-intel-sdw-mockup-match.c new file mode 100644 index 000000000000..a3d33997736a --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-sdw-mockup-match.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// soc-acpi-intel-sdw-mockup-match.c - tables and support for SoundWire +// mockup device ACPI enumeration. +// +// Copyright (c) 2021, Intel Corporation. +// + +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include "soc-acpi-intel-sdw-mockup-match.h" + +static const struct snd_soc_acpi_endpoint sdw_mockup_single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_endpoint sdw_mockup_l_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 0, + .group_id = 1, +}; + +static const struct snd_soc_acpi_endpoint sdw_mockup_r_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 1, + .group_id = 1, +}; + +static const struct snd_soc_acpi_adr_device sdw_mockup_headset_0_adr[] = { + { + .adr = 0x0000000105AA5500ull, + .num_endpoints = 1, + .endpoints = &sdw_mockup_single_endpoint, + .name_prefix = "sdw_mockup_headset0" + } +}; + +static const struct snd_soc_acpi_adr_device sdw_mockup_headset_1_adr[] = { + { + .adr = 0x0001000105AA5500ull, + .num_endpoints = 1, + .endpoints = &sdw_mockup_single_endpoint, + .name_prefix = "sdw_mockup_headset1" + } +}; + +static const struct snd_soc_acpi_adr_device sdw_mockup_amp_1_adr[] = { + { + .adr = 0x000100010555AA00ull, + .num_endpoints = 1, + .endpoints = &sdw_mockup_single_endpoint, + .name_prefix = "sdw_mockup_amp1" + } +}; + +static const struct snd_soc_acpi_adr_device sdw_mockup_amp_2_adr[] = { + { + .adr = 0x000200010555AA00ull, + .num_endpoints = 1, + .endpoints = &sdw_mockup_single_endpoint, + .name_prefix = "sdw_mockup_amp2" + } +}; + +static const struct snd_soc_acpi_adr_device sdw_mockup_mic_0_adr[] = { + { + .adr = 0x0000000105555500ull, + .num_endpoints = 1, + .endpoints = &sdw_mockup_single_endpoint, + .name_prefix = "sdw_mockup_mic0" + } +}; + +static const struct snd_soc_acpi_adr_device sdw_mockup_mic_3_adr[] = { + { + .adr = 0x0003000105555500ull, + .num_endpoints = 1, + .endpoints = &sdw_mockup_single_endpoint, + .name_prefix = "sdw_mockup_mic3" + } +}; + +static const struct snd_soc_acpi_adr_device sdw_mockup_amp_1_group1_adr[] = { + { + .adr = 0x000100010555AA00ull, + .num_endpoints = 1, + .endpoints = &sdw_mockup_l_endpoint, + .name_prefix = "sdw_mockup_amp1_l" + } +}; + +static const struct snd_soc_acpi_adr_device sdw_mockup_amp_2_group1_adr[] = { + { + .adr = 0x000200010555AA00ull, + .num_endpoints = 1, + .endpoints = &sdw_mockup_r_endpoint, + .name_prefix = "sdw_mockup_amp2_r" + } +}; + +const struct snd_soc_acpi_link_adr sdw_mockup_headset_1amp_mic[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(sdw_mockup_headset_0_adr), + .adr_d = sdw_mockup_headset_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(sdw_mockup_amp_1_adr), + .adr_d = sdw_mockup_amp_1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(sdw_mockup_mic_3_adr), + .adr_d = sdw_mockup_mic_3_adr, + }, + {} +}; + +const struct snd_soc_acpi_link_adr sdw_mockup_headset_2amps_mic[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(sdw_mockup_headset_0_adr), + .adr_d = sdw_mockup_headset_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(sdw_mockup_amp_1_group1_adr), + .adr_d = sdw_mockup_amp_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(sdw_mockup_amp_2_group1_adr), + .adr_d = sdw_mockup_amp_2_group1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(sdw_mockup_mic_3_adr), + .adr_d = sdw_mockup_mic_3_adr, + }, + {} +}; + +const struct snd_soc_acpi_link_adr sdw_mockup_mic_headset_1amp[] = { + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(sdw_mockup_headset_1_adr), + .adr_d = sdw_mockup_headset_1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(sdw_mockup_amp_2_adr), + .adr_d = sdw_mockup_amp_2_adr, + }, + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(sdw_mockup_mic_0_adr), + .adr_d = sdw_mockup_mic_0_adr, + }, + {} +}; diff --git a/sound/soc/intel/common/soc-acpi-intel-sdw-mockup-match.h b/sound/soc/intel/common/soc-acpi-intel-sdw-mockup-match.h new file mode 100644 index 000000000000..c99eecd19e03 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-sdw-mockup-match.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * soc-acpi-intel-sdw-mockup-match.h - tables and support for SoundWire + * mockup device ACPI enumeration. + * + * Copyright (c) 2021, Intel Corporation. + * + */ + +#ifndef _SND_SOC_ACPI_INTEL_SDW_MOCKUP_MATCH +#define _SND_SOC_ACPI_INTEL_SDW_MOCKUP_MATCH + +extern const struct snd_soc_acpi_link_adr sdw_mockup_headset_1amp_mic[]; +extern const struct snd_soc_acpi_link_adr sdw_mockup_headset_2amps_mic[]; +extern const struct snd_soc_acpi_link_adr sdw_mockup_mic_headset_1amp[]; + +#endif diff --git a/sound/soc/intel/common/soc-acpi-intel-skl-match.c b/sound/soc/intel/common/soc-acpi-intel-skl-match.c index 42fa40a8d932..75302e956742 100644 --- a/sound/soc/intel/common/soc-acpi-intel-skl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-skl-match.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * soc-acpi-intel-skl-match.c - tables and support for SKL ACPI enumeration. * @@ -12,7 +12,7 @@ static struct skl_machine_pdata skl_dmic_data; -static struct snd_soc_acpi_codecs skl_codecs = { +static const struct snd_soc_acpi_codecs skl_codecs = { .num_codecs = 1, .codecs = {"10508825"} }; @@ -42,6 +42,3 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_skl_machines[] = { {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_skl_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c index 5984dd151f3e..ef19150e7b2e 100644 --- a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c @@ -1,6 +1,6 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* - * soc-apci-intel-tgl-match.c - tables and support for ICL ACPI enumeration. + * soc-acpi-intel-tgl-match.c - tables and support for TGL ACPI enumeration. * * Copyright (c) 2019, Intel Corporation. * @@ -8,77 +8,499 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> +#include "soc-acpi-intel-sdw-mockup-match.h" -static struct snd_soc_acpi_codecs tgl_codecs = { +static const struct snd_soc_acpi_codecs essx_83x6 = { + .num_codecs = 3, + .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"}, +}; + +static const struct snd_soc_acpi_codecs tgl_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; -static const u64 rt711_0_adr[] = { - 0x000010025D071100 +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_endpoint spk_l_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 0, + .group_id = 1, +}; + +static const struct snd_soc_acpi_endpoint spk_r_endpoint = { + .num = 0, + .aggregated = 1, + .group_position = 1, + .group_id = 1, +}; + +static const struct snd_soc_acpi_adr_device rt711_0_adr[] = { + { + .adr = 0x000020025D071100ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_adr_device rt711_1_adr[] = { + { + .adr = 0x000120025D071100ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_1_dual_adr[] = { + { + .adr = 0x000120025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1308-1" + }, + { + .adr = 0x000122025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1308-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_1_single_adr[] = { + { + .adr = 0x000120025D130800ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1308-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_2_single_adr[] = { + { + .adr = 0x000220025D130800ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1308-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_1_group1_adr[] = { + { + .adr = 0x000120025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1308-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1308_2_group1_adr[] = { + { + .adr = 0x000220025D130800ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1308-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt715_0_adr[] = { + { + .adr = 0x000021025D071500ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt715" + } +}; + +static const struct snd_soc_acpi_adr_device rt715_3_adr[] = { + { + .adr = 0x000320025D071500ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt715" + } +}; + +static const struct snd_soc_acpi_adr_device mx8373_1_adr[] = { + { + .adr = 0x000123019F837300ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "Right" + }, + { + .adr = 0x000127019F837300ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "Left" + } +}; + +static const struct snd_soc_acpi_adr_device rt5682_0_adr[] = { + { + .adr = 0x000021025D568200ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt5682" + } +}; + +static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { + { + .adr = 0x000030025D071101ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } }; -static const u64 rt1308_1_adr[] = { - 0x000120025D130800, - 0x000122025D130800 +static const struct snd_soc_acpi_adr_device rt1316_1_single_adr[] = { + { + .adr = 0x000131025D131601ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1316-1" + } }; -static const struct snd_soc_acpi_link_adr tgl_i2s_rt1308[] = { +static const struct snd_soc_acpi_adr_device rt1316_1_group1_adr[] = { + { + .adr = 0x000131025D131601ull, /* unique ID is set for some reason */ + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "rt1316-1" + } +}; + +static const struct snd_soc_acpi_adr_device rt1316_2_group1_adr[] = { + { + .adr = 0x000230025D131601ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "rt1316-2" + } +}; + +static const struct snd_soc_acpi_adr_device rt714_3_adr[] = { + { + .adr = 0x000330025D071401ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt714" + } +}; + +static const struct snd_soc_acpi_link_adr tgl_rvp[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_0_adr), + .adr_d = rt711_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1308_1_dual_adr), + .adr_d = rt1308_1_dual_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr tgl_rvp_headset_only[] = { { .mask = BIT(0), .num_adr = ARRAY_SIZE(rt711_0_adr), - .adr = rt711_0_adr, + .adr_d = rt711_0_adr, }, {} }; -static const struct snd_soc_acpi_link_adr tgl_rvp[] = { +static const struct snd_soc_acpi_link_adr tgl_hp[] = { { .mask = BIT(0), .num_adr = ARRAY_SIZE(rt711_0_adr), - .adr = rt711_0_adr, + .adr_d = rt711_0_adr, }, { .mask = BIT(1), - .num_adr = ARRAY_SIZE(rt1308_1_adr), - .adr = rt1308_1_adr, + .num_adr = ARRAY_SIZE(rt1308_1_single_adr), + .adr_d = rt1308_1_single_adr, }, {} }; -struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_machines[] = { +static const struct snd_soc_acpi_link_adr tgl_chromebook_base[] = { { - .id = "10EC1308", - .drv_name = "rt711_rt1308", - .link_mask = 0x1, /* RT711 on SoundWire link0 */ - .links = tgl_i2s_rt1308, - .sof_fw_filename = "sof-tgl.ri", - .sof_tplg_filename = "sof-tgl-rt711-rt1308.tplg", + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt5682_0_adr), + .adr_d = rt5682_0_adr, }, { - .id = "10EC5682", - .drv_name = "tgl_max98357a_rt5682", + .mask = BIT(1), + .num_adr = ARRAY_SIZE(mx8373_1_adr), + .adr_d = mx8373_1_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr tgl_3_in_1_default[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_0_adr), + .adr_d = rt711_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1308_1_group1_adr), + .adr_d = rt1308_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1308_2_group1_adr), + .adr_d = rt1308_2_group1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt715_3_adr), + .adr_d = rt715_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr tgl_3_in_1_mono_amp[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_0_adr), + .adr_d = rt711_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1308_1_single_adr), + .adr_d = rt1308_1_single_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt715_3_adr), + .adr_d = rt715_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr tgl_sdw_rt711_link1_rt1308_link2_rt715_link0[] = { + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt711_1_adr), + .adr_d = rt711_1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1308_2_single_adr), + .adr_d = rt1308_2_single_adr, + }, + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt715_0_adr), + .adr_d = rt715_0_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr tgl_3_in_1_sdca[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group1_adr), + .adr_d = rt1316_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1316_2_group1_adr), + .adr_d = rt1316_2_group1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt714_3_adr), + .adr_d = rt714_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr tgl_3_in_1_sdca_mono[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_single_adr), + .adr_d = rt1316_1_single_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt714_3_adr), + .adr_d = rt714_3_adr, + }, + {} +}; + +static const struct snd_soc_acpi_codecs tgl_max98373_amp = { + .num_codecs = 1, + .codecs = {"MX98373"} +}; + +static const struct snd_soc_acpi_codecs tgl_rt1011_amp = { + .num_codecs = 1, + .codecs = {"10EC1011"} +}; + +static const struct snd_soc_acpi_codecs tgl_rt5682_rt5682s_hp = { + .num_codecs = 2, + .codecs = {"10EC5682", "RTL5682"}, +}; + +static const struct snd_soc_acpi_codecs tgl_lt6911_hdmi = { + .num_codecs = 1, + .codecs = {"INTC10B0"} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_machines[] = { + { + .comp_ids = &tgl_rt5682_rt5682s_hp, + .drv_name = "tgl_mx98357_rt5682", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &tgl_codecs, - .sof_fw_filename = "sof-tgl.ri", .sof_tplg_filename = "sof-tgl-max98357a-rt5682.tplg", }, + { + .comp_ids = &tgl_rt5682_rt5682s_hp, + .drv_name = "tgl_mx98373_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &tgl_max98373_amp, + .sof_tplg_filename = "sof-tgl-max98373-rt5682.tplg", + }, + { + .comp_ids = &tgl_rt5682_rt5682s_hp, + .drv_name = "tgl_rt1011_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &tgl_rt1011_amp, + .sof_tplg_filename = "sof-tgl-rt1011-rt5682.tplg", + }, + { + .comp_ids = &essx_83x6, + .drv_name = "sof-essx8336", + .sof_tplg_filename = "sof-tgl-es8336", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | + SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | + SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, + }, + { + .id = "10EC1308", + .drv_name = "tgl_rt1308_hdmi_ssp", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &tgl_lt6911_hdmi, + .sof_tplg_filename = "sof-tgl-rt1308-ssp2-hdmi-ssp15.tplg" + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_tgl_machines); /* this table is used when there is no I2S codec present */ struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_sdw_machines[] = { + /* mockup tests need to be first */ + { + .link_mask = GENMASK(3, 0), + .links = sdw_mockup_headset_2amps_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711-rt1308-rt715.tplg", + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = sdw_mockup_headset_1amp_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711-rt1308-mono-rt715.tplg", + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(2), + .links = sdw_mockup_mic_headset_1amp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt715-rt711-rt1308-mono.tplg", + }, + { + .link_mask = 0x7, + .links = tgl_sdw_rt711_link1_rt1308_link2_rt715_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt715-rt711-rt1308-mono.tplg", + }, + { + .link_mask = 0xF, /* 4 active links required */ + .links = tgl_3_in_1_default, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711-rt1308-rt715.tplg", + }, + { + /* + * link_mask should be 0xB, but all links are enabled by BIOS. + * This entry will be selected if there is no rt1308 exposed + * on link2 since it will fail to match the above entry. + */ + .link_mask = 0xF, + .links = tgl_3_in_1_mono_amp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711-rt1308-mono-rt715.tplg", + }, + { + .link_mask = 0xF, /* 4 active links required */ + .links = tgl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711-rt1316-rt714.tplg", + }, + { + /* + * link_mask should be 0xB, but all links are enabled by BIOS. + * This entry will be selected if there is no rt1316 amplifier exposed + * on link2 since it will fail to match the above entry. + */ + + .link_mask = 0xF, /* 4 active links required */ + .links = tgl_3_in_1_sdca_mono, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711-l0-rt1316-l1-mono-rt714-l3.tplg", + }, + + { + .link_mask = 0x3, /* rt711 on link 0 and 1 rt1308 on link 1 */ + .links = tgl_hp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711-rt1308.tplg", + }, { .link_mask = 0x3, /* rt711 on link 0 and 2 rt1308s on link 1 */ .links = tgl_rvp, - .drv_name = "sdw_rt711_rt1308_rt715", - .sof_fw_filename = "sof-tgl.ri", + .drv_name = "sof_sdw", .sof_tplg_filename = "sof-tgl-rt711-rt1308.tplg", }, + { + .link_mask = 0x3, /* rt5682 on link0 & 2xmax98373 on link 1 */ + .links = tgl_chromebook_base, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-sdw-max98373-rt5682.tplg", + }, + { + .link_mask = 0x1, /* rt711 on link 0 */ + .links = tgl_rvp_headset_only, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_tgl_sdw_machines); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-intel-quirks.h b/sound/soc/intel/common/soc-intel-quirks.h index 863a477d3405..de4e550c5b34 100644 --- a/sound/soc/intel/common/soc-intel-quirks.h +++ b/sound/soc/intel/common/soc-intel-quirks.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * soc-intel-quirks.h - prototypes for quirk autodetection * @@ -9,43 +9,45 @@ #ifndef _SND_SOC_INTEL_QUIRKS_H #define _SND_SOC_INTEL_QUIRKS_H +#include <linux/platform_data/x86/soc.h> + #if IS_ENABLED(CONFIG_X86) -#include <asm/cpu_device_id.h> -#include <asm/intel-family.h> +#include <linux/dmi.h> #include <asm/iosf_mbi.h> -#define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, } - -#define SOC_INTEL_IS_CPU(soc, type) \ -static inline bool soc_intel_is_##soc(void) \ -{ \ - static const struct x86_cpu_id soc##_cpu_ids[] = { \ - ICPU(type), \ - {} \ - }; \ - const struct x86_cpu_id *id; \ - \ - id = x86_match_cpu(soc##_cpu_ids); \ - if (id) \ - return true; \ - return false; \ -} - -SOC_INTEL_IS_CPU(byt, INTEL_FAM6_ATOM_SILVERMONT); -SOC_INTEL_IS_CPU(cht, INTEL_FAM6_ATOM_AIRMONT); -SOC_INTEL_IS_CPU(apl, INTEL_FAM6_ATOM_GOLDMONT); -SOC_INTEL_IS_CPU(glk, INTEL_FAM6_ATOM_GOLDMONT_PLUS); -SOC_INTEL_IS_CPU(cml, INTEL_FAM6_KABYLAKE_L); - static inline bool soc_intel_is_byt_cr(struct platform_device *pdev) { + /* + * List of systems which: + * 1. Use a non CR version of the Bay Trail SoC + * 2. Contain at least 6 interrupt resources so that the + * platform_get_resource(pdev, IORESOURCE_IRQ, 5) check below + * succeeds + * 3. Despite 1. and 2. still have their IPC IRQ at index 0 rather then 5 + * + * This needs to be here so that it can be shared between the SST and + * SOF drivers. We rely on the compiler to optimize this out in files + * where soc_intel_is_byt_cr is not used. + */ + static const struct dmi_system_id force_bytcr_table[] = { + { /* Lenovo Yoga Tablet 2 series */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "YOGATablet2"), + }, + }, + {} + }; struct device *dev = &pdev->dev; int status = 0; if (!soc_intel_is_byt()) return false; + if (dmi_check_system(force_bytcr_table)) + return true; + if (iosf_mbi_available()) { u32 bios_status; @@ -91,30 +93,6 @@ static inline bool soc_intel_is_byt_cr(struct platform_device *pdev) return false; } -static inline bool soc_intel_is_byt(void) -{ - return false; -} - -static inline bool soc_intel_is_cht(void) -{ - return false; -} - -static inline bool soc_intel_is_apl(void) -{ - return false; -} - -static inline bool soc_intel_is_glk(void) -{ - return false; -} - -static inline bool soc_intel_is_cml(void) -{ - return false; -} #endif - #endif /* _SND_SOC_INTEL_QUIRKS_H */ +#endif /* _SND_SOC_INTEL_QUIRKS_H */ diff --git a/sound/soc/intel/common/sst-acpi.c b/sound/soc/intel/common/sst-acpi.c deleted file mode 100644 index 5854868650b9..000000000000 --- a/sound/soc/intel/common/sst-acpi.c +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel SST loader on ACPI systems - * - * Copyright (C) 2013, Intel Corporation. All rights reserved. - */ - -#include <linux/acpi.h> -#include <linux/device.h> -#include <linux/firmware.h> -#include <linux/module.h> -#include <linux/platform_device.h> - -#include "sst-dsp.h" -#include <sound/soc-acpi.h> -#include <sound/soc-acpi-intel-match.h> - -#define SST_LPT_DSP_DMA_ADDR_OFFSET 0x0F0000 -#define SST_WPT_DSP_DMA_ADDR_OFFSET 0x0FE000 -#define SST_LPT_DSP_DMA_SIZE (1024 - 1) - -/* Descriptor for setting up SST platform data */ -struct sst_acpi_desc { - const char *drv_name; - struct snd_soc_acpi_mach *machines; - /* Platform resource indexes. Must set to -1 if not used */ - int resindex_lpe_base; - int resindex_pcicfg_base; - int resindex_fw_base; - int irqindex_host_ipc; - int resindex_dma_base; - /* Unique number identifying the SST core on platform */ - int sst_id; - /* DMA only valid when resindex_dma_base != -1*/ - int dma_engine; - int dma_size; -}; - -struct sst_acpi_priv { - struct platform_device *pdev_mach; - struct platform_device *pdev_pcm; - struct sst_pdata sst_pdata; - struct sst_acpi_desc *desc; - struct snd_soc_acpi_mach *mach; -}; - -static void sst_acpi_fw_cb(const struct firmware *fw, void *context) -{ - struct platform_device *pdev = context; - struct device *dev = &pdev->dev; - struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); - struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; - struct sst_acpi_desc *desc = sst_acpi->desc; - struct snd_soc_acpi_mach *mach = sst_acpi->mach; - - sst_pdata->fw = fw; - if (!fw) { - dev_err(dev, "Cannot load firmware %s\n", mach->fw_filename); - return; - } - - /* register PCM and DAI driver */ - sst_acpi->pdev_pcm = - platform_device_register_data(dev, desc->drv_name, -1, - sst_pdata, sizeof(*sst_pdata)); - if (IS_ERR(sst_acpi->pdev_pcm)) { - dev_err(dev, "Cannot register device %s. Error %d\n", - desc->drv_name, (int)PTR_ERR(sst_acpi->pdev_pcm)); - } - - return; -} - -static int sst_acpi_probe(struct platform_device *pdev) -{ - const struct acpi_device_id *id; - struct device *dev = &pdev->dev; - struct sst_acpi_priv *sst_acpi; - struct sst_pdata *sst_pdata; - struct snd_soc_acpi_mach *mach; - struct sst_acpi_desc *desc; - struct resource *mmio; - int ret = 0; - - sst_acpi = devm_kzalloc(dev, sizeof(*sst_acpi), GFP_KERNEL); - if (sst_acpi == NULL) - return -ENOMEM; - - id = acpi_match_device(dev->driver->acpi_match_table, dev); - if (!id) - return -ENODEV; - - desc = (struct sst_acpi_desc *)id->driver_data; - mach = snd_soc_acpi_find_machine(desc->machines); - if (mach == NULL) { - dev_err(dev, "No matching ASoC machine driver found\n"); - return -ENODEV; - } - - sst_pdata = &sst_acpi->sst_pdata; - sst_pdata->id = desc->sst_id; - sst_pdata->dma_dev = dev; - sst_acpi->desc = desc; - sst_acpi->mach = mach; - - sst_pdata->resindex_dma_base = desc->resindex_dma_base; - if (desc->resindex_dma_base >= 0) { - sst_pdata->dma_engine = desc->dma_engine; - sst_pdata->dma_base = desc->resindex_dma_base; - sst_pdata->dma_size = desc->dma_size; - } - - if (desc->irqindex_host_ipc >= 0) - sst_pdata->irq = platform_get_irq(pdev, desc->irqindex_host_ipc); - - if (desc->resindex_lpe_base >= 0) { - mmio = platform_get_resource(pdev, IORESOURCE_MEM, - desc->resindex_lpe_base); - if (mmio) { - sst_pdata->lpe_base = mmio->start; - sst_pdata->lpe_size = resource_size(mmio); - } - } - - if (desc->resindex_pcicfg_base >= 0) { - mmio = platform_get_resource(pdev, IORESOURCE_MEM, - desc->resindex_pcicfg_base); - if (mmio) { - sst_pdata->pcicfg_base = mmio->start; - sst_pdata->pcicfg_size = resource_size(mmio); - } - } - - if (desc->resindex_fw_base >= 0) { - mmio = platform_get_resource(pdev, IORESOURCE_MEM, - desc->resindex_fw_base); - if (mmio) { - sst_pdata->fw_base = mmio->start; - sst_pdata->fw_size = resource_size(mmio); - } - } - - platform_set_drvdata(pdev, sst_acpi); - mach->pdata = sst_pdata; - - /* register machine driver */ - sst_acpi->pdev_mach = - platform_device_register_data(dev, mach->drv_name, -1, - mach, sizeof(*mach)); - if (IS_ERR(sst_acpi->pdev_mach)) - return PTR_ERR(sst_acpi->pdev_mach); - - /* continue SST probing after firmware is loaded */ - ret = request_firmware_nowait(THIS_MODULE, true, mach->fw_filename, - dev, GFP_KERNEL, pdev, sst_acpi_fw_cb); - if (ret) - platform_device_unregister(sst_acpi->pdev_mach); - - return ret; -} - -static int sst_acpi_remove(struct platform_device *pdev) -{ - struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); - struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; - - platform_device_unregister(sst_acpi->pdev_mach); - if (!IS_ERR_OR_NULL(sst_acpi->pdev_pcm)) - platform_device_unregister(sst_acpi->pdev_pcm); - release_firmware(sst_pdata->fw); - - return 0; -} - -static struct sst_acpi_desc sst_acpi_haswell_desc = { - .drv_name = "haswell-pcm-audio", - .machines = snd_soc_acpi_intel_haswell_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = 1, - .resindex_fw_base = -1, - .irqindex_host_ipc = 0, - .sst_id = SST_DEV_ID_LYNX_POINT, - .dma_engine = SST_DMA_TYPE_DW, - .resindex_dma_base = SST_LPT_DSP_DMA_ADDR_OFFSET, - .dma_size = SST_LPT_DSP_DMA_SIZE, -}; - -static struct sst_acpi_desc sst_acpi_broadwell_desc = { - .drv_name = "haswell-pcm-audio", - .machines = snd_soc_acpi_intel_broadwell_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = 1, - .resindex_fw_base = -1, - .irqindex_host_ipc = 0, - .sst_id = SST_DEV_ID_WILDCAT_POINT, - .dma_engine = SST_DMA_TYPE_DW, - .resindex_dma_base = SST_WPT_DSP_DMA_ADDR_OFFSET, - .dma_size = SST_LPT_DSP_DMA_SIZE, -}; - -#if !IS_ENABLED(CONFIG_SND_SST_IPC_ACPI) -static struct sst_acpi_desc sst_acpi_baytrail_desc = { - .drv_name = "baytrail-pcm-audio", - .machines = snd_soc_acpi_intel_baytrail_legacy_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = 1, - .resindex_fw_base = 2, - .irqindex_host_ipc = 5, - .sst_id = SST_DEV_ID_BYT, - .resindex_dma_base = -1, -}; -#endif - -static const struct acpi_device_id sst_acpi_match[] = { - { "INT33C8", (unsigned long)&sst_acpi_haswell_desc }, - { "INT3438", (unsigned long)&sst_acpi_broadwell_desc }, -#if !IS_ENABLED(CONFIG_SND_SST_IPC_ACPI) - { "80860F28", (unsigned long)&sst_acpi_baytrail_desc }, -#endif - { } -}; -MODULE_DEVICE_TABLE(acpi, sst_acpi_match); - -static struct platform_driver sst_acpi_driver = { - .probe = sst_acpi_probe, - .remove = sst_acpi_remove, - .driver = { - .name = "sst-acpi", - .acpi_match_table = ACPI_PTR(sst_acpi_match), - }, -}; -module_platform_driver(sst_acpi_driver); - -MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>"); -MODULE_DESCRIPTION("Intel SST loader on ACPI systems"); -MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-dsp-priv.h b/sound/soc/intel/common/sst-dsp-priv.h index 3d8765ce3e0d..de2b44568feb 100644 --- a/sound/soc/intel/common/sst-dsp-priv.h +++ b/sound/soc/intel/common/sst-dsp-priv.h @@ -15,67 +15,32 @@ #include "../skylake/skl-sst-dsp.h" -struct sst_mem_block; -struct sst_module; -struct sst_fw; - -/* do we need to remove or keep */ -#define DSP_DRAM_ADDR_OFFSET 0x400000 - /* * DSP Operations exported by platform Audio DSP driver. */ struct sst_ops { - /* DSP core boot / reset */ - void (*boot)(struct sst_dsp *); - void (*reset)(struct sst_dsp *); - int (*wake)(struct sst_dsp *); - void (*sleep)(struct sst_dsp *); - void (*stall)(struct sst_dsp *); - /* Shim IO */ void (*write)(void __iomem *addr, u32 offset, u32 value); u32 (*read)(void __iomem *addr, u32 offset); - void (*write64)(void __iomem *addr, u32 offset, u64 value); - u64 (*read64)(void __iomem *addr, u32 offset); - - /* DSP I/DRAM IO */ - void (*ram_read)(struct sst_dsp *sst, void *dest, void __iomem *src, - size_t bytes); - void (*ram_write)(struct sst_dsp *sst, void __iomem *dest, void *src, - size_t bytes); - - void (*dump)(struct sst_dsp *); /* IRQ handlers */ irqreturn_t (*irq_handler)(int irq, void *context); /* SST init and free */ - int (*init)(struct sst_dsp *sst, struct sst_pdata *pdata); + int (*init)(struct sst_dsp *sst); void (*free)(struct sst_dsp *sst); - - /* FW module parser/loader */ - int (*parse_fw)(struct sst_fw *sst_fw); }; /* * Audio DSP memory offsets and addresses. */ struct sst_addr { - u32 lpe_base; - u32 shim_offset; - u32 iram_offset; - u32 dram_offset; - u32 dsp_iram_offset; - u32 dsp_dram_offset; u32 sram0_base; u32 sram1_base; u32 w0_stat_sz; u32 w0_up_sz; void __iomem *lpe; void __iomem *shim; - void __iomem *pci_cfg; - void __iomem *fw_ext; }; /* @@ -89,168 +54,6 @@ struct sst_mailbox { }; /* - * Audio DSP memory block types. - */ -enum sst_mem_type { - SST_MEM_IRAM = 0, - SST_MEM_DRAM = 1, - SST_MEM_ANY = 2, - SST_MEM_CACHE= 3, -}; - -/* - * Audio DSP Generic Firmware File. - * - * SST Firmware files can consist of 1..N modules. This generic structure is - * used to manage each firmware file and it's modules regardless of SST firmware - * type. A SST driver may load multiple FW files. - */ -struct sst_fw { - struct sst_dsp *dsp; - - /* base addresses of FW file data */ - dma_addr_t dmable_fw_paddr; /* physical address of fw data */ - void *dma_buf; /* virtual address of fw data */ - u32 size; /* size of fw data */ - - /* lists */ - struct list_head list; /* DSP list of FW */ - struct list_head module_list; /* FW list of modules */ - - void *private; /* core doesn't touch this */ -}; - -/* - * Audio DSP Generic Module Template. - * - * Used to define and register a new FW module. This data is extracted from - * FW module header information. - */ -struct sst_module_template { - u32 id; - u32 entry; /* entry point */ - u32 scratch_size; - u32 persistent_size; -}; - -/* - * Block Allocator - Used to allocate blocks of DSP memory. - */ -struct sst_block_allocator { - u32 id; - u32 offset; - int size; - enum sst_mem_type type; -}; - -/* - * Runtime Module Instance - A module object can be instantiated multiple - * times within the DSP FW. - */ -struct sst_module_runtime { - struct sst_dsp *dsp; - int id; - struct sst_module *module; /* parent module we belong too */ - - u32 persistent_offset; /* private memory offset */ - void *private; - - struct list_head list; - struct list_head block_list; /* list of blocks used */ -}; - -/* - * Runtime Module Context - The runtime context must be manually stored by the - * driver prior to enter S3 and restored after leaving S3. This should really be - * part of the memory context saved by the enter D3 message IPC ??? - */ -struct sst_module_runtime_context { - dma_addr_t dma_buffer; - u32 *buffer; -}; - -/* - * Audio DSP Module State - */ -enum sst_module_state { - SST_MODULE_STATE_UNLOADED = 0, /* default state */ - SST_MODULE_STATE_LOADED, - SST_MODULE_STATE_INITIALIZED, /* and inactive */ - SST_MODULE_STATE_ACTIVE, -}; - -/* - * Audio DSP Generic Module. - * - * Each Firmware file can consist of 1..N modules. A module can span multiple - * ADSP memory blocks. The simplest FW will be a file with 1 module. A module - * can be instantiated multiple times in the DSP. - */ -struct sst_module { - struct sst_dsp *dsp; - struct sst_fw *sst_fw; /* parent FW we belong too */ - - /* module configuration */ - u32 id; - u32 entry; /* module entry point */ - s32 offset; /* module offset in firmware file */ - u32 size; /* module size */ - u32 scratch_size; /* global scratch memory required */ - u32 persistent_size; /* private memory required */ - enum sst_mem_type type; /* destination memory type */ - u32 data_offset; /* offset in ADSP memory space */ - void *data; /* module data */ - - /* runtime */ - u32 usage_count; /* can be unloaded if count == 0 */ - void *private; /* core doesn't touch this */ - - /* lists */ - struct list_head block_list; /* Module list of blocks in use */ - struct list_head list; /* DSP list of modules */ - struct list_head list_fw; /* FW list of modules */ - struct list_head runtime_list; /* list of runtime module objects*/ - - /* state */ - enum sst_module_state state; -}; - -/* - * SST Memory Block operations. - */ -struct sst_block_ops { - int (*enable)(struct sst_mem_block *block); - int (*disable)(struct sst_mem_block *block); -}; - -/* - * SST Generic Memory Block. - * - * SST ADP memory has multiple IRAM and DRAM blocks. Some ADSP blocks can be - * power gated. - */ -struct sst_mem_block { - struct sst_dsp *dsp; - struct sst_module *module; /* module that uses this block */ - - /* block config */ - u32 offset; /* offset from base */ - u32 size; /* block size */ - u32 index; /* block index 0..N */ - enum sst_mem_type type; /* block memory type IRAM/DRAM */ - const struct sst_block_ops *ops;/* block operations, if any */ - - /* block status */ - u32 bytes_used; /* bytes in use by modules */ - void *private; /* generic core does not touch this */ - int users; /* number of modules using this block */ - - /* block lists */ - struct list_head module_list; /* Module list of blocks */ - struct list_head list; /* Map list of free/used blocks */ -}; - -/* * Generic SST Shim Interface. */ struct sst_dsp { @@ -262,7 +65,6 @@ struct sst_dsp { spinlock_t spinlock; /* IPC locking */ struct mutex mutex; /* DSP FW lock */ struct device *dev; - struct device *dma_dev; void *thread_context; int irq; u32 id; @@ -279,27 +81,8 @@ struct sst_dsp { /* mailbox */ struct sst_mailbox mailbox; - /* HSW/Byt data */ - - /* list of free and used ADSP memory blocks */ - struct list_head used_block_list; - struct list_head free_block_list; - /* SST FW files loaded and their modules */ struct list_head module_list; - struct list_head fw_list; - - /* scratch buffer */ - struct list_head scratch_block_list; - u32 scratch_offset; - u32 scratch_size; - - /* platform data */ - struct sst_pdata *pdata; - - /* DMA FW loading */ - struct sst_dma *dma; - bool fw_use_dma; /* SKL data */ @@ -315,69 +98,4 @@ struct sst_dsp { struct snd_dma_buffer dmab; }; -/* Size optimised DRAM/IRAM memcpy */ -static inline void sst_dsp_write(struct sst_dsp *sst, void *src, - u32 dest_offset, size_t bytes) -{ - sst->ops->ram_write(sst, sst->addr.lpe + dest_offset, src, bytes); -} - -static inline void sst_dsp_read(struct sst_dsp *sst, void *dest, - u32 src_offset, size_t bytes) -{ - sst->ops->ram_read(sst, dest, sst->addr.lpe + src_offset, bytes); -} - -static inline void *sst_dsp_get_thread_context(struct sst_dsp *sst) -{ - return sst->thread_context; -} - -/* Create/Free FW files - can contain multiple modules */ -struct sst_fw *sst_fw_new(struct sst_dsp *dsp, - const struct firmware *fw, void *private); -void sst_fw_free(struct sst_fw *sst_fw); -void sst_fw_free_all(struct sst_dsp *dsp); -int sst_fw_reload(struct sst_fw *sst_fw); -void sst_fw_unload(struct sst_fw *sst_fw); - -/* Create/Free firmware modules */ -struct sst_module *sst_module_new(struct sst_fw *sst_fw, - struct sst_module_template *template, void *private); -void sst_module_free(struct sst_module *module); -struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id); -int sst_module_alloc_blocks(struct sst_module *module); -int sst_module_free_blocks(struct sst_module *module); - -/* Create/Free firmware module runtime instances */ -struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, - int id, void *private); -void sst_module_runtime_free(struct sst_module_runtime *runtime); -struct sst_module_runtime *sst_module_runtime_get_from_id( - struct sst_module *module, u32 id); -int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, - int offset); -int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime); -int sst_module_runtime_save(struct sst_module_runtime *runtime, - struct sst_module_runtime_context *context); -int sst_module_runtime_restore(struct sst_module_runtime *runtime, - struct sst_module_runtime_context *context); - -/* generic block allocation */ -int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, - struct list_head *block_list); -int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list); - -/* scratch allocation */ -int sst_block_alloc_scratch(struct sst_dsp *dsp); -void sst_block_free_scratch(struct sst_dsp *dsp); - -/* Register the DSPs memory blocks - would be nice to read from ACPI */ -struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, - u32 size, enum sst_mem_type type, const struct sst_block_ops *ops, - u32 index, void *private); -void sst_mem_block_unregister_all(struct sst_dsp *dsp); - -u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, - enum sst_mem_type type); #endif diff --git a/sound/soc/intel/common/sst-dsp.c b/sound/soc/intel/common/sst-dsp.c index ec66be269b69..229d09d61afb 100644 --- a/sound/soc/intel/common/sst-dsp.c +++ b/sound/soc/intel/common/sst-dsp.c @@ -10,7 +10,7 @@ #include <linux/interrupt.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/io.h> +#include <linux/io-64-nonatomic-lo-hi.h> #include <linux/delay.h> #include "sst-dsp.h" @@ -34,51 +34,16 @@ EXPORT_SYMBOL_GPL(sst_shim32_read); void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value) { - memcpy_toio(addr + offset, &value, sizeof(value)); + writeq(value, addr + offset); } EXPORT_SYMBOL_GPL(sst_shim32_write64); u64 sst_shim32_read64(void __iomem *addr, u32 offset) { - u64 val; - - memcpy_fromio(&val, addr + offset, sizeof(val)); - return val; + return readq(addr + offset); } EXPORT_SYMBOL_GPL(sst_shim32_read64); -static inline void _sst_memcpy_toio_32(volatile u32 __iomem *dest, - u32 *src, size_t bytes) -{ - int i, words = bytes >> 2; - - for (i = 0; i < words; i++) - writel(src[i], dest + i); -} - -static inline void _sst_memcpy_fromio_32(u32 *dest, - const volatile __iomem u32 *src, size_t bytes) -{ - int i, words = bytes >> 2; - - for (i = 0; i < words; i++) - dest[i] = readl(src + i); -} - -void sst_memcpy_toio_32(struct sst_dsp *sst, - void __iomem *dest, void *src, size_t bytes) -{ - _sst_memcpy_toio_32(dest, src, bytes); -} -EXPORT_SYMBOL_GPL(sst_memcpy_toio_32); - -void sst_memcpy_fromio_32(struct sst_dsp *sst, void *dest, - void __iomem *src, size_t bytes) -{ - _sst_memcpy_fromio_32(dest, src, bytes); -} -EXPORT_SYMBOL_GPL(sst_memcpy_fromio_32); - /* Public API */ void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value) { @@ -103,29 +68,6 @@ u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset) } EXPORT_SYMBOL_GPL(sst_dsp_shim_read); -void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value) -{ - unsigned long flags; - - spin_lock_irqsave(&sst->spinlock, flags); - sst->ops->write64(sst->addr.shim, offset, value); - spin_unlock_irqrestore(&sst->spinlock, flags); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_write64); - -u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset) -{ - unsigned long flags; - u64 val; - - spin_lock_irqsave(&sst->spinlock, flags); - val = sst->ops->read64(sst->addr.shim, offset); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return val; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_read64); - void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value) { sst->ops->write(sst->addr.shim, offset, value); @@ -138,18 +80,6 @@ u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset) } EXPORT_SYMBOL_GPL(sst_dsp_shim_read_unlocked); -void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value) -{ - sst->ops->write64(sst->addr.shim, offset, value); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_write64_unlocked); - -u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset) -{ - return sst->ops->read64(sst->addr.shim, offset); -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_read64_unlocked); - int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, u32 mask, u32 value) { @@ -170,24 +100,6 @@ int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, } EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_unlocked); -int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, - u64 mask, u64 value) -{ - bool change; - u64 old, new; - - old = sst_dsp_shim_read64_unlocked(sst, offset); - - new = (old & (~mask)) | (value & mask); - - change = (old != new); - if (change) - sst_dsp_shim_write64_unlocked(sst, offset, new); - - return change; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64_unlocked); - /* This is for registers bits with attribute RWC */ void sst_dsp_shim_update_bits_forced_unlocked(struct sst_dsp *sst, u32 offset, u32 mask, u32 value) @@ -217,19 +129,6 @@ int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, } EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits); -int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, - u64 mask, u64 value) -{ - unsigned long flags; - bool change; - - spin_lock_irqsave(&sst->spinlock, flags); - change = sst_dsp_shim_update_bits64_unlocked(sst, offset, mask, value); - spin_unlock_irqrestore(&sst->spinlock, flags); - return change; -} -EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64); - /* This is for registers bits with attribute RWC */ void sst_dsp_shim_update_bits_forced(struct sst_dsp *sst, u32 offset, u32 mask, u32 value) @@ -282,70 +181,6 @@ int sst_dsp_register_poll(struct sst_dsp *ctx, u32 offset, u32 mask, } EXPORT_SYMBOL_GPL(sst_dsp_register_poll); -void sst_dsp_dump(struct sst_dsp *sst) -{ - if (sst->ops->dump) - sst->ops->dump(sst); -} -EXPORT_SYMBOL_GPL(sst_dsp_dump); - -void sst_dsp_reset(struct sst_dsp *sst) -{ - if (sst->ops->reset) - sst->ops->reset(sst); -} -EXPORT_SYMBOL_GPL(sst_dsp_reset); - -int sst_dsp_boot(struct sst_dsp *sst) -{ - if (sst->ops->boot) - sst->ops->boot(sst); - - return 0; -} -EXPORT_SYMBOL_GPL(sst_dsp_boot); - -int sst_dsp_wake(struct sst_dsp *sst) -{ - if (sst->ops->wake) - return sst->ops->wake(sst); - - return 0; -} -EXPORT_SYMBOL_GPL(sst_dsp_wake); - -void sst_dsp_sleep(struct sst_dsp *sst) -{ - if (sst->ops->sleep) - sst->ops->sleep(sst); -} -EXPORT_SYMBOL_GPL(sst_dsp_sleep); - -void sst_dsp_stall(struct sst_dsp *sst) -{ - if (sst->ops->stall) - sst->ops->stall(sst); -} -EXPORT_SYMBOL_GPL(sst_dsp_stall); - -void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg) -{ - sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY); - trace_sst_ipc_msg_tx(msg); -} -EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_tx); - -u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp) -{ - u32 msg; - - msg = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); - trace_sst_ipc_msg_rx(msg); - - return msg; -} -EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_rx); - int sst_dsp_mailbox_init(struct sst_dsp *sst, u32 inbox_offset, size_t inbox_size, u32 outbox_offset, size_t outbox_size) { diff --git a/sound/soc/intel/common/sst-dsp.h b/sound/soc/intel/common/sst-dsp.h index 604a80c5859b..f111fd3f334b 100644 --- a/sound/soc/intel/common/sst-dsp.h +++ b/sound/soc/intel/common/sst-dsp.h @@ -12,159 +12,6 @@ #include <linux/types.h> #include <linux/interrupt.h> -/* SST Device IDs */ -#define SST_DEV_ID_LYNX_POINT 0x33C8 -#define SST_DEV_ID_WILDCAT_POINT 0x3438 -#define SST_DEV_ID_BYT 0x0F28 - -/* Supported SST DMA Devices */ -#define SST_DMA_TYPE_DW 1 - -/* autosuspend delay 5s*/ -#define SST_RUNTIME_SUSPEND_DELAY (5 * 1000) - -/* SST Shim register map - * The register naming can differ between products. Some products also - * contain extra functionality. - */ -#define SST_CSR 0x00 -#define SST_PISR 0x08 -#define SST_PIMR 0x10 -#define SST_ISRX 0x18 -#define SST_ISRD 0x20 -#define SST_IMRX 0x28 -#define SST_IMRD 0x30 -#define SST_IPCX 0x38 /* IPC IA -> SST */ -#define SST_IPCD 0x40 /* IPC SST -> IA */ -#define SST_ISRSC 0x48 -#define SST_ISRLPESC 0x50 -#define SST_IMRSC 0x58 -#define SST_IMRLPESC 0x60 -#define SST_IPCSC 0x68 -#define SST_IPCLPESC 0x70 -#define SST_CLKCTL 0x78 -#define SST_CSR2 0x80 -#define SST_LTRC 0xE0 -#define SST_HMDC 0xE8 - -#define SST_SHIM_BEGIN SST_CSR -#define SST_SHIM_END SST_HDMC - -#define SST_DBGO 0xF0 - -#define SST_SHIM_SIZE 0x100 -#define SST_PWMCTRL 0x1000 - -/* SST Shim Register bits - * The register bit naming can differ between products. Some products also - * contain extra functionality. - */ - -/* CSR / CS */ -#define SST_CSR_RST (0x1 << 1) -#define SST_CSR_SBCS0 (0x1 << 2) -#define SST_CSR_SBCS1 (0x1 << 3) -#define SST_CSR_DCS(x) (x << 4) -#define SST_CSR_DCS_MASK (0x7 << 4) -#define SST_CSR_STALL (0x1 << 10) -#define SST_CSR_S0IOCS (0x1 << 21) -#define SST_CSR_S1IOCS (0x1 << 23) -#define SST_CSR_LPCS (0x1 << 31) -#define SST_CSR_24MHZ_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1 | SST_CSR_LPCS) -#define SST_CSR_24MHZ_NO_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1) -#define SST_BYT_CSR_RST (0x1 << 0) -#define SST_BYT_CSR_VECTOR_SEL (0x1 << 1) -#define SST_BYT_CSR_STALL (0x1 << 2) -#define SST_BYT_CSR_PWAITMODE (0x1 << 3) - -/* ISRX / ISC */ -#define SST_ISRX_BUSY (0x1 << 1) -#define SST_ISRX_DONE (0x1 << 0) -#define SST_BYT_ISRX_REQUEST (0x1 << 1) - -/* ISRD / ISD */ -#define SST_ISRD_BUSY (0x1 << 1) -#define SST_ISRD_DONE (0x1 << 0) - -/* IMRX / IMC */ -#define SST_IMRX_BUSY (0x1 << 1) -#define SST_IMRX_DONE (0x1 << 0) -#define SST_BYT_IMRX_REQUEST (0x1 << 1) - -/* IMRD / IMD */ -#define SST_IMRD_DONE (0x1 << 0) -#define SST_IMRD_BUSY (0x1 << 1) -#define SST_IMRD_SSP0 (0x1 << 16) -#define SST_IMRD_DMAC0 (0x1 << 21) -#define SST_IMRD_DMAC1 (0x1 << 22) -#define SST_IMRD_DMAC (SST_IMRD_DMAC0 | SST_IMRD_DMAC1) - -/* IPCX / IPCC */ -#define SST_IPCX_DONE (0x1 << 30) -#define SST_IPCX_BUSY (0x1 << 31) -#define SST_BYT_IPCX_DONE ((u64)0x1 << 62) -#define SST_BYT_IPCX_BUSY ((u64)0x1 << 63) - -/* IPCD */ -#define SST_IPCD_DONE (0x1 << 30) -#define SST_IPCD_BUSY (0x1 << 31) -#define SST_BYT_IPCD_DONE ((u64)0x1 << 62) -#define SST_BYT_IPCD_BUSY ((u64)0x1 << 63) - -/* CLKCTL */ -#define SST_CLKCTL_SMOS(x) (x << 24) -#define SST_CLKCTL_MASK (3 << 24) -#define SST_CLKCTL_DCPLCG (1 << 18) -#define SST_CLKCTL_SCOE1 (1 << 17) -#define SST_CLKCTL_SCOE0 (1 << 16) - -/* CSR2 / CS2 */ -#define SST_CSR2_SDFD_SSP0 (1 << 1) -#define SST_CSR2_SDFD_SSP1 (1 << 2) - -/* LTRC */ -#define SST_LTRC_VAL(x) (x << 0) - -/* HMDC */ -#define SST_HMDC_HDDA0(x) (x << 0) -#define SST_HMDC_HDDA1(x) (x << 7) -#define SST_HMDC_HDDA_E0_CH0 1 -#define SST_HMDC_HDDA_E0_CH1 2 -#define SST_HMDC_HDDA_E0_CH2 4 -#define SST_HMDC_HDDA_E0_CH3 8 -#define SST_HMDC_HDDA_E1_CH0 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH0) -#define SST_HMDC_HDDA_E1_CH1 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH1) -#define SST_HMDC_HDDA_E1_CH2 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH2) -#define SST_HMDC_HDDA_E1_CH3 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH3) -#define SST_HMDC_HDDA_E0_ALLCH (SST_HMDC_HDDA_E0_CH0 | SST_HMDC_HDDA_E0_CH1 | \ - SST_HMDC_HDDA_E0_CH2 | SST_HMDC_HDDA_E0_CH3) -#define SST_HMDC_HDDA_E1_ALLCH (SST_HMDC_HDDA_E1_CH0 | SST_HMDC_HDDA_E1_CH1 | \ - SST_HMDC_HDDA_E1_CH2 | SST_HMDC_HDDA_E1_CH3) - - -/* SST Vendor Defined Registers and bits */ -#define SST_VDRTCTL0 0xa0 -#define SST_VDRTCTL1 0xa4 -#define SST_VDRTCTL2 0xa8 -#define SST_VDRTCTL3 0xaC - -/* VDRTCTL0 */ -#define SST_VDRTCL0_D3PGD (1 << 0) -#define SST_VDRTCL0_D3SRAMPGD (1 << 1) -#define SST_VDRTCL0_DSRAMPGE_SHIFT 12 -#define SST_VDRTCL0_DSRAMPGE_MASK (0xfffff << SST_VDRTCL0_DSRAMPGE_SHIFT) -#define SST_VDRTCL0_ISRAMPGE_SHIFT 2 -#define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) - -/* VDRTCTL2 */ -#define SST_VDRTCL2_DCLCGE (1 << 1) -#define SST_VDRTCL2_DTCGE (1 << 10) -#define SST_VDRTCL2_APLLSE_MASK (1 << 31) - -/* PMCS */ -#define SST_PMCS 0x84 -#define SST_PMCS_PS_MASK 0x3 - struct sst_dsp; /* @@ -179,50 +26,11 @@ struct sst_dsp_device { void *thread_context; }; -/* - * SST Platform Data. - */ -struct sst_pdata { - /* ACPI data */ - u32 lpe_base; - u32 lpe_size; - u32 pcicfg_base; - u32 pcicfg_size; - u32 fw_base; - u32 fw_size; - int irq; - - /* Firmware */ - const struct firmware *fw; - - /* DMA */ - int resindex_dma_base; /* other fields invalid if equals to -1 */ - u32 dma_base; - u32 dma_size; - int dma_engine; - struct device *dma_dev; - - /* DSP */ - u32 id; - void *dsp; -}; - -#if IS_ENABLED(CONFIG_DW_DMAC_CORE) -/* Initialization */ -struct sst_dsp *sst_dsp_new(struct device *dev, - struct sst_dsp_device *sst_dev, struct sst_pdata *pdata); -void sst_dsp_free(struct sst_dsp *sst); -#endif - /* SHIM Read / Write */ void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value); u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset); int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, u32 mask, u32 value); -void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value); -u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset); -int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, - u64 mask, u64 value); void sst_dsp_shim_update_bits_forced(struct sst_dsp *sst, u32 offset, u32 mask, u32 value); @@ -231,10 +39,6 @@ void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value); u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset); int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, u32 mask, u32 value); -void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value); -u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset); -int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, - u64 mask, u64 value); void sst_dsp_shim_update_bits_forced_unlocked(struct sst_dsp *sst, u32 offset, u32 mask, u32 value); @@ -243,42 +47,15 @@ void sst_shim32_write(void __iomem *addr, u32 offset, u32 value); u32 sst_shim32_read(void __iomem *addr, u32 offset); void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value); u64 sst_shim32_read64(void __iomem *addr, u32 offset); -void sst_memcpy_toio_32(struct sst_dsp *sst, - void __iomem *dest, void *src, size_t bytes); -void sst_memcpy_fromio_32(struct sst_dsp *sst, - void *dest, void __iomem *src, size_t bytes); - -/* DSP reset & boot */ -void sst_dsp_reset(struct sst_dsp *sst); -int sst_dsp_boot(struct sst_dsp *sst); -int sst_dsp_wake(struct sst_dsp *sst); -void sst_dsp_sleep(struct sst_dsp *sst); -void sst_dsp_stall(struct sst_dsp *sst); - -/* DMA */ -int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id); -void sst_dsp_dma_put_channel(struct sst_dsp *dsp); -int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size); -int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size); - -/* Msg IO */ -void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg); -u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp); /* Mailbox management */ -int sst_dsp_mailbox_init(struct sst_dsp *dsp, u32 inbox_offset, +int sst_dsp_mailbox_init(struct sst_dsp *sst, u32 inbox_offset, size_t inbox_size, u32 outbox_offset, size_t outbox_size); -void sst_dsp_inbox_write(struct sst_dsp *dsp, void *message, size_t bytes); -void sst_dsp_inbox_read(struct sst_dsp *dsp, void *message, size_t bytes); -void sst_dsp_outbox_write(struct sst_dsp *dsp, void *message, size_t bytes); -void sst_dsp_outbox_read(struct sst_dsp *dsp, void *message, size_t bytes); -void sst_dsp_mailbox_dump(struct sst_dsp *dsp, size_t bytes); -int sst_dsp_register_poll(struct sst_dsp *dsp, u32 offset, u32 mask, - u32 expected_value, u32 timeout, char *operation); - -/* Debug */ -void sst_dsp_dump(struct sst_dsp *sst); +void sst_dsp_inbox_write(struct sst_dsp *sst, void *message, size_t bytes); +void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes); +void sst_dsp_outbox_write(struct sst_dsp *sst, void *message, size_t bytes); +void sst_dsp_outbox_read(struct sst_dsp *sst, void *message, size_t bytes); +int sst_dsp_register_poll(struct sst_dsp *ctx, u32 offset, u32 mask, + u32 target, u32 time, char *operation); #endif diff --git a/sound/soc/intel/common/sst-firmware.c b/sound/soc/intel/common/sst-firmware.c deleted file mode 100644 index d27947aeb079..000000000000 --- a/sound/soc/intel/common/sst-firmware.c +++ /dev/null @@ -1,1273 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel SST Firmware Loader - * - * Copyright (C) 2013, Intel Corporation. All rights reserved. - */ - -#include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/sched.h> -#include <linux/firmware.h> -#include <linux/export.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/dma-mapping.h> -#include <linux/dmaengine.h> -#include <linux/pci.h> -#include <linux/acpi.h> - -/* supported DMA engine drivers */ -#include <linux/dma/dw.h> - -#include <asm/page.h> -#include <asm/pgtable.h> - -#include "sst-dsp.h" -#include "sst-dsp-priv.h" - -#define SST_DMA_RESOURCES 2 -#define SST_DSP_DMA_MAX_BURST 0x3 -#define SST_HSW_BLOCK_ANY 0xffffffff - -#define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000 - -struct sst_dma { - struct sst_dsp *sst; - - struct dw_dma_chip *chip; - - struct dma_async_tx_descriptor *desc; - struct dma_chan *ch; -}; - -static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) -{ - u32 tmp = 0; - int i, m, n; - const u8 *src_byte = src; - - m = bytes / 4; - n = bytes % 4; - - /* __iowrite32_copy use 32bit size values so divide by 4 */ - __iowrite32_copy((void *)dest, src, m); - - if (n) { - for (i = 0; i < n; i++) - tmp |= (u32)*(src_byte + m * 4 + i) << (i * 8); - __iowrite32_copy((void *)(dest + m * 4), &tmp, 1); - } - -} - -static void sst_dma_transfer_complete(void *arg) -{ - struct sst_dsp *sst = (struct sst_dsp *)arg; - - dev_dbg(sst->dev, "DMA: callback\n"); -} - -static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size) -{ - struct dma_async_tx_descriptor *desc; - struct sst_dma *dma = sst->dma; - - if (dma->ch == NULL) { - dev_err(sst->dev, "error: no DMA channel\n"); - return -ENODEV; - } - - dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n", - (unsigned long)src_addr, (unsigned long)dest_addr, size); - - desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr, - src_addr, size, DMA_CTRL_ACK); - if (!desc){ - dev_err(sst->dev, "error: dma prep memcpy failed\n"); - return -EINVAL; - } - - desc->callback = sst_dma_transfer_complete; - desc->callback_param = sst; - - desc->tx_submit(desc); - dma_wait_for_async_tx(desc); - - return 0; -} - -/* copy to DSP */ -int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size) -{ - return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP, - src_addr, size); -} -EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto); - -/* copy from DSP */ -int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, - dma_addr_t src_addr, size_t size) -{ - return sst_dsp_dma_copy(sst, dest_addr, - src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size); -} -EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom); - -/* remove module from memory - callers hold locks */ -static void block_list_remove(struct sst_dsp *dsp, - struct list_head *block_list) -{ - struct sst_mem_block *block, *tmp; - int err; - - /* disable each block */ - list_for_each_entry(block, block_list, module_list) { - - if (block->ops && block->ops->disable) { - err = block->ops->disable(block); - if (err < 0) - dev_err(dsp->dev, - "error: cant disable block %d:%d\n", - block->type, block->index); - } - } - - /* mark each block as free */ - list_for_each_entry_safe(block, tmp, block_list, module_list) { - list_del(&block->module_list); - list_move(&block->list, &dsp->free_block_list); - dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - } -} - -/* prepare the memory block to receive data from host - callers hold locks */ -static int block_list_prepare(struct sst_dsp *dsp, - struct list_head *block_list) -{ - struct sst_mem_block *block; - int ret = 0; - - /* enable each block so that's it'e ready for data */ - list_for_each_entry(block, block_list, module_list) { - - if (block->ops && block->ops->enable && !block->users) { - ret = block->ops->enable(block); - if (ret < 0) { - dev_err(dsp->dev, - "error: cant disable block %d:%d\n", - block->type, block->index); - goto err; - } - } - } - return ret; - -err: - list_for_each_entry(block, block_list, module_list) { - if (block->ops && block->ops->disable) - block->ops->disable(block); - } - return ret; -} - -static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem, - int irq) -{ - struct dw_dma_chip *chip; - int err; - - chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return ERR_PTR(-ENOMEM); - - chip->irq = irq; - chip->regs = devm_ioremap_resource(dev, mem); - if (IS_ERR(chip->regs)) - return ERR_CAST(chip->regs); - - err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); - if (err) - return ERR_PTR(err); - - chip->dev = dev; - - err = dw_dma_probe(chip); - if (err) - return ERR_PTR(err); - - return chip; -} - -static void dw_remove(struct dw_dma_chip *chip) -{ - dw_dma_remove(chip); -} - -static bool dma_chan_filter(struct dma_chan *chan, void *param) -{ - struct sst_dsp *dsp = (struct sst_dsp *)param; - - return chan->device->dev == dsp->dma_dev; -} - -int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id) -{ - struct sst_dma *dma = dsp->dma; - struct dma_slave_config slave; - dma_cap_mask_t mask; - int ret; - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - dma_cap_set(DMA_MEMCPY, mask); - - dma->ch = dma_request_channel(mask, dma_chan_filter, dsp); - if (dma->ch == NULL) { - dev_err(dsp->dev, "error: DMA request channel failed\n"); - return -EIO; - } - - memset(&slave, 0, sizeof(slave)); - slave.direction = DMA_MEM_TO_DEV; - slave.src_addr_width = - slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST; - - ret = dmaengine_slave_config(dma->ch, &slave); - if (ret) { - dev_err(dsp->dev, "error: unable to set DMA slave config %d\n", - ret); - dma_release_channel(dma->ch); - dma->ch = NULL; - } - - return ret; -} -EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel); - -void sst_dsp_dma_put_channel(struct sst_dsp *dsp) -{ - struct sst_dma *dma = dsp->dma; - - if (!dma->ch) - return; - - dma_release_channel(dma->ch); - dma->ch = NULL; -} -EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel); - -static int sst_dma_new(struct sst_dsp *sst) -{ - struct sst_pdata *sst_pdata = sst->pdata; - struct sst_dma *dma; - struct resource mem; - int ret = 0; - - if (sst->pdata->resindex_dma_base == -1) - /* DMA is not used, return and squelsh error messages */ - return 0; - - /* configure the correct platform data for whatever DMA engine - * is attached to the ADSP IP. */ - switch (sst->pdata->dma_engine) { - case SST_DMA_TYPE_DW: - break; - default: - dev_err(sst->dev, "error: invalid DMA engine %d\n", - sst->pdata->dma_engine); - return -EINVAL; - } - - dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL); - if (!dma) - return -ENOMEM; - - dma->sst = sst; - - memset(&mem, 0, sizeof(mem)); - - mem.start = sst->addr.lpe_base + sst_pdata->dma_base; - mem.end = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1; - mem.flags = IORESOURCE_MEM; - - /* now register DMA engine device */ - dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq); - if (IS_ERR(dma->chip)) { - dev_err(sst->dev, "error: DMA device register failed\n"); - ret = PTR_ERR(dma->chip); - goto err_dma_dev; - } - - sst->dma = dma; - sst->fw_use_dma = true; - return 0; - -err_dma_dev: - devm_kfree(sst->dev, dma); - return ret; -} - -static void sst_dma_free(struct sst_dma *dma) -{ - - if (dma == NULL) - return; - - if (dma->ch) - dma_release_channel(dma->ch); - - if (dma->chip) - dw_remove(dma->chip); - -} - -/* create new generic firmware object */ -struct sst_fw *sst_fw_new(struct sst_dsp *dsp, - const struct firmware *fw, void *private) -{ - struct sst_fw *sst_fw; - int err; - - if (!dsp->ops->parse_fw) - return NULL; - - sst_fw = kzalloc(sizeof(*sst_fw), GFP_KERNEL); - if (sst_fw == NULL) - return NULL; - - sst_fw->dsp = dsp; - sst_fw->private = private; - sst_fw->size = fw->size; - - /* allocate DMA buffer to store FW data */ - sst_fw->dma_buf = dma_alloc_coherent(dsp->dma_dev, sst_fw->size, - &sst_fw->dmable_fw_paddr, GFP_KERNEL); - if (!sst_fw->dma_buf) { - dev_err(dsp->dev, "error: DMA alloc failed\n"); - kfree(sst_fw); - return NULL; - } - - /* copy FW data to DMA-able memory */ - memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size); - - if (dsp->fw_use_dma) { - err = sst_dsp_dma_get_channel(dsp, 0); - if (err < 0) - goto chan_err; - } - - /* call core specific FW paser to load FW data into DSP */ - err = dsp->ops->parse_fw(sst_fw); - if (err < 0) { - dev_err(dsp->dev, "error: parse fw failed %d\n", err); - goto parse_err; - } - - if (dsp->fw_use_dma) - sst_dsp_dma_put_channel(dsp); - - mutex_lock(&dsp->mutex); - list_add(&sst_fw->list, &dsp->fw_list); - mutex_unlock(&dsp->mutex); - - return sst_fw; - -parse_err: - if (dsp->fw_use_dma) - sst_dsp_dma_put_channel(dsp); -chan_err: - dma_free_coherent(dsp->dma_dev, sst_fw->size, - sst_fw->dma_buf, - sst_fw->dmable_fw_paddr); - sst_fw->dma_buf = NULL; - kfree(sst_fw); - return NULL; -} -EXPORT_SYMBOL_GPL(sst_fw_new); - -int sst_fw_reload(struct sst_fw *sst_fw) -{ - struct sst_dsp *dsp = sst_fw->dsp; - int ret; - - dev_dbg(dsp->dev, "reloading firmware\n"); - - /* call core specific FW paser to load FW data into DSP */ - ret = dsp->ops->parse_fw(sst_fw); - if (ret < 0) - dev_err(dsp->dev, "error: parse fw failed %d\n", ret); - - return ret; -} -EXPORT_SYMBOL_GPL(sst_fw_reload); - -void sst_fw_unload(struct sst_fw *sst_fw) -{ - struct sst_dsp *dsp = sst_fw->dsp; - struct sst_module *module, *mtmp; - struct sst_module_runtime *runtime, *rtmp; - - dev_dbg(dsp->dev, "unloading firmware\n"); - - mutex_lock(&dsp->mutex); - - /* check module by module */ - list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) { - if (module->sst_fw == sst_fw) { - - /* remove runtime modules */ - list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) { - - block_list_remove(dsp, &runtime->block_list); - list_del(&runtime->list); - kfree(runtime); - } - - /* now remove the module */ - block_list_remove(dsp, &module->block_list); - list_del(&module->list); - kfree(module); - } - } - - /* remove all scratch blocks */ - block_list_remove(dsp, &dsp->scratch_block_list); - - mutex_unlock(&dsp->mutex); -} -EXPORT_SYMBOL_GPL(sst_fw_unload); - -/* free single firmware object */ -void sst_fw_free(struct sst_fw *sst_fw) -{ - struct sst_dsp *dsp = sst_fw->dsp; - - mutex_lock(&dsp->mutex); - list_del(&sst_fw->list); - mutex_unlock(&dsp->mutex); - - if (sst_fw->dma_buf) - dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, - sst_fw->dmable_fw_paddr); - kfree(sst_fw); -} -EXPORT_SYMBOL_GPL(sst_fw_free); - -/* free all firmware objects */ -void sst_fw_free_all(struct sst_dsp *dsp) -{ - struct sst_fw *sst_fw, *t; - - mutex_lock(&dsp->mutex); - list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { - - list_del(&sst_fw->list); - dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf, - sst_fw->dmable_fw_paddr); - kfree(sst_fw); - } - mutex_unlock(&dsp->mutex); -} -EXPORT_SYMBOL_GPL(sst_fw_free_all); - -/* create a new SST generic module from FW template */ -struct sst_module *sst_module_new(struct sst_fw *sst_fw, - struct sst_module_template *template, void *private) -{ - struct sst_dsp *dsp = sst_fw->dsp; - struct sst_module *sst_module; - - sst_module = kzalloc(sizeof(*sst_module), GFP_KERNEL); - if (sst_module == NULL) - return NULL; - - sst_module->id = template->id; - sst_module->dsp = dsp; - sst_module->sst_fw = sst_fw; - sst_module->scratch_size = template->scratch_size; - sst_module->persistent_size = template->persistent_size; - sst_module->entry = template->entry; - sst_module->state = SST_MODULE_STATE_UNLOADED; - - INIT_LIST_HEAD(&sst_module->block_list); - INIT_LIST_HEAD(&sst_module->runtime_list); - - mutex_lock(&dsp->mutex); - list_add(&sst_module->list, &dsp->module_list); - mutex_unlock(&dsp->mutex); - - return sst_module; -} -EXPORT_SYMBOL_GPL(sst_module_new); - -/* free firmware module and remove from available list */ -void sst_module_free(struct sst_module *sst_module) -{ - struct sst_dsp *dsp = sst_module->dsp; - - mutex_lock(&dsp->mutex); - list_del(&sst_module->list); - mutex_unlock(&dsp->mutex); - - kfree(sst_module); -} -EXPORT_SYMBOL_GPL(sst_module_free); - -struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, - int id, void *private) -{ - struct sst_dsp *dsp = module->dsp; - struct sst_module_runtime *runtime; - - runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); - if (runtime == NULL) - return NULL; - - runtime->id = id; - runtime->dsp = dsp; - runtime->module = module; - INIT_LIST_HEAD(&runtime->block_list); - - mutex_lock(&dsp->mutex); - list_add(&runtime->list, &module->runtime_list); - mutex_unlock(&dsp->mutex); - - return runtime; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_new); - -void sst_module_runtime_free(struct sst_module_runtime *runtime) -{ - struct sst_dsp *dsp = runtime->dsp; - - mutex_lock(&dsp->mutex); - list_del(&runtime->list); - mutex_unlock(&dsp->mutex); - - kfree(runtime); -} -EXPORT_SYMBOL_GPL(sst_module_runtime_free); - -static struct sst_mem_block *find_block(struct sst_dsp *dsp, - struct sst_block_allocator *ba) -{ - struct sst_mem_block *block; - - list_for_each_entry(block, &dsp->free_block_list, list) { - if (block->type == ba->type && block->offset == ba->offset) - return block; - } - - return NULL; -} - -/* Block allocator must be on block boundary */ -static int block_alloc_contiguous(struct sst_dsp *dsp, - struct sst_block_allocator *ba, struct list_head *block_list) -{ - struct list_head tmp = LIST_HEAD_INIT(tmp); - struct sst_mem_block *block; - u32 block_start = SST_HSW_BLOCK_ANY; - int size = ba->size, offset = ba->offset; - - while (ba->size > 0) { - - block = find_block(dsp, ba); - if (!block) { - list_splice(&tmp, &dsp->free_block_list); - - ba->size = size; - ba->offset = offset; - return -ENOMEM; - } - - list_move_tail(&block->list, &tmp); - ba->offset += block->size; - ba->size -= block->size; - } - ba->size = size; - ba->offset = offset; - - list_for_each_entry(block, &tmp, list) { - - if (block->offset < block_start) - block_start = block->offset; - - list_add(&block->module_list, block_list); - - dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - } - - list_splice(&tmp, &dsp->used_block_list); - return 0; -} - -/* allocate first free DSP blocks for data - callers hold locks */ -static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba, - struct list_head *block_list) -{ - struct sst_mem_block *block, *tmp; - int ret = 0; - - if (ba->size == 0) - return 0; - - /* find first free whole blocks that can hold module */ - list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { - - /* ignore blocks with wrong type */ - if (block->type != ba->type) - continue; - - if (ba->size > block->size) - continue; - - ba->offset = block->offset; - block->bytes_used = ba->size % block->size; - list_add(&block->module_list, block_list); - list_move(&block->list, &dsp->used_block_list); - dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - return 0; - } - - /* then find free multiple blocks that can hold module */ - list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { - - /* ignore blocks with wrong type */ - if (block->type != ba->type) - continue; - - /* do we span > 1 blocks */ - if (ba->size > block->size) { - - /* align ba to block boundary */ - ba->offset = block->offset; - - ret = block_alloc_contiguous(dsp, ba, block_list); - if (ret == 0) - return ret; - - } - } - - /* not enough free block space */ - return -ENOMEM; -} - -int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, - struct list_head *block_list) -{ - int ret; - - dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", - ba->size, ba->offset, ba->type); - - mutex_lock(&dsp->mutex); - - ret = block_alloc(dsp, ba, block_list); - if (ret < 0) { - dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret); - goto out; - } - - /* prepare DSP blocks for module usage */ - ret = block_list_prepare(dsp, block_list); - if (ret < 0) - dev_err(dsp->dev, "error: prepare failed\n"); - -out: - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_alloc_blocks); - -int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list) -{ - mutex_lock(&dsp->mutex); - block_list_remove(dsp, block_list); - mutex_unlock(&dsp->mutex); - return 0; -} -EXPORT_SYMBOL_GPL(sst_free_blocks); - -/* allocate memory blocks for static module addresses - callers hold locks */ -static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba, - struct list_head *block_list) -{ - struct sst_mem_block *block, *tmp; - struct sst_block_allocator ba_tmp = *ba; - u32 end = ba->offset + ba->size, block_end; - int err; - - /* only IRAM/DRAM blocks are managed */ - if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM) - return 0; - - /* are blocks already attached to this module */ - list_for_each_entry_safe(block, tmp, block_list, module_list) { - - /* ignore blocks with wrong type */ - if (block->type != ba->type) - continue; - - block_end = block->offset + block->size; - - /* find block that holds section */ - if (ba->offset >= block->offset && end <= block_end) - return 0; - - /* does block span more than 1 section */ - if (ba->offset >= block->offset && ba->offset < block_end) { - - /* align ba to block boundary */ - ba_tmp.size -= block_end - ba->offset; - ba_tmp.offset = block_end; - err = block_alloc_contiguous(dsp, &ba_tmp, block_list); - if (err < 0) - return -ENOMEM; - - /* module already owns blocks */ - return 0; - } - } - - /* find first free blocks that can hold section in free list */ - list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { - block_end = block->offset + block->size; - - /* ignore blocks with wrong type */ - if (block->type != ba->type) - continue; - - /* find block that holds section */ - if (ba->offset >= block->offset && end <= block_end) { - - /* add block */ - list_move(&block->list, &dsp->used_block_list); - list_add(&block->module_list, block_list); - dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - return 0; - } - - /* does block span more than 1 section */ - if (ba->offset >= block->offset && ba->offset < block_end) { - - /* add block */ - list_move(&block->list, &dsp->used_block_list); - list_add(&block->module_list, block_list); - /* align ba to block boundary */ - ba_tmp.size -= block_end - ba->offset; - ba_tmp.offset = block_end; - - err = block_alloc_contiguous(dsp, &ba_tmp, block_list); - if (err < 0) - return -ENOMEM; - - return 0; - } - } - - return -ENOMEM; -} - -/* Load fixed module data into DSP memory blocks */ -int sst_module_alloc_blocks(struct sst_module *module) -{ - struct sst_dsp *dsp = module->dsp; - struct sst_fw *sst_fw = module->sst_fw; - struct sst_block_allocator ba; - int ret; - - memset(&ba, 0, sizeof(ba)); - ba.size = module->size; - ba.type = module->type; - ba.offset = module->offset; - - dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", - ba.size, ba.offset, ba.type); - - mutex_lock(&dsp->mutex); - - /* alloc blocks that includes this section */ - ret = block_alloc_fixed(dsp, &ba, &module->block_list); - if (ret < 0) { - dev_err(dsp->dev, - "error: no free blocks for section at offset 0x%x size 0x%x\n", - module->offset, module->size); - mutex_unlock(&dsp->mutex); - return -ENOMEM; - } - - /* prepare DSP blocks for module copy */ - ret = block_list_prepare(dsp, &module->block_list); - if (ret < 0) { - dev_err(dsp->dev, "error: fw module prepare failed\n"); - goto err; - } - - /* copy partial module data to blocks */ - if (dsp->fw_use_dma) { - ret = sst_dsp_dma_copyto(dsp, - dsp->addr.lpe_base + module->offset, - sst_fw->dmable_fw_paddr + module->data_offset, - module->size); - if (ret < 0) { - dev_err(dsp->dev, "error: module copy failed\n"); - goto err; - } - } else - sst_memcpy32(dsp->addr.lpe + module->offset, module->data, - module->size); - - mutex_unlock(&dsp->mutex); - return ret; - -err: - block_list_remove(dsp, &module->block_list); - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_module_alloc_blocks); - -/* Unload entire module from DSP memory */ -int sst_module_free_blocks(struct sst_module *module) -{ - struct sst_dsp *dsp = module->dsp; - - mutex_lock(&dsp->mutex); - block_list_remove(dsp, &module->block_list); - mutex_unlock(&dsp->mutex); - return 0; -} -EXPORT_SYMBOL_GPL(sst_module_free_blocks); - -int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, - int offset) -{ - struct sst_dsp *dsp = runtime->dsp; - struct sst_module *module = runtime->module; - struct sst_block_allocator ba; - int ret; - - if (module->persistent_size == 0) - return 0; - - memset(&ba, 0, sizeof(ba)); - ba.size = module->persistent_size; - ba.type = SST_MEM_DRAM; - - mutex_lock(&dsp->mutex); - - /* do we need to allocate at a fixed address ? */ - if (offset != 0) { - - ba.offset = offset; - - dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n", - ba.size, ba.type, ba.offset); - - /* alloc blocks that includes this section */ - ret = block_alloc_fixed(dsp, &ba, &runtime->block_list); - - } else { - dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n", - ba.size, ba.type); - - /* alloc blocks that includes this section */ - ret = block_alloc(dsp, &ba, &runtime->block_list); - } - if (ret < 0) { - dev_err(dsp->dev, - "error: no free blocks for runtime module size 0x%x\n", - module->persistent_size); - mutex_unlock(&dsp->mutex); - return -ENOMEM; - } - runtime->persistent_offset = ba.offset; - - /* prepare DSP blocks for module copy */ - ret = block_list_prepare(dsp, &runtime->block_list); - if (ret < 0) { - dev_err(dsp->dev, "error: runtime block prepare failed\n"); - goto err; - } - - mutex_unlock(&dsp->mutex); - return ret; - -err: - block_list_remove(dsp, &module->block_list); - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks); - -int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime) -{ - struct sst_dsp *dsp = runtime->dsp; - - mutex_lock(&dsp->mutex); - block_list_remove(dsp, &runtime->block_list); - mutex_unlock(&dsp->mutex); - return 0; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks); - -int sst_module_runtime_save(struct sst_module_runtime *runtime, - struct sst_module_runtime_context *context) -{ - struct sst_dsp *dsp = runtime->dsp; - struct sst_module *module = runtime->module; - int ret = 0; - - dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n", - runtime->id, runtime->persistent_offset, - module->persistent_size); - - context->buffer = dma_alloc_coherent(dsp->dma_dev, - module->persistent_size, - &context->dma_buffer, GFP_DMA | GFP_KERNEL); - if (!context->buffer) { - dev_err(dsp->dev, "error: DMA context alloc failed\n"); - return -ENOMEM; - } - - mutex_lock(&dsp->mutex); - - if (dsp->fw_use_dma) { - - ret = sst_dsp_dma_get_channel(dsp, 0); - if (ret < 0) - goto err; - - ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer, - dsp->addr.lpe_base + runtime->persistent_offset, - module->persistent_size); - sst_dsp_dma_put_channel(dsp); - if (ret < 0) { - dev_err(dsp->dev, "error: context copy failed\n"); - goto err; - } - } else - sst_memcpy32(context->buffer, dsp->addr.lpe + - runtime->persistent_offset, - module->persistent_size); - -err: - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_save); - -int sst_module_runtime_restore(struct sst_module_runtime *runtime, - struct sst_module_runtime_context *context) -{ - struct sst_dsp *dsp = runtime->dsp; - struct sst_module *module = runtime->module; - int ret = 0; - - dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n", - runtime->id, runtime->persistent_offset, - module->persistent_size); - - mutex_lock(&dsp->mutex); - - if (!context->buffer) { - dev_info(dsp->dev, "no context buffer need to restore!\n"); - goto err; - } - - if (dsp->fw_use_dma) { - - ret = sst_dsp_dma_get_channel(dsp, 0); - if (ret < 0) - goto err; - - ret = sst_dsp_dma_copyto(dsp, - dsp->addr.lpe_base + runtime->persistent_offset, - context->dma_buffer, module->persistent_size); - sst_dsp_dma_put_channel(dsp); - if (ret < 0) { - dev_err(dsp->dev, "error: module copy failed\n"); - goto err; - } - } else - sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset, - context->buffer, module->persistent_size); - - dma_free_coherent(dsp->dma_dev, module->persistent_size, - context->buffer, context->dma_buffer); - context->buffer = NULL; - -err: - mutex_unlock(&dsp->mutex); - return ret; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_restore); - -/* register a DSP memory block for use with FW based modules */ -struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, - u32 size, enum sst_mem_type type, const struct sst_block_ops *ops, - u32 index, void *private) -{ - struct sst_mem_block *block; - - block = kzalloc(sizeof(*block), GFP_KERNEL); - if (block == NULL) - return NULL; - - block->offset = offset; - block->size = size; - block->index = index; - block->type = type; - block->dsp = dsp; - block->private = private; - block->ops = ops; - - mutex_lock(&dsp->mutex); - list_add(&block->list, &dsp->free_block_list); - mutex_unlock(&dsp->mutex); - - return block; -} -EXPORT_SYMBOL_GPL(sst_mem_block_register); - -/* unregister all DSP memory blocks */ -void sst_mem_block_unregister_all(struct sst_dsp *dsp) -{ - struct sst_mem_block *block, *tmp; - - mutex_lock(&dsp->mutex); - - /* unregister used blocks */ - list_for_each_entry_safe(block, tmp, &dsp->used_block_list, list) { - list_del(&block->list); - kfree(block); - } - - /* unregister free blocks */ - list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { - list_del(&block->list); - kfree(block); - } - - mutex_unlock(&dsp->mutex); -} -EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all); - -/* allocate scratch buffer blocks */ -int sst_block_alloc_scratch(struct sst_dsp *dsp) -{ - struct sst_module *module; - struct sst_block_allocator ba; - int ret; - - mutex_lock(&dsp->mutex); - - /* calculate required scratch size */ - dsp->scratch_size = 0; - list_for_each_entry(module, &dsp->module_list, list) { - dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n", - module->id, module->scratch_size); - if (dsp->scratch_size < module->scratch_size) - dsp->scratch_size = module->scratch_size; - } - - dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n", - dsp->scratch_size); - - if (dsp->scratch_size == 0) { - dev_info(dsp->dev, "no modules need scratch buffer\n"); - mutex_unlock(&dsp->mutex); - return 0; - } - - /* allocate blocks for module scratch buffers */ - dev_dbg(dsp->dev, "allocating scratch blocks\n"); - - ba.size = dsp->scratch_size; - ba.type = SST_MEM_DRAM; - - /* do we need to allocate at fixed offset */ - if (dsp->scratch_offset != 0) { - - dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n", - ba.size, ba.type, ba.offset); - - ba.offset = dsp->scratch_offset; - - /* alloc blocks that includes this section */ - ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list); - - } else { - dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n", - ba.size, ba.type); - - ba.offset = 0; - ret = block_alloc(dsp, &ba, &dsp->scratch_block_list); - } - if (ret < 0) { - dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); - mutex_unlock(&dsp->mutex); - return ret; - } - - ret = block_list_prepare(dsp, &dsp->scratch_block_list); - if (ret < 0) { - dev_err(dsp->dev, "error: scratch block prepare failed\n"); - mutex_unlock(&dsp->mutex); - return ret; - } - - /* assign the same offset of scratch to each module */ - dsp->scratch_offset = ba.offset; - mutex_unlock(&dsp->mutex); - return dsp->scratch_size; -} -EXPORT_SYMBOL_GPL(sst_block_alloc_scratch); - -/* free all scratch blocks */ -void sst_block_free_scratch(struct sst_dsp *dsp) -{ - mutex_lock(&dsp->mutex); - block_list_remove(dsp, &dsp->scratch_block_list); - mutex_unlock(&dsp->mutex); -} -EXPORT_SYMBOL_GPL(sst_block_free_scratch); - -/* get a module from it's unique ID */ -struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) -{ - struct sst_module *module; - - mutex_lock(&dsp->mutex); - - list_for_each_entry(module, &dsp->module_list, list) { - if (module->id == id) { - mutex_unlock(&dsp->mutex); - return module; - } - } - - mutex_unlock(&dsp->mutex); - return NULL; -} -EXPORT_SYMBOL_GPL(sst_module_get_from_id); - -struct sst_module_runtime *sst_module_runtime_get_from_id( - struct sst_module *module, u32 id) -{ - struct sst_module_runtime *runtime; - struct sst_dsp *dsp = module->dsp; - - mutex_lock(&dsp->mutex); - - list_for_each_entry(runtime, &module->runtime_list, list) { - if (runtime->id == id) { - mutex_unlock(&dsp->mutex); - return runtime; - } - } - - mutex_unlock(&dsp->mutex); - return NULL; -} -EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id); - -/* returns block address in DSP address space */ -u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, - enum sst_mem_type type) -{ - switch (type) { - case SST_MEM_IRAM: - return offset - dsp->addr.iram_offset + - dsp->addr.dsp_iram_offset; - case SST_MEM_DRAM: - return offset - dsp->addr.dram_offset + - dsp->addr.dsp_dram_offset; - default: - return 0; - } -} -EXPORT_SYMBOL_GPL(sst_dsp_get_offset); - -struct sst_dsp *sst_dsp_new(struct device *dev, - struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) -{ - struct sst_dsp *sst; - int err; - - dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); - - sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); - if (sst == NULL) - return NULL; - - spin_lock_init(&sst->spinlock); - mutex_init(&sst->mutex); - sst->dev = dev; - sst->dma_dev = pdata->dma_dev; - sst->thread_context = sst_dev->thread_context; - sst->sst_dev = sst_dev; - sst->id = pdata->id; - sst->irq = pdata->irq; - sst->ops = sst_dev->ops; - sst->pdata = pdata; - INIT_LIST_HEAD(&sst->used_block_list); - INIT_LIST_HEAD(&sst->free_block_list); - INIT_LIST_HEAD(&sst->module_list); - INIT_LIST_HEAD(&sst->fw_list); - INIT_LIST_HEAD(&sst->scratch_block_list); - - /* Initialise SST Audio DSP */ - if (sst->ops->init) { - err = sst->ops->init(sst, pdata); - if (err < 0) - return NULL; - } - - /* Register the ISR */ - err = request_threaded_irq(sst->irq, sst->ops->irq_handler, - sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); - if (err) - goto irq_err; - - err = sst_dma_new(sst); - if (err) { - dev_err(dev, "sst_dma_new failed %d\n", err); - goto dma_err; - } - - return sst; - -dma_err: - free_irq(sst->irq, sst); -irq_err: - if (sst->ops->free) - sst->ops->free(sst); - - return NULL; -} -EXPORT_SYMBOL_GPL(sst_dsp_new); - -void sst_dsp_free(struct sst_dsp *sst) -{ - free_irq(sst->irq, sst); - if (sst->ops->free) - sst->ops->free(sst); - - sst_dma_free(sst->dma); -} -EXPORT_SYMBOL_GPL(sst_dsp_free); - -MODULE_DESCRIPTION("Intel SST Firmware Loader"); -MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-ipc.c b/sound/soc/intel/common/sst-ipc.c index 6068bb697e22..89c10724d2a3 100644 --- a/sound/soc/intel/common/sst-ipc.c +++ b/sound/soc/intel/common/sst-ipc.c @@ -254,33 +254,6 @@ void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc, } EXPORT_SYMBOL_GPL(sst_ipc_tx_msg_reply_complete); -void sst_ipc_drop_all(struct sst_generic_ipc *ipc) -{ - struct ipc_message *msg, *tmp; - unsigned long flags; - int tx_drop_cnt = 0, rx_drop_cnt = 0; - - /* drop all TX and Rx messages before we stall + reset DSP */ - spin_lock_irqsave(&ipc->dsp->spinlock, flags); - - list_for_each_entry_safe(msg, tmp, &ipc->tx_list, list) { - list_move(&msg->list, &ipc->empty_list); - tx_drop_cnt++; - } - - list_for_each_entry_safe(msg, tmp, &ipc->rx_list, list) { - list_move(&msg->list, &ipc->empty_list); - rx_drop_cnt++; - } - - spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); - - if (tx_drop_cnt || rx_drop_cnt) - dev_err(ipc->dev, "dropped IPC msg RX=%d, TX=%d\n", - tx_drop_cnt, rx_drop_cnt); -} -EXPORT_SYMBOL_GPL(sst_ipc_drop_all); - int sst_ipc_init(struct sst_generic_ipc *ipc) { int ret; diff --git a/sound/soc/intel/common/sst-ipc.h b/sound/soc/intel/common/sst-ipc.h index 08c4831b2664..77d651e888f9 100644 --- a/sound/soc/intel/common/sst-ipc.h +++ b/sound/soc/intel/common/sst-ipc.h @@ -15,8 +15,6 @@ #include <linux/workqueue.h> #include <linux/sched.h> -#define IPC_MAX_MAILBOX_BYTES 256 - struct sst_ipc_message { u64 header; void *data; @@ -82,7 +80,6 @@ struct ipc_message *sst_ipc_reply_find_msg(struct sst_generic_ipc *ipc, void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc, struct ipc_message *msg); -void sst_ipc_drop_all(struct sst_generic_ipc *ipc); int sst_ipc_init(struct sst_generic_ipc *ipc); void sst_ipc_fini(struct sst_generic_ipc *ipc); diff --git a/sound/soc/intel/haswell/Makefile b/sound/soc/intel/haswell/Makefile deleted file mode 100644 index ad2341aea8ae..000000000000 --- a/sound/soc/intel/haswell/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -snd-soc-sst-haswell-pcm-objs := \ - sst-haswell-ipc.o sst-haswell-pcm.o sst-haswell-dsp.o - -obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += snd-soc-sst-haswell-pcm.o diff --git a/sound/soc/intel/haswell/sst-haswell-dsp.c b/sound/soc/intel/haswell/sst-haswell-dsp.c deleted file mode 100644 index 88c3f63bded9..000000000000 --- a/sound/soc/intel/haswell/sst-haswell-dsp.c +++ /dev/null @@ -1,705 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Haswell SST DSP driver - * - * Copyright (C) 2013, Intel Corporation. All rights reserved. - */ - -#include <linux/delay.h> -#include <linux/fs.h> -#include <linux/slab.h> -#include <linux/device.h> -#include <linux/sched.h> -#include <linux/export.h> -#include <linux/interrupt.h> -#include <linux/module.h> -#include <linux/dma-mapping.h> -#include <linux/platform_device.h> -#include <linux/pci.h> -#include <linux/firmware.h> -#include <linux/pm_runtime.h> - -#include "../common/sst-dsp.h" -#include "../common/sst-dsp-priv.h" -#include "../haswell/sst-haswell-ipc.h" - -#include <trace/events/hswadsp.h> - -#define SST_HSW_FW_SIGNATURE_SIZE 4 -#define SST_HSW_FW_SIGN "$SST" -#define SST_HSW_FW_LIB_SIGN "$LIB" - -#define SST_WPT_SHIM_OFFSET 0xFB000 -#define SST_LP_SHIM_OFFSET 0xE7000 -#define SST_WPT_IRAM_OFFSET 0xA0000 -#define SST_LP_IRAM_OFFSET 0x80000 -#define SST_WPT_DSP_DRAM_OFFSET 0x400000 -#define SST_WPT_DSP_IRAM_OFFSET 0x00000 -#define SST_LPT_DSP_DRAM_OFFSET 0x400000 -#define SST_LPT_DSP_IRAM_OFFSET 0x00000 - -#define SST_SHIM_PM_REG 0x84 - -#define SST_HSW_IRAM 1 -#define SST_HSW_DRAM 2 -#define SST_HSW_REGS 3 - -struct dma_block_info { - __le32 type; /* IRAM/DRAM */ - __le32 size; /* Bytes */ - __le32 ram_offset; /* Offset in I/DRAM */ - __le32 rsvd; /* Reserved field */ -} __attribute__((packed)); - -struct fw_module_info { - __le32 persistent_size; - __le32 scratch_size; -} __attribute__((packed)); - -struct fw_header { - unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* FW signature */ - __le32 file_size; /* size of fw minus this header */ - __le32 modules; /* # of modules */ - __le32 file_format; /* version of header format */ - __le32 reserved[4]; -} __attribute__((packed)); - -struct fw_module_header { - unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* module signature */ - __le32 mod_size; /* size of module */ - __le32 blocks; /* # of blocks */ - __le16 padding; - __le16 type; /* codec type, pp lib */ - __le32 entry_point; - struct fw_module_info info; -} __attribute__((packed)); - -static void hsw_free(struct sst_dsp *sst); - -static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, - struct fw_module_header *module) -{ - struct dma_block_info *block; - struct sst_module *mod; - struct sst_module_template template; - int count, ret; - void __iomem *ram; - int type = le16_to_cpu(module->type); - int entry_point = le32_to_cpu(module->entry_point); - - /* TODO: allowed module types need to be configurable */ - if (type != SST_HSW_MODULE_BASE_FW && - type != SST_HSW_MODULE_PCM_SYSTEM && - type != SST_HSW_MODULE_PCM && - type != SST_HSW_MODULE_PCM_REFERENCE && - type != SST_HSW_MODULE_PCM_CAPTURE && - type != SST_HSW_MODULE_WAVES && - type != SST_HSW_MODULE_LPAL) - return 0; - - dev_dbg(dsp->dev, "new module sign 0x%s size 0x%x blocks 0x%x type 0x%x\n", - module->signature, module->mod_size, - module->blocks, type); - dev_dbg(dsp->dev, " entrypoint 0x%x\n", entry_point); - dev_dbg(dsp->dev, " persistent 0x%x scratch 0x%x\n", - module->info.persistent_size, module->info.scratch_size); - - memset(&template, 0, sizeof(template)); - template.id = type; - template.entry = entry_point - 4; - template.persistent_size = le32_to_cpu(module->info.persistent_size); - template.scratch_size = le32_to_cpu(module->info.scratch_size); - - mod = sst_module_new(fw, &template, NULL); - if (mod == NULL) - return -ENOMEM; - - block = (void *)module + sizeof(*module); - - for (count = 0; count < le32_to_cpu(module->blocks); count++) { - - if (le32_to_cpu(block->size) <= 0) { - dev_err(dsp->dev, - "error: block %d size invalid\n", count); - sst_module_free(mod); - return -EINVAL; - } - - switch (le32_to_cpu(block->type)) { - case SST_HSW_IRAM: - ram = dsp->addr.lpe; - mod->offset = le32_to_cpu(block->ram_offset) + - dsp->addr.iram_offset; - mod->type = SST_MEM_IRAM; - break; - case SST_HSW_DRAM: - case SST_HSW_REGS: - ram = dsp->addr.lpe; - mod->offset = le32_to_cpu(block->ram_offset); - mod->type = SST_MEM_DRAM; - break; - default: - dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n", - block->type, count); - sst_module_free(mod); - return -EINVAL; - } - - mod->size = le32_to_cpu(block->size); - mod->data = (void *)block + sizeof(*block); - mod->data_offset = mod->data - fw->dma_buf; - - dev_dbg(dsp->dev, "module block %d type 0x%x " - "size 0x%x ==> ram %p offset 0x%x\n", - count, mod->type, block->size, ram, - block->ram_offset); - - ret = sst_module_alloc_blocks(mod); - if (ret < 0) { - dev_err(dsp->dev, "error: could not allocate blocks for module %d\n", - count); - sst_module_free(mod); - return ret; - } - - block = (void *)block + sizeof(*block) + - le32_to_cpu(block->size); - } - mod->state = SST_MODULE_STATE_LOADED; - - return 0; -} - -static int hsw_parse_fw_image(struct sst_fw *sst_fw) -{ - struct fw_header *header; - struct fw_module_header *module; - struct sst_dsp *dsp = sst_fw->dsp; - int ret, count; - - /* Read the header information from the data pointer */ - header = (struct fw_header *)sst_fw->dma_buf; - - /* verify FW */ - if ((strncmp(header->signature, SST_HSW_FW_SIGN, 4) != 0) || - (sst_fw->size != - le32_to_cpu(header->file_size) + sizeof(*header))) { - dev_err(dsp->dev, "error: invalid fw sign/filesize mismatch\n"); - return -EINVAL; - } - - dev_dbg(dsp->dev, "header size=0x%x modules=0x%x fmt=0x%x size=%zu\n", - header->file_size, header->modules, - header->file_format, sizeof(*header)); - - /* parse each module */ - module = (void *)sst_fw->dma_buf + sizeof(*header); - for (count = 0; count < le32_to_cpu(header->modules); count++) { - - /* module */ - ret = hsw_parse_module(dsp, sst_fw, module); - if (ret < 0) { - dev_err(dsp->dev, "error: invalid module %d\n", count); - return ret; - } - module = (void *)module + sizeof(*module) + - le32_to_cpu(module->mod_size); - } - - return 0; -} - -static irqreturn_t hsw_irq(int irq, void *context) -{ - struct sst_dsp *sst = (struct sst_dsp *) context; - u32 isr; - int ret = IRQ_NONE; - - spin_lock(&sst->spinlock); - - /* Interrupt arrived, check src */ - isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); - if (isr & SST_ISRX_DONE) { - trace_sst_irq_done(isr, - sst_dsp_shim_read_unlocked(sst, SST_IMRX)); - - /* Mask Done interrupt before return */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, - SST_IMRX_DONE, SST_IMRX_DONE); - ret = IRQ_WAKE_THREAD; - } - - if (isr & SST_ISRX_BUSY) { - trace_sst_irq_busy(isr, - sst_dsp_shim_read_unlocked(sst, SST_IMRX)); - - /* Mask Busy interrupt before return */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, - SST_IMRX_BUSY, SST_IMRX_BUSY); - ret = IRQ_WAKE_THREAD; - } - - spin_unlock(&sst->spinlock); - return ret; -} - -static void hsw_set_dsp_D3(struct sst_dsp *sst) -{ - u32 val; - u32 reg; - - /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - /* enable power gating and switch off DRAM & IRAM blocks */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - val |= SST_VDRTCL0_DSRAMPGE_MASK | - SST_VDRTCL0_ISRAMPGE_MASK; - val &= ~(SST_VDRTCL0_D3PGD | SST_VDRTCL0_D3SRAMPGD); - writel(val, sst->addr.pci_cfg + SST_VDRTCTL0); - - /* switch off audio PLL */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val |= SST_VDRTCL2_APLLSE_MASK; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - /* disable MCLK(clkctl.smos = 0) */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, - SST_CLKCTL_MASK, 0); - - /* Set D3 state, delay 50 us */ - val = readl(sst->addr.pci_cfg + SST_PMCS); - val |= SST_PMCS_PS_MASK; - writel(val, sst->addr.pci_cfg + SST_PMCS); - udelay(50); - - /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - udelay(50); - -} - -static void hsw_reset(struct sst_dsp *sst) -{ - /* put DSP into reset and stall */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, - SST_CSR_RST | SST_CSR_STALL, - SST_CSR_RST | SST_CSR_STALL); - - /* keep in reset for 10ms */ - mdelay(10); - - /* take DSP out of reset and keep stalled for FW loading */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, - SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL); -} - -static int hsw_set_dsp_D0(struct sst_dsp *sst) -{ - int tries = 10; - u32 reg, fw_dump_bit; - - /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - /* Disable D3PG (VDRTCTL0.D3PGD = 1) */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - reg |= SST_VDRTCL0_D3PGD; - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0); - - /* Set D0 state */ - reg = readl(sst->addr.pci_cfg + SST_PMCS); - reg &= ~SST_PMCS_PS_MASK; - writel(reg, sst->addr.pci_cfg + SST_PMCS); - - /* check that ADSP shim is enabled */ - while (tries--) { - reg = readl(sst->addr.pci_cfg + SST_PMCS) & SST_PMCS_PS_MASK; - if (reg == 0) - goto finish; - - msleep(1); - } - - return -ENODEV; - -finish: - /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, - SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0); - - /* stall DSP core, set clk to 192/96Mhz */ - sst_dsp_shim_update_bits_unlocked(sst, - SST_CSR, SST_CSR_STALL | SST_CSR_DCS_MASK, - SST_CSR_STALL | SST_CSR_DCS(4)); - - /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, - SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0, - SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0); - - /* Stall and reset core, set CSR */ - hsw_reset(sst); - - /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - udelay(50); - - /* switch on audio PLL */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - reg &= ~SST_VDRTCL2_APLLSE_MASK; - writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); - - /* set default power gating control, enable power gating control for all blocks. that is, - can't be accessed, please enable each block before accessing. */ - reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - reg |= SST_VDRTCL0_DSRAMPGE_MASK | SST_VDRTCL0_ISRAMPGE_MASK; - /* for D0, always enable the block(DSRAM[0]) used for FW dump */ - fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT; - writel(reg & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0); - - - /* disable DMA finish function for SSP0 & SSP1 */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1, - SST_CSR2_SDFD_SSP1); - - /* set on-demond mode on engine 0,1 for all channels */ - sst_dsp_shim_update_bits(sst, SST_HMDC, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); - - /* Enable Interrupt from both sides */ - sst_dsp_shim_update_bits(sst, SST_IMRX, (SST_IMRX_BUSY | SST_IMRX_DONE), - 0x0); - sst_dsp_shim_update_bits(sst, SST_IMRD, (SST_IMRD_DONE | SST_IMRD_BUSY | - SST_IMRD_SSP0 | SST_IMRD_DMAC), 0x0); - - /* clear IPC registers */ - sst_dsp_shim_write(sst, SST_IPCX, 0x0); - sst_dsp_shim_write(sst, SST_IPCD, 0x0); - sst_dsp_shim_write(sst, 0x80, 0x6); - sst_dsp_shim_write(sst, 0xe0, 0x300a); - - return 0; -} - -static void hsw_boot(struct sst_dsp *sst) -{ - /* set oportunistic mode on engine 0,1 for all channels */ - sst_dsp_shim_update_bits(sst, SST_HMDC, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, 0); - - /* set DSP to RUN */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0); -} - -static void hsw_stall(struct sst_dsp *sst) -{ - /* stall DSP */ - sst_dsp_shim_update_bits(sst, SST_CSR, - SST_CSR_24MHZ_LPCS | SST_CSR_STALL, - SST_CSR_STALL | SST_CSR_24MHZ_LPCS); -} - -static void hsw_sleep(struct sst_dsp *sst) -{ - dev_dbg(sst->dev, "HSW_PM dsp runtime suspend\n"); - - /* put DSP into reset and stall */ - sst_dsp_shim_update_bits(sst, SST_CSR, - SST_CSR_24MHZ_LPCS | SST_CSR_RST | SST_CSR_STALL, - SST_CSR_RST | SST_CSR_STALL | SST_CSR_24MHZ_LPCS); - - hsw_set_dsp_D3(sst); - dev_dbg(sst->dev, "HSW_PM dsp runtime suspend exit\n"); -} - -static int hsw_wake(struct sst_dsp *sst) -{ - int ret; - - dev_dbg(sst->dev, "HSW_PM dsp runtime resume\n"); - - ret = hsw_set_dsp_D0(sst); - if (ret < 0) - return ret; - - dev_dbg(sst->dev, "HSW_PM dsp runtime resume exit\n"); - - return 0; -} - -struct sst_adsp_memregion { - u32 start; - u32 end; - int blocks; - enum sst_mem_type type; -}; - -/* lynx point ADSP mem regions */ -static const struct sst_adsp_memregion lp_region[] = { - {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ - {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */ - {0x80000, 0xE0000, 12, SST_MEM_IRAM}, /* I-SRAM - 12 * 32kB */ -}; - -/* wild cat point ADSP mem regions */ -static const struct sst_adsp_memregion wpt_region[] = { - {0x00000, 0xA0000, 20, SST_MEM_DRAM}, /* D-SRAM0,D-SRAM1,D-SRAM2 - 20 * 32kB */ - {0xA0000, 0xF0000, 10, SST_MEM_IRAM}, /* I-SRAM - 10 * 32kB */ -}; - -static int hsw_acpi_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) -{ - /* ADSP DRAM & IRAM */ - sst->addr.lpe_base = pdata->lpe_base; - sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); - if (!sst->addr.lpe) - return -ENODEV; - - /* ADSP PCI MMIO config space */ - sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); - if (!sst->addr.pci_cfg) { - iounmap(sst->addr.lpe); - return -ENODEV; - } - - /* SST Shim */ - sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; - return 0; -} - -struct sst_sram_shift { - u32 dev_id; /* SST Device IDs */ - u32 iram_shift; - u32 dram_shift; -}; - -static const struct sst_sram_shift sram_shift[] = { - {SST_DEV_ID_LYNX_POINT, 6, 16}, /* lp */ - {SST_DEV_ID_WILDCAT_POINT, 2, 12}, /* wpt */ -}; - -static u32 hsw_block_get_bit(struct sst_mem_block *block) -{ - u32 bit = 0, shift = 0, index; - struct sst_dsp *sst = block->dsp; - - for (index = 0; index < ARRAY_SIZE(sram_shift); index++) { - if (sram_shift[index].dev_id == sst->id) - break; - } - - if (index < ARRAY_SIZE(sram_shift)) { - switch (block->type) { - case SST_MEM_DRAM: - shift = sram_shift[index].dram_shift; - break; - case SST_MEM_IRAM: - shift = sram_shift[index].iram_shift; - break; - default: - shift = 0; - } - } else - shift = 0; - - bit = 1 << (block->index + shift); - - return bit; -} - -/*dummy read a SRAM block.*/ -static void sst_mem_block_dummy_read(struct sst_mem_block *block) -{ - u32 size; - u8 tmp_buf[4]; - struct sst_dsp *sst = block->dsp; - - size = block->size > 4 ? 4 : block->size; - memcpy_fromio(tmp_buf, sst->addr.lpe + block->offset, size); -} - -/* enable 32kB memory block - locks held by caller */ -static int hsw_block_enable(struct sst_mem_block *block) -{ - struct sst_dsp *sst = block->dsp; - u32 bit, val; - - if (block->users++ > 0) - return 0; - - dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - - /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val &= ~SST_VDRTCL2_DCLCGE; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - bit = hsw_block_get_bit(block); - writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0); - - /* wait 18 DSP clock ticks */ - udelay(10); - - /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val |= SST_VDRTCL2_DCLCGE; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - udelay(50); - - /*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/ - sst_mem_block_dummy_read(block); - return 0; -} - -/* disable 32kB memory block - locks held by caller */ -static int hsw_block_disable(struct sst_mem_block *block) -{ - struct sst_dsp *sst = block->dsp; - u32 bit, val; - - if (--block->users > 0) - return 0; - - dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n", - block->type, block->index, block->offset); - - /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val &= ~SST_VDRTCL2_DCLCGE; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - - val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); - bit = hsw_block_get_bit(block); - /* don't disable DSRAM[0], keep it always enable for FW dump*/ - if (bit != (1 << SST_VDRTCL0_DSRAMPGE_SHIFT)) - writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0); - - /* wait 18 DSP clock ticks */ - udelay(10); - - /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ - val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); - val |= SST_VDRTCL2_DCLCGE; - writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); - - udelay(50); - - return 0; -} - -static const struct sst_block_ops sst_hsw_ops = { - .enable = hsw_block_enable, - .disable = hsw_block_disable, -}; - -static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) -{ - const struct sst_adsp_memregion *region; - struct device *dev; - int ret = -ENODEV, i, j, region_count; - u32 offset, size, fw_dump_bit; - - dev = sst->dma_dev; - - switch (sst->id) { - case SST_DEV_ID_LYNX_POINT: - region = lp_region; - region_count = ARRAY_SIZE(lp_region); - sst->addr.iram_offset = SST_LP_IRAM_OFFSET; - sst->addr.dsp_iram_offset = SST_LPT_DSP_IRAM_OFFSET; - sst->addr.dsp_dram_offset = SST_LPT_DSP_DRAM_OFFSET; - sst->addr.shim_offset = SST_LP_SHIM_OFFSET; - break; - case SST_DEV_ID_WILDCAT_POINT: - region = wpt_region; - region_count = ARRAY_SIZE(wpt_region); - sst->addr.iram_offset = SST_WPT_IRAM_OFFSET; - sst->addr.dsp_iram_offset = SST_WPT_DSP_IRAM_OFFSET; - sst->addr.dsp_dram_offset = SST_WPT_DSP_DRAM_OFFSET; - sst->addr.shim_offset = SST_WPT_SHIM_OFFSET; - break; - default: - dev_err(dev, "error: failed to get mem resources\n"); - return ret; - } - - ret = hsw_acpi_resource_map(sst, pdata); - if (ret < 0) { - dev_err(dev, "error: failed to map resources\n"); - return ret; - } - - /* enable the DSP SHIM */ - ret = hsw_set_dsp_D0(sst); - if (ret < 0) { - dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n"); - return ret; - } - - ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); - if (ret) - return ret; - - - /* register DSP memory blocks - ideally we should get this from ACPI */ - for (i = 0; i < region_count; i++) { - offset = region[i].start; - size = (region[i].end - region[i].start) / region[i].blocks; - - /* register individual memory blocks */ - for (j = 0; j < region[i].blocks; j++) { - sst_mem_block_register(sst, offset, size, - region[i].type, &sst_hsw_ops, j, sst); - offset += size; - } - } - - /* always enable the block(DSRAM[0]) used for FW dump */ - fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT; - /* set default power gating control, enable power gating control for all blocks. that is, - can't be accessed, please enable each block before accessing. */ - writel(0xffffffff & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0); - - return 0; -} - -static void hsw_free(struct sst_dsp *sst) -{ - sst_mem_block_unregister_all(sst); - iounmap(sst->addr.lpe); - iounmap(sst->addr.pci_cfg); -} - -struct sst_ops haswell_ops = { - .reset = hsw_reset, - .boot = hsw_boot, - .stall = hsw_stall, - .wake = hsw_wake, - .sleep = hsw_sleep, - .write = sst_shim32_write, - .read = sst_shim32_read, - .write64 = sst_shim32_write64, - .read64 = sst_shim32_read64, - .ram_read = sst_memcpy_fromio_32, - .ram_write = sst_memcpy_toio_32, - .irq_handler = hsw_irq, - .init = hsw_init, - .free = hsw_free, - .parse_fw = hsw_parse_fw_image, -}; diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c deleted file mode 100644 index 0ff89ea96ccf..000000000000 --- a/sound/soc/intel/haswell/sst-haswell-ipc.c +++ /dev/null @@ -1,2222 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel SST Haswell/Broadwell IPC Support - * - * Copyright (C) 2013, Intel Corporation. All rights reserved. - */ - -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/list.h> -#include <linux/device.h> -#include <linux/wait.h> -#include <linux/spinlock.h> -#include <linux/workqueue.h> -#include <linux/export.h> -#include <linux/slab.h> -#include <linux/delay.h> -#include <linux/sched.h> -#include <linux/platform_device.h> -#include <linux/firmware.h> -#include <linux/dma-mapping.h> -#include <linux/debugfs.h> -#include <linux/pm_runtime.h> -#include <sound/asound.h> - -#include "sst-haswell-ipc.h" -#include "../common/sst-dsp.h" -#include "../common/sst-dsp-priv.h" -#include "../common/sst-ipc.h" - -/* Global Message - Generic */ -#define IPC_GLB_TYPE_SHIFT 24 -#define IPC_GLB_TYPE_MASK (0x1f << IPC_GLB_TYPE_SHIFT) -#define IPC_GLB_TYPE(x) (x << IPC_GLB_TYPE_SHIFT) - -/* Global Message - Reply */ -#define IPC_GLB_REPLY_SHIFT 0 -#define IPC_GLB_REPLY_MASK (0x1f << IPC_GLB_REPLY_SHIFT) -#define IPC_GLB_REPLY_TYPE(x) (x << IPC_GLB_REPLY_TYPE_SHIFT) - -/* Stream Message - Generic */ -#define IPC_STR_TYPE_SHIFT 20 -#define IPC_STR_TYPE_MASK (0xf << IPC_STR_TYPE_SHIFT) -#define IPC_STR_TYPE(x) (x << IPC_STR_TYPE_SHIFT) -#define IPC_STR_ID_SHIFT 16 -#define IPC_STR_ID_MASK (0xf << IPC_STR_ID_SHIFT) -#define IPC_STR_ID(x) (x << IPC_STR_ID_SHIFT) - -/* Stream Message - Reply */ -#define IPC_STR_REPLY_SHIFT 0 -#define IPC_STR_REPLY_MASK (0x1f << IPC_STR_REPLY_SHIFT) - -/* Stream Stage Message - Generic */ -#define IPC_STG_TYPE_SHIFT 12 -#define IPC_STG_TYPE_MASK (0xf << IPC_STG_TYPE_SHIFT) -#define IPC_STG_TYPE(x) (x << IPC_STG_TYPE_SHIFT) -#define IPC_STG_ID_SHIFT 10 -#define IPC_STG_ID_MASK (0x3 << IPC_STG_ID_SHIFT) -#define IPC_STG_ID(x) (x << IPC_STG_ID_SHIFT) - -/* Stream Stage Message - Reply */ -#define IPC_STG_REPLY_SHIFT 0 -#define IPC_STG_REPLY_MASK (0x1f << IPC_STG_REPLY_SHIFT) - -/* Debug Log Message - Generic */ -#define IPC_LOG_OP_SHIFT 20 -#define IPC_LOG_OP_MASK (0xf << IPC_LOG_OP_SHIFT) -#define IPC_LOG_OP_TYPE(x) (x << IPC_LOG_OP_SHIFT) -#define IPC_LOG_ID_SHIFT 16 -#define IPC_LOG_ID_MASK (0xf << IPC_LOG_ID_SHIFT) -#define IPC_LOG_ID(x) (x << IPC_LOG_ID_SHIFT) - -/* Module Message */ -#define IPC_MODULE_OPERATION_SHIFT 20 -#define IPC_MODULE_OPERATION_MASK (0xf << IPC_MODULE_OPERATION_SHIFT) -#define IPC_MODULE_OPERATION(x) (x << IPC_MODULE_OPERATION_SHIFT) - -#define IPC_MODULE_ID_SHIFT 16 -#define IPC_MODULE_ID_MASK (0xf << IPC_MODULE_ID_SHIFT) -#define IPC_MODULE_ID(x) (x << IPC_MODULE_ID_SHIFT) - -/* IPC message timeout (msecs) */ -#define IPC_TIMEOUT_MSECS 300 -#define IPC_BOOT_MSECS 200 -#define IPC_MSG_WAIT 0 -#define IPC_MSG_NOWAIT 1 - -/* Firmware Ready Message */ -#define IPC_FW_READY (0x1 << 29) -#define IPC_STATUS_MASK (0x3 << 30) - -#define IPC_EMPTY_LIST_SIZE 8 -#define IPC_MAX_STREAMS 4 - -/* Mailbox */ -#define IPC_MAX_MAILBOX_BYTES 256 - -#define INVALID_STREAM_HW_ID 0xffffffff - -/* Global Message - Types and Replies */ -enum ipc_glb_type { - IPC_GLB_GET_FW_VERSION = 0, /* Retrieves firmware version */ - IPC_GLB_PERFORMANCE_MONITOR = 1, /* Performance monitoring actions */ - IPC_GLB_ALLOCATE_STREAM = 3, /* Request to allocate new stream */ - IPC_GLB_FREE_STREAM = 4, /* Request to free stream */ - IPC_GLB_GET_FW_CAPABILITIES = 5, /* Retrieves firmware capabilities */ - IPC_GLB_STREAM_MESSAGE = 6, /* Message directed to stream or its stages */ - /* Request to store firmware context during D0->D3 transition */ - IPC_GLB_REQUEST_DUMP = 7, - /* Request to restore firmware context during D3->D0 transition */ - IPC_GLB_RESTORE_CONTEXT = 8, - IPC_GLB_GET_DEVICE_FORMATS = 9, /* Set device format */ - IPC_GLB_SET_DEVICE_FORMATS = 10, /* Get device format */ - IPC_GLB_SHORT_REPLY = 11, - IPC_GLB_ENTER_DX_STATE = 12, - IPC_GLB_GET_MIXER_STREAM_INFO = 13, /* Request mixer stream params */ - IPC_GLB_DEBUG_LOG_MESSAGE = 14, /* Message to or from the debug logger. */ - IPC_GLB_MODULE_OPERATION = 15, /* Message to loadable fw module */ - IPC_GLB_REQUEST_TRANSFER = 16, /* < Request Transfer for host */ - IPC_GLB_MAX_IPC_MESSAGE_TYPE = 17, /* Maximum message number */ -}; - -enum ipc_glb_reply { - IPC_GLB_REPLY_SUCCESS = 0, /* The operation was successful. */ - IPC_GLB_REPLY_ERROR_INVALID_PARAM = 1, /* Invalid parameter was passed. */ - IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE = 2, /* Uknown message type was resceived. */ - IPC_GLB_REPLY_OUT_OF_RESOURCES = 3, /* No resources to satisfy the request. */ - IPC_GLB_REPLY_BUSY = 4, /* The system or resource is busy. */ - IPC_GLB_REPLY_PENDING = 5, /* The action was scheduled for processing. */ - IPC_GLB_REPLY_FAILURE = 6, /* Critical error happened. */ - IPC_GLB_REPLY_INVALID_REQUEST = 7, /* Request can not be completed. */ - IPC_GLB_REPLY_STAGE_UNINITIALIZED = 8, /* Processing stage was uninitialized. */ - IPC_GLB_REPLY_NOT_FOUND = 9, /* Required resource can not be found. */ - IPC_GLB_REPLY_SOURCE_NOT_STARTED = 10, /* Source was not started. */ -}; - -enum ipc_module_operation { - IPC_MODULE_NOTIFICATION = 0, - IPC_MODULE_ENABLE = 1, - IPC_MODULE_DISABLE = 2, - IPC_MODULE_GET_PARAMETER = 3, - IPC_MODULE_SET_PARAMETER = 4, - IPC_MODULE_GET_INFO = 5, - IPC_MODULE_MAX_MESSAGE -}; - -/* Stream Message - Types */ -enum ipc_str_operation { - IPC_STR_RESET = 0, - IPC_STR_PAUSE = 1, - IPC_STR_RESUME = 2, - IPC_STR_STAGE_MESSAGE = 3, - IPC_STR_NOTIFICATION = 4, - IPC_STR_MAX_MESSAGE -}; - -/* Stream Stage Message Types */ -enum ipc_stg_operation { - IPC_STG_GET_VOLUME = 0, - IPC_STG_SET_VOLUME, - IPC_STG_SET_WRITE_POSITION, - IPC_STG_SET_FX_ENABLE, - IPC_STG_SET_FX_DISABLE, - IPC_STG_SET_FX_GET_PARAM, - IPC_STG_SET_FX_SET_PARAM, - IPC_STG_SET_FX_GET_INFO, - IPC_STG_MUTE_LOOPBACK, - IPC_STG_MAX_MESSAGE -}; - -/* Stream Stage Message Types For Notification*/ -enum ipc_stg_operation_notify { - IPC_POSITION_CHANGED = 0, - IPC_STG_GLITCH, - IPC_STG_MAX_NOTIFY -}; - -enum ipc_glitch_type { - IPC_GLITCH_UNDERRUN = 1, - IPC_GLITCH_DECODER_ERROR, - IPC_GLITCH_DOUBLED_WRITE_POS, - IPC_GLITCH_MAX -}; - -/* Debug Control */ -enum ipc_debug_operation { - IPC_DEBUG_ENABLE_LOG = 0, - IPC_DEBUG_DISABLE_LOG = 1, - IPC_DEBUG_REQUEST_LOG_DUMP = 2, - IPC_DEBUG_NOTIFY_LOG_DUMP = 3, - IPC_DEBUG_MAX_DEBUG_LOG -}; - -/* Firmware Ready */ -struct sst_hsw_ipc_fw_ready { - u32 inbox_offset; - u32 outbox_offset; - u32 inbox_size; - u32 outbox_size; - u32 fw_info_size; - u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; -} __attribute__((packed)); - -struct sst_hsw_stream; -struct sst_hsw; - -/* Stream infomation */ -struct sst_hsw_stream { - /* configuration */ - struct sst_hsw_ipc_stream_alloc_req request; - struct sst_hsw_ipc_stream_alloc_reply reply; - struct sst_hsw_ipc_stream_free_req free_req; - - /* Mixer info */ - u32 mute_volume[SST_HSW_NO_CHANNELS]; - u32 mute[SST_HSW_NO_CHANNELS]; - - /* runtime info */ - struct sst_hsw *hsw; - int host_id; - bool commited; - bool running; - - /* Notification work */ - struct work_struct notify_work; - u32 header; - - /* Position info from DSP */ - struct sst_hsw_ipc_stream_set_position wpos; - struct sst_hsw_ipc_stream_get_position rpos; - struct sst_hsw_ipc_stream_glitch_position glitch; - - /* Volume info */ - struct sst_hsw_ipc_volume_req vol_req; - - /* driver callback */ - u32 (*notify_position)(struct sst_hsw_stream *stream, void *data); - void *pdata; - - /* record the fw read position when playback */ - snd_pcm_uframes_t old_position; - bool play_silence; - struct list_head node; -}; - -/* FW log ring information */ -struct sst_hsw_log_stream { - dma_addr_t dma_addr; - unsigned char *dma_area; - unsigned char *ring_descr; - int pages; - int size; - - /* Notification work */ - struct work_struct notify_work; - wait_queue_head_t readers_wait_q; - struct mutex rw_mutex; - - u32 last_pos; - u32 curr_pos; - u32 reader_pos; - - /* fw log config */ - u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; - - struct sst_hsw *hsw; -}; - -/* SST Haswell IPC data */ -struct sst_hsw { - struct device *dev; - struct sst_dsp *dsp; - struct platform_device *pdev_pcm; - - /* FW config */ - struct sst_hsw_ipc_fw_ready fw_ready; - struct sst_hsw_ipc_fw_version version; - bool fw_done; - struct sst_fw *sst_fw; - - /* stream */ - struct list_head stream_list; - - /* global mixer */ - struct sst_hsw_ipc_stream_info_reply mixer_info; - enum sst_hsw_volume_curve curve_type; - u32 curve_duration; - u32 mute[SST_HSW_NO_CHANNELS]; - u32 mute_volume[SST_HSW_NO_CHANNELS]; - - /* DX */ - struct sst_hsw_ipc_dx_reply dx; - void *dx_context; - dma_addr_t dx_context_paddr; - enum sst_hsw_device_id dx_dev; - enum sst_hsw_device_mclk dx_mclk; - enum sst_hsw_device_mode dx_mode; - u32 dx_clock_divider; - - /* boot */ - wait_queue_head_t boot_wait; - bool boot_complete; - bool shutdown; - - /* IPC messaging */ - struct sst_generic_ipc ipc; - - /* FW log stream */ - struct sst_hsw_log_stream log_stream; - - /* flags bit field to track module state when resume from RTD3, - * each bit represent state (enabled/disabled) of single module */ - u32 enabled_modules_rtd3; - - /* buffer to store parameter lines */ - u32 param_idx_w; /* write index */ - u32 param_idx_r; /* read index */ - u8 param_buf[WAVES_PARAM_LINES][WAVES_PARAM_COUNT]; -}; - -#define CREATE_TRACE_POINTS -#include <trace/events/hswadsp.h> - -static inline u32 msg_get_global_type(u32 msg) -{ - return (msg & IPC_GLB_TYPE_MASK) >> IPC_GLB_TYPE_SHIFT; -} - -static inline u32 msg_get_global_reply(u32 msg) -{ - return (msg & IPC_GLB_REPLY_MASK) >> IPC_GLB_REPLY_SHIFT; -} - -static inline u32 msg_get_stream_type(u32 msg) -{ - return (msg & IPC_STR_TYPE_MASK) >> IPC_STR_TYPE_SHIFT; -} - -static inline u32 msg_get_stream_id(u32 msg) -{ - return (msg & IPC_STR_ID_MASK) >> IPC_STR_ID_SHIFT; -} - -static inline u32 msg_get_notify_reason(u32 msg) -{ - return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; -} - -static inline u32 msg_get_module_operation(u32 msg) -{ - return (msg & IPC_MODULE_OPERATION_MASK) >> IPC_MODULE_OPERATION_SHIFT; -} - -static inline u32 msg_get_module_id(u32 msg) -{ - return (msg & IPC_MODULE_ID_MASK) >> IPC_MODULE_ID_SHIFT; -} - -u32 create_channel_map(enum sst_hsw_channel_config config) -{ - switch (config) { - case SST_HSW_CHANNEL_CONFIG_MONO: - return (0xFFFFFFF0 | SST_HSW_CHANNEL_CENTER); - case SST_HSW_CHANNEL_CONFIG_STEREO: - return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_RIGHT << 4)); - case SST_HSW_CHANNEL_CONFIG_2_POINT_1: - return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_RIGHT << 4) - | (SST_HSW_CHANNEL_LFE << 8 )); - case SST_HSW_CHANNEL_CONFIG_3_POINT_0: - return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_CENTER << 4) - | (SST_HSW_CHANNEL_RIGHT << 8)); - case SST_HSW_CHANNEL_CONFIG_3_POINT_1: - return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_CENTER << 4) - | (SST_HSW_CHANNEL_RIGHT << 8) - | (SST_HSW_CHANNEL_LFE << 12)); - case SST_HSW_CHANNEL_CONFIG_QUATRO: - return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_RIGHT << 4) - | (SST_HSW_CHANNEL_LEFT_SURROUND << 8) - | (SST_HSW_CHANNEL_RIGHT_SURROUND << 12)); - case SST_HSW_CHANNEL_CONFIG_4_POINT_0: - return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_CENTER << 4) - | (SST_HSW_CHANNEL_RIGHT << 8) - | (SST_HSW_CHANNEL_CENTER_SURROUND << 12)); - case SST_HSW_CHANNEL_CONFIG_5_POINT_0: - return (0xFFF00000 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_CENTER << 4) - | (SST_HSW_CHANNEL_RIGHT << 8) - | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) - | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16)); - case SST_HSW_CHANNEL_CONFIG_5_POINT_1: - return (0xFF000000 | SST_HSW_CHANNEL_CENTER - | (SST_HSW_CHANNEL_LEFT << 4) - | (SST_HSW_CHANNEL_RIGHT << 8) - | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) - | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16) - | (SST_HSW_CHANNEL_LFE << 20)); - case SST_HSW_CHANNEL_CONFIG_DUAL_MONO: - return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT - | (SST_HSW_CHANNEL_LEFT << 4)); - default: - return 0xFFFFFFFF; - } -} - -static struct sst_hsw_stream *get_stream_by_id(struct sst_hsw *hsw, - int stream_id) -{ - struct sst_hsw_stream *stream; - - list_for_each_entry(stream, &hsw->stream_list, node) { - if (stream->reply.stream_hw_id == stream_id) - return stream; - } - - return NULL; -} - -static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) -{ - struct sst_hsw_ipc_fw_ready fw_ready; - u32 offset; - u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; - char *tmp[5], *pinfo; - int i = 0; - - offset = (header & 0x1FFFFFFF) << 3; - - dev_dbg(hsw->dev, "ipc: DSP is ready 0x%8.8x offset %d\n", - header, offset); - - /* copy data from the DSP FW ready offset */ - sst_dsp_read(hsw->dsp, &fw_ready, offset, sizeof(fw_ready)); - - sst_dsp_mailbox_init(hsw->dsp, fw_ready.inbox_offset, - fw_ready.inbox_size, fw_ready.outbox_offset, - fw_ready.outbox_size); - - hsw->boot_complete = true; - wake_up(&hsw->boot_wait); - - dev_dbg(hsw->dev, " mailbox upstream 0x%x - size 0x%x\n", - fw_ready.inbox_offset, fw_ready.inbox_size); - dev_dbg(hsw->dev, " mailbox downstream 0x%x - size 0x%x\n", - fw_ready.outbox_offset, fw_ready.outbox_size); - if (fw_ready.fw_info_size < sizeof(fw_ready.fw_info)) { - fw_ready.fw_info[fw_ready.fw_info_size] = 0; - dev_dbg(hsw->dev, " Firmware info: %s \n", fw_ready.fw_info); - - /* log the FW version info got from the mailbox here. */ - memcpy(fw_info, fw_ready.fw_info, fw_ready.fw_info_size); - pinfo = &fw_info[0]; - for (i = 0; i < ARRAY_SIZE(tmp); i++) - tmp[i] = strsep(&pinfo, " "); - dev_info(hsw->dev, "FW loaded, mailbox readback FW info: type %s, - " - "version: %s.%s, build %s, source commit id: %s\n", - tmp[0], tmp[1], tmp[2], tmp[3], tmp[4]); - } -} - -static void hsw_notification_work(struct work_struct *work) -{ - struct sst_hsw_stream *stream = container_of(work, - struct sst_hsw_stream, notify_work); - struct sst_hsw_ipc_stream_glitch_position *glitch = &stream->glitch; - struct sst_hsw_ipc_stream_get_position *pos = &stream->rpos; - struct sst_hsw *hsw = stream->hsw; - u32 reason; - - reason = msg_get_notify_reason(stream->header); - - switch (reason) { - case IPC_STG_GLITCH: - trace_ipc_notification("DSP stream under/overrun", - stream->reply.stream_hw_id); - sst_dsp_inbox_read(hsw->dsp, glitch, sizeof(*glitch)); - - dev_err(hsw->dev, "glitch %d pos 0x%x write pos 0x%x\n", - glitch->glitch_type, glitch->present_pos, - glitch->write_pos); - break; - - case IPC_POSITION_CHANGED: - trace_ipc_notification("DSP stream position changed for", - stream->reply.stream_hw_id); - sst_dsp_inbox_read(hsw->dsp, pos, sizeof(*pos)); - - if (stream->notify_position) - stream->notify_position(stream, stream->pdata); - - break; - default: - dev_err(hsw->dev, "error: unknown notification 0x%x\n", - stream->header); - break; - } - - /* tell DSP that notification has been handled */ - sst_dsp_shim_update_bits(hsw->dsp, SST_IPCD, - SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); - - /* unmask busy interrupt */ - sst_dsp_shim_update_bits(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); -} - -static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) -{ - struct sst_hsw_stream *stream; - u32 header = msg->tx.header & ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); - u32 stream_id = msg_get_stream_id(header); - u32 stream_msg = msg_get_stream_type(header); - - stream = get_stream_by_id(hsw, stream_id); - if (stream == NULL) - return; - - switch (stream_msg) { - case IPC_STR_STAGE_MESSAGE: - case IPC_STR_NOTIFICATION: - break; - case IPC_STR_RESET: - trace_ipc_notification("stream reset", stream->reply.stream_hw_id); - break; - case IPC_STR_PAUSE: - stream->running = false; - trace_ipc_notification("stream paused", - stream->reply.stream_hw_id); - break; - case IPC_STR_RESUME: - stream->running = true; - trace_ipc_notification("stream running", - stream->reply.stream_hw_id); - break; - } -} - -static int hsw_process_reply(struct sst_hsw *hsw, u32 header) -{ - struct ipc_message *msg; - u32 reply = msg_get_global_reply(header); - - trace_ipc_reply("processing -->", header); - - msg = sst_ipc_reply_find_msg(&hsw->ipc, header); - if (msg == NULL) { - trace_ipc_error("error: can't find message header", header); - return -EIO; - } - - msg->rx.header = header; - /* first process the header */ - switch (reply) { - case IPC_GLB_REPLY_PENDING: - trace_ipc_pending_reply("received", header); - msg->pending = true; - hsw->ipc.pending = true; - return 1; - case IPC_GLB_REPLY_SUCCESS: - if (msg->pending) { - trace_ipc_pending_reply("completed", header); - sst_dsp_inbox_read(hsw->dsp, msg->rx.data, - msg->rx.size); - hsw->ipc.pending = false; - } else { - /* copy data from the DSP */ - sst_dsp_outbox_read(hsw->dsp, msg->rx.data, - msg->rx.size); - } - break; - /* these will be rare - but useful for debug */ - case IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE: - trace_ipc_error("error: unknown message type", header); - msg->errno = -EBADMSG; - break; - case IPC_GLB_REPLY_OUT_OF_RESOURCES: - trace_ipc_error("error: out of resources", header); - msg->errno = -ENOMEM; - break; - case IPC_GLB_REPLY_BUSY: - trace_ipc_error("error: reply busy", header); - msg->errno = -EBUSY; - break; - case IPC_GLB_REPLY_FAILURE: - trace_ipc_error("error: reply failure", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_STAGE_UNINITIALIZED: - trace_ipc_error("error: stage uninitialized", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_NOT_FOUND: - trace_ipc_error("error: reply not found", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_SOURCE_NOT_STARTED: - trace_ipc_error("error: source not started", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_INVALID_REQUEST: - trace_ipc_error("error: invalid request", header); - msg->errno = -EINVAL; - break; - case IPC_GLB_REPLY_ERROR_INVALID_PARAM: - trace_ipc_error("error: invalid parameter", header); - msg->errno = -EINVAL; - break; - default: - trace_ipc_error("error: unknown reply", header); - msg->errno = -EINVAL; - break; - } - - /* update any stream states */ - if (msg_get_global_type(header) == IPC_GLB_STREAM_MESSAGE) - hsw_stream_update(hsw, msg); - - /* wake up and return the error if we have waiters on this message ? */ - list_del(&msg->list); - sst_ipc_tx_msg_reply_complete(&hsw->ipc, msg); - - return 1; -} - -static int hsw_module_message(struct sst_hsw *hsw, u32 header) -{ - u32 operation, module_id; - int handled = 0; - - operation = msg_get_module_operation(header); - module_id = msg_get_module_id(header); - dev_dbg(hsw->dev, "received module message header: 0x%8.8x\n", - header); - dev_dbg(hsw->dev, "operation: 0x%8.8x module_id: 0x%8.8x\n", - operation, module_id); - - switch (operation) { - case IPC_MODULE_NOTIFICATION: - dev_dbg(hsw->dev, "module notification received"); - handled = 1; - break; - default: - handled = hsw_process_reply(hsw, header); - break; - } - - return handled; -} - -static int hsw_stream_message(struct sst_hsw *hsw, u32 header) -{ - u32 stream_msg, stream_id; - struct sst_hsw_stream *stream; - int handled = 0; - - stream_msg = msg_get_stream_type(header); - stream_id = msg_get_stream_id(header); - - stream = get_stream_by_id(hsw, stream_id); - if (stream == NULL) - return handled; - - stream->header = header; - - switch (stream_msg) { - case IPC_STR_STAGE_MESSAGE: - dev_err(hsw->dev, "error: stage msg not implemented 0x%8.8x\n", - header); - break; - case IPC_STR_NOTIFICATION: - schedule_work(&stream->notify_work); - break; - default: - /* handle pending message complete request */ - handled = hsw_process_reply(hsw, header); - break; - } - - return handled; -} - -static int hsw_log_message(struct sst_hsw *hsw, u32 header) -{ - u32 operation = (header & IPC_LOG_OP_MASK) >> IPC_LOG_OP_SHIFT; - struct sst_hsw_log_stream *stream = &hsw->log_stream; - int ret = 1; - - if (operation != IPC_DEBUG_REQUEST_LOG_DUMP) { - dev_err(hsw->dev, - "error: log msg not implemented 0x%8.8x\n", header); - return 0; - } - - mutex_lock(&stream->rw_mutex); - stream->last_pos = stream->curr_pos; - sst_dsp_inbox_read( - hsw->dsp, &stream->curr_pos, sizeof(stream->curr_pos)); - mutex_unlock(&stream->rw_mutex); - - schedule_work(&stream->notify_work); - - return ret; -} - -static int hsw_process_notification(struct sst_hsw *hsw) -{ - struct sst_dsp *sst = hsw->dsp; - u32 type, header; - int handled = 1; - - header = sst_dsp_shim_read_unlocked(sst, SST_IPCD); - type = msg_get_global_type(header); - - trace_ipc_request("processing -->", header); - - /* FW Ready is a special case */ - if (!hsw->boot_complete && header & IPC_FW_READY) { - hsw_fw_ready(hsw, header); - return handled; - } - - switch (type) { - case IPC_GLB_GET_FW_VERSION: - case IPC_GLB_ALLOCATE_STREAM: - case IPC_GLB_FREE_STREAM: - case IPC_GLB_GET_FW_CAPABILITIES: - case IPC_GLB_REQUEST_DUMP: - case IPC_GLB_GET_DEVICE_FORMATS: - case IPC_GLB_SET_DEVICE_FORMATS: - case IPC_GLB_ENTER_DX_STATE: - case IPC_GLB_GET_MIXER_STREAM_INFO: - case IPC_GLB_MAX_IPC_MESSAGE_TYPE: - case IPC_GLB_RESTORE_CONTEXT: - case IPC_GLB_SHORT_REPLY: - dev_err(hsw->dev, "error: message type %d header 0x%x\n", - type, header); - break; - case IPC_GLB_STREAM_MESSAGE: - handled = hsw_stream_message(hsw, header); - break; - case IPC_GLB_DEBUG_LOG_MESSAGE: - handled = hsw_log_message(hsw, header); - break; - case IPC_GLB_MODULE_OPERATION: - handled = hsw_module_message(hsw, header); - break; - default: - dev_err(hsw->dev, "error: unexpected type %d hdr 0x%8.8x\n", - type, header); - break; - } - - return handled; -} - -static irqreturn_t hsw_irq_thread(int irq, void *context) -{ - struct sst_dsp *sst = (struct sst_dsp *) context; - struct sst_hsw *hsw = sst_dsp_get_thread_context(sst); - struct sst_generic_ipc *ipc = &hsw->ipc; - u32 ipcx, ipcd; - unsigned long flags; - - spin_lock_irqsave(&sst->spinlock, flags); - - ipcx = sst_dsp_ipc_msg_rx(hsw->dsp); - ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); - - /* reply message from DSP */ - if (ipcx & SST_IPCX_DONE) { - - /* Handle Immediate reply from DSP Core */ - hsw_process_reply(hsw, ipcx); - - /* clear DONE bit - tell DSP we have completed */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX, - SST_IPCX_DONE, 0); - - /* unmask Done interrupt */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, - SST_IMRX_DONE, 0); - } - - /* new message from DSP */ - if (ipcd & SST_IPCD_BUSY) { - - /* Handle Notification and Delayed reply from DSP Core */ - hsw_process_notification(hsw); - - /* clear BUSY bit and set DONE bit - accept new messages */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD, - SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); - - /* unmask busy interrupt */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, - SST_IMRX_BUSY, 0); - } - - spin_unlock_irqrestore(&sst->spinlock, flags); - - /* continue to send any remaining messages... */ - schedule_work(&ipc->kwork); - - return IRQ_HANDLED; -} - -int sst_hsw_fw_get_version(struct sst_hsw *hsw, - struct sst_hsw_ipc_fw_version *version) -{ - struct sst_ipc_message request = {0}, reply = {0}; - int ret; - - request.header = IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION); - reply.data = version; - reply.size = sizeof(*version); - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, &reply); - if (ret < 0) - dev_err(hsw->dev, "error: get version failed\n"); - - return ret; -} - -/* Mixer Controls */ -int sst_hsw_stream_get_volume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - u32 stage_id, u32 channel, u32 *volume) -{ - if (channel > 1) - return -EINVAL; - - sst_dsp_read(hsw->dsp, volume, - stream->reply.volume_register_address[channel], - sizeof(*volume)); - - return 0; -} - -/* stream volume */ -int sst_hsw_stream_set_volume(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume) -{ - struct sst_hsw_ipc_volume_req *req; - struct sst_ipc_message request; - int ret; - - trace_ipc_request("set stream volume", stream->reply.stream_hw_id); - - if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) - return -EINVAL; - - request.header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | - IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); - request.header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); - request.header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); - request.header |= (stage_id << IPC_STG_ID_SHIFT); - - req = &stream->vol_req; - req->target_volume = volume; - - /* set both at same time ? */ - if (channel == SST_HSW_CHANNELS_ALL) { - if (hsw->mute[0] && hsw->mute[1]) { - hsw->mute_volume[0] = hsw->mute_volume[1] = volume; - return 0; - } else if (hsw->mute[0]) - req->channel = 1; - else if (hsw->mute[1]) - req->channel = 0; - else - req->channel = SST_HSW_CHANNELS_ALL; - } else { - /* set only 1 channel */ - if (hsw->mute[channel]) { - hsw->mute_volume[channel] = volume; - return 0; - } - req->channel = channel; - } - - request.data = req; - request.size = sizeof(*req); - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, NULL); - if (ret < 0) { - dev_err(hsw->dev, "error: set stream volume failed\n"); - return ret; - } - - return 0; -} - -int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, - u32 *volume) -{ - if (channel > 1) - return -EINVAL; - - sst_dsp_read(hsw->dsp, volume, - hsw->mixer_info.volume_register_address[channel], - sizeof(*volume)); - - return 0; -} - -/* global mixer volume */ -int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, - u32 volume) -{ - struct sst_hsw_ipc_volume_req req; - struct sst_ipc_message request; - int ret; - - trace_ipc_request("set mixer volume", volume); - - if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) - return -EINVAL; - - /* set both at same time ? */ - if (channel == SST_HSW_CHANNELS_ALL) { - if (hsw->mute[0] && hsw->mute[1]) { - hsw->mute_volume[0] = hsw->mute_volume[1] = volume; - return 0; - } else if (hsw->mute[0]) - req.channel = 1; - else if (hsw->mute[1]) - req.channel = 0; - else - req.channel = SST_HSW_CHANNELS_ALL; - } else { - /* set only 1 channel */ - if (hsw->mute[channel]) { - hsw->mute_volume[channel] = volume; - return 0; - } - req.channel = channel; - } - - request.header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | - IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); - request.header |= (hsw->mixer_info.mixer_hw_id << IPC_STR_ID_SHIFT); - request.header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); - request.header |= (stage_id << IPC_STG_ID_SHIFT); - - req.curve_duration = hsw->curve_duration; - req.curve_type = hsw->curve_type; - req.target_volume = volume; - - request.data = &req; - request.size = sizeof(req); - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, NULL); - if (ret < 0) { - dev_err(hsw->dev, "error: set mixer volume failed\n"); - return ret; - } - - return 0; -} - -/* Stream API */ -struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, - u32 (*notify_position)(struct sst_hsw_stream *stream, void *data), - void *data) -{ - struct sst_hsw_stream *stream; - struct sst_dsp *sst = hsw->dsp; - unsigned long flags; - - stream = kzalloc(sizeof(*stream), GFP_KERNEL); - if (stream == NULL) - return NULL; - - spin_lock_irqsave(&sst->spinlock, flags); - stream->reply.stream_hw_id = INVALID_STREAM_HW_ID; - list_add(&stream->node, &hsw->stream_list); - stream->notify_position = notify_position; - stream->pdata = data; - stream->hsw = hsw; - stream->host_id = id; - - /* work to process notification messages */ - INIT_WORK(&stream->notify_work, hsw_notification_work); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return stream; -} - -int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream) -{ - struct sst_ipc_message request; - int ret = 0; - struct sst_dsp *sst = hsw->dsp; - unsigned long flags; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to free, ignore it.\n"); - return 0; - } - - /* dont free DSP streams that are not commited */ - if (!stream->commited) - goto out; - - trace_ipc_request("stream free", stream->host_id); - - stream->free_req.stream_id = stream->reply.stream_hw_id; - request.header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM); - request.data = &stream->free_req; - request.size = sizeof(stream->free_req); - - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, NULL); - if (ret < 0) { - dev_err(hsw->dev, "error: free stream %d failed\n", - stream->free_req.stream_id); - return -EAGAIN; - } - - trace_hsw_stream_free_req(stream, &stream->free_req); - -out: - cancel_work_sync(&stream->notify_work); - spin_lock_irqsave(&sst->spinlock, flags); - list_del(&stream->node); - kfree(stream); - spin_unlock_irqrestore(&sst->spinlock, flags); - - return ret; -} - -int sst_hsw_stream_set_bits(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, enum sst_hsw_bitdepth bits) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set bits\n"); - return -EINVAL; - } - - stream->request.format.bitdepth = bits; - return 0; -} - -int sst_hsw_stream_set_channels(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, int channels) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set channels\n"); - return -EINVAL; - } - - stream->request.format.ch_num = channels; - return 0; -} - -int sst_hsw_stream_set_rate(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, int rate) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set rate\n"); - return -EINVAL; - } - - stream->request.format.frequency = rate; - return 0; -} - -int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 map, - enum sst_hsw_channel_config config) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set map\n"); - return -EINVAL; - } - - stream->request.format.map = map; - stream->request.format.config = config; - return 0; -} - -int sst_hsw_stream_set_style(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, enum sst_hsw_interleaving style) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set style\n"); - return -EINVAL; - } - - stream->request.format.style = style; - return 0; -} - -int sst_hsw_stream_set_valid(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 bits) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set valid bits\n"); - return -EINVAL; - } - - stream->request.format.valid_bit = bits; - return 0; -} - -/* Stream Configuration */ -int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - enum sst_hsw_stream_path_id path_id, - enum sst_hsw_stream_type stream_type, - enum sst_hsw_stream_format format_id) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set format\n"); - return -EINVAL; - } - - stream->request.path_id = path_id; - stream->request.stream_type = stream_type; - stream->request.format_id = format_id; - - trace_hsw_stream_alloc_request(stream, &stream->request); - - return 0; -} - -int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - u32 ring_pt_address, u32 num_pages, - u32 ring_size, u32 ring_offset, u32 ring_first_pfn) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for buffer\n"); - return -EINVAL; - } - - stream->request.ringinfo.ring_pt_address = ring_pt_address; - stream->request.ringinfo.num_pages = num_pages; - stream->request.ringinfo.ring_size = ring_size; - stream->request.ringinfo.ring_offset = ring_offset; - stream->request.ringinfo.ring_first_pfn = ring_first_pfn; - - trace_hsw_stream_buffer(stream); - - return 0; -} - -int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, struct sst_module_runtime *runtime) -{ - struct sst_hsw_module_map *map = &stream->request.map; - struct sst_dsp *dsp = sst_hsw_get_dsp(hsw); - struct sst_module *module = runtime->module; - - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set module\n"); - return -EINVAL; - } - - /* only support initial module atm */ - map->module_entries_count = 1; - map->module_entries[0].module_id = module->id; - map->module_entries[0].entry_point = module->entry; - - stream->request.persistent_mem.offset = - sst_dsp_get_offset(dsp, runtime->persistent_offset, SST_MEM_DRAM); - stream->request.persistent_mem.size = module->persistent_size; - - stream->request.scratch_mem.offset = - sst_dsp_get_offset(dsp, dsp->scratch_offset, SST_MEM_DRAM); - stream->request.scratch_mem.size = dsp->scratch_size; - - dev_dbg(hsw->dev, "module %d runtime %d using:\n", module->id, - runtime->id); - dev_dbg(hsw->dev, " persistent offset 0x%x bytes 0x%x\n", - stream->request.persistent_mem.offset, - stream->request.persistent_mem.size); - dev_dbg(hsw->dev, " scratch offset 0x%x bytes 0x%x\n", - stream->request.scratch_mem.offset, - stream->request.scratch_mem.size); - - return 0; -} - -int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream) -{ - struct sst_ipc_message request, reply = {0}; - int ret; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to commit, ignore it.\n"); - return 0; - } - - if (stream->commited) { - dev_warn(hsw->dev, "warning: stream is already committed, ignore it.\n"); - return 0; - } - - trace_ipc_request("stream alloc", stream->host_id); - - request.header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM); - request.data = &stream->request; - request.size = sizeof(stream->request); - reply.data = &stream->reply; - reply.size = sizeof(stream->reply); - - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, &reply); - if (ret < 0) { - dev_err(hsw->dev, "error: stream commit failed\n"); - return ret; - } - - stream->commited = true; - trace_hsw_stream_alloc_reply(stream); - - return 0; -} - -snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream) -{ - return stream->old_position; -} - -void sst_hsw_stream_set_old_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, snd_pcm_uframes_t val) -{ - stream->old_position = val; -} - -bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw, - struct sst_hsw_stream *stream) -{ - return stream->play_silence; -} - -void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, bool val) -{ - stream->play_silence = val; -} - -/* Stream Information - these calls could be inline but we want the IPC - ABI to be opaque to client PCM drivers to cope with any future ABI changes */ -int sst_hsw_mixer_get_info(struct sst_hsw *hsw) -{ - struct sst_ipc_message request = {0}, reply = {0}; - int ret; - - request.header = IPC_GLB_TYPE(IPC_GLB_GET_MIXER_STREAM_INFO); - reply.data = &hsw->mixer_info; - reply.size = sizeof(hsw->mixer_info); - - trace_ipc_request("get global mixer info", 0); - - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, &reply); - if (ret < 0) { - dev_err(hsw->dev, "error: get stream info failed\n"); - return ret; - } - - trace_hsw_mixer_info_reply(&hsw->mixer_info); - - return 0; -} - -/* Send stream command */ -static int sst_hsw_stream_operations(struct sst_hsw *hsw, int type, - int stream_id, int wait) -{ - struct sst_ipc_message request = {0}; - - request.header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE); - request.header |= IPC_STR_TYPE(type) | (stream_id << IPC_STR_ID_SHIFT); - - if (wait) - return sst_ipc_tx_message_wait(&hsw->ipc, request, NULL); - else - return sst_ipc_tx_message_nowait(&hsw->ipc, request); -} - -/* Stream ALSA trigger operations */ -int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int wait) -{ - int ret; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to pause, ignore it.\n"); - return 0; - } - - trace_ipc_request("stream pause", stream->reply.stream_hw_id); - - ret = sst_hsw_stream_operations(hsw, IPC_STR_PAUSE, - stream->reply.stream_hw_id, wait); - if (ret < 0) - dev_err(hsw->dev, "error: failed to pause stream %d\n", - stream->reply.stream_hw_id); - - return ret; -} - -int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int wait) -{ - int ret; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to resume, ignore it.\n"); - return 0; - } - - trace_ipc_request("stream resume", stream->reply.stream_hw_id); - - ret = sst_hsw_stream_operations(hsw, IPC_STR_RESUME, - stream->reply.stream_hw_id, wait); - if (ret < 0) - dev_err(hsw->dev, "error: failed to resume stream %d\n", - stream->reply.stream_hw_id); - - return ret; -} - -int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream) -{ - int ret, tries = 10; - - if (!stream) { - dev_warn(hsw->dev, "warning: stream is NULL, no stream to reset, ignore it.\n"); - return 0; - } - - /* dont reset streams that are not commited */ - if (!stream->commited) - return 0; - - /* wait for pause to complete before we reset the stream */ - while (stream->running && --tries) - msleep(1); - if (!tries) { - dev_err(hsw->dev, "error: reset stream %d still running\n", - stream->reply.stream_hw_id); - return -EINVAL; - } - - trace_ipc_request("stream reset", stream->reply.stream_hw_id); - - ret = sst_hsw_stream_operations(hsw, IPC_STR_RESET, - stream->reply.stream_hw_id, 1); - if (ret < 0) - dev_err(hsw->dev, "error: failed to reset stream %d\n", - stream->reply.stream_hw_id); - return ret; -} - -/* Stream pointer positions */ -u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream) -{ - u32 rpos; - - sst_dsp_read(hsw->dsp, &rpos, - stream->reply.read_position_register_address, sizeof(rpos)); - - return rpos; -} - -/* Stream presentation (monotonic) positions */ -u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream) -{ - u64 ppos; - - sst_dsp_read(hsw->dsp, &ppos, - stream->reply.presentation_position_register_address, - sizeof(ppos)); - - return ppos; -} - -/* physical BE config */ -int sst_hsw_device_set_config(struct sst_hsw *hsw, - enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, - enum sst_hsw_device_mode mode, u32 clock_divider) -{ - struct sst_ipc_message request; - struct sst_hsw_ipc_device_config_req config; - int ret; - - trace_ipc_request("set device config", dev); - - hsw->dx_dev = config.ssp_interface = dev; - hsw->dx_mclk = config.clock_frequency = mclk; - hsw->dx_mode = config.mode = mode; - hsw->dx_clock_divider = config.clock_divider = clock_divider; - if (mode == SST_HSW_DEVICE_TDM_CLOCK_MASTER) - config.channels = 4; - else - config.channels = 2; - - trace_hsw_device_config_req(&config); - - request.header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS); - request.data = &config; - request.size = sizeof(config); - - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, NULL); - if (ret < 0) - dev_err(hsw->dev, "error: set device formats failed\n"); - - return ret; -} -EXPORT_SYMBOL_GPL(sst_hsw_device_set_config); - -/* DX Config */ -int sst_hsw_dx_set_state(struct sst_hsw *hsw, - enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx) -{ - struct sst_ipc_message request, reply = {0}; - u32 state_; - int ret, item; - - state_ = state; - request.header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE); - request.data = &state_; - request.size = sizeof(state_); - reply.data = dx; - reply.size = sizeof(*dx); - - trace_ipc_request("PM enter Dx state", state); - - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, &reply); - if (ret < 0) { - dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state); - return ret; - } - - for (item = 0; item < dx->entries_no; item++) { - dev_dbg(hsw->dev, - "Item[%d] offset[%x] - size[%x] - source[%x]\n", - item, dx->mem_info[item].offset, - dx->mem_info[item].size, - dx->mem_info[item].source); - } - dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", - dx->entries_no, state); - - return ret; -} - -struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, - int mod_id, int offset) -{ - struct sst_dsp *dsp = hsw->dsp; - struct sst_module *module; - struct sst_module_runtime *runtime; - int err; - - module = sst_module_get_from_id(dsp, mod_id); - if (module == NULL) { - dev_err(dsp->dev, "error: failed to get module %d for pcm\n", - mod_id); - return NULL; - } - - runtime = sst_module_runtime_new(module, mod_id, NULL); - if (runtime == NULL) { - dev_err(dsp->dev, "error: failed to create module %d runtime\n", - mod_id); - return NULL; - } - - err = sst_module_runtime_alloc_blocks(runtime, offset); - if (err < 0) { - dev_err(dsp->dev, "error: failed to alloc blocks for module %d runtime\n", - mod_id); - sst_module_runtime_free(runtime); - return NULL; - } - - dev_dbg(dsp->dev, "runtime id %d created for module %d\n", runtime->id, - mod_id); - return runtime; -} - -void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime) -{ - sst_module_runtime_free_blocks(runtime); - sst_module_runtime_free(runtime); -} - -#ifdef CONFIG_PM -static int sst_hsw_dx_state_dump(struct sst_hsw *hsw) -{ - struct sst_dsp *sst = hsw->dsp; - u32 item, offset, size; - int ret = 0; - - trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS); - - if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) { - dev_err(hsw->dev, - "error: number of FW context regions greater than %d\n", - SST_HSW_MAX_DX_REGIONS); - memset(&hsw->dx, 0, sizeof(hsw->dx)); - return -EINVAL; - } - - ret = sst_dsp_dma_get_channel(sst, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); - return ret; - } - - /* set on-demond mode on engine 0 channel 3 */ - sst_dsp_shim_update_bits(sst, SST_HMDC, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, - SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); - - for (item = 0; item < hsw->dx.entries_no; item++) { - if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP - && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET - && hsw->dx.mem_info[item].offset < - DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { - - offset = hsw->dx.mem_info[item].offset - - DSP_DRAM_ADDR_OFFSET; - size = (hsw->dx.mem_info[item].size + 3) & (~3); - - ret = sst_dsp_dma_copyfrom(sst, hsw->dx_context_paddr + offset, - sst->addr.lpe_base + offset, size); - if (ret < 0) { - dev_err(hsw->dev, - "error: FW context dump failed\n"); - memset(&hsw->dx, 0, sizeof(hsw->dx)); - goto out; - } - } - } - -out: - sst_dsp_dma_put_channel(sst); - return ret; -} - -static int sst_hsw_dx_state_restore(struct sst_hsw *hsw) -{ - struct sst_dsp *sst = hsw->dsp; - u32 item, offset, size; - int ret; - - for (item = 0; item < hsw->dx.entries_no; item++) { - if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP - && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET - && hsw->dx.mem_info[item].offset < - DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { - - offset = hsw->dx.mem_info[item].offset - - DSP_DRAM_ADDR_OFFSET; - size = (hsw->dx.mem_info[item].size + 3) & (~3); - - ret = sst_dsp_dma_copyto(sst, sst->addr.lpe_base + offset, - hsw->dx_context_paddr + offset, size); - if (ret < 0) { - dev_err(hsw->dev, - "error: FW context restore failed\n"); - return ret; - } - } - } - - return 0; -} - -int sst_hsw_dsp_load(struct sst_hsw *hsw) -{ - struct sst_dsp *dsp = hsw->dsp; - struct sst_fw *sst_fw, *t; - int ret; - - dev_dbg(hsw->dev, "loading audio DSP...."); - - ret = sst_dsp_wake(dsp); - if (ret < 0) { - dev_err(hsw->dev, "error: failed to wake audio DSP\n"); - return -ENODEV; - } - - ret = sst_dsp_dma_get_channel(dsp, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); - return ret; - } - - list_for_each_entry_safe_reverse(sst_fw, t, &dsp->fw_list, list) { - ret = sst_fw_reload(sst_fw); - if (ret < 0) { - dev_err(hsw->dev, "error: SST FW reload failed\n"); - sst_dsp_dma_put_channel(dsp); - return -ENOMEM; - } - } - ret = sst_block_alloc_scratch(hsw->dsp); - if (ret < 0) - return -EINVAL; - - sst_dsp_dma_put_channel(dsp); - return 0; -} - -static int sst_hsw_dsp_restore(struct sst_hsw *hsw) -{ - struct sst_dsp *dsp = hsw->dsp; - int ret; - - dev_dbg(hsw->dev, "restoring audio DSP...."); - - ret = sst_dsp_dma_get_channel(dsp, 0); - if (ret < 0) { - dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); - return ret; - } - - ret = sst_hsw_dx_state_restore(hsw); - if (ret < 0) { - dev_err(hsw->dev, "error: SST FW context restore failed\n"); - sst_dsp_dma_put_channel(dsp); - return -ENOMEM; - } - sst_dsp_dma_put_channel(dsp); - - /* wait for DSP boot completion */ - sst_dsp_boot(dsp); - - return ret; -} - -int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw) -{ - int ret; - - dev_dbg(hsw->dev, "audio dsp runtime suspend\n"); - - ret = sst_hsw_dx_set_state(hsw, SST_HSW_DX_STATE_D3, &hsw->dx); - if (ret < 0) - return ret; - - sst_dsp_stall(hsw->dsp); - - ret = sst_hsw_dx_state_dump(hsw); - if (ret < 0) - return ret; - - sst_ipc_drop_all(&hsw->ipc); - - return 0; -} - -int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw) -{ - struct sst_fw *sst_fw, *t; - struct sst_dsp *dsp = hsw->dsp; - - list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { - sst_fw_unload(sst_fw); - } - sst_block_free_scratch(dsp); - - hsw->boot_complete = false; - - sst_dsp_sleep(dsp); - - return 0; -} - -int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw) -{ - struct device *dev = hsw->dev; - int ret; - - dev_dbg(dev, "audio dsp runtime resume\n"); - - if (hsw->boot_complete) - return 1; /* tell caller no action is required */ - - ret = sst_hsw_dsp_restore(hsw); - if (ret < 0) - dev_err(dev, "error: audio DSP boot failure\n"); - - sst_hsw_init_module_state(hsw); - - ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, - msecs_to_jiffies(IPC_BOOT_MSECS)); - if (ret == 0) { - dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", - sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), - sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); - return -EIO; - } - - /* Set ADSP SSP port settings - sadly the FW does not store SSP port - settings as part of the PM context. */ - ret = sst_hsw_device_set_config(hsw, hsw->dx_dev, hsw->dx_mclk, - hsw->dx_mode, hsw->dx_clock_divider); - if (ret < 0) - dev_err(dev, "error: SSP re-initialization failed\n"); - - return ret; -} -#endif - -struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) -{ - return hsw->dsp; -} - -void sst_hsw_init_module_state(struct sst_hsw *hsw) -{ - struct sst_module *module; - enum sst_hsw_module_id id; - - /* the base fw contains several modules */ - for (id = SST_HSW_MODULE_BASE_FW; id < SST_HSW_MAX_MODULE_ID; id++) { - module = sst_module_get_from_id(hsw->dsp, id); - if (module) { - /* module waves is active only after being enabled */ - if (id == SST_HSW_MODULE_WAVES) - module->state = SST_MODULE_STATE_INITIALIZED; - else - module->state = SST_MODULE_STATE_ACTIVE; - } - } -} - -bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id) -{ - struct sst_module *module; - - module = sst_module_get_from_id(hsw->dsp, module_id); - if (module == NULL || module->state == SST_MODULE_STATE_UNLOADED) - return false; - else - return true; -} - -bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id) -{ - struct sst_module *module; - - module = sst_module_get_from_id(hsw->dsp, module_id); - if (module != NULL && module->state == SST_MODULE_STATE_ACTIVE) - return true; - else - return false; -} - -void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) -{ - hsw->enabled_modules_rtd3 |= (1 << module_id); -} - -void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id) -{ - hsw->enabled_modules_rtd3 &= ~(1 << module_id); -} - -bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) -{ - return hsw->enabled_modules_rtd3 & (1 << module_id); -} - -void sst_hsw_reset_param_buf(struct sst_hsw *hsw) -{ - hsw->param_idx_w = 0; - hsw->param_idx_r = 0; - memset((void *)hsw->param_buf, 0, sizeof(hsw->param_buf)); -} - -int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf) -{ - /* save line to the first available position of param buffer */ - if (hsw->param_idx_w > WAVES_PARAM_LINES - 1) { - dev_warn(hsw->dev, "warning: param buffer overflow!\n"); - return -EPERM; - } - memcpy(hsw->param_buf[hsw->param_idx_w], buf, WAVES_PARAM_COUNT); - hsw->param_idx_w++; - return 0; -} - -int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf) -{ - u8 id = 0; - - /* read the first matching line from param buffer */ - while (hsw->param_idx_r < WAVES_PARAM_LINES) { - id = hsw->param_buf[hsw->param_idx_r][0]; - hsw->param_idx_r++; - if (buf[0] == id) { - memcpy(buf, hsw->param_buf[hsw->param_idx_r], - WAVES_PARAM_COUNT); - break; - } - } - if (hsw->param_idx_r > WAVES_PARAM_LINES - 1) { - dev_dbg(hsw->dev, "end of buffer, roll to the beginning\n"); - hsw->param_idx_r = 0; - return 0; - } - return 0; -} - -int sst_hsw_launch_param_buf(struct sst_hsw *hsw) -{ - int ret, idx; - - if (!sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { - dev_dbg(hsw->dev, "module waves is not active\n"); - return 0; - } - - /* put all param lines to DSP through ipc */ - for (idx = 0; idx < hsw->param_idx_w; idx++) { - ret = sst_hsw_module_set_param(hsw, - SST_HSW_MODULE_WAVES, 0, hsw->param_buf[idx][0], - WAVES_PARAM_COUNT, hsw->param_buf[idx]); - if (ret < 0) - return ret; - } - return 0; -} - -int sst_hsw_module_load(struct sst_hsw *hsw, - u32 module_id, u32 instance_id, char *name) -{ - int ret = 0; - const struct firmware *fw = NULL; - struct sst_fw *hsw_sst_fw; - struct sst_module *module; - struct device *dev = hsw->dev; - struct sst_dsp *dsp = hsw->dsp; - - dev_dbg(dev, "sst_hsw_module_load id=%d, name='%s'", module_id, name); - - module = sst_module_get_from_id(dsp, module_id); - if (module == NULL) { - /* loading for the first time */ - if (module_id == SST_HSW_MODULE_BASE_FW) { - /* for base module: use fw requested in acpi probe */ - fw = dsp->pdata->fw; - if (!fw) { - dev_err(dev, "request Base fw failed\n"); - return -ENODEV; - } - } else { - /* try and load any other optional modules if they are - * available. Use dev_info instead of dev_err in case - * request firmware failed */ - ret = request_firmware(&fw, name, dev); - if (ret) { - dev_info(dev, "fw image %s not available(%d)\n", - name, ret); - return ret; - } - } - hsw_sst_fw = sst_fw_new(dsp, fw, hsw); - if (hsw_sst_fw == NULL) { - dev_err(dev, "error: failed to load firmware\n"); - ret = -ENOMEM; - goto out; - } - module = sst_module_get_from_id(dsp, module_id); - if (module == NULL) { - dev_err(dev, "error: no module %d in firmware %s\n", - module_id, name); - } - } else - dev_info(dev, "module %d (%s) already loaded\n", - module_id, name); -out: - /* release fw, but base fw should be released by acpi driver */ - if (fw && module_id != SST_HSW_MODULE_BASE_FW) - release_firmware(fw); - - return ret; -} - -int sst_hsw_module_enable(struct sst_hsw *hsw, - u32 module_id, u32 instance_id) -{ - int ret; - struct sst_ipc_message request; - struct sst_hsw_ipc_module_config config; - struct sst_module *module; - struct sst_module_runtime *runtime; - struct device *dev = hsw->dev; - struct sst_dsp *dsp = hsw->dsp; - - if (!sst_hsw_is_module_loaded(hsw, module_id)) { - dev_dbg(dev, "module %d not loaded\n", module_id); - return 0; - } - - if (sst_hsw_is_module_active(hsw, module_id)) { - dev_info(dev, "module %d already enabled\n", module_id); - return 0; - } - - module = sst_module_get_from_id(dsp, module_id); - if (module == NULL) { - dev_err(dev, "module %d not valid\n", module_id); - return -ENXIO; - } - - runtime = sst_module_runtime_get_from_id(module, module_id); - if (runtime == NULL) { - dev_err(dev, "runtime %d not valid", module_id); - return -ENXIO; - } - - request.header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | - IPC_MODULE_OPERATION(IPC_MODULE_ENABLE) | - IPC_MODULE_ID(module_id); - dev_dbg(dev, "module enable header: %x\n", (u32)request.header); - - config.map.module_entries_count = 1; - config.map.module_entries[0].module_id = module->id; - config.map.module_entries[0].entry_point = module->entry; - - config.persistent_mem.offset = - sst_dsp_get_offset(dsp, - runtime->persistent_offset, SST_MEM_DRAM); - config.persistent_mem.size = module->persistent_size; - - config.scratch_mem.offset = - sst_dsp_get_offset(dsp, - dsp->scratch_offset, SST_MEM_DRAM); - config.scratch_mem.size = module->scratch_size; - dev_dbg(dev, "mod %d enable p:%d @ %x, s:%d @ %x, ep: %x", - config.map.module_entries[0].module_id, - config.persistent_mem.size, - config.persistent_mem.offset, - config.scratch_mem.size, config.scratch_mem.offset, - config.map.module_entries[0].entry_point); - - request.data = &config; - request.size = sizeof(config); - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, NULL); - if (ret < 0) - dev_err(dev, "ipc: module enable failed - %d\n", ret); - else - module->state = SST_MODULE_STATE_ACTIVE; - - return ret; -} - -int sst_hsw_module_disable(struct sst_hsw *hsw, - u32 module_id, u32 instance_id) -{ - int ret; - struct sst_ipc_message request = {0}; - struct sst_module *module; - struct device *dev = hsw->dev; - struct sst_dsp *dsp = hsw->dsp; - - if (!sst_hsw_is_module_loaded(hsw, module_id)) { - dev_dbg(dev, "module %d not loaded\n", module_id); - return 0; - } - - if (!sst_hsw_is_module_active(hsw, module_id)) { - dev_info(dev, "module %d already disabled\n", module_id); - return 0; - } - - module = sst_module_get_from_id(dsp, module_id); - if (module == NULL) { - dev_err(dev, "module %d not valid\n", module_id); - return -ENXIO; - } - - request.header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | - IPC_MODULE_OPERATION(IPC_MODULE_DISABLE) | - IPC_MODULE_ID(module_id); - - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, NULL); - if (ret < 0) - dev_err(dev, "module disable failed - %d\n", ret); - else - module->state = SST_MODULE_STATE_INITIALIZED; - - return ret; -} - -int sst_hsw_module_set_param(struct sst_hsw *hsw, - u32 module_id, u32 instance_id, u32 parameter_id, - u32 param_size, char *param) -{ - int ret; - struct sst_ipc_message request = {0}; - u32 payload_size = 0; - struct sst_hsw_transfer_parameter *parameter; - struct device *dev = hsw->dev; - - request.header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | - IPC_MODULE_OPERATION(IPC_MODULE_SET_PARAMETER) | - IPC_MODULE_ID(module_id); - dev_dbg(dev, "sst_hsw_module_set_param header=%x\n", - (u32)request.header); - - payload_size = param_size + - sizeof(struct sst_hsw_transfer_parameter) - - sizeof(struct sst_hsw_transfer_list); - dev_dbg(dev, "parameter size : %d\n", param_size); - dev_dbg(dev, "payload size : %d\n", payload_size); - - if (payload_size <= SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE) { - /* short parameter, mailbox can contain data */ - dev_dbg(dev, "transfer parameter size : %zu\n", - request.size); - - request.size = ALIGN(payload_size, 4); - dev_dbg(dev, "transfer parameter aligned size : %zu\n", - request.size); - - parameter = kzalloc(request.size, GFP_KERNEL); - if (parameter == NULL) - return -ENOMEM; - - memcpy(parameter->data, param, param_size); - } else { - dev_warn(dev, "transfer parameter size too large!"); - return 0; - } - - parameter->parameter_id = parameter_id; - parameter->data_size = param_size; - request.data = parameter; - - ret = sst_ipc_tx_message_wait(&hsw->ipc, request, NULL); - if (ret < 0) - dev_err(dev, "ipc: module set parameter failed - %d\n", ret); - - kfree(parameter); - - return ret; -} - -static struct sst_dsp_device hsw_dev = { - .thread = hsw_irq_thread, - .ops = &haswell_ops, -}; - -static void hsw_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) -{ - /* send the message */ - sst_dsp_outbox_write(ipc->dsp, msg->tx.data, msg->tx.size); - sst_dsp_ipc_msg_tx(ipc->dsp, msg->tx.header); -} - -static void hsw_shim_dbg(struct sst_generic_ipc *ipc, const char *text) -{ - struct sst_dsp *sst = ipc->dsp; - u32 isr, ipcd, imrx, ipcx; - - ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX); - isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); - ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); - imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX); - - dev_err(ipc->dev, - "ipc: --%s-- ipcx 0x%8.8x isr 0x%8.8x ipcd 0x%8.8x imrx 0x%8.8x\n", - text, ipcx, isr, ipcd, imrx); -} - -static void hsw_tx_data_copy(struct ipc_message *msg, char *tx_data, - size_t tx_size) -{ - memcpy(msg->tx.data, tx_data, tx_size); -} - -static u64 hsw_reply_msg_match(u64 header, u64 *mask) -{ - /* clear reply bits & status bits */ - header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); - *mask = (u64)-1; - - return header; -} - -static bool hsw_is_dsp_busy(struct sst_dsp *dsp) -{ - u64 ipcx; - - ipcx = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); - return (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)); -} - -int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_hsw_ipc_fw_version version; - struct sst_hsw *hsw; - struct sst_generic_ipc *ipc; - int ret; - - dev_dbg(dev, "initialising Audio DSP IPC\n"); - - hsw = devm_kzalloc(dev, sizeof(*hsw), GFP_KERNEL); - if (hsw == NULL) - return -ENOMEM; - - hsw->dev = dev; - - ipc = &hsw->ipc; - ipc->dev = dev; - ipc->ops.tx_msg = hsw_tx_msg; - ipc->ops.shim_dbg = hsw_shim_dbg; - ipc->ops.tx_data_copy = hsw_tx_data_copy; - ipc->ops.reply_msg_match = hsw_reply_msg_match; - ipc->ops.is_dsp_busy = hsw_is_dsp_busy; - - ipc->tx_data_max_size = IPC_MAX_MAILBOX_BYTES; - ipc->rx_data_max_size = IPC_MAX_MAILBOX_BYTES; - - ret = sst_ipc_init(ipc); - if (ret != 0) - goto ipc_init_err; - - INIT_LIST_HEAD(&hsw->stream_list); - init_waitqueue_head(&hsw->boot_wait); - hsw_dev.thread_context = hsw; - - /* init SST shim */ - hsw->dsp = sst_dsp_new(dev, &hsw_dev, pdata); - if (hsw->dsp == NULL) { - ret = -ENODEV; - goto dsp_new_err; - } - - ipc->dsp = hsw->dsp; - - /* allocate DMA buffer for context storage */ - hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev, - SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL); - if (hsw->dx_context == NULL) { - ret = -ENOMEM; - goto dma_err; - } - - /* keep the DSP in reset state for base FW loading */ - sst_dsp_reset(hsw->dsp); - - /* load base module and other modules in base firmware image */ - ret = sst_hsw_module_load(hsw, SST_HSW_MODULE_BASE_FW, 0, "Base"); - if (ret < 0) - goto fw_err; - - /* try to load module waves */ - sst_hsw_module_load(hsw, SST_HSW_MODULE_WAVES, 0, "intel/IntcPP01.bin"); - - /* allocate scratch mem regions */ - ret = sst_block_alloc_scratch(hsw->dsp); - if (ret < 0) - goto boot_err; - - /* init param buffer */ - sst_hsw_reset_param_buf(hsw); - - /* wait for DSP boot completion */ - sst_dsp_boot(hsw->dsp); - ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, - msecs_to_jiffies(IPC_BOOT_MSECS)); - if (ret == 0) { - ret = -EIO; - dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", - sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), - sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); - goto boot_err; - } - - /* init module state after boot */ - sst_hsw_init_module_state(hsw); - - /* get the FW version */ - sst_hsw_fw_get_version(hsw, &version); - - /* get the globalmixer */ - ret = sst_hsw_mixer_get_info(hsw); - if (ret < 0) { - dev_err(hsw->dev, "error: failed to get stream info\n"); - goto boot_err; - } - - pdata->dsp = hsw; - return 0; - -boot_err: - sst_dsp_reset(hsw->dsp); - sst_fw_free_all(hsw->dsp); -fw_err: - dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, - hsw->dx_context, hsw->dx_context_paddr); -dma_err: - sst_dsp_free(hsw->dsp); -dsp_new_err: - sst_ipc_fini(ipc); -ipc_init_err: - return ret; -} -EXPORT_SYMBOL_GPL(sst_hsw_dsp_init); - -void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) -{ - struct sst_hsw *hsw = pdata->dsp; - - sst_dsp_reset(hsw->dsp); - sst_fw_free_all(hsw->dsp); - dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, - hsw->dx_context, hsw->dx_context_paddr); - sst_dsp_free(hsw->dsp); - sst_ipc_fini(&hsw->ipc); -} -EXPORT_SYMBOL_GPL(sst_hsw_dsp_free); diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.h b/sound/soc/intel/haswell/sst-haswell-ipc.h deleted file mode 100644 index fdc70c77e688..000000000000 --- a/sound/soc/intel/haswell/sst-haswell-ipc.h +++ /dev/null @@ -1,527 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Intel SST Haswell/Broadwell IPC Support - * - * Copyright (C) 2013, Intel Corporation. All rights reserved. - */ - -#ifndef __SST_HASWELL_IPC_H -#define __SST_HASWELL_IPC_H - -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/platform_device.h> -#include <sound/asound.h> - -#define DRV_NAME "haswell-dai" - -#define SST_HSW_NO_CHANNELS 4 -#define SST_HSW_MAX_DX_REGIONS 14 -#define SST_HSW_DX_CONTEXT_SIZE (640 * 1024) -#define SST_HSW_CHANNELS_ALL 0xffffffff - -#define SST_HSW_FW_LOG_CONFIG_DWORDS 12 -#define SST_HSW_GLOBAL_LOG 15 - -/** - * Upfront defined maximum message size that is - * expected by the in/out communication pipes in FW. - */ -#define SST_HSW_IPC_MAX_PAYLOAD_SIZE 400 -#define SST_HSW_MAX_INFO_SIZE 64 -#define SST_HSW_BUILD_HASH_LENGTH 40 -#define SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE 500 -#define WAVES_PARAM_COUNT 128 -#define WAVES_PARAM_LINES 160 - -struct sst_hsw; -struct sst_hsw_stream; -struct sst_hsw_log_stream; -struct sst_pdata; -struct sst_module; -struct sst_module_runtime; -extern struct sst_ops haswell_ops; - -/* Stream Allocate Path ID */ -enum sst_hsw_stream_path_id { - SST_HSW_STREAM_PATH_SSP0_OUT = 0, - SST_HSW_STREAM_PATH_SSP0_IN = 1, - SST_HSW_STREAM_PATH_MAX_PATH_ID = 2, -}; - -/* Stream Allocate Stream Type */ -enum sst_hsw_stream_type { - SST_HSW_STREAM_TYPE_RENDER = 0, - SST_HSW_STREAM_TYPE_SYSTEM = 1, - SST_HSW_STREAM_TYPE_CAPTURE = 2, - SST_HSW_STREAM_TYPE_LOOPBACK = 3, - SST_HSW_STREAM_TYPE_MAX_STREAM_TYPE = 4, -}; - -/* Stream Allocate Stream Format */ -enum sst_hsw_stream_format { - SST_HSW_STREAM_FORMAT_PCM_FORMAT = 0, - SST_HSW_STREAM_FORMAT_MP3_FORMAT = 1, - SST_HSW_STREAM_FORMAT_AAC_FORMAT = 2, - SST_HSW_STREAM_FORMAT_MAX_FORMAT_ID = 3, -}; - -/* Device ID */ -enum sst_hsw_device_id { - SST_HSW_DEVICE_SSP_0 = 0, - SST_HSW_DEVICE_SSP_1 = 1, -}; - -/* Device Master Clock Frequency */ -enum sst_hsw_device_mclk { - SST_HSW_DEVICE_MCLK_OFF = 0, - SST_HSW_DEVICE_MCLK_FREQ_6_MHZ = 1, - SST_HSW_DEVICE_MCLK_FREQ_12_MHZ = 2, - SST_HSW_DEVICE_MCLK_FREQ_24_MHZ = 3, -}; - -/* Device Clock Master */ -enum sst_hsw_device_mode { - SST_HSW_DEVICE_CLOCK_SLAVE = 0, - SST_HSW_DEVICE_CLOCK_MASTER = 1, - SST_HSW_DEVICE_TDM_CLOCK_MASTER = 2, -}; - -/* DX Power State */ -enum sst_hsw_dx_state { - SST_HSW_DX_STATE_D0 = 0, - SST_HSW_DX_STATE_D1 = 1, - SST_HSW_DX_STATE_D3 = 3, - SST_HSW_DX_STATE_MAX = 3, -}; - -/* Audio stream stage IDs */ -enum sst_hsw_fx_stage_id { - SST_HSW_STAGE_ID_WAVES = 0, - SST_HSW_STAGE_ID_DTS = 1, - SST_HSW_STAGE_ID_DOLBY = 2, - SST_HSW_STAGE_ID_BOOST = 3, - SST_HSW_STAGE_ID_MAX_FX_ID -}; - -/* DX State Type */ -enum sst_hsw_dx_type { - SST_HSW_DX_TYPE_FW_IMAGE = 0, - SST_HSW_DX_TYPE_MEMORY_DUMP = 1 -}; - -/* Volume Curve Type*/ -enum sst_hsw_volume_curve { - SST_HSW_VOLUME_CURVE_NONE = 0, - SST_HSW_VOLUME_CURVE_FADE = 1 -}; - -/* Sample ordering */ -enum sst_hsw_interleaving { - SST_HSW_INTERLEAVING_PER_CHANNEL = 0, - SST_HSW_INTERLEAVING_PER_SAMPLE = 1, -}; - -/* Channel indices */ -enum sst_hsw_channel_index { - SST_HSW_CHANNEL_LEFT = 0, - SST_HSW_CHANNEL_CENTER = 1, - SST_HSW_CHANNEL_RIGHT = 2, - SST_HSW_CHANNEL_LEFT_SURROUND = 3, - SST_HSW_CHANNEL_CENTER_SURROUND = 3, - SST_HSW_CHANNEL_RIGHT_SURROUND = 4, - SST_HSW_CHANNEL_LFE = 7, - SST_HSW_CHANNEL_INVALID = 0xF, -}; - -/* List of supported channel maps. */ -enum sst_hsw_channel_config { - SST_HSW_CHANNEL_CONFIG_MONO = 0, /* mono only. */ - SST_HSW_CHANNEL_CONFIG_STEREO = 1, /* L & R. */ - SST_HSW_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only. */ - SST_HSW_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only. */ - SST_HSW_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only. */ - SST_HSW_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only. */ - SST_HSW_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only. */ - SST_HSW_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs. */ - SST_HSW_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE. */ - SST_HSW_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two. */ - SST_HSW_CHANNEL_CONFIG_INVALID, -}; - -/* List of supported bit depths. */ -enum sst_hsw_bitdepth { - SST_HSW_DEPTH_8BIT = 8, - SST_HSW_DEPTH_16BIT = 16, - SST_HSW_DEPTH_24BIT = 24, /* Default. */ - SST_HSW_DEPTH_32BIT = 32, - SST_HSW_DEPTH_INVALID = 33, -}; - -enum sst_hsw_module_id { - SST_HSW_MODULE_BASE_FW = 0x0, - SST_HSW_MODULE_MP3 = 0x1, - SST_HSW_MODULE_AAC_5_1 = 0x2, - SST_HSW_MODULE_AAC_2_0 = 0x3, - SST_HSW_MODULE_SRC = 0x4, - SST_HSW_MODULE_WAVES = 0x5, - SST_HSW_MODULE_DOLBY = 0x6, - SST_HSW_MODULE_BOOST = 0x7, - SST_HSW_MODULE_LPAL = 0x8, - SST_HSW_MODULE_DTS = 0x9, - SST_HSW_MODULE_PCM_CAPTURE = 0xA, - SST_HSW_MODULE_PCM_SYSTEM = 0xB, - SST_HSW_MODULE_PCM_REFERENCE = 0xC, - SST_HSW_MODULE_PCM = 0xD, - SST_HSW_MODULE_BLUETOOTH_RENDER_MODULE = 0xE, - SST_HSW_MODULE_BLUETOOTH_CAPTURE_MODULE = 0xF, - SST_HSW_MAX_MODULE_ID, -}; - -enum sst_hsw_performance_action { - SST_HSW_PERF_START = 0, - SST_HSW_PERF_STOP = 1, -}; - -struct sst_hsw_transfer_info { - uint32_t destination; /* destination address */ - uint32_t reverse:1; /* if 1 data flows from destination */ - uint32_t size:31; /* transfer size in bytes.*/ - uint16_t first_page_offset; /* offset to data in the first page. */ - uint8_t packed_pages; /* page addresses. Each occupies 20 bits */ -} __attribute__((packed)); - -struct sst_hsw_transfer_list { - uint32_t transfers_count; - struct sst_hsw_transfer_info transfers; -} __attribute__((packed)); - -struct sst_hsw_transfer_parameter { - uint32_t parameter_id; - uint32_t data_size; - union { - uint8_t data[1]; - struct sst_hsw_transfer_list transfer_list; - }; -} __attribute__((packed)); - -/* SST firmware module info */ -struct sst_hsw_module_info { - u8 name[SST_HSW_MAX_INFO_SIZE]; - u8 version[SST_HSW_MAX_INFO_SIZE]; -} __attribute__((packed)); - -/* Module entry point */ -struct sst_hsw_module_entry { - enum sst_hsw_module_id module_id; - u32 entry_point; -} __attribute__((packed)); - -/* Module map - alignement matches DSP */ -struct sst_hsw_module_map { - u8 module_entries_count; - struct sst_hsw_module_entry module_entries[1]; -} __attribute__((packed)); - -struct sst_hsw_memory_info { - u32 offset; - u32 size; -} __attribute__((packed)); - -struct sst_hsw_fx_enable { - struct sst_hsw_module_map module_map; - struct sst_hsw_memory_info persistent_mem; -} __attribute__((packed)); - -struct sst_hsw_ipc_module_config { - struct sst_hsw_module_map map; - struct sst_hsw_memory_info persistent_mem; - struct sst_hsw_memory_info scratch_mem; -} __attribute__((packed)); - -struct sst_hsw_get_fx_param { - u32 parameter_id; - u32 param_size; -} __attribute__((packed)); - -struct sst_hsw_perf_action { - u32 action; -} __attribute__((packed)); - -struct sst_hsw_perf_data { - u64 timestamp; - u64 cycles; - u64 datatime; -} __attribute__((packed)); - -/* FW version */ -struct sst_hsw_ipc_fw_version { - u8 build; - u8 minor; - u8 major; - u8 type; - u8 fw_build_hash[SST_HSW_BUILD_HASH_LENGTH]; - u32 fw_log_providers_hash; -} __attribute__((packed)); - -/* Stream ring info */ -struct sst_hsw_ipc_stream_ring { - u32 ring_pt_address; - u32 num_pages; - u32 ring_size; - u32 ring_offset; - u32 ring_first_pfn; -} __attribute__((packed)); - -/* Debug Dump Log Enable Request */ -struct sst_hsw_ipc_debug_log_enable_req { - struct sst_hsw_ipc_stream_ring ringinfo; - u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; -} __attribute__((packed)); - -/* Debug Dump Log Reply */ -struct sst_hsw_ipc_debug_log_reply { - u32 log_buffer_begining; - u32 log_buffer_size; -} __attribute__((packed)); - -/* Stream glitch position */ -struct sst_hsw_ipc_stream_glitch_position { - u32 glitch_type; - u32 present_pos; - u32 write_pos; -} __attribute__((packed)); - -/* Stream get position */ -struct sst_hsw_ipc_stream_get_position { - u32 position; - u32 fw_cycle_count; -} __attribute__((packed)); - -/* Stream set position */ -struct sst_hsw_ipc_stream_set_position { - u32 position; - u32 end_of_buffer; -} __attribute__((packed)); - -/* Stream Free Request */ -struct sst_hsw_ipc_stream_free_req { - u8 stream_id; - u8 reserved[3]; -} __attribute__((packed)); - -/* Set Volume Request */ -struct sst_hsw_ipc_volume_req { - u32 channel; - u32 target_volume; - u64 curve_duration; - u32 curve_type; -} __attribute__((packed)); - -/* Device Configuration Request */ -struct sst_hsw_ipc_device_config_req { - u32 ssp_interface; - u32 clock_frequency; - u32 mode; - u16 clock_divider; - u8 channels; - u8 reserved; -} __attribute__((packed)); - -/* Audio Data formats */ -struct sst_hsw_audio_data_format_ipc { - u32 frequency; - u32 bitdepth; - u32 map; - u32 config; - u32 style; - u8 ch_num; - u8 valid_bit; - u8 reserved[2]; -} __attribute__((packed)); - -/* Stream Allocate Request */ -struct sst_hsw_ipc_stream_alloc_req { - u8 path_id; - u8 stream_type; - u8 format_id; - u8 reserved; - struct sst_hsw_audio_data_format_ipc format; - struct sst_hsw_ipc_stream_ring ringinfo; - struct sst_hsw_module_map map; - struct sst_hsw_memory_info persistent_mem; - struct sst_hsw_memory_info scratch_mem; - u32 number_of_notifications; -} __attribute__((packed)); - -/* Stream Allocate Reply */ -struct sst_hsw_ipc_stream_alloc_reply { - u32 stream_hw_id; - u32 mixer_hw_id; // returns rate ???? - u32 read_position_register_address; - u32 presentation_position_register_address; - u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; - u32 volume_register_address[SST_HSW_NO_CHANNELS]; -} __attribute__((packed)); - -/* Get Mixer Stream Info */ -struct sst_hsw_ipc_stream_info_reply { - u32 mixer_hw_id; - u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; - u32 volume_register_address[SST_HSW_NO_CHANNELS]; -} __attribute__((packed)); - -/* DX State Request */ -struct sst_hsw_ipc_dx_req { - u8 state; - u8 reserved[3]; -} __attribute__((packed)); - -/* DX State Reply Memory Info Item */ -struct sst_hsw_ipc_dx_memory_item { - u32 offset; - u32 size; - u32 source; -} __attribute__((packed)); - -/* DX State Reply */ -struct sst_hsw_ipc_dx_reply { - u32 entries_no; - struct sst_hsw_ipc_dx_memory_item mem_info[SST_HSW_MAX_DX_REGIONS]; -} __attribute__((packed)); - -struct sst_hsw_ipc_fw_version; - -/* SST Init & Free */ -struct sst_hsw *sst_hsw_new(struct device *dev, const u8 *fw, size_t fw_length, - u32 fw_offset); -void sst_hsw_free(struct sst_hsw *hsw); -int sst_hsw_fw_get_version(struct sst_hsw *hsw, - struct sst_hsw_ipc_fw_version *version); -u32 create_channel_map(enum sst_hsw_channel_config config); - -/* Stream Mixer Controls - */ -int sst_hsw_stream_set_volume(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume); -int sst_hsw_stream_get_volume(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 *volume); - -/* Global Mixer Controls - */ -int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, - u32 volume); -int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, - u32 *volume); - -/* Stream API */ -struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, - u32 (*get_write_position)(struct sst_hsw_stream *stream, void *data), - void *data); - -int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream); - -/* Stream Configuration */ -int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - enum sst_hsw_stream_path_id path_id, - enum sst_hsw_stream_type stream_type, - enum sst_hsw_stream_format format_id); - -int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - u32 ring_pt_address, u32 num_pages, - u32 ring_size, u32 ring_offset, u32 ring_first_pfn); - -int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream); - -int sst_hsw_stream_set_valid(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - u32 bits); -int sst_hsw_stream_set_rate(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int rate); -int sst_hsw_stream_set_bits(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - enum sst_hsw_bitdepth bits); -int sst_hsw_stream_set_channels(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, int channels); -int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 map, - enum sst_hsw_channel_config config); -int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - enum sst_hsw_interleaving style); -int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, struct sst_module_runtime *runtime); -int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 offset, u32 size); -int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 offset, u32 size); -snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream); -void sst_hsw_stream_set_old_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, snd_pcm_uframes_t val); -bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw, - struct sst_hsw_stream *stream); -void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, bool val); -int sst_hsw_mixer_get_info(struct sst_hsw *hsw); - -/* Stream ALSA trigger operations */ -int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int wait); -int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, - int wait); -int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream); - -/* Stream pointer positions */ -int sst_hsw_stream_get_read_pos(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 *position); -int sst_hsw_stream_get_write_pos(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 *position); -u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream); -u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw, - struct sst_hsw_stream *stream); - -/* HW port config */ -int sst_hsw_device_set_config(struct sst_hsw *hsw, - enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, - enum sst_hsw_device_mode mode, u32 clock_divider); - -/* DX Config */ -int sst_hsw_dx_set_state(struct sst_hsw *hsw, - enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx); - -/* init */ -int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata); -void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata); -struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); - -/* fw module function */ -void sst_hsw_init_module_state(struct sst_hsw *hsw); -bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id); -bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id); -void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); -void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id); -bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); -void sst_hsw_reset_param_buf(struct sst_hsw *hsw); -int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf); -int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf); -int sst_hsw_launch_param_buf(struct sst_hsw *hsw); - -int sst_hsw_module_load(struct sst_hsw *hsw, - u32 module_id, u32 instance_id, char *name); -int sst_hsw_module_enable(struct sst_hsw *hsw, - u32 module_id, u32 instance_id); -int sst_hsw_module_disable(struct sst_hsw *hsw, - u32 module_id, u32 instance_id); -int sst_hsw_module_set_param(struct sst_hsw *hsw, - u32 module_id, u32 instance_id, u32 parameter_id, - u32 param_size, char *param); - -/* runtime module management */ -struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, - int mod_id, int offset); -void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime); - -/* PM */ -int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw); -int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw); -int sst_hsw_dsp_load(struct sst_hsw *hsw); -int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw); - -#endif diff --git a/sound/soc/intel/haswell/sst-haswell-pcm.c b/sound/soc/intel/haswell/sst-haswell-pcm.c deleted file mode 100644 index 033d7c05d7fb..000000000000 --- a/sound/soc/intel/haswell/sst-haswell-pcm.c +++ /dev/null @@ -1,1369 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel SST Haswell/Broadwell PCM Support - * - * Copyright (C) 2013, Intel Corporation. All rights reserved. - */ - -#include <linux/module.h> -#include <linux/dma-mapping.h> -#include <linux/slab.h> -#include <linux/delay.h> -#include <linux/pm_runtime.h> -#include <asm/page.h> -#include <asm/pgtable.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/dmaengine_pcm.h> -#include <sound/soc.h> -#include <sound/tlv.h> -#include <sound/compress_driver.h> - -#include "../haswell/sst-haswell-ipc.h" -#include "../common/sst-dsp-priv.h" -#include "../common/sst-dsp.h" - -#define HSW_PCM_COUNT 6 -#define HSW_VOLUME_MAX 0x7FFFFFFF /* 0dB */ - -#define SST_OLD_POSITION(d, r, o) ((d) + \ - frames_to_bytes(r, o)) -#define SST_SAMPLES(r, x) (bytes_to_samples(r, \ - frames_to_bytes(r, (x)))) - -/* simple volume table */ -static const u32 volume_map[] = { - HSW_VOLUME_MAX >> 30, - HSW_VOLUME_MAX >> 29, - HSW_VOLUME_MAX >> 28, - HSW_VOLUME_MAX >> 27, - HSW_VOLUME_MAX >> 26, - HSW_VOLUME_MAX >> 25, - HSW_VOLUME_MAX >> 24, - HSW_VOLUME_MAX >> 23, - HSW_VOLUME_MAX >> 22, - HSW_VOLUME_MAX >> 21, - HSW_VOLUME_MAX >> 20, - HSW_VOLUME_MAX >> 19, - HSW_VOLUME_MAX >> 18, - HSW_VOLUME_MAX >> 17, - HSW_VOLUME_MAX >> 16, - HSW_VOLUME_MAX >> 15, - HSW_VOLUME_MAX >> 14, - HSW_VOLUME_MAX >> 13, - HSW_VOLUME_MAX >> 12, - HSW_VOLUME_MAX >> 11, - HSW_VOLUME_MAX >> 10, - HSW_VOLUME_MAX >> 9, - HSW_VOLUME_MAX >> 8, - HSW_VOLUME_MAX >> 7, - HSW_VOLUME_MAX >> 6, - HSW_VOLUME_MAX >> 5, - HSW_VOLUME_MAX >> 4, - HSW_VOLUME_MAX >> 3, - HSW_VOLUME_MAX >> 2, - HSW_VOLUME_MAX >> 1, - HSW_VOLUME_MAX >> 0, -}; - -#define HSW_PCM_PERIODS_MAX 64 -#define HSW_PCM_PERIODS_MIN 2 - -#define HSW_PCM_DAI_ID_SYSTEM 0 -#define HSW_PCM_DAI_ID_OFFLOAD0 1 -#define HSW_PCM_DAI_ID_OFFLOAD1 2 -#define HSW_PCM_DAI_ID_LOOPBACK 3 - - -static const struct snd_pcm_hardware hsw_pcm_hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | - SNDRV_PCM_INFO_DRAIN_TRIGGER, - .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | - SNDRV_PCM_FMTBIT_S32_LE, - .period_bytes_min = PAGE_SIZE, - .period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE, - .periods_min = HSW_PCM_PERIODS_MIN, - .periods_max = HSW_PCM_PERIODS_MAX, - .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE, -}; - -struct hsw_pcm_module_map { - int dai_id; - int stream; - enum sst_hsw_module_id mod_id; -}; - -/* private data for each PCM DSP stream */ -struct hsw_pcm_data { - int dai_id; - struct sst_hsw_stream *stream; - struct sst_module_runtime *runtime; - struct sst_module_runtime_context context; - struct snd_pcm *hsw_pcm; - u32 volume[2]; - struct snd_pcm_substream *substream; - struct snd_compr_stream *cstream; - unsigned int wpos; - struct mutex mutex; - bool allocated; - int persistent_offset; -}; - -enum hsw_pm_state { - HSW_PM_STATE_D0 = 0, - HSW_PM_STATE_RTD3 = 1, - HSW_PM_STATE_D3 = 2, -}; - -/* private data for the driver */ -struct hsw_priv_data { - /* runtime DSP */ - struct sst_hsw *hsw; - struct device *dev; - enum hsw_pm_state pm_state; - struct snd_soc_card *soc_card; - struct sst_module_runtime *runtime_waves; /* sound effect module */ - - /* page tables */ - struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; - - /* DAI data */ - struct hsw_pcm_data pcm[HSW_PCM_COUNT][2]; -}; - - -/* static mappings between PCMs and modules - may be dynamic in future */ -static struct hsw_pcm_module_map mod_map[] = { - {HSW_PCM_DAI_ID_SYSTEM, 0, SST_HSW_MODULE_PCM_SYSTEM}, - {HSW_PCM_DAI_ID_OFFLOAD0, 0, SST_HSW_MODULE_PCM}, - {HSW_PCM_DAI_ID_OFFLOAD1, 0, SST_HSW_MODULE_PCM}, - {HSW_PCM_DAI_ID_LOOPBACK, 1, SST_HSW_MODULE_PCM_REFERENCE}, - {HSW_PCM_DAI_ID_SYSTEM, 1, SST_HSW_MODULE_PCM_CAPTURE}, -}; - -static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data); - -static inline u32 hsw_mixer_to_ipc(unsigned int value) -{ - if (value >= ARRAY_SIZE(volume_map)) - return volume_map[0]; - else - return volume_map[value]; -} - -static inline unsigned int hsw_ipc_to_mixer(u32 value) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(volume_map); i++) { - if (volume_map[i] >= value) - return i; - } - - return i - 1; -} - -static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct hsw_priv_data *pdata = - snd_soc_component_get_drvdata(component); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - u32 volume; - int dai, stream; - - dai = mod_map[mc->reg].dai_id; - stream = mod_map[mc->reg].stream; - pcm_data = &pdata->pcm[dai][stream]; - - mutex_lock(&pcm_data->mutex); - pm_runtime_get_sync(pdata->dev); - - if (!pcm_data->stream) { - pcm_data->volume[0] = - hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - pcm_data->volume[1] = - hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return 0; - } - - if (ucontrol->value.integer.value[0] == - ucontrol->value.integer.value[1]) { - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - /* apply volume value to all channels */ - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, SST_HSW_CHANNELS_ALL, volume); - } else { - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume); - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume); - } - - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return 0; -} - -static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct hsw_priv_data *pdata = - snd_soc_component_get_drvdata(component); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - u32 volume; - int dai, stream; - - dai = mod_map[mc->reg].dai_id; - stream = mod_map[mc->reg].stream; - pcm_data = &pdata->pcm[dai][stream]; - - mutex_lock(&pcm_data->mutex); - pm_runtime_get_sync(pdata->dev); - - if (!pcm_data->stream) { - ucontrol->value.integer.value[0] = - hsw_ipc_to_mixer(pcm_data->volume[0]); - ucontrol->value.integer.value[1] = - hsw_ipc_to_mixer(pcm_data->volume[1]); - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return 0; - } - - sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 0, &volume); - ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); - sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume); - ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); - - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - - return 0; -} - -static int hsw_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_hsw *hsw = pdata->hsw; - u32 volume; - - pm_runtime_get_sync(pdata->dev); - - if (ucontrol->value.integer.value[0] == - ucontrol->value.integer.value[1]) { - - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - sst_hsw_mixer_set_volume(hsw, 0, SST_HSW_CHANNELS_ALL, volume); - - } else { - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - sst_hsw_mixer_set_volume(hsw, 0, 0, volume); - - volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); - sst_hsw_mixer_set_volume(hsw, 0, 1, volume); - } - - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - return 0; -} - -static int hsw_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_hsw *hsw = pdata->hsw; - unsigned int volume = 0; - - pm_runtime_get_sync(pdata->dev); - sst_hsw_mixer_get_volume(hsw, 0, 0, &volume); - ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); - - sst_hsw_mixer_get_volume(hsw, 0, 1, &volume); - ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); - - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - return 0; -} - -static int hsw_waves_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_hsw *hsw = pdata->hsw; - enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; - - ucontrol->value.integer.value[0] = - (sst_hsw_is_module_active(hsw, id) || - sst_hsw_is_module_enabled_rtd3(hsw, id)); - return 0; -} - -static int hsw_waves_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_hsw *hsw = pdata->hsw; - int ret = 0; - enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; - bool switch_on = (bool)ucontrol->value.integer.value[0]; - - /* if module is in RAM on the DSP, apply user settings to module through - * ipc. If module is not in RAM on the DSP, store user setting for - * track */ - if (sst_hsw_is_module_loaded(hsw, id)) { - if (switch_on == sst_hsw_is_module_active(hsw, id)) - return 0; - - if (switch_on) - ret = sst_hsw_module_enable(hsw, id, 0); - else - ret = sst_hsw_module_disable(hsw, id, 0); - } else { - if (switch_on == sst_hsw_is_module_enabled_rtd3(hsw, id)) - return 0; - - if (switch_on) - sst_hsw_set_module_enabled_rtd3(hsw, id); - else - sst_hsw_set_module_disabled_rtd3(hsw, id); - } - - return ret; -} - -static int hsw_waves_param_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_hsw *hsw = pdata->hsw; - - /* return a matching line from param buffer */ - return sst_hsw_load_param_line(hsw, ucontrol->value.bytes.data); -} - -static int hsw_waves_param_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_hsw *hsw = pdata->hsw; - int ret; - enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; - int param_id = ucontrol->value.bytes.data[0]; - int param_size = WAVES_PARAM_COUNT; - - /* clear param buffer and reset buffer index */ - if (param_id == 0xFF) { - sst_hsw_reset_param_buf(hsw); - return 0; - } - - /* store params into buffer */ - ret = sst_hsw_store_param_line(hsw, ucontrol->value.bytes.data); - if (ret < 0) - return ret; - - if (sst_hsw_is_module_active(hsw, id)) - ret = sst_hsw_module_set_param(hsw, id, 0, param_id, - param_size, ucontrol->value.bytes.data); - return ret; -} - -/* TLV used by both global and stream volumes */ -static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); - -/* System Pin has no volume control */ -static const struct snd_kcontrol_new hsw_volume_controls[] = { - /* Global DSP volume */ - SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8, - ARRAY_SIZE(volume_map) - 1, 0, - hsw_volume_get, hsw_volume_put, hsw_vol_tlv), - /* Offload 0 volume */ - SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8, - ARRAY_SIZE(volume_map) - 1, 0, - hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), - /* Offload 1 volume */ - SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8, - ARRAY_SIZE(volume_map) - 1, 0, - hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), - /* Mic Capture volume */ - SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, - ARRAY_SIZE(volume_map) - 1, 0, - hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), - /* enable/disable module waves */ - SOC_SINGLE_BOOL_EXT("Waves Switch", 0, - hsw_waves_switch_get, hsw_waves_switch_put), - /* set parameters to module waves */ - SND_SOC_BYTES_EXT("Waves Set Param", WAVES_PARAM_COUNT, - hsw_waves_param_get, hsw_waves_param_put), -}; - -/* Create DMA buffer page table for DSP */ -static int create_adsp_page_table(struct snd_pcm_substream *substream, - struct hsw_priv_data *pdata, struct snd_soc_pcm_runtime *rtd, - unsigned char *dma_area, size_t size, int pcm) -{ - struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); - int i, pages, stream = substream->stream; - - pages = snd_sgbuf_aligned_pages(size); - - dev_dbg(rtd->dev, "generating page table for %p size 0x%zx pages %d\n", - dma_area, size, pages); - - for (i = 0; i < pages; i++) { - u32 idx = (((i << 2) + i)) >> 1; - u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; - u32 *pg_table; - - dev_dbg(rtd->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); - - pg_table = (u32 *)(pdata->dmab[pcm][stream].area + idx); - - if (i & 1) - *pg_table |= (pfn << 4); - else - *pg_table |= pfn; - } - - return 0; -} - -/* this may get called several times by oss emulation */ -static int hsw_pcm_hw_params(struct snd_soc_component *component, - struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - struct sst_module *module_data; - struct sst_dsp *dsp; - struct snd_dma_buffer *dmab; - enum sst_hsw_stream_type stream_type; - enum sst_hsw_stream_path_id path_id; - u32 rate, bits, map, pages, module_id; - u8 channels; - int ret, dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - - /* check if we are being called a subsequent time */ - if (pcm_data->allocated) { - ret = sst_hsw_stream_reset(hsw, pcm_data->stream); - if (ret < 0) - dev_dbg(rtd->dev, "error: reset stream failed %d\n", - ret); - - ret = sst_hsw_stream_free(hsw, pcm_data->stream); - if (ret < 0) { - dev_dbg(rtd->dev, "error: free stream failed %d\n", - ret); - return ret; - } - pcm_data->allocated = false; - - pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, - hsw_notify_pointer, pcm_data); - if (pcm_data->stream == NULL) { - dev_err(rtd->dev, "error: failed to create stream\n"); - return -EINVAL; - } - } - - /* stream direction */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - path_id = SST_HSW_STREAM_PATH_SSP0_OUT; - else - path_id = SST_HSW_STREAM_PATH_SSP0_IN; - - /* DSP stream type depends on DAI ID */ - switch (rtd->cpu_dai->id) { - case 0: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - stream_type = SST_HSW_STREAM_TYPE_SYSTEM; - module_id = SST_HSW_MODULE_PCM_SYSTEM; - } - else { - stream_type = SST_HSW_STREAM_TYPE_CAPTURE; - module_id = SST_HSW_MODULE_PCM_CAPTURE; - } - break; - case 1: - case 2: - stream_type = SST_HSW_STREAM_TYPE_RENDER; - module_id = SST_HSW_MODULE_PCM; - break; - case 3: - /* path ID needs to be OUT for loopback */ - stream_type = SST_HSW_STREAM_TYPE_LOOPBACK; - path_id = SST_HSW_STREAM_PATH_SSP0_OUT; - module_id = SST_HSW_MODULE_PCM_REFERENCE; - break; - default: - dev_err(rtd->dev, "error: invalid DAI ID %d\n", - rtd->cpu_dai->id); - return -EINVAL; - } - - ret = sst_hsw_stream_format(hsw, pcm_data->stream, - path_id, stream_type, SST_HSW_STREAM_FORMAT_PCM_FORMAT); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to set format %d\n", ret); - return ret; - } - - rate = params_rate(params); - ret = sst_hsw_stream_set_rate(hsw, pcm_data->stream, rate); - if (ret < 0) { - dev_err(rtd->dev, "error: could not set rate %d\n", rate); - return ret; - } - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - bits = SST_HSW_DEPTH_16BIT; - sst_hsw_stream_set_valid(hsw, pcm_data->stream, 16); - break; - case SNDRV_PCM_FORMAT_S24_LE: - bits = SST_HSW_DEPTH_32BIT; - sst_hsw_stream_set_valid(hsw, pcm_data->stream, 24); - break; - case SNDRV_PCM_FORMAT_S8: - bits = SST_HSW_DEPTH_8BIT; - sst_hsw_stream_set_valid(hsw, pcm_data->stream, 8); - break; - case SNDRV_PCM_FORMAT_S32_LE: - bits = SST_HSW_DEPTH_32BIT; - sst_hsw_stream_set_valid(hsw, pcm_data->stream, 32); - break; - default: - dev_err(rtd->dev, "error: invalid format %d\n", - params_format(params)); - return -EINVAL; - } - - ret = sst_hsw_stream_set_bits(hsw, pcm_data->stream, bits); - if (ret < 0) { - dev_err(rtd->dev, "error: could not set bits %d\n", bits); - return ret; - } - - channels = params_channels(params); - map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO); - sst_hsw_stream_set_map_config(hsw, pcm_data->stream, - map, SST_HSW_CHANNEL_CONFIG_STEREO); - - ret = sst_hsw_stream_set_channels(hsw, pcm_data->stream, channels); - if (ret < 0) { - dev_err(rtd->dev, "error: could not set channels %d\n", - channels); - return ret; - } - - dmab = snd_pcm_get_dma_buf(substream); - - ret = create_adsp_page_table(substream, pdata, rtd, runtime->dma_area, - runtime->dma_bytes, rtd->cpu_dai->id); - if (ret < 0) - return ret; - - sst_hsw_stream_set_style(hsw, pcm_data->stream, - SST_HSW_INTERLEAVING_PER_CHANNEL); - - if (runtime->dma_bytes % PAGE_SIZE) - pages = (runtime->dma_bytes / PAGE_SIZE) + 1; - else - pages = runtime->dma_bytes / PAGE_SIZE; - - ret = sst_hsw_stream_buffer(hsw, pcm_data->stream, - pdata->dmab[rtd->cpu_dai->id][substream->stream].addr, - pages, runtime->dma_bytes, 0, - snd_sgbuf_get_addr(dmab, 0) >> PAGE_SHIFT); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to set DMA buffer %d\n", ret); - return ret; - } - - dsp = sst_hsw_get_dsp(hsw); - - module_data = sst_module_get_from_id(dsp, module_id); - if (module_data == NULL) { - dev_err(rtd->dev, "error: failed to get module config\n"); - return -EINVAL; - } - - sst_hsw_stream_set_module_info(hsw, pcm_data->stream, - pcm_data->runtime); - - ret = sst_hsw_stream_commit(hsw, pcm_data->stream); - if (ret < 0) { - dev_err(rtd->dev, "error: failed to commit stream %d\n", ret); - return ret; - } - - if (!pcm_data->allocated) { - /* Set previous saved volume */ - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, - 0, pcm_data->volume[0]); - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, - 1, pcm_data->volume[1]); - pcm_data->allocated = true; - } - - ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1); - if (ret < 0) - dev_err(rtd->dev, "error: failed to pause %d\n", ret); - - return 0; -} - -static int hsw_pcm_trigger(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int cmd) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct hsw_pcm_data *pcm_data; - struct sst_hsw_stream *sst_stream; - struct sst_hsw *hsw = pdata->hsw; - struct snd_pcm_runtime *runtime = substream->runtime; - snd_pcm_uframes_t pos; - int dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - sst_stream = pcm_data->stream; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - sst_hsw_stream_set_silence_start(hsw, sst_stream, false); - sst_hsw_stream_resume(hsw, pcm_data->stream, 0); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - sst_hsw_stream_set_silence_start(hsw, sst_stream, false); - sst_hsw_stream_pause(hsw, pcm_data->stream, 0); - break; - case SNDRV_PCM_TRIGGER_DRAIN: - pos = runtime->control->appl_ptr % runtime->buffer_size; - sst_hsw_stream_set_old_position(hsw, pcm_data->stream, pos); - sst_hsw_stream_set_silence_start(hsw, sst_stream, true); - break; - default: - break; - } - - return 0; -} - -static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data) -{ - struct hsw_pcm_data *pcm_data = data; - struct snd_pcm_substream *substream = pcm_data->substream; - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct sst_hsw *hsw = pdata->hsw; - u32 pos; - snd_pcm_uframes_t position = bytes_to_frames(runtime, - sst_hsw_get_dsp_position(hsw, pcm_data->stream)); - unsigned char *dma_area = runtime->dma_area; - snd_pcm_uframes_t dma_frames = - bytes_to_frames(runtime, runtime->dma_bytes); - snd_pcm_uframes_t old_position; - ssize_t samples; - - pos = frames_to_bytes(runtime, - (runtime->control->appl_ptr % runtime->buffer_size)); - - dev_vdbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); - - /* SST fw don't know where to stop dma - * So, SST driver need to clean the data which has been consumed - */ - if (dma_area == NULL || dma_frames <= 0 - || (substream->stream == SNDRV_PCM_STREAM_CAPTURE) - || !sst_hsw_stream_get_silence_start(hsw, stream)) { - snd_pcm_period_elapsed(substream); - return pos; - } - - old_position = sst_hsw_stream_get_old_position(hsw, stream); - if (position > old_position) { - if (position < dma_frames) { - samples = SST_SAMPLES(runtime, position - old_position); - snd_pcm_format_set_silence(runtime->format, - SST_OLD_POSITION(dma_area, - runtime, old_position), - samples); - } else - dev_err(rtd->dev, "PCM: position is wrong\n"); - } else { - if (old_position < dma_frames) { - samples = SST_SAMPLES(runtime, - dma_frames - old_position); - snd_pcm_format_set_silence(runtime->format, - SST_OLD_POSITION(dma_area, - runtime, old_position), - samples); - } else - dev_err(rtd->dev, "PCM: dma_bytes is wrong\n"); - if (position < dma_frames) { - samples = SST_SAMPLES(runtime, position); - snd_pcm_format_set_silence(runtime->format, - dma_area, samples); - } else - dev_err(rtd->dev, "PCM: position is wrong\n"); - } - sst_hsw_stream_set_old_position(hsw, stream, position); - - /* let alsa know we have play a period */ - snd_pcm_period_elapsed(substream); - return pos; -} - -static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_soc_component *component, - struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - snd_pcm_uframes_t offset; - uint64_t ppos; - u32 position; - int dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - position = sst_hsw_get_dsp_position(hsw, pcm_data->stream); - - offset = bytes_to_frames(runtime, position); - ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream); - - dev_vdbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", - position, ppos); - return offset; -} - -static int hsw_pcm_open(struct snd_soc_component *component, - struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - int dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - - mutex_lock(&pcm_data->mutex); - pm_runtime_get_sync(pdata->dev); - - pcm_data->substream = substream; - - snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware); - - pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, - hsw_notify_pointer, pcm_data); - if (pcm_data->stream == NULL) { - dev_err(rtd->dev, "error: failed to create stream\n"); - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return -EINVAL; - } - - mutex_unlock(&pcm_data->mutex); - return 0; -} - -static int hsw_pcm_close(struct snd_soc_component *component, - struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); - struct hsw_pcm_data *pcm_data; - struct sst_hsw *hsw = pdata->hsw; - int ret, dai; - - dai = mod_map[rtd->cpu_dai->id].dai_id; - pcm_data = &pdata->pcm[dai][substream->stream]; - - mutex_lock(&pcm_data->mutex); - ret = sst_hsw_stream_reset(hsw, pcm_data->stream); - if (ret < 0) { - dev_dbg(rtd->dev, "error: reset stream failed %d\n", ret); - goto out; - } - - ret = sst_hsw_stream_free(hsw, pcm_data->stream); - if (ret < 0) { - dev_dbg(rtd->dev, "error: free stream failed %d\n", ret); - goto out; - } - pcm_data->allocated = false; - pcm_data->stream = NULL; - -out: - pm_runtime_mark_last_busy(pdata->dev); - pm_runtime_put_autosuspend(pdata->dev); - mutex_unlock(&pcm_data->mutex); - return ret; -} - -static int hsw_pcm_create_modules(struct hsw_priv_data *pdata) -{ - struct sst_hsw *hsw = pdata->hsw; - struct hsw_pcm_data *pcm_data; - int i; - - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - - /* create new runtime module, use same offset if recreated */ - pcm_data->runtime = sst_hsw_runtime_module_create(hsw, - mod_map[i].mod_id, pcm_data->persistent_offset); - if (pcm_data->runtime == NULL) - goto err; - pcm_data->persistent_offset = - pcm_data->runtime->persistent_offset; - } - - /* create runtime blocks for module waves */ - if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { - pdata->runtime_waves = sst_hsw_runtime_module_create(hsw, - SST_HSW_MODULE_WAVES, 0); - if (pdata->runtime_waves == NULL) - goto err; - } - - return 0; - -err: - for (--i; i >= 0; i--) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - sst_hsw_runtime_module_free(pcm_data->runtime); - } - - return -ENODEV; -} - -static void hsw_pcm_free_modules(struct hsw_priv_data *pdata) -{ - struct sst_hsw *hsw = pdata->hsw; - struct hsw_pcm_data *pcm_data; - int i; - - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - if (pcm_data->runtime){ - sst_hsw_runtime_module_free(pcm_data->runtime); - pcm_data->runtime = NULL; - } - } - if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES) && - pdata->runtime_waves) { - sst_hsw_runtime_module_free(pdata->runtime_waves); - pdata->runtime_waves = NULL; - } -} - -static int hsw_pcm_new(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd) -{ - struct snd_pcm *pcm = rtd->pcm; - struct sst_pdata *pdata = dev_get_platdata(component->dev); - struct hsw_priv_data *priv_data = dev_get_drvdata(component->dev); - struct device *dev = pdata->dma_dev; - - if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || - pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { - snd_pcm_set_managed_buffer_all(pcm, - SNDRV_DMA_TYPE_DEV_SG, - dev, - hsw_pcm_hardware.buffer_bytes_max, - hsw_pcm_hardware.buffer_bytes_max); - } - if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) - priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_PLAYBACK].hsw_pcm = pcm; - if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) - priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_CAPTURE].hsw_pcm = pcm; - - return 0; -} - -#define HSW_FORMATS \ - (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) - -static struct snd_soc_dai_driver hsw_dais[] = { - { - .name = "System Pin", - .id = HSW_PCM_DAI_ID_SYSTEM, - .playback = { - .stream_name = "System Playback", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "Analog Capture", - .channels_min = 2, - .channels_max = 4, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, - }, - }, - { - /* PCM */ - .name = "Offload0 Pin", - .id = HSW_PCM_DAI_ID_OFFLOAD0, - .playback = { - .stream_name = "Offload0 Playback", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, - .formats = HSW_FORMATS, - }, - }, - { - /* PCM */ - .name = "Offload1 Pin", - .id = HSW_PCM_DAI_ID_OFFLOAD1, - .playback = { - .stream_name = "Offload1 Playback", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, - .formats = HSW_FORMATS, - }, - }, - { - .name = "Loopback Pin", - .id = HSW_PCM_DAI_ID_LOOPBACK, - .capture = { - .stream_name = "Loopback Capture", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, - }, - }, -}; - -static const struct snd_soc_dapm_widget widgets[] = { - - /* Backend DAIs */ - SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0), - - /* Global Playback Mixer */ - SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), -}; - -static const struct snd_soc_dapm_route graph[] = { - - /* Playback Mixer */ - {"Playback VMixer", NULL, "System Playback"}, - {"Playback VMixer", NULL, "Offload0 Playback"}, - {"Playback VMixer", NULL, "Offload1 Playback"}, - - {"SSP0 CODEC OUT", NULL, "Playback VMixer"}, - - {"Analog Capture", NULL, "SSP0 CODEC IN"}, -}; - -static int hsw_pcm_probe(struct snd_soc_component *component) -{ - struct hsw_priv_data *priv_data = snd_soc_component_get_drvdata(component); - struct sst_pdata *pdata = dev_get_platdata(component->dev); - struct device *dma_dev, *dev; - int i, ret = 0; - - if (!pdata) - return -ENODEV; - - dev = component->dev; - dma_dev = pdata->dma_dev; - - priv_data->hsw = pdata->dsp; - priv_data->dev = dev; - priv_data->pm_state = HSW_PM_STATE_D0; - priv_data->soc_card = component->card; - - /* allocate DSP buffer page tables */ - for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { - - /* playback */ - if (hsw_dais[i].playback.channels_min) { - mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_PLAYBACK].mutex); - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, - PAGE_SIZE, &priv_data->dmab[i][0]); - if (ret < 0) - goto err; - } - - /* capture */ - if (hsw_dais[i].capture.channels_min) { - mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_CAPTURE].mutex); - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, - PAGE_SIZE, &priv_data->dmab[i][1]); - if (ret < 0) - goto err; - } - } - - /* allocate runtime modules */ - ret = hsw_pcm_create_modules(priv_data); - if (ret < 0) - goto err; - - /* enable runtime PM with auto suspend */ - pm_runtime_set_autosuspend_delay(dev, SST_RUNTIME_SUSPEND_DELAY); - pm_runtime_use_autosuspend(dev); - pm_runtime_enable(dev); - pm_runtime_idle(dev); - - return 0; - -err: - for (--i; i >= 0; i--) { - if (hsw_dais[i].playback.channels_min) - snd_dma_free_pages(&priv_data->dmab[i][0]); - if (hsw_dais[i].capture.channels_min) - snd_dma_free_pages(&priv_data->dmab[i][1]); - } - return ret; -} - -static void hsw_pcm_remove(struct snd_soc_component *component) -{ - struct hsw_priv_data *priv_data = - snd_soc_component_get_drvdata(component); - int i; - - pm_runtime_disable(component->dev); - hsw_pcm_free_modules(priv_data); - - for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { - if (hsw_dais[i].playback.channels_min) - snd_dma_free_pages(&priv_data->dmab[i][0]); - if (hsw_dais[i].capture.channels_min) - snd_dma_free_pages(&priv_data->dmab[i][1]); - } -} - -static const struct snd_soc_component_driver hsw_dai_component = { - .name = DRV_NAME, - .probe = hsw_pcm_probe, - .remove = hsw_pcm_remove, - .open = hsw_pcm_open, - .close = hsw_pcm_close, - .hw_params = hsw_pcm_hw_params, - .trigger = hsw_pcm_trigger, - .pointer = hsw_pcm_pointer, - .pcm_construct = hsw_pcm_new, - .controls = hsw_volume_controls, - .num_controls = ARRAY_SIZE(hsw_volume_controls), - .dapm_widgets = widgets, - .num_dapm_widgets = ARRAY_SIZE(widgets), - .dapm_routes = graph, - .num_dapm_routes = ARRAY_SIZE(graph), -}; - -static int hsw_pcm_dev_probe(struct platform_device *pdev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); - struct hsw_priv_data *priv_data; - int ret; - - if (!sst_pdata) - return -EINVAL; - - priv_data = devm_kzalloc(&pdev->dev, sizeof(*priv_data), GFP_KERNEL); - if (!priv_data) - return -ENOMEM; - - ret = sst_hsw_dsp_init(&pdev->dev, sst_pdata); - if (ret < 0) - return -ENODEV; - - priv_data->hsw = sst_pdata->dsp; - platform_set_drvdata(pdev, priv_data); - - ret = devm_snd_soc_register_component(&pdev->dev, &hsw_dai_component, - hsw_dais, ARRAY_SIZE(hsw_dais)); - if (ret < 0) - goto err_plat; - - return 0; - -err_plat: - sst_hsw_dsp_free(&pdev->dev, sst_pdata); - return 0; -} - -static int hsw_pcm_dev_remove(struct platform_device *pdev) -{ - struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); - - sst_hsw_dsp_free(&pdev->dev, sst_pdata); - - return 0; -} - -#ifdef CONFIG_PM - -static int hsw_pcm_runtime_idle(struct device *dev) -{ - return 0; -} - -static int hsw_pcm_suspend(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct sst_hsw *hsw = pdata->hsw; - - /* enter D3 state and stall */ - sst_hsw_dsp_runtime_suspend(hsw); - /* free all runtime modules */ - hsw_pcm_free_modules(pdata); - /* put the DSP to sleep, fw unloaded after runtime modules freed */ - sst_hsw_dsp_runtime_sleep(hsw); - return 0; -} - -static int hsw_pcm_runtime_suspend(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct sst_hsw *hsw = pdata->hsw; - int ret; - - if (pdata->pm_state >= HSW_PM_STATE_RTD3) - return 0; - - /* fw modules will be unloaded on RTD3, set flag to track */ - if (sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { - ret = sst_hsw_module_disable(hsw, SST_HSW_MODULE_WAVES, 0); - if (ret < 0) - return ret; - sst_hsw_set_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES); - } - hsw_pcm_suspend(dev); - pdata->pm_state = HSW_PM_STATE_RTD3; - - return 0; -} - -static int hsw_pcm_runtime_resume(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct sst_hsw *hsw = pdata->hsw; - int ret; - - if (pdata->pm_state != HSW_PM_STATE_RTD3) - return 0; - - ret = sst_hsw_dsp_load(hsw); - if (ret < 0) { - dev_err(dev, "failed to reload %d\n", ret); - return ret; - } - - ret = hsw_pcm_create_modules(pdata); - if (ret < 0) { - dev_err(dev, "failed to create modules %d\n", ret); - return ret; - } - - ret = sst_hsw_dsp_runtime_resume(hsw); - if (ret < 0) - return ret; - else if (ret == 1) /* no action required */ - return 0; - - /* check flag when resume */ - if (sst_hsw_is_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES)) { - ret = sst_hsw_module_enable(hsw, SST_HSW_MODULE_WAVES, 0); - if (ret < 0) - return ret; - /* put parameters from buffer to dsp */ - ret = sst_hsw_launch_param_buf(hsw); - if (ret < 0) - return ret; - /* unset flag */ - sst_hsw_set_module_disabled_rtd3(hsw, SST_HSW_MODULE_WAVES); - } - - pdata->pm_state = HSW_PM_STATE_D0; - return ret; -} - -#else -#define hsw_pcm_runtime_idle NULL -#define hsw_pcm_runtime_suspend NULL -#define hsw_pcm_runtime_resume NULL -#endif - -#ifdef CONFIG_PM - -static void hsw_pcm_complete(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct sst_hsw *hsw = pdata->hsw; - struct hsw_pcm_data *pcm_data; - int i, err; - - if (pdata->pm_state != HSW_PM_STATE_D3) - return; - - err = sst_hsw_dsp_load(hsw); - if (err < 0) { - dev_err(dev, "failed to reload %d\n", err); - return; - } - - err = hsw_pcm_create_modules(pdata); - if (err < 0) { - dev_err(dev, "failed to create modules %d\n", err); - return; - } - - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - - if (!pcm_data->substream) - continue; - - err = sst_module_runtime_restore(pcm_data->runtime, - &pcm_data->context); - if (err < 0) - dev_err(dev, "failed to restore context for PCM %d\n", i); - } - - snd_soc_resume(pdata->soc_card->dev); - - err = sst_hsw_dsp_runtime_resume(hsw); - if (err < 0) - return; - else if (err == 1) /* no action required */ - return; - - pdata->pm_state = HSW_PM_STATE_D0; - return; -} - -static int hsw_pcm_prepare(struct device *dev) -{ - struct hsw_priv_data *pdata = dev_get_drvdata(dev); - struct hsw_pcm_data *pcm_data; - int i, err; - - if (pdata->pm_state == HSW_PM_STATE_D3) - return 0; - else if (pdata->pm_state == HSW_PM_STATE_D0) { - /* suspend all active streams */ - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - - if (!pcm_data->substream) - continue; - dev_dbg(dev, "suspending pcm %d\n", i); - snd_pcm_suspend_all(pcm_data->hsw_pcm); - - /* We need to wait until the DSP FW stops the streams */ - msleep(2); - } - - /* preserve persistent memory */ - for (i = 0; i < ARRAY_SIZE(mod_map); i++) { - pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; - - if (!pcm_data->substream) - continue; - - dev_dbg(dev, "saving context pcm %d\n", i); - err = sst_module_runtime_save(pcm_data->runtime, - &pcm_data->context); - if (err < 0) - dev_err(dev, "failed to save context for PCM %d\n", i); - } - hsw_pcm_suspend(dev); - } - - snd_soc_suspend(pdata->soc_card->dev); - snd_soc_poweroff(pdata->soc_card->dev); - - pdata->pm_state = HSW_PM_STATE_D3; - - return 0; -} - -#else -#define hsw_pcm_prepare NULL -#define hsw_pcm_complete NULL -#endif - -static const struct dev_pm_ops hsw_pcm_pm = { - .runtime_idle = hsw_pcm_runtime_idle, - .runtime_suspend = hsw_pcm_runtime_suspend, - .runtime_resume = hsw_pcm_runtime_resume, - .prepare = hsw_pcm_prepare, - .complete = hsw_pcm_complete, -}; - -static struct platform_driver hsw_pcm_driver = { - .driver = { - .name = "haswell-pcm-audio", - .pm = &hsw_pcm_pm, - }, - - .probe = hsw_pcm_dev_probe, - .remove = hsw_pcm_dev_remove, -}; -module_platform_driver(hsw_pcm_driver); - -MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); -MODULE_DESCRIPTION("Haswell/Lynxpoint + Broadwell/Wildcatpoint PCM"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:haswell-pcm-audio"); diff --git a/sound/soc/intel/keembay/Makefile b/sound/soc/intel/keembay/Makefile new file mode 100644 index 000000000000..9084e8c63854 --- /dev/null +++ b/sound/soc/intel/keembay/Makefile @@ -0,0 +1,4 @@ +snd-soc-kmb_platform-objs := \ + kmb_platform.o + +obj-$(CONFIG_SND_SOC_INTEL_KEEMBAY) += snd-soc-kmb_platform.o diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c new file mode 100644 index 000000000000..b4893365d01d --- /dev/null +++ b/sound/soc/intel/keembay/kmb_platform.c @@ -0,0 +1,940 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2020 Intel Corporation. +// +// Intel KeemBay Platform driver. +// + +#include <linux/bitrev.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "kmb_platform.h" + +#define PERIODS_MIN 2 +#define PERIODS_MAX 48 +#define PERIOD_BYTES_MIN 4096 +#define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN) +#define TDM_OPERATION 5 +#define I2S_OPERATION 0 +#define DATA_WIDTH_CONFIG_BIT 6 +#define TDM_CHANNEL_CONFIG_BIT 3 + +static const struct snd_pcm_hardware kmb_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = PERIODS_MAX, + .fifo_size = 16, +}; + +/* + * Convert to ADV7511 HDMI hardware format. + * ADV7511 HDMI chip need parity bit replaced by block start bit and + * with the preamble bits left out. + * ALSA IEC958 subframe format: + * bit 0-3 = preamble (0x8 = block start) + * 4-7 = AUX (=0) + * 8-27 = audio data (without AUX if 24bit sample) + * 28 = validity + * 29 = user data + * 30 = channel status + * 31 = parity + * + * ADV7511 IEC958 subframe format: + * bit 0-23 = audio data + * 24 = validity + * 25 = user data + * 26 = channel status + * 27 = block start + * 28-31 = 0 + * MSB to LSB bit reverse by software as hardware not supporting it. + */ +static void hdmi_reformat_iec958(struct snd_pcm_runtime *runtime, + struct kmb_i2s_info *kmb_i2s, + unsigned int tx_ptr) +{ + u32(*buf)[2] = (void *)runtime->dma_area; + unsigned long temp; + u32 i, j, sample; + + for (i = 0; i < kmb_i2s->fifo_th; i++) { + j = 0; + do { + temp = buf[tx_ptr][j]; + /* Replace parity with block start*/ + assign_bit(31, &temp, (BIT(3) & temp)); + sample = bitrev32(temp); + buf[tx_ptr][j] = sample << 4; + j++; + } while (j < 2); + tx_ptr++; + } +} + +static unsigned int kmb_pcm_tx_fn(struct kmb_i2s_info *kmb_i2s, + struct snd_pcm_runtime *runtime, + unsigned int tx_ptr, bool *period_elapsed) +{ + unsigned int period_pos = tx_ptr % runtime->period_size; + void __iomem *i2s_base = kmb_i2s->i2s_base; + void *buf = runtime->dma_area; + int i; + + if (kmb_i2s->iec958_fmt) + hdmi_reformat_iec958(runtime, kmb_i2s, tx_ptr); + + /* KMB i2s uses two separate L/R FIFO */ + for (i = 0; i < kmb_i2s->fifo_th; i++) { + if (kmb_i2s->config.data_width == 16) { + writel(((u16(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0)); + writel(((u16(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0)); + } else { + writel(((u32(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0)); + writel(((u32(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0)); + } + + period_pos++; + + if (++tx_ptr >= runtime->buffer_size) + tx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + + return tx_ptr; +} + +static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s, + struct snd_pcm_runtime *runtime, + unsigned int rx_ptr, bool *period_elapsed) +{ + unsigned int period_pos = rx_ptr % runtime->period_size; + void __iomem *i2s_base = kmb_i2s->i2s_base; + int chan = kmb_i2s->config.chan_nr; + void *buf = runtime->dma_area; + int i, j; + + /* KMB i2s uses two separate L/R FIFO */ + for (i = 0; i < kmb_i2s->fifo_th; i++) { + for (j = 0; j < chan / 2; j++) { + if (kmb_i2s->config.data_width == 16) { + ((u16 *)buf)[rx_ptr * chan + (j * 2)] = + readl(i2s_base + LRBR_LTHR(j)); + ((u16 *)buf)[rx_ptr * chan + ((j * 2) + 1)] = + readl(i2s_base + RRBR_RTHR(j)); + } else { + ((u32 *)buf)[rx_ptr * chan + (j * 2)] = + readl(i2s_base + LRBR_LTHR(j)); + ((u32 *)buf)[rx_ptr * chan + ((j * 2) + 1)] = + readl(i2s_base + RRBR_RTHR(j)); + } + } + period_pos++; + + if (++rx_ptr >= runtime->buffer_size) + rx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + + return rx_ptr; +} + +static inline void kmb_i2s_disable_channels(struct kmb_i2s_info *kmb_i2s, + u32 stream) +{ + u32 i; + + /* Disable all channels regardless of configuration*/ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < MAX_ISR; i++) + writel(0, kmb_i2s->i2s_base + TER(i)); + } else { + for (i = 0; i < MAX_ISR; i++) + writel(0, kmb_i2s->i2s_base + RER(i)); + } +} + +static inline void kmb_i2s_clear_irqs(struct kmb_i2s_info *kmb_i2s, u32 stream) +{ + struct i2s_clk_config_data *config = &kmb_i2s->config; + u32 i; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < config->chan_nr / 2; i++) + readl(kmb_i2s->i2s_base + TOR(i)); + } else { + for (i = 0; i < config->chan_nr / 2; i++) + readl(kmb_i2s->i2s_base + ROR(i)); + } +} + +static inline void kmb_i2s_irq_trigger(struct kmb_i2s_info *kmb_i2s, + u32 stream, int chan_nr, bool trigger) +{ + u32 i, irq; + u32 flag; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + flag = TX_INT_FLAG; + else + flag = RX_INT_FLAG; + + for (i = 0; i < chan_nr / 2; i++) { + irq = readl(kmb_i2s->i2s_base + IMR(i)); + + if (trigger) + irq = irq & ~flag; + else + irq = irq | flag; + + writel(irq, kmb_i2s->i2s_base + IMR(i)); + } +} + +static void kmb_pcm_operation(struct kmb_i2s_info *kmb_i2s, bool playback) +{ + struct snd_pcm_substream *substream; + bool period_elapsed; + unsigned int new_ptr; + unsigned int ptr; + + if (playback) + substream = kmb_i2s->tx_substream; + else + substream = kmb_i2s->rx_substream; + + if (!substream || !snd_pcm_running(substream)) + return; + + if (playback) { + ptr = kmb_i2s->tx_ptr; + new_ptr = kmb_pcm_tx_fn(kmb_i2s, substream->runtime, + ptr, &period_elapsed); + cmpxchg(&kmb_i2s->tx_ptr, ptr, new_ptr); + } else { + ptr = kmb_i2s->rx_ptr; + new_ptr = kmb_pcm_rx_fn(kmb_i2s, substream->runtime, + ptr, &period_elapsed); + cmpxchg(&kmb_i2s->rx_ptr, ptr, new_ptr); + } + + if (period_elapsed) + snd_pcm_period_elapsed(substream); +} + +static int kmb_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct kmb_i2s_info *kmb_i2s; + + kmb_i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + snd_soc_set_runtime_hwparams(substream, &kmb_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = kmb_i2s; + + return 0; +} + +static int kmb_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct kmb_i2s_info *kmb_i2s = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + kmb_i2s->tx_ptr = 0; + kmb_i2s->tx_substream = substream; + } else { + kmb_i2s->rx_ptr = 0; + kmb_i2s->rx_substream = substream; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + kmb_i2s->tx_substream = NULL; + else + kmb_i2s->rx_substream = NULL; + kmb_i2s->iec958_fmt = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id) +{ + struct kmb_i2s_info *kmb_i2s = dev_id; + struct i2s_clk_config_data *config = &kmb_i2s->config; + irqreturn_t ret = IRQ_NONE; + u32 tx_enabled = 0; + u32 isr[4]; + int i; + + for (i = 0; i < config->chan_nr / 2; i++) + isr[i] = readl(kmb_i2s->i2s_base + ISR(i)); + + kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK); + kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE); + /* Only check TX interrupt if TX is active */ + tx_enabled = readl(kmb_i2s->i2s_base + ITER); + + /* + * Data available. Retrieve samples from FIFO + */ + + /* + * 8 channel audio will have isr[0..2] triggered, + * reading the specific isr based on the audio configuration, + * to avoid reading the buffers too early. + */ + switch (config->chan_nr) { + case 2: + if (isr[0] & ISR_RXDA) + kmb_pcm_operation(kmb_i2s, false); + ret = IRQ_HANDLED; + break; + case 4: + if (isr[1] & ISR_RXDA) + kmb_pcm_operation(kmb_i2s, false); + ret = IRQ_HANDLED; + break; + case 8: + if (isr[3] & ISR_RXDA) + kmb_pcm_operation(kmb_i2s, false); + ret = IRQ_HANDLED; + break; + } + + for (i = 0; i < config->chan_nr / 2; i++) { + /* + * Check if TX fifo is empty. If empty fill FIFO with samples + */ + if ((isr[i] & ISR_TXFE) && tx_enabled) { + kmb_pcm_operation(kmb_i2s, true); + ret = IRQ_HANDLED; + } + + /* Error Handling: TX */ + if (isr[i] & ISR_TXFO) { + dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n", i); + ret = IRQ_HANDLED; + } + /* Error Handling: RX */ + if (isr[i] & ISR_RXFO) { + dev_dbg(kmb_i2s->dev, "RX overrun (ch_id=%d)\n", i); + ret = IRQ_HANDLED; + } + } + + return ret; +} + +static int kmb_platform_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *soc_runtime) +{ + size_t size = kmb_pcm_hardware.buffer_bytes_max; + /* Use SNDRV_DMA_TYPE_CONTINUOUS as KMB doesn't use PCI sg buffer */ + snd_pcm_set_managed_buffer_all(soc_runtime->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, size, size); + return 0; +} + +static snd_pcm_uframes_t kmb_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct kmb_i2s_info *kmb_i2s = runtime->private_data; + snd_pcm_uframes_t pos; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pos = kmb_i2s->tx_ptr; + else + pos = kmb_i2s->rx_ptr; + + return pos < runtime->buffer_size ? pos : 0; +} + +static const struct snd_soc_component_driver kmb_component = { + .name = "kmb", + .pcm_construct = kmb_platform_pcm_new, + .open = kmb_pcm_open, + .trigger = kmb_pcm_trigger, + .pointer = kmb_pcm_pointer, + .legacy_dai_naming = 1, +}; + +static const struct snd_soc_component_driver kmb_component_dma = { + .name = "kmb", + .legacy_dai_naming = 1, +}; + +static int kmb_probe(struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + + if (kmb_i2s->use_pio) + return 0; + + snd_soc_dai_init_dma_data(cpu_dai, &kmb_i2s->play_dma_data, + &kmb_i2s->capture_dma_data); + + return 0; +} + +static inline void kmb_i2s_enable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream) +{ + u32 dma_reg; + + dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR); + /* Enable DMA handshake for stream */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_reg |= I2S_DMAEN_TXBLOCK; + else + dma_reg |= I2S_DMAEN_RXBLOCK; + + writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR); +} + +static inline void kmb_i2s_disable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream) +{ + u32 dma_reg; + + dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR); + /* Disable DMA handshake for stream */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_reg &= ~I2S_DMAEN_TXBLOCK; + writel(1, kmb_i2s->i2s_base + I2S_RTXDMA); + } else { + dma_reg &= ~I2S_DMAEN_RXBLOCK; + writel(1, kmb_i2s->i2s_base + I2S_RRXDMA); + } + writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR); +} + +static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s, + struct snd_pcm_substream *substream) +{ + struct i2s_clk_config_data *config = &kmb_i2s->config; + + /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ + writel(1, kmb_i2s->i2s_base + IER); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(1, kmb_i2s->i2s_base + ITER); + else + writel(1, kmb_i2s->i2s_base + IRER); + + if (kmb_i2s->use_pio) + kmb_i2s_irq_trigger(kmb_i2s, substream->stream, + config->chan_nr, true); + else + kmb_i2s_enable_dma(kmb_i2s, substream->stream); + + if (kmb_i2s->clock_provider) + writel(1, kmb_i2s->i2s_base + CER); + else + writel(0, kmb_i2s->i2s_base + CER); +} + +static void kmb_i2s_stop(struct kmb_i2s_info *kmb_i2s, + struct snd_pcm_substream *substream) +{ + /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ + kmb_i2s_clear_irqs(kmb_i2s, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(0, kmb_i2s->i2s_base + ITER); + else + writel(0, kmb_i2s->i2s_base + IRER); + + kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false); + + if (!kmb_i2s->active) { + writel(0, kmb_i2s->i2s_base + CER); + writel(0, kmb_i2s->i2s_base + IER); + } +} + +static void kmb_disable_clk(void *clk) +{ + clk_disable_unprepare(clk); +} + +static int kmb_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BC_FC: + kmb_i2s->clock_provider = false; + ret = 0; + break; + case SND_SOC_DAIFMT_BP_FP: + writel(CLOCK_PROVIDER_MODE, kmb_i2s->pss_base + I2S_GEN_CFG_0); + + ret = clk_prepare_enable(kmb_i2s->clk_i2s); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(kmb_i2s->dev, kmb_disable_clk, + kmb_i2s->clk_i2s); + if (ret) + return ret; + + kmb_i2s->clock_provider = true; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int kmb_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* Keep track of i2s activity before turn off + * the i2s interface + */ + kmb_i2s->active++; + kmb_i2s_start(kmb_i2s, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + kmb_i2s->active--; + if (kmb_i2s->use_pio) + kmb_i2s_stop(kmb_i2s, substream); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void kmb_i2s_config(struct kmb_i2s_info *kmb_i2s, int stream) +{ + struct i2s_clk_config_data *config = &kmb_i2s->config; + u32 ch_reg; + + kmb_i2s_disable_channels(kmb_i2s, stream); + + for (ch_reg = 0; ch_reg < config->chan_nr / 2; ch_reg++) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + writel(kmb_i2s->xfer_resolution, + kmb_i2s->i2s_base + TCR(ch_reg)); + + writel(kmb_i2s->fifo_th - 1, + kmb_i2s->i2s_base + TFCR(ch_reg)); + + writel(1, kmb_i2s->i2s_base + TER(ch_reg)); + } else { + writel(kmb_i2s->xfer_resolution, + kmb_i2s->i2s_base + RCR(ch_reg)); + + writel(kmb_i2s->fifo_th - 1, + kmb_i2s->i2s_base + RFCR(ch_reg)); + + writel(1, kmb_i2s->i2s_base + RER(ch_reg)); + } + } +} + +static int kmb_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + struct i2s_clk_config_data *config = &kmb_i2s->config; + u32 write_val; + int ret; + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + config->data_width = 16; + kmb_i2s->ccr = 0x00; + kmb_i2s->xfer_resolution = 0x02; + kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case SNDRV_PCM_FORMAT_S24_LE: + config->data_width = 32; + kmb_i2s->ccr = 0x14; + kmb_i2s->xfer_resolution = 0x05; + kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + kmb_i2s->iec958_fmt = true; + fallthrough; + case SNDRV_PCM_FORMAT_S32_LE: + config->data_width = 32; + kmb_i2s->ccr = 0x10; + kmb_i2s->xfer_resolution = 0x05; + kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt"); + return -EINVAL; + } + + config->chan_nr = params_channels(hw_params); + + switch (config->chan_nr) { + case 8: + case 4: + /* + * Platform is not capable of providing clocks for + * multi channel audio + */ + if (kmb_i2s->clock_provider) + return -EINVAL; + + write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) | + (config->data_width << DATA_WIDTH_CONFIG_BIT) | + TDM_OPERATION; + + writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0); + break; + case 2: + /* + * Platform is only capable of providing clocks need for + * 2 channel master mode + */ + if (!(kmb_i2s->clock_provider)) + return -EINVAL; + + write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) | + (config->data_width << DATA_WIDTH_CONFIG_BIT) | + CLOCK_PROVIDER_MODE | I2S_OPERATION; + + writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0); + break; + default: + dev_dbg(kmb_i2s->dev, "channel not supported\n"); + return -EINVAL; + } + + kmb_i2s_config(kmb_i2s, substream->stream); + + writel(kmb_i2s->ccr, kmb_i2s->i2s_base + CCR); + + config->sample_rate = params_rate(hw_params); + + if (kmb_i2s->clock_provider) { + /* Only 2 ch supported in Master mode */ + u32 bitclk = config->sample_rate * config->data_width * 2; + + ret = clk_set_rate(kmb_i2s->clk_i2s, bitclk); + if (ret) { + dev_err(kmb_i2s->dev, + "Can't set I2S clock rate: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int kmb_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(1, kmb_i2s->i2s_base + TXFFR); + else + writel(1, kmb_i2s->i2s_base + RXFFR); + + return 0; +} + +static int kmb_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + struct snd_dmaengine_dai_dma_data *dma_data; + + if (kmb_i2s->use_pio) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &kmb_i2s->play_dma_data; + else + dma_data = &kmb_i2s->capture_dma_data; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + return 0; +} + +static int kmb_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ + if (kmb_i2s->use_pio) + kmb_i2s_clear_irqs(kmb_i2s, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(0, kmb_i2s->i2s_base + ITER); + else + writel(0, kmb_i2s->i2s_base + IRER); + + if (kmb_i2s->use_pio) + kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false); + else + kmb_i2s_disable_dma(kmb_i2s, substream->stream); + + if (!kmb_i2s->active) { + writel(0, kmb_i2s->i2s_base + CER); + writel(0, kmb_i2s->i2s_base + IER); + } + + return 0; +} + +static const struct snd_soc_dai_ops kmb_dai_ops = { + .startup = kmb_dai_startup, + .trigger = kmb_dai_trigger, + .hw_params = kmb_dai_hw_params, + .hw_free = kmb_dai_hw_free, + .prepare = kmb_dai_prepare, + .set_fmt = kmb_set_dai_fmt, +}; + +static struct snd_soc_dai_driver intel_kmb_hdmi_dai[] = { + { + .name = "intel_kmb_hdmi_i2s", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE), + }, + .ops = &kmb_dai_ops, + .probe = kmb_probe, + }, +}; + +static struct snd_soc_dai_driver intel_kmb_i2s_dai[] = { + { + .name = "intel_kmb_i2s", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = (SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S16_LE), + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = (SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S16_LE), + }, + .ops = &kmb_dai_ops, + .probe = kmb_probe, + }, +}; + +static struct snd_soc_dai_driver intel_kmb_tdm_dai[] = { + { + .name = "intel_kmb_tdm", + .capture = { + .channels_min = 4, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = (SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S16_LE), + }, + .ops = &kmb_dai_ops, + .probe = kmb_probe, + }, +}; + +static const struct of_device_id kmb_plat_of_match[] = { + { .compatible = "intel,keembay-i2s", .data = &intel_kmb_i2s_dai}, + { .compatible = "intel,keembay-hdmi-i2s", .data = &intel_kmb_hdmi_dai}, + { .compatible = "intel,keembay-tdm", .data = &intel_kmb_tdm_dai}, + {} +}; + +static int kmb_plat_dai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_dai_driver *kmb_i2s_dai; + const struct of_device_id *match; + struct device *dev = &pdev->dev; + struct kmb_i2s_info *kmb_i2s; + struct resource *res; + int ret, irq; + u32 comp1_reg; + + kmb_i2s = devm_kzalloc(dev, sizeof(*kmb_i2s), GFP_KERNEL); + if (!kmb_i2s) + return -ENOMEM; + + kmb_i2s_dai = devm_kzalloc(dev, sizeof(*kmb_i2s_dai), GFP_KERNEL); + if (!kmb_i2s_dai) + return -ENOMEM; + + match = of_match_device(kmb_plat_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + return -ENODEV; + } + kmb_i2s_dai = (struct snd_soc_dai_driver *) match->data; + + /* Prepare the related clocks */ + kmb_i2s->clk_apb = devm_clk_get(dev, "apb_clk"); + if (IS_ERR(kmb_i2s->clk_apb)) { + dev_err(dev, "Failed to get apb clock\n"); + return PTR_ERR(kmb_i2s->clk_apb); + } + + ret = clk_prepare_enable(kmb_i2s->clk_apb); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, kmb_disable_clk, kmb_i2s->clk_apb); + if (ret) { + dev_err(dev, "Failed to add clk_apb reset action\n"); + return ret; + } + + kmb_i2s->clk_i2s = devm_clk_get(dev, "osc"); + if (IS_ERR(kmb_i2s->clk_i2s)) { + dev_err(dev, "Failed to get osc clock\n"); + return PTR_ERR(kmb_i2s->clk_i2s); + } + + kmb_i2s->i2s_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(kmb_i2s->i2s_base)) + return PTR_ERR(kmb_i2s->i2s_base); + + kmb_i2s->pss_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(kmb_i2s->pss_base)) + return PTR_ERR(kmb_i2s->pss_base); + + kmb_i2s->dev = &pdev->dev; + + comp1_reg = readl(kmb_i2s->i2s_base + I2S_COMP_PARAM_1); + + kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2; + + kmb_i2s->use_pio = !(of_property_read_bool(np, "dmas")); + + if (kmb_i2s->use_pio) { + irq = platform_get_irq_optional(pdev, 0); + if (irq > 0) { + ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0, + pdev->name, kmb_i2s); + if (ret < 0) { + dev_err(dev, "failed to request irq\n"); + return ret; + } + } + ret = devm_snd_soc_register_component(dev, &kmb_component, + kmb_i2s_dai, 1); + } else { + kmb_i2s->play_dma_data.addr = res->start + I2S_TXDMA; + kmb_i2s->capture_dma_data.addr = res->start + I2S_RXDMA; + ret = snd_dmaengine_pcm_register(&pdev->dev, + NULL, 0); + if (ret) { + dev_err(&pdev->dev, "could not register dmaengine: %d\n", + ret); + return ret; + } + ret = devm_snd_soc_register_component(dev, &kmb_component_dma, + kmb_i2s_dai, 1); + } + + if (ret) { + dev_err(dev, "not able to register dai\n"); + return ret; + } + + /* To ensure none of the channels are enabled at boot up */ + kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK); + kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE); + + dev_set_drvdata(dev, kmb_i2s); + + return ret; +} + +static struct platform_driver kmb_plat_dai_driver = { + .driver = { + .name = "kmb-plat-dai", + .of_match_table = kmb_plat_of_match, + }, + .probe = kmb_plat_dai_probe, +}; + +module_platform_driver(kmb_plat_dai_driver); + +MODULE_DESCRIPTION("ASoC Intel KeemBay Platform driver"); +MODULE_AUTHOR("Sia Jee Heng <jee.heng.sia@intel.com>"); +MODULE_AUTHOR("Sit, Michael Wei Hong <michael.wei.hong.sit@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:kmb_platform"); diff --git a/sound/soc/intel/keembay/kmb_platform.h b/sound/soc/intel/keembay/kmb_platform.h new file mode 100644 index 000000000000..29be2cd84ddb --- /dev/null +++ b/sound/soc/intel/keembay/kmb_platform.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Intel KeemBay Platform driver + * + * Copyright (C) 2020 Intel Corporation. + * + */ + +#ifndef KMB_PLATFORM_H_ +#define KMB_PLATFORM_H_ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/types.h> +#include <sound/dmaengine_pcm.h> + +/* Register values with reference to KMB databook v1.1 */ +/* common register for all channel */ +#define IER 0x000 +#define IRER 0x004 +#define ITER 0x008 +#define CER 0x00C +#define CCR 0x010 +#define RXFFR 0x014 +#define TXFFR 0x018 + +/* Interrupt status register fields */ +#define ISR_TXFO BIT(5) +#define ISR_TXFE BIT(4) +#define ISR_RXFO BIT(1) +#define ISR_RXDA BIT(0) + +/* I2S Tx Rx Registers for all channels */ +#define LRBR_LTHR(x) (0x40 * (x) + 0x020) +#define RRBR_RTHR(x) (0x40 * (x) + 0x024) +#define RER(x) (0x40 * (x) + 0x028) +#define TER(x) (0x40 * (x) + 0x02C) +#define RCR(x) (0x40 * (x) + 0x030) +#define TCR(x) (0x40 * (x) + 0x034) +#define ISR(x) (0x40 * (x) + 0x038) +#define IMR(x) (0x40 * (x) + 0x03C) +#define ROR(x) (0x40 * (x) + 0x040) +#define TOR(x) (0x40 * (x) + 0x044) +#define RFCR(x) (0x40 * (x) + 0x048) +#define TFCR(x) (0x40 * (x) + 0x04C) +#define RFF(x) (0x40 * (x) + 0x050) +#define TFF(x) (0x40 * (x) + 0x054) + +/* I2S COMP Registers */ +#define I2S_COMP_PARAM_2 0x01F0 +#define I2S_COMP_PARAM_1 0x01F4 +#define I2S_COMP_VERSION 0x01F8 +#define I2S_COMP_TYPE 0x01FC + +/* PSS_GEN_CTRL_I2S_GEN_CFG_0 Registers */ +#define I2S_GEN_CFG_0 0x000 +#define PSS_CPR_RST_EN 0x010 +#define PSS_CPR_RST_SET 0x014 +#define PSS_CPR_CLK_CLR 0x000 +#define PSS_CPR_AUX_RST_EN 0x070 + +#define CLOCK_PROVIDER_MODE BIT(13) + +/* Interrupt Flag */ +#define TX_INT_FLAG GENMASK(5, 4) +#define RX_INT_FLAG GENMASK(1, 0) +/* + * Component parameter register fields - define the I2S block's + * configuration. + */ +#define COMP1_TX_WORDSIZE_3(r) FIELD_GET(GENMASK(27, 25), (r)) +#define COMP1_TX_WORDSIZE_2(r) FIELD_GET(GENMASK(24, 22), (r)) +#define COMP1_TX_WORDSIZE_1(r) FIELD_GET(GENMASK(21, 19), (r)) +#define COMP1_TX_WORDSIZE_0(r) FIELD_GET(GENMASK(18, 16), (r)) +#define COMP1_RX_ENABLED(r) FIELD_GET(BIT(6), (r)) +#define COMP1_TX_ENABLED(r) FIELD_GET(BIT(5), (r)) +#define COMP1_MODE_EN(r) FIELD_GET(BIT(4), (r)) +#define COMP1_APB_DATA_WIDTH(r) FIELD_GET(GENMASK(1, 0), (r)) +#define COMP2_RX_WORDSIZE_3(r) FIELD_GET(GENMASK(12, 10), (r)) +#define COMP2_RX_WORDSIZE_2(r) FIELD_GET(GENMASK(9, 7), (r)) +#define COMP2_RX_WORDSIZE_1(r) FIELD_GET(GENMASK(5, 3), (r)) +#define COMP2_RX_WORDSIZE_0(r) FIELD_GET(GENMASK(2, 0), (r)) + +/* Add 1 to the below registers to indicate the actual size */ +#define COMP1_TX_CHANNELS(r) (FIELD_GET(GENMASK(10, 9), (r)) + 1) +#define COMP1_RX_CHANNELS(r) (FIELD_GET(GENMASK(8, 7), (r)) + 1) +#define COMP1_FIFO_DEPTH(r) (FIELD_GET(GENMASK(3, 2), (r)) + 1) + +/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */ +#define COMP_MAX_WORDSIZE 8 /* 3 bits register width */ + +#define MAX_CHANNEL_NUM 8 +#define MIN_CHANNEL_NUM 2 +#define MAX_ISR 4 + +#define TWO_CHANNEL_SUPPORT 2 /* up to 2.0 */ +#define FOUR_CHANNEL_SUPPORT 4 /* up to 3.1 */ +#define SIX_CHANNEL_SUPPORT 6 /* up to 5.1 */ +#define EIGHT_CHANNEL_SUPPORT 8 /* up to 7.1 */ + +#define DWC_I2S_PLAY BIT(0) +#define DWC_I2S_RECORD BIT(1) +#define DW_I2S_CONSUMER BIT(2) +#define DW_I2S_PROVIDER BIT(3) + +#define I2S_RXDMA 0x01C0 +#define I2S_RRXDMA 0x01C4 +#define I2S_TXDMA 0x01C8 +#define I2S_RTXDMA 0x01CC +#define I2S_DMACR 0x0200 +#define I2S_DMAEN_RXBLOCK (1 << 16) +#define I2S_DMAEN_TXBLOCK (1 << 17) + +/* + * struct i2s_clk_config_data - represent i2s clk configuration data + * @chan_nr: number of channel + * @data_width: number of bits per sample (8/16/24/32 bit) + * @sample_rate: sampling frequency (8Khz, 16Khz, 48Khz) + */ +struct i2s_clk_config_data { + int chan_nr; + u32 data_width; + u32 sample_rate; +}; + +struct kmb_i2s_info { + void __iomem *i2s_base; + void __iomem *pss_base; + struct clk *clk_i2s; + struct clk *clk_apb; + int active; + unsigned int capability; + unsigned int i2s_reg_comp1; + unsigned int i2s_reg_comp2; + struct device *dev; + u32 ccr; + u32 xfer_resolution; + u32 fifo_th; + bool clock_provider; + /* data related to DMA transfers b/w i2s and DMAC */ + struct snd_dmaengine_dai_dma_data play_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; + + struct i2s_clk_config_data config; + int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); + + /* data related to PIO transfers */ + bool use_pio; + struct snd_pcm_substream *tx_substream; + struct snd_pcm_substream *rx_substream; + unsigned int tx_ptr; + unsigned int rx_ptr; + bool iec958_fmt; +}; + +#endif /* KMB_PLATFORM_H_ */ diff --git a/sound/soc/intel/skylake/Makefile b/sound/soc/intel/skylake/Makefile index 48544ff1a3e6..1c4649bccec5 100644 --- a/sound/soc/intel/skylake/Makefile +++ b/sound/soc/intel/skylake/Makefile @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0 +# SPDX-License-Identifier: GPL-2.0-only snd-soc-skl-objs := skl.o skl-pcm.o skl-nhlt.o skl-messages.o skl-topology.o \ skl-sst-ipc.o skl-sst-dsp.o cnl-sst-dsp.o skl-sst-cldma.o \ skl-sst.o bxt-sst.o cnl-sst.o skl-sst-utils.o @@ -7,7 +7,7 @@ ifdef CONFIG_DEBUG_FS snd-soc-skl-objs += skl-debug.o endif -obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += snd-soc-skl.o +obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE_COMMON) += snd-soc-skl.o #Skylake Clock device support snd-soc-skl-ssp-clk-objs := skl-ssp-clk.o diff --git a/sound/soc/intel/skylake/bxt-sst.c b/sound/soc/intel/skylake/bxt-sst.c index 92a82e6b5fe6..fd4fdcb95224 100644 --- a/sound/soc/intel/skylake/bxt-sst.c +++ b/sound/soc/intel/skylake/bxt-sst.c @@ -17,7 +17,6 @@ #include "skl.h" #define BXT_BASEFW_TIMEOUT 3000 -#define BXT_INIT_TIMEOUT 300 #define BXT_ROM_INIT_TIMEOUT 70 #define BXT_IPC_PURGE_FW 0x01004000 @@ -38,8 +37,6 @@ /* Delay before scheduling D0i3 entry */ #define BXT_D0I3_DELAY 5000 -#define BXT_FW_ROM_INIT_RETRY 3 - static unsigned int bxt_get_errorcode(struct sst_dsp *ctx) { return sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE); @@ -536,8 +533,6 @@ static struct sst_ops skl_ops = { .irq_handler = skl_dsp_sst_interrupt, .write = sst_shim32_write, .read = sst_shim32_read, - .ram_read = sst_memcpy_fromio_32, - .ram_write = sst_memcpy_toio_32, .free = skl_dsp_free, }; diff --git a/sound/soc/intel/skylake/cnl-sst-dsp.h b/sound/soc/intel/skylake/cnl-sst-dsp.h index 7bd4d2a8fdfa..d3cf4bd1a070 100644 --- a/sound/soc/intel/skylake/cnl-sst-dsp.h +++ b/sound/soc/intel/skylake/cnl-sst-dsp.h @@ -82,8 +82,8 @@ struct sst_generic_ipc; #define CNL_ADSPCS_CPA_SHIFT 24 #define CNL_ADSPCS_CPA(x) (x << CNL_ADSPCS_CPA_SHIFT) -int cnl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core); -int cnl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core); +int cnl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask); +int cnl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask); irqreturn_t cnl_dsp_sst_interrupt(int irq, void *dev_id); void cnl_dsp_free(struct sst_dsp *dsp); diff --git a/sound/soc/intel/skylake/cnl-sst.c b/sound/soc/intel/skylake/cnl-sst.c index 4f64f097e9ae..1275c149acc0 100644 --- a/sound/soc/intel/skylake/cnl-sst.c +++ b/sound/soc/intel/skylake/cnl-sst.c @@ -57,18 +57,34 @@ static int cnl_prepare_fw(struct sst_dsp *ctx, const void *fwdata, u32 fwsize) ctx->dsp_ops.stream_tag = stream_tag; memcpy(ctx->dmab.area, fwdata, fwsize); + ret = skl_dsp_core_power_up(ctx, SKL_DSP_CORE0_MASK); + if (ret < 0) { + dev_err(ctx->dev, "dsp core0 power up failed\n"); + ret = -EIO; + goto base_fw_load_failed; + } + /* purge FW request */ sst_dsp_shim_write(ctx, CNL_ADSP_REG_HIPCIDR, CNL_ADSP_REG_HIPCIDR_BUSY | (CNL_IPC_PURGE | ((stream_tag - 1) << CNL_ROM_CTRL_DMA_ID))); - ret = cnl_dsp_enable_core(ctx, SKL_DSP_CORE0_MASK); + ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "dsp boot core failed ret: %d\n", ret); + dev_err(ctx->dev, "Start dsp core failed ret: %d\n", ret); ret = -EIO; goto base_fw_load_failed; } + ret = sst_dsp_register_poll(ctx, CNL_ADSP_REG_HIPCIDA, + CNL_ADSP_REG_HIPCIDA_DONE, + CNL_ADSP_REG_HIPCIDA_DONE, + BXT_INIT_TIMEOUT, "HIPCIDA Done"); + if (ret < 0) { + dev_err(ctx->dev, "timeout for purge request: %d\n", ret); + goto base_fw_load_failed; + } + /* enable interrupt */ cnl_ipc_int_enable(ctx); cnl_ipc_op_int_enable(ctx); @@ -109,7 +125,7 @@ static int cnl_load_base_firmware(struct sst_dsp *ctx) { struct firmware stripped_fw; struct skl_dev *cnl = ctx->thread_context; - int ret; + int ret, i; if (!ctx->fw) { ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); @@ -131,12 +147,16 @@ static int cnl_load_base_firmware(struct sst_dsp *ctx) stripped_fw.size = ctx->fw->size; skl_dsp_strip_extended_manifest(&stripped_fw); - ret = cnl_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); - if (ret < 0) { - dev_err(ctx->dev, "prepare firmware failed: %d\n", ret); - goto cnl_load_base_firmware_failed; + for (i = 0; i < BXT_FW_ROM_INIT_RETRY; i++) { + ret = cnl_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); + if (!ret) + break; + dev_dbg(ctx->dev, "prepare firmware failed: %d\n", ret); } + if (ret < 0) + goto cnl_load_base_firmware_failed; + ret = sst_transfer_fw_host_dma(ctx); if (ret < 0) { dev_err(ctx->dev, "transfer firmware failed: %d\n", ret); @@ -158,6 +178,7 @@ static int cnl_load_base_firmware(struct sst_dsp *ctx) return 0; cnl_load_base_firmware_failed: + dev_err(ctx->dev, "firmware load failed: %d\n", ret); release_firmware(ctx->fw); ctx->fw = NULL; @@ -203,6 +224,7 @@ static int cnl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) "dsp boot timeout, status=%#x error=%#x\n", sst_dsp_shim_read(ctx, CNL_ADSP_FW_STATUS), sst_dsp_shim_read(ctx, CNL_ADSP_ERROR_CODE)); + ret = -ETIMEDOUT; goto err; } } else { @@ -279,8 +301,6 @@ static struct sst_ops cnl_ops = { .irq_handler = cnl_dsp_sst_interrupt, .write = sst_shim32_write, .read = sst_shim32_read, - .ram_read = sst_memcpy_fromio_32, - .ram_write = sst_memcpy_toio_32, .free = cnl_dsp_free, }; @@ -292,7 +312,7 @@ static struct sst_ops cnl_ops = { static irqreturn_t cnl_dsp_irq_thread_handler(int irq, void *context) { struct sst_dsp *dsp = context; - struct skl_dev *cnl = sst_dsp_get_thread_context(dsp); + struct skl_dev *cnl = dsp->thread_context; struct sst_generic_ipc *ipc = &cnl->ipc; struct skl_ipc_header header = {0}; u32 hipcida, hipctdr, hipctdd; diff --git a/sound/soc/intel/skylake/skl-i2s.h b/sound/soc/intel/skylake/skl-i2s.h index d7c15873c0d4..dfce91e11be1 100644 --- a/sound/soc/intel/skylake/skl-i2s.h +++ b/sound/soc/intel/skylake/skl-i2s.h @@ -46,7 +46,7 @@ struct skl_i2s_config_mclk { struct skl_i2s_config_mclk_ext { u32 mdivctrl; u32 mdivr_count; - u32 mdivr[0]; + u32 mdivr[]; } __packed; struct skl_i2s_config_blob_signature { diff --git a/sound/soc/intel/skylake/skl-messages.c b/sound/soc/intel/skylake/skl-messages.c index 476ef1897961..eaad180af42e 100644 --- a/sound/soc/intel/skylake/skl-messages.c +++ b/sound/soc/intel/skylake/skl-messages.c @@ -472,6 +472,75 @@ static void skl_set_base_module_format(struct skl_dev *skl, base_cfg->is_pages = res->is_pages; } +static void fill_pin_params(struct skl_audio_data_format *pin_fmt, + struct skl_module_fmt *format) +{ + pin_fmt->number_of_channels = format->channels; + pin_fmt->s_freq = format->s_freq; + pin_fmt->bit_depth = format->bit_depth; + pin_fmt->valid_bit_depth = format->valid_bit_depth; + pin_fmt->ch_cfg = format->ch_cfg; + pin_fmt->sample_type = format->sample_type; + pin_fmt->channel_map = format->ch_map; + pin_fmt->interleaving = format->interleaving_style; +} + +/* + * Any module configuration begins with a base module configuration but + * can be followed by a generic extension containing audio format for all + * module's pins that are in use. + */ +static void skl_set_base_ext_module_format(struct skl_dev *skl, + struct skl_module_cfg *mconfig, + struct skl_base_cfg_ext *base_cfg_ext) +{ + struct skl_module *module = mconfig->module; + struct skl_module_pin_resources *pin_res; + struct skl_module_iface *fmt = &module->formats[mconfig->fmt_idx]; + struct skl_module_res *res = &module->resources[mconfig->res_idx]; + struct skl_module_fmt *format; + struct skl_pin_format *pin_fmt; + char *params; + int i; + + base_cfg_ext->nr_input_pins = res->nr_input_pins; + base_cfg_ext->nr_output_pins = res->nr_output_pins; + base_cfg_ext->priv_param_length = + mconfig->formats_config[SKL_PARAM_INIT].caps_size; + + for (i = 0; i < res->nr_input_pins; i++) { + pin_res = &res->input[i]; + pin_fmt = &base_cfg_ext->pins_fmt[i]; + + pin_fmt->pin_idx = pin_res->pin_index; + pin_fmt->buf_size = pin_res->buf_size; + + format = &fmt->inputs[pin_res->pin_index].fmt; + fill_pin_params(&pin_fmt->audio_fmt, format); + } + + for (i = 0; i < res->nr_output_pins; i++) { + pin_res = &res->output[i]; + pin_fmt = &base_cfg_ext->pins_fmt[res->nr_input_pins + i]; + + pin_fmt->pin_idx = pin_res->pin_index; + pin_fmt->buf_size = pin_res->buf_size; + + format = &fmt->outputs[pin_res->pin_index].fmt; + fill_pin_params(&pin_fmt->audio_fmt, format); + } + + if (!base_cfg_ext->priv_param_length) + return; + + params = (char *)base_cfg_ext + sizeof(struct skl_base_cfg_ext); + params += (base_cfg_ext->nr_input_pins + base_cfg_ext->nr_output_pins) * + sizeof(struct skl_pin_format); + + memcpy(params, mconfig->formats_config[SKL_PARAM_INIT].caps, + mconfig->formats_config[SKL_PARAM_INIT].caps_size); +} + /* * Copies copier capabilities into copier module and updates copier module * config size. @@ -479,15 +548,15 @@ static void skl_set_base_module_format(struct skl_dev *skl, static void skl_copy_copier_caps(struct skl_module_cfg *mconfig, struct skl_cpr_cfg *cpr_mconfig) { - if (mconfig->formats_config.caps_size == 0) + if (mconfig->formats_config[SKL_PARAM_INIT].caps_size == 0) return; memcpy(cpr_mconfig->gtw_cfg.config_data, - mconfig->formats_config.caps, - mconfig->formats_config.caps_size); + mconfig->formats_config[SKL_PARAM_INIT].caps, + mconfig->formats_config[SKL_PARAM_INIT].caps_size); cpr_mconfig->gtw_cfg.config_length = - (mconfig->formats_config.caps_size) / 4; + (mconfig->formats_config[SKL_PARAM_INIT].caps_size) / 4; } #define SKL_NON_GATEWAY_CPR_NODE_ID 0xFFFFFFFF @@ -738,28 +807,6 @@ static void skl_set_copier_format(struct skl_dev *skl, } /* - * Algo module are DSP pre processing modules. Algo module take base module - * configuration and params - */ - -static void skl_set_algo_format(struct skl_dev *skl, - struct skl_module_cfg *mconfig, - struct skl_algo_cfg *algo_mcfg) -{ - struct skl_base_cfg *base_cfg = (struct skl_base_cfg *)algo_mcfg; - - skl_set_base_module_format(skl, mconfig, base_cfg); - - if (mconfig->formats_config.caps_size == 0) - return; - - memcpy(algo_mcfg->params, - mconfig->formats_config.caps, - mconfig->formats_config.caps_size); - -} - -/* * Mic select module allows selecting one or many input channels, thus * acting as a demux. * @@ -781,12 +828,14 @@ static void skl_set_base_outfmt_format(struct skl_dev *skl, static u16 skl_get_module_param_size(struct skl_dev *skl, struct skl_module_cfg *mconfig) { + struct skl_module_res *res; + struct skl_module *module = mconfig->module; u16 param_size; switch (mconfig->m_type) { case SKL_MODULE_TYPE_COPIER: param_size = sizeof(struct skl_cpr_cfg); - param_size += mconfig->formats_config.caps_size; + param_size += mconfig->formats_config[SKL_PARAM_INIT].caps_size; return param_size; case SKL_MODULE_TYPE_SRCINT: @@ -795,22 +844,24 @@ static u16 skl_get_module_param_size(struct skl_dev *skl, case SKL_MODULE_TYPE_UPDWMIX: return sizeof(struct skl_up_down_mixer_cfg); - case SKL_MODULE_TYPE_ALGO: - param_size = sizeof(struct skl_base_cfg); - param_size += mconfig->formats_config.caps_size; - return param_size; - case SKL_MODULE_TYPE_BASE_OUTFMT: case SKL_MODULE_TYPE_MIC_SELECT: - case SKL_MODULE_TYPE_KPB: return sizeof(struct skl_base_outfmt_cfg); - default: - /* - * return only base cfg when no specific module type is - * specified - */ + case SKL_MODULE_TYPE_MIXER: + case SKL_MODULE_TYPE_KPB: return sizeof(struct skl_base_cfg); + + case SKL_MODULE_TYPE_ALGO: + default: + res = &module->resources[mconfig->res_idx]; + + param_size = sizeof(struct skl_base_cfg) + sizeof(struct skl_base_cfg_ext); + param_size += (res->nr_input_pins + res->nr_output_pins) * + sizeof(struct skl_pin_format); + param_size += mconfig->formats_config[SKL_PARAM_INIT].caps_size; + + return param_size; } return 0; @@ -851,20 +902,23 @@ static int skl_set_module_format(struct skl_dev *skl, skl_set_updown_mixer_format(skl, module_config, *param_data); break; - case SKL_MODULE_TYPE_ALGO: - skl_set_algo_format(skl, module_config, *param_data); - break; - case SKL_MODULE_TYPE_BASE_OUTFMT: case SKL_MODULE_TYPE_MIC_SELECT: - case SKL_MODULE_TYPE_KPB: skl_set_base_outfmt_format(skl, module_config, *param_data); break; - default: + case SKL_MODULE_TYPE_MIXER: + case SKL_MODULE_TYPE_KPB: skl_set_base_module_format(skl, module_config, *param_data); break; + case SKL_MODULE_TYPE_ALGO: + default: + skl_set_base_module_format(skl, module_config, *param_data); + skl_set_base_ext_module_format(skl, module_config, + *param_data + + sizeof(struct skl_base_cfg)); + break; } dev_dbg(skl->dev, "Module type=%d id=%d config size: %d bytes\n", @@ -1085,19 +1139,6 @@ int skl_unbind_modules(struct skl_dev *skl, return ret; } -static void fill_pin_params(struct skl_audio_data_format *pin_fmt, - struct skl_module_fmt *format) -{ - pin_fmt->number_of_channels = format->channels; - pin_fmt->s_freq = format->s_freq; - pin_fmt->bit_depth = format->bit_depth; - pin_fmt->valid_bit_depth = format->valid_bit_depth; - pin_fmt->ch_cfg = format->ch_cfg; - pin_fmt->sample_type = format->sample_type; - pin_fmt->channel_map = format->ch_map; - pin_fmt->interleaving = format->interleaving_style; -} - #define CPR_SINK_FMT_PARAM_ID 2 /* diff --git a/sound/soc/intel/skylake/skl-nhlt.c b/sound/soc/intel/skylake/skl-nhlt.c index 19f328d71f24..e617b4c335a4 100644 --- a/sound/soc/intel/skylake/skl-nhlt.c +++ b/sound/soc/intel/skylake/skl-nhlt.c @@ -13,108 +13,6 @@ #include "skl.h" #include "skl-i2s.h" -static struct nhlt_specific_cfg *skl_get_specific_cfg( - struct device *dev, struct nhlt_fmt *fmt, - u8 no_ch, u32 rate, u16 bps, u8 linktype) -{ - struct nhlt_specific_cfg *sp_config; - struct wav_fmt *wfmt; - struct nhlt_fmt_cfg *fmt_config = fmt->fmt_config; - int i; - - dev_dbg(dev, "Format count =%d\n", fmt->fmt_count); - - for (i = 0; i < fmt->fmt_count; i++) { - wfmt = &fmt_config->fmt_ext.fmt; - dev_dbg(dev, "ch=%d fmt=%d s_rate=%d\n", wfmt->channels, - wfmt->bits_per_sample, wfmt->samples_per_sec); - if (wfmt->channels == no_ch && wfmt->bits_per_sample == bps) { - /* - * if link type is dmic ignore rate check as the blob is - * generic for all rates - */ - sp_config = &fmt_config->config; - if (linktype == NHLT_LINK_DMIC) - return sp_config; - - if (wfmt->samples_per_sec == rate) - return sp_config; - } - - fmt_config = (struct nhlt_fmt_cfg *)(fmt_config->config.caps + - fmt_config->config.size); - } - - return NULL; -} - -static void dump_config(struct device *dev, u32 instance_id, u8 linktype, - u8 s_fmt, u8 num_channels, u32 s_rate, u8 dirn, u16 bps) -{ - dev_dbg(dev, "Input configuration\n"); - dev_dbg(dev, "ch=%d fmt=%d s_rate=%d\n", num_channels, s_fmt, s_rate); - dev_dbg(dev, "vbus_id=%d link_type=%d\n", instance_id, linktype); - dev_dbg(dev, "bits_per_sample=%d\n", bps); -} - -static bool skl_check_ep_match(struct device *dev, struct nhlt_endpoint *epnt, - u32 instance_id, u8 link_type, u8 dirn, u8 dev_type) -{ - dev_dbg(dev, "vbus_id=%d link_type=%d dir=%d dev_type = %d\n", - epnt->virtual_bus_id, epnt->linktype, - epnt->direction, epnt->device_type); - - if ((epnt->virtual_bus_id == instance_id) && - (epnt->linktype == link_type) && - (epnt->direction == dirn)) { - /* do not check dev_type for DMIC link type */ - if (epnt->linktype == NHLT_LINK_DMIC) - return true; - - if (epnt->device_type == dev_type) - return true; - } - - return false; -} - -struct nhlt_specific_cfg -*skl_get_ep_blob(struct skl_dev *skl, u32 instance, u8 link_type, - u8 s_fmt, u8 num_ch, u32 s_rate, - u8 dirn, u8 dev_type) -{ - struct nhlt_fmt *fmt; - struct nhlt_endpoint *epnt; - struct hdac_bus *bus = skl_to_bus(skl); - struct device *dev = bus->dev; - struct nhlt_specific_cfg *sp_config; - struct nhlt_acpi_table *nhlt = skl->nhlt; - u16 bps = (s_fmt == 16) ? 16 : 32; - u8 j; - - dump_config(dev, instance, link_type, s_fmt, num_ch, s_rate, dirn, bps); - - epnt = (struct nhlt_endpoint *)nhlt->desc; - - dev_dbg(dev, "endpoint count =%d\n", nhlt->endpoint_count); - - for (j = 0; j < nhlt->endpoint_count; j++) { - if (skl_check_ep_match(dev, epnt, instance, link_type, - dirn, dev_type)) { - fmt = (struct nhlt_fmt *)(epnt->config.caps + - epnt->config.size); - sp_config = skl_get_specific_cfg(dev, fmt, num_ch, - s_rate, bps, link_type); - if (sp_config) - return sp_config; - } - - epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); - } - - return NULL; -} - static void skl_nhlt_trim_space(char *trim) { char *s = trim; @@ -149,8 +47,8 @@ int skl_nhlt_update_topology_bin(struct skl_dev *skl) return 0; } -static ssize_t skl_nhlt_platform_id_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t platform_id_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct pci_dev *pci = to_pci_dev(dev); struct hdac_bus *bus = pci_get_drvdata(pci); @@ -163,10 +61,10 @@ static ssize_t skl_nhlt_platform_id_show(struct device *dev, nhlt->header.oem_revision); skl_nhlt_trim_space(platform_id); - return sprintf(buf, "%s\n", platform_id); + return sysfs_emit(buf, "%s\n", platform_id); } -static DEVICE_ATTR(platform_id, 0444, skl_nhlt_platform_id_show, NULL); +static DEVICE_ATTR_RO(platform_id); int skl_nhlt_create_sysfs(struct skl_dev *skl) { @@ -182,7 +80,8 @@ void skl_nhlt_remove_sysfs(struct skl_dev *skl) { struct device *dev = &skl->pci->dev; - sysfs_remove_file(&dev->kobj, &dev_attr_platform_id.attr); + if (skl->nhlt) + sysfs_remove_file(&dev->kobj, &dev_attr_platform_id.attr); } /* @@ -199,8 +98,7 @@ static void skl_get_ssp_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks, struct skl_ssp_clk *sclk, *sclkfs; struct nhlt_fmt_cfg *fmt_cfg; struct wav_fmt_ext *wav_fmt; - unsigned long rate = 0; - bool present = false; + unsigned long rate; int rate_index = 0; u16 channels, bps; u8 clk_src; @@ -213,9 +111,12 @@ static void skl_get_ssp_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks, if (fmt->fmt_count == 0) return; + fmt_cfg = (struct nhlt_fmt_cfg *)fmt->fmt_config; for (i = 0; i < fmt->fmt_count; i++) { - fmt_cfg = &fmt->fmt_config[i]; - wav_fmt = &fmt_cfg->fmt_ext; + struct nhlt_fmt_cfg *saved_fmt_cfg = fmt_cfg; + bool present = false; + + wav_fmt = &saved_fmt_cfg->fmt_ext; channels = wav_fmt->fmt.channels; bps = wav_fmt->fmt.bits_per_sample; @@ -233,12 +134,18 @@ static void skl_get_ssp_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks, * derive the rate. */ for (j = i; j < fmt->fmt_count; j++) { - fmt_cfg = &fmt->fmt_config[j]; - wav_fmt = &fmt_cfg->fmt_ext; + struct nhlt_fmt_cfg *tmp_fmt_cfg = fmt_cfg; + + wav_fmt = &tmp_fmt_cfg->fmt_ext; if ((fs == wav_fmt->fmt.samples_per_sec) && - (bps == wav_fmt->fmt.bits_per_sample)) + (bps == wav_fmt->fmt.bits_per_sample)) { channels = max_t(u16, channels, wav_fmt->fmt.channels); + saved_fmt_cfg = tmp_fmt_cfg; + } + /* Move to the next nhlt_fmt_cfg */ + tmp_fmt_cfg = (struct nhlt_fmt_cfg *)(tmp_fmt_cfg->config.caps + + tmp_fmt_cfg->config.size); } rate = channels * bps * fs; @@ -254,8 +161,11 @@ static void skl_get_ssp_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks, /* Fill rate and parent for sclk/sclkfs */ if (!present) { + struct nhlt_fmt_cfg *first_fmt_cfg; + + first_fmt_cfg = (struct nhlt_fmt_cfg *)fmt->fmt_config; i2s_config_ext = (struct skl_i2s_config_blob_ext *) - fmt->fmt_config[0].config.caps; + first_fmt_cfg->config.caps; /* MCLK Divider Source Select */ if (is_legacy_blob(i2s_config_ext->hdr.sig)) { @@ -269,6 +179,9 @@ static void skl_get_ssp_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks, parent = skl_get_parent_clk(clk_src); + /* Move to the next nhlt_fmt_cfg */ + fmt_cfg = (struct nhlt_fmt_cfg *)(fmt_cfg->config.caps + + fmt_cfg->config.size); /* * Do not copy the config data if there is no parent * clock available for this clock source select @@ -277,9 +190,9 @@ static void skl_get_ssp_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks, continue; sclk[id].rate_cfg[rate_index].rate = rate; - sclk[id].rate_cfg[rate_index].config = fmt_cfg; + sclk[id].rate_cfg[rate_index].config = saved_fmt_cfg; sclkfs[id].rate_cfg[rate_index].rate = rate; - sclkfs[id].rate_cfg[rate_index].config = fmt_cfg; + sclkfs[id].rate_cfg[rate_index].config = saved_fmt_cfg; sclk[id].parent_name = parent->name; sclkfs[id].parent_name = parent->name; @@ -293,13 +206,13 @@ static void skl_get_mclk(struct skl_dev *skl, struct skl_ssp_clk *mclk, { struct skl_i2s_config_blob_ext *i2s_config_ext; struct skl_i2s_config_blob_legacy *i2s_config; - struct nhlt_specific_cfg *fmt_cfg; + struct nhlt_fmt_cfg *fmt_cfg; struct skl_clk_parent_src *parent; u32 clkdiv, div_ratio; u8 clk_src; - fmt_cfg = &fmt->fmt_config[0].config; - i2s_config_ext = (struct skl_i2s_config_blob_ext *)fmt_cfg->caps; + fmt_cfg = (struct nhlt_fmt_cfg *)fmt->fmt_config; + i2s_config_ext = (struct skl_i2s_config_blob_ext *)fmt_cfg->config.caps; /* MCLK Divider Source Select and divider */ if (is_legacy_blob(i2s_config_ext->hdr.sig)) { @@ -328,7 +241,7 @@ static void skl_get_mclk(struct skl_dev *skl, struct skl_ssp_clk *mclk, return; mclk[id].rate_cfg[0].rate = parent->rate/div_ratio; - mclk[id].rate_cfg[0].config = &fmt->fmt_config[0]; + mclk[id].rate_cfg[0].config = fmt_cfg; mclk[id].parent_name = parent->name; } diff --git a/sound/soc/intel/skylake/skl-pcm.c b/sound/soc/intel/skylake/skl-pcm.c index b99509675d29..1015716f9336 100644 --- a/sound/soc/intel/skylake/skl-pcm.c +++ b/sound/soc/intel/skylake/skl-pcm.c @@ -112,10 +112,7 @@ static void skl_set_suspend_active(struct snd_pcm_substream *substream, struct snd_soc_dapm_widget *w; struct skl_dev *skl = bus_to_skl(bus); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; + w = snd_soc_dai_get_widget(dai, substream->stream); if (w->ignore_suspend && enable) skl->supend_active++; @@ -278,7 +275,7 @@ static int skl_pcm_prepare(struct snd_pcm_substream *substream, * calls prepare another time, reset the FW pipe to clean state */ if (mconfig && - (substream->runtime->status->state == SNDRV_PCM_STATE_XRUN || + (substream->runtime->state == SNDRV_PCM_STATE_XRUN || mconfig->pipe->state == SKL_PIPE_CREATED || mconfig->pipe->state == SKL_PIPE_PAUSED)) { @@ -320,6 +317,7 @@ static int skl_pcm_hw_params(struct snd_pcm_substream *substream, dev_dbg(dai->dev, "dma_id=%d\n", dma_id); p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.s_cont = snd_pcm_format_physical_width(params_format(params)); p_params.ch = params_channels(params); p_params.s_freq = params_rate(params); p_params.host_dma_id = dma_id; @@ -408,6 +406,7 @@ static int skl_be_hw_params(struct snd_pcm_substream *substream, struct skl_pipe_params p_params = {0}; p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.s_cont = snd_pcm_format_physical_width(params_format(params)); p_params.ch = params_channels(params); p_params.s_freq = params_rate(params); p_params.stream = substream->stream; @@ -475,10 +474,7 @@ static int skl_pcm_trigger(struct snd_pcm_substream *substream, int cmd, if (!mconfig) return -EIO; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; + w = snd_soc_dai_get_widget(dai, substream->stream); switch (cmd) { case SNDRV_PCM_TRIGGER_RESUME: @@ -494,7 +490,7 @@ static int skl_pcm_trigger(struct snd_pcm_substream *substream, int cmd, stream->lpib); snd_hdac_ext_stream_set_lpib(stream, stream->lpib); } - /* fall through */ + fallthrough; case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: @@ -508,7 +504,6 @@ static int skl_pcm_trigger(struct snd_pcm_substream *substream, int cmd, if (ret < 0) return ret; return skl_run_pipe(skl, mconfig->pipe); - break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: @@ -550,8 +545,8 @@ static int skl_link_hw_params(struct snd_pcm_substream *substream, { struct hdac_bus *bus = dev_get_drvdata(dai->dev); struct hdac_ext_stream *link_dev; - struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct skl_pipe_params p_params = {0}; struct hdac_ext_link *link; int stream_tag; @@ -569,13 +564,11 @@ static int skl_link_hw_params(struct snd_pcm_substream *substream, stream_tag = hdac_stream(link_dev)->stream_tag; - /* set the stream tag in the codec dai dma params */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0); - else - snd_soc_dai_set_tdm_slot(codec_dai, 0, stream_tag, 0, 0); + /* set the hdac_stream in the codec dai */ + snd_soc_dai_set_stream(codec_dai, hdac_stream(link_dev), substream->stream); p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.s_cont = snd_pcm_format_physical_width(params_format(params)); p_params.ch = params_channels(params); p_params.s_freq = params_rate(params); p_params.stream = substream->stream; @@ -600,7 +593,7 @@ static int skl_link_pcm_prepare(struct snd_pcm_substream *substream, /* In case of XRUN recovery, reset the FW pipe to clean state */ mconfig = skl_tplg_be_get_cpr_module(dai, substream->stream); if (mconfig && !mconfig->pipe->passthru && - (substream->runtime->status->state == SNDRV_PCM_STATE_XRUN)) + (substream->runtime->state == SNDRV_PCM_STATE_XRUN)) skl_reset_pipe(skl, mconfig->pipe); return 0; @@ -640,7 +633,7 @@ static int skl_link_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct hdac_bus *bus = dev_get_drvdata(dai->dev); - struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct hdac_ext_stream *link_dev = snd_soc_dai_get_dma_data(dai, substream); struct hdac_ext_link *link; @@ -650,7 +643,7 @@ static int skl_link_hw_free(struct snd_pcm_substream *substream, link_dev->link_prepared = 0; - link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name); + link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); if (!link) return -EINVAL; @@ -1077,10 +1070,10 @@ int skl_dai_load(struct snd_soc_component *cmp, int index, static int skl_platform_soc_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai_link *dai_link = rtd->dai_link; - dev_dbg(rtd->cpu_dai->dev, "In %s:%s\n", __func__, + dev_dbg(asoc_rtd_to_cpu(rtd, 0)->dev, "In %s:%s\n", __func__, dai_link->cpus->dai_name); snd_soc_set_runtime_hwparams(substream, &azx_pcm_hw); @@ -1221,18 +1214,11 @@ static snd_pcm_uframes_t skl_platform_soc_pointer( return bytes_to_frames(substream->runtime, pos); } -static int skl_platform_soc_mmap(struct snd_soc_component *component, - struct snd_pcm_substream *substream, - struct vm_area_struct *area) -{ - return snd_pcm_lib_default_mmap(substream, area); -} - static u64 skl_adjust_codec_delay(struct snd_pcm_substream *substream, u64 nsec) { - struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); u64 codec_frames, codec_nsecs; if (!codec_dai->driver->ops->delay) @@ -1265,7 +1251,6 @@ static int skl_platform_soc_get_time_info( snd_pcm_gettime(substream->runtime, system_ts); nsec = timecounter_read(&hstr->tc); - nsec = div_u64(nsec, 3); /* can be optimized */ if (audio_tstamp_config->report_delay) nsec = skl_adjust_codec_delay(substream, nsec); @@ -1287,7 +1272,7 @@ static int skl_platform_soc_get_time_info( static int skl_platform_soc_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0); struct hdac_bus *bus = dev_get_drvdata(dai->dev); struct snd_pcm *pcm = rtd->pcm; unsigned int size; @@ -1324,21 +1309,6 @@ static int skl_get_module_info(struct skl_dev *skl, return -EIO; } - list_for_each_entry(module, &skl->uuid_list, list) { - if (guid_equal(uuid_mod, &module->uuid)) { - mconfig->id.module_id = module->id; - if (mconfig->module) - mconfig->module->loadable = module->is_loadable; - ret = 0; - break; - } - } - - if (ret) - return ret; - - uuid_mod = &module->uuid; - ret = -EIO; for (i = 0; i < skl->nr_modules; i++) { skl_module = skl->modules[i]; uuid_tplg = &skl_module->uuid; @@ -1348,10 +1318,18 @@ static int skl_get_module_info(struct skl_dev *skl, break; } } + if (skl->nr_modules && ret) return ret; + ret = -EIO; list_for_each_entry(module, &skl->uuid_list, list) { + if (guid_equal(uuid_mod, &module->uuid)) { + mconfig->id.module_id = module->id; + mconfig->module->loadable = module->is_loadable; + ret = 0; + } + for (i = 0; i < MAX_IN_QUEUE; i++) { pin_id = &mconfig->m_in_pin[i].id; if (guid_equal(&pin_id->mod_uuid, &module->uuid)) @@ -1365,7 +1343,7 @@ static int skl_get_module_info(struct skl_dev *skl, } } - return 0; + return ret; } static int skl_populate_modules(struct skl_dev *skl) @@ -1402,7 +1380,10 @@ static int skl_platform_soc_probe(struct snd_soc_component *component) const struct skl_dsp_ops *ops; int ret; - pm_runtime_get_sync(component->dev); + ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + if (bus->ppcap) { skl->component = component; @@ -1467,7 +1448,6 @@ static const struct snd_soc_component_driver skl_component = { .trigger = skl_platform_soc_trigger, .pointer = skl_platform_soc_pointer, .get_time_info = skl_platform_soc_get_time_info, - .mmap = skl_platform_soc_mmap, .pcm_construct = skl_platform_soc_new, .module_get_upon_open = 1, /* increment refcount when a pcm is opened */ }; @@ -1515,11 +1495,9 @@ int skl_platform_unregister(struct device *dev) struct skl_dev *skl = bus_to_skl(bus); struct skl_module_deferred_bind *modules, *tmp; - if (!list_empty(&skl->bind_list)) { - list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) { - list_del(&modules->node); - kfree(modules); - } + list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) { + list_del(&modules->node); + kfree(modules); } kfree(skl->dais); diff --git a/sound/soc/intel/skylake/skl-ssp-clk.c b/sound/soc/intel/skylake/skl-ssp-clk.c index bd43885f3805..a3a73c26f9aa 100644 --- a/sound/soc/intel/skylake/skl-ssp-clk.c +++ b/sound/soc/intel/skylake/skl-ssp-clk.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2015-17 Intel Corporation /* diff --git a/sound/soc/intel/skylake/skl-sst-cldma.c b/sound/soc/intel/skylake/skl-sst-cldma.c index 36f697c61074..b91f7a652a2b 100644 --- a/sound/soc/intel/skylake/skl-sst-cldma.c +++ b/sound/soc/intel/skylake/skl-sst-cldma.c @@ -245,7 +245,7 @@ static int skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, u32 total_size, bool wait) { - int ret = 0; + int ret; bool start = true; unsigned int excess_bytes; u32 size; diff --git a/sound/soc/intel/skylake/skl-sst-dsp.c b/sound/soc/intel/skylake/skl-sst-dsp.c index 225706d148d8..4ae3eae0d1fd 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.c +++ b/sound/soc/intel/skylake/skl-sst-dsp.c @@ -422,7 +422,7 @@ struct sst_dsp *skl_dsp_ctx_init(struct device *dev, /* Initialise SST Audio DSP */ if (sst->ops->init) { - ret = sst->ops->init(sst, NULL); + ret = sst->ops->init(sst); if (ret < 0) return NULL; } diff --git a/sound/soc/intel/skylake/skl-sst-dsp.h b/sound/soc/intel/skylake/skl-sst-dsp.h index cdfec0fca577..1df9ef422f61 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.h +++ b/sound/soc/intel/skylake/skl-sst-dsp.h @@ -67,6 +67,8 @@ struct skl_dev; #define SKL_FW_INIT 0x1 #define SKL_FW_RFW_START 0xf +#define BXT_FW_ROM_INIT_RETRY 3 +#define BXT_INIT_TIMEOUT 300 #define SKL_ADSPIC_IPC 1 #define SKL_ADSPIS_IPC 1 diff --git a/sound/soc/intel/skylake/skl-sst-ipc.c b/sound/soc/intel/skylake/skl-sst-ipc.c index 667cdddc289f..7a425271b08b 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.c +++ b/sound/soc/intel/skylake/skl-sst-ipc.c @@ -489,7 +489,7 @@ void skl_ipc_process_reply(struct sst_generic_ipc *ipc, irqreturn_t skl_dsp_irq_thread_handler(int irq, void *context) { struct sst_dsp *dsp = context; - struct skl_dev *skl = sst_dsp_get_thread_context(dsp); + struct skl_dev *skl = dsp->thread_context; struct sst_generic_ipc *ipc = &skl->ipc; struct skl_ipc_header header = {0}; u32 hipcie, hipct, hipcte; diff --git a/sound/soc/intel/skylake/skl-sst-ipc.h b/sound/soc/intel/skylake/skl-sst-ipc.h index 08ac31778325..aaaab3b3ec42 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.h +++ b/sound/soc/intel/skylake/skl-sst-ipc.h @@ -107,12 +107,12 @@ struct skl_ipc_d0ix_msg { irqreturn_t skl_dsp_irq_thread_handler(int irq, void *context); -int skl_ipc_create_pipeline(struct sst_generic_ipc *sst_ipc, +int skl_ipc_create_pipeline(struct sst_generic_ipc *ipc, u16 ppl_mem_size, u8 ppl_type, u8 instance_id, u8 lp_mode); -int skl_ipc_delete_pipeline(struct sst_generic_ipc *sst_ipc, u8 instance_id); +int skl_ipc_delete_pipeline(struct sst_generic_ipc *ipc, u8 instance_id); -int skl_ipc_set_pipeline_state(struct sst_generic_ipc *sst_ipc, +int skl_ipc_set_pipeline_state(struct sst_generic_ipc *ipc, u8 instance_id, enum skl_ipc_pipeline_state state); int skl_ipc_save_pipeline(struct sst_generic_ipc *ipc, @@ -120,10 +120,10 @@ int skl_ipc_save_pipeline(struct sst_generic_ipc *ipc, int skl_ipc_restore_pipeline(struct sst_generic_ipc *ipc, u8 instance_id); -int skl_ipc_init_instance(struct sst_generic_ipc *sst_ipc, +int skl_ipc_init_instance(struct sst_generic_ipc *ipc, struct skl_ipc_init_instance_msg *msg, void *param_data); -int skl_ipc_bind_unbind(struct sst_generic_ipc *sst_ipc, +int skl_ipc_bind_unbind(struct sst_generic_ipc *ipc, struct skl_ipc_bind_unbind_msg *msg); int skl_ipc_load_modules(struct sst_generic_ipc *ipc, @@ -150,12 +150,12 @@ int skl_ipc_set_d0ix(struct sst_generic_ipc *ipc, int skl_ipc_check_D0i0(struct sst_dsp *dsp, bool state); -void skl_ipc_int_enable(struct sst_dsp *dsp); +void skl_ipc_int_enable(struct sst_dsp *ctx); void skl_ipc_op_int_enable(struct sst_dsp *ctx); void skl_ipc_op_int_disable(struct sst_dsp *ctx); -void skl_ipc_int_disable(struct sst_dsp *dsp); +void skl_ipc_int_disable(struct sst_dsp *ctx); -bool skl_ipc_int_status(struct sst_dsp *dsp); +bool skl_ipc_int_status(struct sst_dsp *ctx); void skl_ipc_free(struct sst_generic_ipc *ipc); int skl_ipc_init(struct device *dev, struct skl_dev *skl); void skl_clear_module_cnt(struct sst_dsp *ctx); diff --git a/sound/soc/intel/skylake/skl-sst-utils.c b/sound/soc/intel/skylake/skl-sst-utils.c index d43cbf4a71ef..57ea815d3f04 100644 --- a/sound/soc/intel/skylake/skl-sst-utils.c +++ b/sound/soc/intel/skylake/skl-sst-utils.c @@ -237,7 +237,7 @@ int snd_skl_parse_uuids(struct sst_dsp *ctx, const struct firmware *fw, struct uuid_module *module; struct firmware stripped_fw; unsigned int safe_file; - int ret = 0; + int ret; /* Get the FW pointer to derive ADSP header */ stripped_fw.data = fw->data; @@ -290,7 +290,7 @@ int snd_skl_parse_uuids(struct sst_dsp *ctx, const struct firmware *fw, goto free_uuid_list; } - guid_copy(&module->uuid, (guid_t *)&mod_entry->uuid); + import_guid(&module->uuid, mod_entry->uuid); module->id = (i | (index << 12)); module->is_loadable = mod_entry->type.load_type; diff --git a/sound/soc/intel/skylake/skl-sst.c b/sound/soc/intel/skylake/skl-sst.c index 61a8e4756a2b..39d027ac16ec 100644 --- a/sound/soc/intel/skylake/skl-sst.c +++ b/sound/soc/intel/skylake/skl-sst.c @@ -354,7 +354,7 @@ static int skl_transfer_module(struct sst_dsp *ctx, const void *data, /* * if bytes_left > 0 then wait for BDL complete interrupt and * copy the next chunk till bytes_left is 0. if bytes_left is - * is zero, then wait for load module IPC reply + * zero, then wait for load module IPC reply */ while (bytes_left > 0) { curr_pos = size - bytes_left; @@ -506,8 +506,6 @@ static struct sst_ops skl_ops = { .irq_handler = skl_dsp_sst_interrupt, .write = sst_shim32_write, .read = sst_shim32_read, - .ram_read = sst_memcpy_fromio_32, - .ram_write = sst_memcpy_toio_32, .free = skl_dsp_free, }; diff --git a/sound/soc/intel/skylake/skl-topology.c b/sound/soc/intel/skylake/skl-topology.c index 69cd7a81bf2a..e06eac592da1 100644 --- a/sound/soc/intel/skylake/skl-topology.c +++ b/sound/soc/intel/skylake/skl-topology.c @@ -14,6 +14,7 @@ #include <linux/uuid.h> #include <sound/intel-nhlt.h> #include <sound/soc.h> +#include <sound/soc-acpi.h> #include <sound/soc-topology.h> #include <uapi/sound/snd_sst_tokens.h> #include <uapi/sound/skl-tplg-interface.h> @@ -112,7 +113,7 @@ static int is_skl_dsp_widget_type(struct snd_soc_dapm_widget *w, static void skl_dump_mconfig(struct skl_dev *skl, struct skl_module_cfg *mcfg) { - struct skl_module_iface *iface = &mcfg->module->formats[0]; + struct skl_module_iface *iface = &mcfg->module->formats[mcfg->fmt_idx]; dev_dbg(skl->dev, "Dumping config\n"); dev_dbg(skl->dev, "Input Format:\n"); @@ -194,8 +195,8 @@ static void skl_tplg_update_params_fixup(struct skl_module_cfg *m_cfg, struct skl_module_fmt *in_fmt, *out_fmt; /* Fixups will be applied to pin 0 only */ - in_fmt = &m_cfg->module->formats[0].inputs[0].fmt; - out_fmt = &m_cfg->module->formats[0].outputs[0].fmt; + in_fmt = &m_cfg->module->formats[m_cfg->fmt_idx].inputs[0].fmt; + out_fmt = &m_cfg->module->formats[m_cfg->fmt_idx].outputs[0].fmt; if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (is_fe) { @@ -238,9 +239,9 @@ static void skl_tplg_update_buffer_size(struct skl_dev *skl, /* Since fixups is applied to pin 0 only, ibs, obs needs * change for pin 0 only */ - res = &mcfg->module->resources[0]; - in_fmt = &mcfg->module->formats[0].inputs[0].fmt; - out_fmt = &mcfg->module->formats[0].outputs[0].fmt; + res = &mcfg->module->resources[mcfg->res_idx]; + in_fmt = &mcfg->module->formats[mcfg->fmt_idx].inputs[0].fmt; + out_fmt = &mcfg->module->formats[mcfg->fmt_idx].outputs[0].fmt; if (mcfg->m_type == SKL_MODULE_TYPE_SRCINT) multiplier = 5; @@ -284,14 +285,14 @@ static int skl_tplg_update_be_blob(struct snd_soc_dapm_widget *w, { struct skl_module_cfg *m_cfg = w->priv; int link_type, dir; - u32 ch, s_freq, s_fmt; + u32 ch, s_freq, s_fmt, s_cont; struct nhlt_specific_cfg *cfg; u8 dev_type = skl_tplg_be_dev_type(m_cfg->dev_type); int fmt_idx = m_cfg->fmt_idx; struct skl_module_iface *m_iface = &m_cfg->module->formats[fmt_idx]; /* check if we already have blob */ - if (m_cfg->formats_config.caps_size > 0) + if (m_cfg->formats_config[SKL_PARAM_INIT].caps_size > 0) return 0; dev_dbg(skl->dev, "Applying default cfg blob\n"); @@ -300,7 +301,8 @@ static int skl_tplg_update_be_blob(struct snd_soc_dapm_widget *w, link_type = NHLT_LINK_DMIC; dir = SNDRV_PCM_STREAM_CAPTURE; s_freq = m_iface->inputs[0].fmt.s_freq; - s_fmt = m_iface->inputs[0].fmt.bit_depth; + s_fmt = m_iface->inputs[0].fmt.valid_bit_depth; + s_cont = m_iface->inputs[0].fmt.bit_depth; ch = m_iface->inputs[0].fmt.channels; break; @@ -309,12 +311,14 @@ static int skl_tplg_update_be_blob(struct snd_soc_dapm_widget *w, if (m_cfg->hw_conn_type == SKL_CONN_SOURCE) { dir = SNDRV_PCM_STREAM_PLAYBACK; s_freq = m_iface->outputs[0].fmt.s_freq; - s_fmt = m_iface->outputs[0].fmt.bit_depth; + s_fmt = m_iface->outputs[0].fmt.valid_bit_depth; + s_cont = m_iface->outputs[0].fmt.bit_depth; ch = m_iface->outputs[0].fmt.channels; } else { dir = SNDRV_PCM_STREAM_CAPTURE; s_freq = m_iface->inputs[0].fmt.s_freq; - s_fmt = m_iface->inputs[0].fmt.bit_depth; + s_fmt = m_iface->inputs[0].fmt.valid_bit_depth; + s_cont = m_iface->inputs[0].fmt.bit_depth; ch = m_iface->inputs[0].fmt.channels; } break; @@ -324,16 +328,17 @@ static int skl_tplg_update_be_blob(struct snd_soc_dapm_widget *w, } /* update the blob based on virtual bus_id and default params */ - cfg = skl_get_ep_blob(skl, m_cfg->vbus_id, link_type, - s_fmt, ch, s_freq, dir, dev_type); + cfg = intel_nhlt_get_endpoint_blob(skl->dev, skl->nhlt, m_cfg->vbus_id, + link_type, s_fmt, s_cont, ch, + s_freq, dir, dev_type); if (cfg) { - m_cfg->formats_config.caps_size = cfg->size; - m_cfg->formats_config.caps = (u32 *) &cfg->caps; + m_cfg->formats_config[SKL_PARAM_INIT].caps_size = cfg->size; + m_cfg->formats_config[SKL_PARAM_INIT].caps = (u32 *)&cfg->caps; } else { dev_err(skl->dev, "Blob NULL for id %x type %d dirn %d\n", m_cfg->vbus_id, link_type, dir); - dev_err(skl->dev, "PCM: ch %d, freq %d, fmt %d\n", - ch, s_freq, s_fmt); + dev_err(skl->dev, "PCM: ch %d, freq %d, fmt %d/%d\n", + ch, s_freq, s_fmt, s_cont); return -EIO; } @@ -385,9 +390,9 @@ static int skl_tplg_set_module_params(struct snd_soc_dapm_widget *w, struct skl_algo_data *bc; struct skl_specific_cfg *sp_cfg; - if (mconfig->formats_config.caps_size > 0 && - mconfig->formats_config.set_params == SKL_PARAM_SET) { - sp_cfg = &mconfig->formats_config; + if (mconfig->formats_config[SKL_PARAM_SET].caps_size > 0 && + mconfig->formats_config[SKL_PARAM_SET].set_params == SKL_PARAM_SET) { + sp_cfg = &mconfig->formats_config[SKL_PARAM_SET]; ret = skl_set_module_params(skl, sp_cfg->caps, sp_cfg->caps_size, sp_cfg->param_id, mconfig); @@ -437,8 +442,10 @@ static int skl_tplg_set_module_init_data(struct snd_soc_dapm_widget *w) if (bc->set_params != SKL_PARAM_INIT) continue; - mconfig->formats_config.caps = (u32 *)bc->params; - mconfig->formats_config.caps_size = bc->size; + mconfig->formats_config[SKL_PARAM_INIT].caps = + (u32 *)bc->params; + mconfig->formats_config[SKL_PARAM_INIT].caps_size = + bc->size; break; } @@ -497,8 +504,6 @@ skl_tplg_init_pipe_modules(struct skl_dev *skl, struct skl_pipe *pipe) mconfig->id.module_id, mconfig->guid); if (ret < 0) return ret; - - mconfig->m_state = SKL_MODULE_LOADED; } /* prepare the DMA if the module is gateway cpr */ @@ -549,16 +554,15 @@ static int skl_tplg_unload_pipe_modules(struct skl_dev *skl, struct skl_pipe *pipe) { int ret = 0; - struct skl_pipe_module *w_module = NULL; - struct skl_module_cfg *mconfig = NULL; + struct skl_pipe_module *w_module; + struct skl_module_cfg *mconfig; list_for_each_entry(w_module, &pipe->w_list, node) { guid_t *uuid_mod; mconfig = w_module->w->priv; uuid_mod = (guid_t *)mconfig->guid; - if (mconfig->module->loadable && skl->dsp->fw_ops.unload_mod && - mconfig->m_state > SKL_MODULE_UNINIT) { + if (mconfig->module->loadable && skl->dsp->fw_ops.unload_mod) { ret = skl->dsp->fw_ops.unload_mod(skl->dsp, mconfig->id.module_id); if (ret < 0) @@ -578,6 +582,38 @@ static int skl_tplg_unload_pipe_modules(struct skl_dev *skl, return ret; } +static bool skl_tplg_is_multi_fmt(struct skl_dev *skl, struct skl_pipe *pipe) +{ + struct skl_pipe_fmt *cur_fmt; + struct skl_pipe_fmt *next_fmt; + int i; + + if (pipe->nr_cfgs <= 1) + return false; + + if (pipe->conn_type != SKL_PIPE_CONN_TYPE_FE) + return true; + + for (i = 0; i < pipe->nr_cfgs - 1; i++) { + if (pipe->direction == SNDRV_PCM_STREAM_PLAYBACK) { + cur_fmt = &pipe->configs[i].out_fmt; + next_fmt = &pipe->configs[i + 1].out_fmt; + } else { + cur_fmt = &pipe->configs[i].in_fmt; + next_fmt = &pipe->configs[i + 1].in_fmt; + } + + if (!CHECK_HW_PARAMS(cur_fmt->channels, cur_fmt->freq, + cur_fmt->bps, + next_fmt->channels, + next_fmt->freq, + next_fmt->bps)) + return true; + } + + return false; +} + /* * Here, we select pipe format based on the pipe type and pipe * direction to determine the current config index for the pipeline. @@ -600,8 +636,17 @@ skl_tplg_get_pipe_config(struct skl_dev *skl, struct skl_module_cfg *mconfig) return 0; } - if (pipe->conn_type == SKL_PIPE_CONN_TYPE_NONE) { - dev_dbg(skl->dev, "No conn_type detected, take 0th config\n"); + if (skl_tplg_is_multi_fmt(skl, pipe)) { + pipe->cur_config_idx = pipe->pipe_config_idx; + pipe->memory_pages = pconfig->mem_pages; + dev_dbg(skl->dev, "found pipe config idx:%d\n", + pipe->cur_config_idx); + return 0; + } + + if (pipe->conn_type == SKL_PIPE_CONN_TYPE_NONE || pipe->nr_cfgs == 1) { + dev_dbg(skl->dev, "No conn_type or just 1 pathcfg, taking 0th for %d\n", + pipe->ppl_id); pipe->cur_config_idx = 0; pipe->memory_pages = pconfig->mem_pages; @@ -760,9 +805,10 @@ static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w, return 0; } - if (mconfig->formats_config.caps_size > 0 && - mconfig->formats_config.set_params == SKL_PARAM_BIND) { - sp_cfg = &mconfig->formats_config; + if (mconfig->formats_config[SKL_PARAM_BIND].caps_size > 0 && + mconfig->formats_config[SKL_PARAM_BIND].set_params == + SKL_PARAM_BIND) { + sp_cfg = &mconfig->formats_config[SKL_PARAM_BIND]; ret = skl_set_module_params(skl, sp_cfg->caps, sp_cfg->caps_size, sp_cfg->param_id, mconfig); @@ -1314,6 +1360,68 @@ static int skl_tplg_pga_event(struct snd_soc_dapm_widget *w, return 0; } +static int skl_tplg_multi_config_set_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + bool is_set) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct hdac_bus *bus = snd_soc_component_get_drvdata(component); + struct skl_dev *skl = bus_to_skl(bus); + struct skl_pipeline *ppl; + struct skl_pipe *pipe = NULL; + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + u32 *pipe_id; + + if (!ec) + return -EINVAL; + + if (is_set && ucontrol->value.enumerated.item[0] > ec->items) + return -EINVAL; + + pipe_id = ec->dobj.private; + + list_for_each_entry(ppl, &skl->ppl_list, node) { + if (ppl->pipe->ppl_id == *pipe_id) { + pipe = ppl->pipe; + break; + } + } + if (!pipe) + return -EIO; + + if (is_set) + pipe->pipe_config_idx = ucontrol->value.enumerated.item[0]; + else + ucontrol->value.enumerated.item[0] = pipe->pipe_config_idx; + + return 0; +} + +static int skl_tplg_multi_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return skl_tplg_multi_config_set_get(kcontrol, ucontrol, false); +} + +static int skl_tplg_multi_config_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return skl_tplg_multi_config_set_get(kcontrol, ucontrol, true); +} + +static int skl_tplg_multi_config_get_dmic(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return skl_tplg_multi_config_set_get(kcontrol, ucontrol, false); +} + +static int skl_tplg_multi_config_set_dmic(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return skl_tplg_multi_config_set_get(kcontrol, ucontrol, true); +} + static int skl_tplg_tlv_control_get(struct snd_kcontrol *kcontrol, unsigned int __user *data, unsigned int size) { @@ -1360,12 +1468,6 @@ static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol, struct skl_dev *skl = get_skl_ctx(w->dapm->dev); if (ac->params) { - /* - * Widget data is expected to be stripped of T and L - */ - size -= 2 * sizeof(unsigned int); - data += 2; - if (size > ac->max) return -EINVAL; ac->size = size; @@ -1402,7 +1504,8 @@ static int skl_tplg_mic_control_get(struct snd_kcontrol *kcontrol, static int skl_fill_mic_sel_params(struct skl_module_cfg *mconfig, struct skl_mic_sel_config *mic_cfg, struct device *dev) { - struct skl_specific_cfg *sp_cfg = &mconfig->formats_config; + struct skl_specific_cfg *sp_cfg = + &mconfig->formats_config[SKL_PARAM_INIT]; sp_cfg->caps_size = sizeof(struct skl_mic_sel_config); sp_cfg->set_params = SKL_PARAM_SET; @@ -1534,11 +1637,12 @@ int skl_tplg_update_pipe_params(struct device *dev, struct skl_module_cfg *mconfig, struct skl_pipe_params *params) { - struct skl_module_res *res = &mconfig->module->resources[0]; + struct skl_module_res *res; struct skl_dev *skl = get_skl_ctx(dev); struct skl_module_fmt *format = NULL; u8 cfg_idx = mconfig->pipe->cur_config_idx; + res = &mconfig->module->resources[mconfig->res_idx]; skl_tplg_fill_dma_id(mconfig, params); mconfig->fmt_idx = mconfig->mod_cfg[cfg_idx].fmt_idx; mconfig->res_idx = mconfig->mod_cfg[cfg_idx].res_idx; @@ -1547,9 +1651,9 @@ int skl_tplg_update_pipe_params(struct device *dev, return 0; if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) - format = &mconfig->module->formats[0].inputs[0].fmt; + format = &mconfig->module->formats[mconfig->fmt_idx].inputs[0].fmt; else - format = &mconfig->module->formats[0].outputs[0].fmt; + format = &mconfig->module->formats[mconfig->fmt_idx].outputs[0].fmt; /* set the hw_params */ format->s_freq = params->s_freq; @@ -1724,7 +1828,7 @@ static u8 skl_tplg_be_link_type(int dev_type) * Fill the BE gateway parameters * The BE gateway expects a blob of parameters which are kept in the ACPI * NHLT blob, so query the blob for interface type (i2s/pdm) and instance. - * The port can have multiple settings so pick based on the PCM + * The port can have multiple settings so pick based on the pipeline * parameters */ static int skl_tplg_be_fill_pipe_params(struct snd_soc_dai *dai, @@ -1732,6 +1836,8 @@ static int skl_tplg_be_fill_pipe_params(struct snd_soc_dai *dai, struct skl_pipe_params *params) { struct nhlt_specific_cfg *cfg; + struct skl_pipe *pipe = mconfig->pipe; + struct skl_pipe_fmt *pipe_fmt; struct skl_dev *skl = get_skl_ctx(dai->dev); int link_type = skl_tplg_be_link_type(mconfig->dev_type); u8 dev_type = skl_tplg_be_dev_type(mconfig->dev_type); @@ -1741,20 +1847,24 @@ static int skl_tplg_be_fill_pipe_params(struct snd_soc_dai *dai, if (link_type == NHLT_LINK_HDA) return 0; + if (pipe->direction == SNDRV_PCM_STREAM_PLAYBACK) + pipe_fmt = &pipe->configs[pipe->pipe_config_idx].out_fmt; + else + pipe_fmt = &pipe->configs[pipe->pipe_config_idx].in_fmt; + /* update the blob based on virtual bus_id*/ - cfg = skl_get_ep_blob(skl, mconfig->vbus_id, link_type, - params->s_fmt, params->ch, - params->s_freq, params->stream, - dev_type); + cfg = intel_nhlt_get_endpoint_blob(dai->dev, skl->nhlt, + mconfig->vbus_id, link_type, + pipe_fmt->bps, params->s_cont, + pipe_fmt->channels, pipe_fmt->freq, + pipe->direction, dev_type); if (cfg) { - mconfig->formats_config.caps_size = cfg->size; - mconfig->formats_config.caps = (u32 *) &cfg->caps; + mconfig->formats_config[SKL_PARAM_INIT].caps_size = cfg->size; + mconfig->formats_config[SKL_PARAM_INIT].caps = (u32 *)&cfg->caps; } else { - dev_err(dai->dev, "Blob NULL for id %x type %d dirn %d\n", - mconfig->vbus_id, link_type, - params->stream); - dev_err(dai->dev, "PCM: ch %d, freq %d, fmt %d\n", - params->ch, params->s_freq, params->s_fmt); + dev_err(dai->dev, "Blob NULL for id:%d type:%d dirn:%d ch:%d, freq:%d, fmt:%d\n", + mconfig->vbus_id, link_type, params->stream, + params->ch, params->s_freq, params->s_fmt); return -EINVAL; } @@ -1790,7 +1900,7 @@ static int skl_tplg_be_set_src_pipe_params(struct snd_soc_dai *dai, static int skl_tplg_be_set_sink_pipe_params(struct snd_soc_dai *dai, struct snd_soc_dapm_widget *w, struct skl_pipe_params *params) { - struct snd_soc_dapm_path *p = NULL; + struct snd_soc_dapm_path *p; int ret = -EIO; snd_soc_dapm_widget_for_each_sink_path(w, p) { @@ -1853,6 +1963,16 @@ static const struct snd_soc_tplg_kcontrol_ops skl_tplg_kcontrol_ops[] = { .get = skl_tplg_mic_control_get, .put = skl_tplg_mic_control_set, }, + { + .id = SKL_CONTROL_TYPE_MULTI_IO_SELECT, + .get = skl_tplg_multi_config_get, + .put = skl_tplg_multi_config_set, + }, + { + .id = SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC, + .get = skl_tplg_multi_config_get_dmic, + .put = skl_tplg_multi_config_set_dmic, + } }; static int skl_tplg_fill_pipe_cfg(struct device *dev, @@ -1989,7 +2109,7 @@ static int skl_tplg_get_uuid(struct device *dev, guid_t *guid, struct snd_soc_tplg_vendor_uuid_elem *uuid_tkn) { if (uuid_tkn->token == SKL_TKN_UUID) { - guid_copy(guid, (guid_t *)&uuid_tkn->uuid); + import_guid(guid, uuid_tkn->uuid); return 0; } @@ -2457,19 +2577,26 @@ static int skl_tplg_get_token(struct device *dev, break; + case SKL_TKN_U32_FMT_CFG_IDX: + if (tkn_elem->value > SKL_MAX_PARAMS_TYPES) + return -EINVAL; + + mconfig->fmt_cfg_idx = tkn_elem->value; + break; + case SKL_TKN_U32_CAPS_SIZE: - mconfig->formats_config.caps_size = + mconfig->formats_config[mconfig->fmt_cfg_idx].caps_size = tkn_elem->value; break; case SKL_TKN_U32_CAPS_SET_PARAMS: - mconfig->formats_config.set_params = + mconfig->formats_config[mconfig->fmt_cfg_idx].set_params = tkn_elem->value; break; case SKL_TKN_U32_CAPS_PARAMS_ID: - mconfig->formats_config.param_id = + mconfig->formats_config[mconfig->fmt_cfg_idx].param_id = tkn_elem->value; break; @@ -2683,6 +2810,7 @@ static int skl_tplg_get_pvt_data_v4(struct snd_soc_tplg_dapm_widget *tplg_w, struct skl_dfw_v4_module *dfw = (struct skl_dfw_v4_module *)tplg_w->priv.data; int ret; + int idx = mconfig->fmt_cfg_idx; dev_dbg(dev, "Parsing Skylake v4 widget topology data\n"); @@ -2716,7 +2844,7 @@ static int skl_tplg_get_pvt_data_v4(struct snd_soc_tplg_dapm_widget *tplg_w, mconfig->dev_type = dfw->dev_type; mconfig->hw_conn_type = dfw->hw_conn_type; mconfig->time_slot = dfw->time_slot; - mconfig->formats_config.caps_size = dfw->caps.caps_size; + mconfig->formats_config[idx].caps_size = dfw->caps.caps_size; mconfig->m_in_pin = devm_kcalloc(dev, MAX_IN_QUEUE, sizeof(*mconfig->m_in_pin), @@ -2737,21 +2865,39 @@ static int skl_tplg_get_pvt_data_v4(struct snd_soc_tplg_dapm_widget *tplg_w, dfw->is_dynamic_out_pin, mconfig->module->max_output_pins); - if (mconfig->formats_config.caps_size) { - mconfig->formats_config.set_params = dfw->caps.set_params; - mconfig->formats_config.param_id = dfw->caps.param_id; - mconfig->formats_config.caps = - devm_kzalloc(dev, mconfig->formats_config.caps_size, + if (mconfig->formats_config[idx].caps_size) { + mconfig->formats_config[idx].set_params = dfw->caps.set_params; + mconfig->formats_config[idx].param_id = dfw->caps.param_id; + mconfig->formats_config[idx].caps = + devm_kzalloc(dev, mconfig->formats_config[idx].caps_size, GFP_KERNEL); - if (!mconfig->formats_config.caps) + if (!mconfig->formats_config[idx].caps) return -ENOMEM; - memcpy(mconfig->formats_config.caps, dfw->caps.caps, + memcpy(mconfig->formats_config[idx].caps, dfw->caps.caps, dfw->caps.caps_size); } return 0; } +static int skl_tplg_get_caps_data(struct device *dev, char *data, + struct skl_module_cfg *mconfig) +{ + int idx = mconfig->fmt_cfg_idx; + + if (mconfig->formats_config[idx].caps_size > 0) { + mconfig->formats_config[idx].caps = + devm_kzalloc(dev, mconfig->formats_config[idx].caps_size, + GFP_KERNEL); + if (!mconfig->formats_config[idx].caps) + return -ENOMEM; + memcpy(mconfig->formats_config[idx].caps, data, + mconfig->formats_config[idx].caps_size); + } + + return mconfig->formats_config[idx].caps_size; +} + /* * Parse the private data for the token and corresponding value. * The private data can have multiple data blocks. So, a data block @@ -2763,7 +2909,7 @@ static int skl_tplg_get_pvt_data(struct snd_soc_tplg_dapm_widget *tplg_w, struct skl_module_cfg *mconfig) { struct snd_soc_tplg_vendor_array *array; - int num_blocks, block_size = 0, block_type, off = 0; + int num_blocks, block_size, block_type, off = 0; char *data; int ret; @@ -2804,26 +2950,19 @@ static int skl_tplg_get_pvt_data(struct snd_soc_tplg_dapm_widget *tplg_w, block_size = ret; off += array->size; - array = (struct snd_soc_tplg_vendor_array *) - (tplg_w->priv.data + off); - data = (tplg_w->priv.data + off); if (block_type == SKL_TYPE_TUPLE) { ret = skl_tplg_get_tokens(dev, data, skl, mconfig, block_size); - - if (ret < 0) - return ret; - - --num_blocks; } else { - if (mconfig->formats_config.caps_size > 0) - memcpy(mconfig->formats_config.caps, data, - mconfig->formats_config.caps_size); - --num_blocks; - ret = mconfig->formats_config.caps_size; + ret = skl_tplg_get_caps_data(dev, data, mconfig); } + + if (ret < 0) + return ret; + + --num_blocks; off += ret; } @@ -2914,6 +3053,9 @@ static int skl_tplg_widget_load(struct snd_soc_component *cmpnt, int index, */ mconfig->id.module_id = -1; + /* To provide backward compatibility, set default as SKL_PARAM_INIT */ + mconfig->fmt_cfg_idx = SKL_PARAM_INIT; + /* Parse private data for tuples */ ret = skl_tplg_get_pvt_data(tplg_w, skl, bus->dev, mconfig); if (ret < 0) @@ -3013,12 +3155,21 @@ static int skl_tplg_control_load(struct snd_soc_component *cmpnt, case SND_SOC_TPLG_CTL_ENUM: tplg_ec = container_of(hdr, struct snd_soc_tplg_enum_control, hdr); - if (kctl->access & SNDRV_CTL_ELEM_ACCESS_READWRITE) { + if (kctl->access & SNDRV_CTL_ELEM_ACCESS_READ) { se = (struct soc_enum *)kctl->private_value; if (tplg_ec->priv.size) - return skl_init_enum_data(bus->dev, se, - tplg_ec); + skl_init_enum_data(bus->dev, se, tplg_ec); } + + /* + * now that the control initializations are done, remove + * write permission for the DMIC configuration enums to + * avoid conflicts between NHLT settings and user interaction + */ + + if (hdr->ops.get == SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC) + kctl->access = SNDRV_CTL_ELEM_ACCESS_READ; + break; default: @@ -3376,8 +3527,8 @@ static int skl_tplg_get_manifest_tkn(struct device *dev, dev_err(dev, "Too many UUID tokens\n"); return -EINVAL; } - guid_copy(&skl->modules[uuid_index++]->uuid, - (guid_t *)&array->uuid->uuid); + import_guid(&skl->modules[uuid_index++]->uuid, + array->uuid->uuid); tuple_size += sizeof(*array->uuid); continue; @@ -3445,9 +3596,6 @@ static int skl_tplg_get_manifest_data(struct snd_soc_tplg_manifest *manifest, block_size = ret; off += array->size; - array = (struct snd_soc_tplg_vendor_array *) - (manifest->priv.data + off); - data = (manifest->priv.data + off); if (block_type == SKL_TYPE_TUPLE) { @@ -3488,6 +3636,45 @@ static int skl_manifest_load(struct snd_soc_component *cmpnt, int index, return 0; } +static int skl_tplg_complete(struct snd_soc_component *component) +{ + struct snd_soc_dobj *dobj; + struct snd_soc_acpi_mach *mach; + struct snd_ctl_elem_value *val; + int i; + + val = kmalloc(sizeof(*val), GFP_KERNEL); + if (!val) + return -ENOMEM; + + mach = dev_get_platdata(component->card->dev); + list_for_each_entry(dobj, &component->dobj_list, list) { + struct snd_kcontrol *kcontrol = dobj->control.kcontrol; + struct soc_enum *se; + char **texts; + char chan_text[4]; + + if (dobj->type != SND_SOC_DOBJ_ENUM || !kcontrol || + kcontrol->put != skl_tplg_multi_config_set_dmic) + continue; + + se = (struct soc_enum *)kcontrol->private_value; + texts = dobj->control.dtexts; + sprintf(chan_text, "c%d", mach->mach_params.dmic_num); + + for (i = 0; i < se->items; i++) { + if (strstr(texts[i], chan_text)) { + memset(val, 0, sizeof(*val)); + val->value.enumerated.item[0] = i; + kcontrol->put(kcontrol, val); + } + } + } + + kfree(val); + return 0; +} + static struct snd_soc_tplg_ops skl_tplg_ops = { .widget_load = skl_tplg_widget_load, .control_load = skl_tplg_control_load, @@ -3497,6 +3684,7 @@ static struct snd_soc_tplg_ops skl_tplg_ops = { .io_ops_count = ARRAY_SIZE(skl_tplg_kcontrol_ops), .manifest = skl_manifest_load, .dai_load = skl_dai_load, + .complete = skl_tplg_complete, }; /* @@ -3565,8 +3753,20 @@ int skl_tplg_init(struct snd_soc_component *component, struct hdac_bus *bus) ret = request_firmware(&fw, skl->tplg_name, bus->dev); if (ret < 0) { - dev_info(bus->dev, "tplg fw %s load failed with %d, falling back to dfw_sst.bin", - skl->tplg_name, ret); + char alt_tplg_name[64]; + + snprintf(alt_tplg_name, sizeof(alt_tplg_name), "%s-tplg.bin", + skl->mach->drv_name); + dev_info(bus->dev, "tplg fw %s load failed with %d, trying alternative tplg name %s", + skl->tplg_name, ret, alt_tplg_name); + + ret = request_firmware(&fw, alt_tplg_name, bus->dev); + if (!ret) + goto component_load; + + dev_info(bus->dev, "tplg %s failed with %d, falling back to dfw_sst.bin", + alt_tplg_name, ret); + ret = request_firmware(&fw, "dfw_sst.bin", bus->dev); if (ret < 0) { dev_err(bus->dev, "Fallback tplg fw %s load failed with %d\n", @@ -3575,11 +3775,8 @@ int skl_tplg_init(struct snd_soc_component *component, struct hdac_bus *bus) } } - /* - * The complete tplg for SKL is loaded as index 0, we don't use - * any other index - */ - ret = snd_soc_tplg_component_load(component, &skl_tplg_ops, fw, 0); +component_load: + ret = snd_soc_tplg_component_load(component, &skl_tplg_ops, fw); if (ret < 0) { dev_err(bus->dev, "tplg component load failed%d\n", ret); goto err; @@ -3605,10 +3802,9 @@ void skl_tplg_exit(struct snd_soc_component *component, struct hdac_bus *bus) struct skl_dev *skl = bus_to_skl(bus); struct skl_pipeline *ppl, *tmp; - if (!list_empty(&skl->ppl_list)) - list_for_each_entry_safe(ppl, tmp, &skl->ppl_list, node) - list_del(&ppl->node); + list_for_each_entry_safe(ppl, tmp, &skl->ppl_list, node) + list_del(&ppl->node); /* clean up topology */ - snd_soc_tplg_component_remove(component, SND_SOC_TPLG_INDEX_ALL); + snd_soc_tplg_component_remove(component); } diff --git a/sound/soc/intel/skylake/skl-topology.h b/sound/soc/intel/skylake/skl-topology.h index e967800dbb62..017ac0ef324d 100644 --- a/sound/soc/intel/skylake/skl-topology.h +++ b/sound/soc/intel/skylake/skl-topology.h @@ -81,6 +81,8 @@ enum skl_s_freq { SKL_FS_INVALID }; +#define SKL_MAX_PARAMS_TYPES 4 + enum skl_widget_type { SKL_WIDGET_VMIXER = 1, SKL_WIDGET_MIXER = 2, @@ -97,7 +99,7 @@ struct skl_audio_data_format { u8 number_of_channels; u8 valid_bit_depth; u8 sample_type; - u8 reserved[1]; + u8 reserved; } __packed; struct skl_base_cfg { @@ -119,7 +121,7 @@ struct skl_cpr_gtw_cfg { struct skl_dma_control { u32 node_id; u32 config_length; - u32 config_data[0]; + u32 config_data[]; } __packed; struct skl_cpr_cfg { @@ -150,9 +152,24 @@ struct skl_up_down_mixer_cfg { u32 ch_map; } __packed; +struct skl_pin_format { + u32 pin_idx; + u32 buf_size; + struct skl_audio_data_format audio_fmt; +} __packed; + +struct skl_base_cfg_ext { + u16 nr_input_pins; + u16 nr_output_pins; + u8 reserved[8]; + u32 priv_param_length; + /* Input pin formats followed by output ones. */ + struct skl_pin_format pins_fmt[]; +} __packed; + struct skl_algo_cfg { struct skl_base_cfg base_cfg; - char params[0]; + char params[]; } __packed; struct skl_base_outfmt_cfg { @@ -216,8 +233,8 @@ struct skl_uuid_inst_map { struct skl_kpb_params { u32 num_modules; union { - struct skl_mod_inst_map map[0]; - struct skl_uuid_inst_map map_uuid[0]; + DECLARE_FLEX_ARRAY(struct skl_mod_inst_map, map); + DECLARE_FLEX_ARRAY(struct skl_uuid_inst_map, map_uuid); } u; }; @@ -267,6 +284,7 @@ struct skl_pipe_params { u32 ch; u32 s_freq; u32 s_fmt; + u32 s_cont; u8 linktype; snd_pcm_format_t format; int link_index; @@ -306,14 +324,13 @@ struct skl_pipe { struct skl_path_config configs[SKL_MAX_PATH_CONFIGS]; struct list_head w_list; bool passthru; + u32 pipe_config_idx; }; enum skl_module_state { SKL_MODULE_UNINIT = 0, - SKL_MODULE_LOADED = 1, - SKL_MODULE_INIT_DONE = 2, - SKL_MODULE_BIND_DONE = 3, - SKL_MODULE_UNLOADED = 4, + SKL_MODULE_INIT_DONE = 1, + SKL_MODULE_BIND_DONE = 2, }; enum d0i3_capability { @@ -372,6 +389,7 @@ struct skl_module_cfg { struct skl_module *module; int res_idx; int fmt_idx; + int fmt_cfg_idx; u8 domain; bool homogenous_inputs; bool homogenous_outputs; @@ -402,7 +420,7 @@ struct skl_module_cfg { enum skl_hw_conn_type hw_conn_type; enum skl_module_state m_state; struct skl_pipe *pipe; - struct skl_specific_cfg formats_config; + struct skl_specific_cfg formats_config[SKL_MAX_PARAMS_TYPES]; struct skl_pipe_mcfg mod_cfg[SKL_MAX_MODULES_IN_PIPE]; }; @@ -452,7 +470,7 @@ int skl_dsp_set_dma_control(struct skl_dev *skl, u32 *caps, void skl_tplg_set_be_dmic_config(struct snd_soc_dai *dai, struct skl_pipe_params *params, int stream); int skl_tplg_init(struct snd_soc_component *component, - struct hdac_bus *ebus); + struct hdac_bus *bus); void skl_tplg_exit(struct snd_soc_component *component, struct hdac_bus *bus); struct skl_module_cfg *skl_tplg_fe_get_cpr_module( @@ -475,13 +493,13 @@ int skl_stop_pipe(struct skl_dev *skl, struct skl_pipe *pipe); int skl_reset_pipe(struct skl_dev *skl, struct skl_pipe *pipe); -int skl_init_module(struct skl_dev *skl, struct skl_module_cfg *module_config); +int skl_init_module(struct skl_dev *skl, struct skl_module_cfg *mconfig); int skl_bind_modules(struct skl_dev *skl, struct skl_module_cfg - *src_module, struct skl_module_cfg *dst_module); + *src_mcfg, struct skl_module_cfg *dst_mcfg); int skl_unbind_modules(struct skl_dev *skl, struct skl_module_cfg - *src_module, struct skl_module_cfg *dst_module); + *src_mcfg, struct skl_module_cfg *dst_mcfg); int skl_set_module_params(struct skl_dev *skl, u32 *params, int size, u32 param_id, struct skl_module_cfg *mcfg); diff --git a/sound/soc/intel/skylake/skl.c b/sound/soc/intel/skylake/skl.c index f755ca2484cf..3312b57e3c0c 100644 --- a/sound/soc/intel/skylake/skl.c +++ b/sound/soc/intel/skylake/skl.c @@ -130,6 +130,7 @@ static int skl_init_chip(struct hdac_bus *bus, bool full_reset) struct hdac_ext_link *hlink; int ret; + snd_hdac_set_codec_wakeup(bus, true); skl_enable_miscbdcge(bus->dev, false); ret = snd_hdac_bus_init_chip(bus, full_reset); @@ -138,6 +139,7 @@ static int skl_init_chip(struct hdac_bus *bus, bool full_reset) writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV); skl_enable_miscbdcge(bus->dev, true); + snd_hdac_set_codec_wakeup(bus, false); return ret; } @@ -359,7 +361,7 @@ static int skl_resume(struct device *dev) struct pci_dev *pci = to_pci_dev(dev); struct hdac_bus *bus = pci_get_drvdata(pci); struct skl_dev *skl = bus_to_skl(bus); - struct hdac_ext_link *hlink = NULL; + struct hdac_ext_link *hlink; int ret; /* @@ -437,12 +439,12 @@ static int skl_free(struct hdac_bus *bus) skl->init_done = 0; /* to be sure */ - snd_hdac_ext_stop_streams(bus); + snd_hdac_stop_streams_and_chip(bus); if (bus->irq >= 0) free_irq(bus->irq, (void *)bus); snd_hdac_bus_free_stream_pages(bus); - snd_hdac_stream_free_all(bus); + snd_hdac_ext_stream_free_all(bus); snd_hdac_link_free_all(bus); if (bus->remap_addr) @@ -481,13 +483,8 @@ static struct skl_ssp_clk skl_ssp_clks[] = { static struct snd_soc_acpi_mach *skl_find_hda_machine(struct skl_dev *skl, struct snd_soc_acpi_mach *machines) { - struct hdac_bus *bus = skl_to_bus(skl); struct snd_soc_acpi_mach *mach; - /* check if we have any codecs detected on bus */ - if (bus->codec_mask == 0) - return NULL; - /* point to common table */ mach = snd_soc_acpi_intel_hda_machines; @@ -636,6 +633,9 @@ static int skl_clock_device_register(struct skl_dev *skl) struct platform_device_info pdevinfo = {NULL}; struct skl_clk_pdata *clk_pdata; + if (!skl->nhlt) + return 0; + clk_pdata = devm_kzalloc(&skl->pci->dev, sizeof(*clk_pdata), GFP_KERNEL); if (!clk_pdata) @@ -689,6 +689,29 @@ static void load_codec_module(struct hda_codec *codec) #endif /* CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC */ +static struct hda_codec *skl_codec_device_init(struct hdac_bus *bus, int addr) +{ + struct hda_codec *codec; + int ret; + + codec = snd_hda_codec_device_init(to_hda_bus(bus), addr, "ehdaudio%dD%d", bus->idx, addr); + if (IS_ERR(codec)) { + dev_err(bus->dev, "device init failed for hdac device\n"); + return codec; + } + + codec->core.type = HDA_DEV_ASOC; + + ret = snd_hdac_device_register(&codec->core); + if (ret) { + dev_err(bus->dev, "failed to register hdac device\n"); + put_device(&codec->core.dev); + return ERR_PTR(ret); + } + + return codec; +} + /* * Probe the given codec address */ @@ -697,12 +720,11 @@ static int probe_codec(struct hdac_bus *bus, int addr) unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) | (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; unsigned int res = -1; - struct skl_dev *skl = bus_to_skl(bus); #if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC) + struct skl_dev *skl = bus_to_skl(bus); struct hdac_hda_priv *hda_codec; - int err; #endif - struct hdac_device *hdev; + struct hda_codec *codec; mutex_lock(&bus->cmd_mutex); snd_hdac_bus_send_cmd(bus, cmd); @@ -718,25 +740,22 @@ static int probe_codec(struct hdac_bus *bus, int addr) if (!hda_codec) return -ENOMEM; - hda_codec->codec.bus = skl_to_hbus(skl); - hdev = &hda_codec->codec.core; + codec = skl_codec_device_init(bus, addr); + if (IS_ERR(codec)) + return PTR_ERR(codec); - err = snd_hdac_ext_bus_device_init(bus, addr, hdev); - if (err < 0) - return err; + hda_codec->codec = codec; + dev_set_drvdata(&codec->core.dev, hda_codec); /* use legacy bus only for HDA codecs, idisp uses ext bus */ if ((res & 0xFFFF0000) != IDISP_INTEL_VENDOR_ID) { - hdev->type = HDA_DEV_LEGACY; - load_codec_module(&hda_codec->codec); + codec->core.type = HDA_DEV_LEGACY; + load_codec_module(hda_codec->codec); } return 0; #else - hdev = devm_kzalloc(&skl->pci->dev, sizeof(*hdev), GFP_KERNEL); - if (!hdev) - return -ENOMEM; - - return snd_hdac_ext_bus_device_init(bus, addr, hdev); + codec = skl_codec_device_init(bus, addr); + return PTR_ERR_OR_ZERO(codec); #endif /* CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC */ } @@ -794,7 +813,7 @@ static void skl_probe_work(struct work_struct *work) { struct skl_dev *skl = container_of(work, struct skl_dev, probe_work); struct hdac_bus *bus = skl_to_bus(skl); - struct hdac_ext_link *hlink = NULL; + struct hdac_ext_link *hlink; int err; if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) { @@ -803,6 +822,9 @@ static void skl_probe_work(struct work_struct *work) return; } + skl_init_pci(skl); + skl_dum_set(bus); + err = skl_init_chip(bus, true); if (err < 0) { dev_err(bus->dev, "Init chip failed with err: %d\n", err); @@ -918,13 +940,11 @@ static int skl_first_init(struct hdac_bus *bus) return -ENXIO; } - snd_hdac_bus_reset_link(bus, true); - snd_hdac_bus_parse_capabilities(bus); /* check if PPCAP exists */ if (!bus->ppcap) { - dev_err(bus->dev, "bus ppcap not set, HDaudio or DSP not present?\n"); + dev_err(bus->dev, "bus ppcap not set, HDAudio or DSP not present?\n"); return -ENODEV; } @@ -949,12 +969,9 @@ static int skl_first_init(struct hdac_bus *bus) bus->num_streams = cp_streams + pb_streams; /* allow 64bit DMA address if supported by H/W */ - if (!dma_set_mask(bus->dev, DMA_BIT_MASK(64))) { - dma_set_coherent_mask(bus->dev, DMA_BIT_MASK(64)); - } else { - dma_set_mask(bus->dev, DMA_BIT_MASK(32)); - dma_set_coherent_mask(bus->dev, DMA_BIT_MASK(32)); - } + if (dma_set_mask_and_coherent(bus->dev, DMA_BIT_MASK(64))) + dma_set_mask_and_coherent(bus->dev, DMA_BIT_MASK(32)); + dma_set_max_seg_size(bus->dev, UINT_MAX); /* initialize streams */ snd_hdac_ext_stream_init_all @@ -967,11 +984,7 @@ static int skl_first_init(struct hdac_bus *bus) if (err < 0) return err; - /* initialize chip */ - skl_init_pci(skl); - skl_dum_set(bus); - - return skl_init_chip(bus, true); + return 0; } static int skl_probe(struct pci_dev *pci, @@ -989,7 +1002,7 @@ static int skl_probe(struct pci_dev *pci, return -ENODEV; break; case SND_SKL_PCI_BIND_LEGACY: - dev_info(&pci->dev, "Module parameter forced binding with HDaudio legacy, aborting probe\n"); + dev_info(&pci->dev, "Module parameter forced binding with HDAudio legacy, aborting probe\n"); return -ENODEV; case SND_SKL_PCI_BIND_ASOC: dev_info(&pci->dev, "Module parameter forced binding with SKL driver, bypassed detection logic\n"); @@ -1024,7 +1037,7 @@ static int skl_probe(struct pci_dev *pci, err = -ENODEV; goto out_free; #else - dev_warn(bus->dev, "no nhlt info found, continuing to try to enable HDaudio codec\n"); + dev_warn(bus->dev, "no nhlt info found, continuing to try to enable HDAudio codec\n"); #endif } else { @@ -1064,8 +1077,6 @@ static int skl_probe(struct pci_dev *pci, if (bus->mlcap) snd_hdac_ext_bus_get_ml_capabilities(bus); - snd_hdac_bus_stop_chip(bus); - /* create device for soc dmic */ err = skl_dmic_device_register(skl); if (err < 0) { @@ -1082,7 +1093,8 @@ out_dsp_free: out_clk_free: skl_clock_device_unregister(skl); out_nhlt_free: - intel_nhlt_free(skl->nhlt); + if (skl->nhlt) + intel_nhlt_free(skl->nhlt); out_free: skl_free(bus); @@ -1104,7 +1116,7 @@ static void skl_shutdown(struct pci_dev *pci) if (!skl->init_done) return; - snd_hdac_ext_stop_streams(bus); + snd_hdac_stop_streams_and_chip(bus); list_for_each_entry(s, &bus->stream_list, list) { stream = stream_to_hdac_ext_stream(s); snd_hdac_ext_stream_decouple(bus, stream, false); @@ -1131,9 +1143,9 @@ static void skl_remove(struct pci_dev *pci) skl_dmic_device_unregister(skl); skl_clock_device_unregister(skl); skl_nhlt_remove_sysfs(skl); - intel_nhlt_free(skl->nhlt); + if (skl->nhlt) + intel_nhlt_free(skl->nhlt); skl_free(bus); - dev_set_drvdata(&pci->dev, NULL); } /* PCI IDs */ diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h index 2bfbf59277c4..f55f8b3dbdc3 100644 --- a/sound/soc/intel/skylake/skl.h +++ b/sound/soc/intel/skylake/skl.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * skl.h - HD Audio skylake defintions. + * skl.h - HD Audio skylake definitions. * * Copyright (C) 2015 Intel Corp * Author: Jeeja KP <jeeja.kp@intel.com> @@ -49,7 +49,7 @@ struct skl_astate_param { struct skl_astate_config { u32 count; - struct skl_astate_param astate_table[0]; + struct skl_astate_param astate_table[]; }; struct skl_fw_config { @@ -165,10 +165,6 @@ struct skl_dsp_ops { int skl_platform_unregister(struct device *dev); int skl_platform_register(struct device *dev); -struct nhlt_specific_cfg *skl_get_ep_blob(struct skl_dev *skl, u32 instance, - u8 link_type, u8 s_fmt, u8 no_ch, - u32 s_rate, u8 dirn, u8 dev_type); - int skl_nhlt_update_topology_bin(struct skl_dev *skl); int skl_init_dsp(struct skl_dev *skl); int skl_free_dsp(struct skl_dev *skl); |