diff options
Diffstat (limited to 'sound/soc/intel')
23 files changed, 9605 insertions, 0 deletions
diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig new file mode 100644 index 00000000000..c30fedb3e14 --- /dev/null +++ b/sound/soc/intel/Kconfig @@ -0,0 +1,60 @@ +config SND_MFLD_MACHINE +	tristate "SOC Machine Audio driver for Intel Medfield MID platform" +	depends on INTEL_SCU_IPC +	select SND_SOC_SN95031 +	select SND_SST_MFLD_PLATFORM +	help +          This adds support for ASoC machine driver for Intel(R) MID Medfield platform +          used as alsa device in audio substem in Intel(R) MID devices +          Say Y if you have such a device +          If unsure select "N". + +config SND_SST_MFLD_PLATFORM +	tristate + +config SND_SOC_INTEL_SST +	tristate "ASoC support for Intel(R) Smart Sound Technology" +	select SND_SOC_INTEL_SST_ACPI if ACPI +	depends on (X86 || COMPILE_TEST) +	help +          This adds support for Intel(R) Smart Sound Technology (SST). +          Say Y if you have such a device +          If unsure select "N". + +config SND_SOC_INTEL_SST_ACPI +	tristate + +config SND_SOC_INTEL_HASWELL +	tristate + +config SND_SOC_INTEL_BAYTRAIL +	tristate + +config SND_SOC_INTEL_HASWELL_MACH +	tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint" +	depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C +	select SND_SOC_INTEL_HASWELL +	select SND_SOC_RT5640 +	help +	  This adds support for the Lynxpoint Audio DSP on Intel(R) Haswell +	  Ultrabook platforms. +	  Say Y if you have such a device +	  If unsure select "N". + +config SND_SOC_INTEL_BYT_RT5640_MACH +	tristate "ASoC Audio driver for Intel Baytrail with RT5640 codec" +	depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C +	select SND_SOC_INTEL_BAYTRAIL +	select SND_SOC_RT5640 +	help +	  This adds audio driver for Intel Baytrail platform based boards +	  with the RT5640 audio codec. + +config SND_SOC_INTEL_BYT_MAX98090_MACH +	tristate "ASoC Audio driver for Intel Baytrail with MAX98090 codec" +	depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C +	select SND_SOC_INTEL_BAYTRAIL +	select SND_SOC_MAX98090 +	help +	  This adds audio driver for Intel Baytrail platform based boards +	  with the MAX98090 audio codec. diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile new file mode 100644 index 00000000000..4bfca79a42b --- /dev/null +++ b/sound/soc/intel/Makefile @@ -0,0 +1,30 @@ +# Core support +snd-soc-sst-dsp-objs := sst-dsp.o sst-firmware.o +snd-soc-sst-acpi-objs := sst-acpi.o + +snd-soc-sst-mfld-platform-objs := sst-mfld-platform-pcm.o sst-mfld-platform-compress.o +snd-soc-mfld-machine-objs := mfld_machine.o + +obj-$(CONFIG_SND_SST_MFLD_PLATFORM) += snd-soc-sst-mfld-platform.o +obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o + +obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o +obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o + +# Platform Support +snd-soc-sst-haswell-pcm-objs := \ +	sst-haswell-ipc.o sst-haswell-pcm.o sst-haswell-dsp.o +snd-soc-sst-baytrail-pcm-objs := \ +	sst-baytrail-ipc.o sst-baytrail-pcm.o sst-baytrail-dsp.o + +obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += snd-soc-sst-haswell-pcm.o +obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o + +# Machine support +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 + +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 diff --git a/sound/soc/intel/byt-max98090.c b/sound/soc/intel/byt-max98090.c new file mode 100644 index 00000000000..5fc98c64a3f --- /dev/null +++ b/sound/soc/intel/byt-max98090.c @@ -0,0 +1,203 @@ +/* + * Intel Baytrail SST MAX98090 machine driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + */ + +#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"}, +	{"IN34", NULL, "MICBIAS"}, +	{"MICBIAS", NULL, "Headset Mic"}, +	{"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, +	}, +	{ +		.pin	= "Ext Spk", +		.mask	= SND_JACK_LINEOUT, +	}, +	{ +		.pin	= "Int Mic", +		.mask	= SND_JACK_LINEIN, +	}, +}; + +static struct snd_soc_jack_gpio hs_jack_gpios[] = { +	{ +		.name		= "hp-gpio", +		.idx		= 0, +		.report		= SND_JACK_HEADPHONE | SND_JACK_LINEOUT, +		.debounce_time	= 200, +	}, +	{ +		.name		= "mic-gpio", +		.idx		= 1, +		.report		= SND_JACK_MICROPHONE | SND_JACK_LINEIN, +		.debounce_time	= 200, +	}, +}; + +static int byt_max98090_init(struct snd_soc_pcm_runtime *runtime) +{ +	int ret; +	struct snd_soc_codec *codec = runtime->codec; +	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_jack_new(codec, "Headphone", SND_JACK_HEADPHONE, jack); +	if (ret) +		return ret; + +	ret = snd_soc_jack_add_pins(jack, ARRAY_SIZE(hs_jack_pins), +				    hs_jack_pins); +	if (ret) +		return ret; + +	ret = snd_soc_jack_add_gpiods(card->dev->parent, jack, +				      ARRAY_SIZE(hs_jack_gpios), +				      hs_jack_gpios); +	if (ret) +		return ret; + +	return max98090_mic_detect(codec, jack); +} + +static struct snd_soc_dai_link byt_max98090_dais[] = { +	{ +		.name = "Baytrail Audio", +		.stream_name = "Audio", +		.cpu_dai_name = "baytrail-pcm-audio", +		.codec_dai_name = "HiFi", +		.codec_name = "i2c-193C9890:00", +		.platform_name = "baytrail-pcm-audio", +		.init = byt_max98090_init, +		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | +			   SND_SOC_DAIFMT_CBS_CFS, +	}, +}; + +static struct snd_soc_card byt_max98090_card = { +	.name = "byt-max98090", +	.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), +}; + +static int byt_max98090_probe(struct platform_device *pdev) +{ +	int ret_val = 0; +	struct byt_max98090_private *priv; + +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC); +	if (!priv) { +		dev_err(&pdev->dev, "allocation failed\n"); +		return -ENOMEM; +	} + +	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 ret_val; +} + +static int byt_max98090_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	struct byt_max98090_private *priv = snd_soc_card_get_drvdata(card); + +	snd_soc_jack_free_gpios(&priv->jack, ARRAY_SIZE(hs_jack_gpios), +				hs_jack_gpios); + +	return 0; +} + +static struct platform_driver byt_max98090_driver = { +	.probe = byt_max98090_probe, +	.remove = byt_max98090_remove, +	.driver = { +		.name = "byt-max98090", +		.owner = THIS_MODULE, +		.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/byt-rt5640.c b/sound/soc/intel/byt-rt5640.c new file mode 100644 index 00000000000..53d160d3997 --- /dev/null +++ b/sound/soc/intel/byt-rt5640.c @@ -0,0 +1,156 @@ +/* + * Intel Baytrail SST RT5640 machine driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/acpi.h> +#include <linux/device.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 "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[] = { +	{"IN2P", NULL, "Headset Mic"}, +	{"IN2N", NULL, "Headset Mic"}, +	{"DMIC1", NULL, "Internal Mic"}, +	{"Headphone", NULL, "HPOL"}, +	{"Headphone", NULL, "HPOR"}, +	{"Speaker", NULL, "SPOLP"}, +	{"Speaker", NULL, "SPOLN"}, +	{"Speaker", NULL, "SPORP"}, +	{"Speaker", NULL, "SPORN"}, +}; + +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_init(struct snd_soc_pcm_runtime *runtime) +{ +	int ret; +	struct snd_soc_codec *codec = runtime->codec; +	struct snd_soc_dapm_context *dapm = &codec->dapm; +	struct snd_soc_card *card = runtime->card; + +	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; +	} + +	snd_soc_dapm_ignore_suspend(dapm, "HPOL"); +	snd_soc_dapm_ignore_suspend(dapm, "HPOR"); + +	snd_soc_dapm_ignore_suspend(dapm, "SPOLP"); +	snd_soc_dapm_ignore_suspend(dapm, "SPOLN"); +	snd_soc_dapm_ignore_suspend(dapm, "SPORP"); +	snd_soc_dapm_ignore_suspend(dapm, "SPORN"); + +	return ret; +} + +static struct snd_soc_ops byt_rt5640_ops = { +	.hw_params = byt_rt5640_hw_params, +}; + +static struct snd_soc_dai_link byt_rt5640_dais[] = { +	{ +		.name = "Baytrail Audio", +		.stream_name = "Audio", +		.cpu_dai_name = "baytrail-pcm-audio", +		.codec_dai_name = "rt5640-aif1", +		.codec_name = "i2c-10EC5640:00", +		.platform_name = "baytrail-pcm-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, +	}, +}; + +static struct snd_soc_card byt_rt5640_card = { +	.name = "byt-rt5640", +	.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), +}; + +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", +		.owner = THIS_MODULE, +		.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/haswell.c b/sound/soc/intel/haswell.c new file mode 100644 index 00000000000..3981982674a --- /dev/null +++ b/sound/soc/intel/haswell.c @@ -0,0 +1,222 @@ +/* + * Intel Haswell Lynxpoint SST Audio + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "sst-dsp.h" +#include "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 */ +	snd_mask_set(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT - +				    SNDRV_PCM_HW_PARAM_FIRST_MASK], +				    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_update_bits(rtd->codec, 0x83, 0xffff, 0x8000); + +	return ret; +} + +static 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 sst_pdata *pdata = dev_get_platdata(rtd->platform->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; +} + +static struct snd_soc_dai_link haswell_rt5640_dais[] = { +	/* Front End DAI links */ +	{ +		.name = "System", +		.stream_name = "System Playback", +		.cpu_dai_name = "System Pin", +		.platform_name = "haswell-pcm-audio", +		.dynamic = 1, +		.codec_name = "snd-soc-dummy", +		.codec_dai_name = "snd-soc-dummy-dai", +		.init = haswell_rtd_init, +		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, +		.dpcm_playback = 1, +	}, +	{ +		.name = "Offload0", +		.stream_name = "Offload0 Playback", +		.cpu_dai_name = "Offload0 Pin", +		.platform_name = "haswell-pcm-audio", +		.dynamic = 1, +		.codec_name = "snd-soc-dummy", +		.codec_dai_name = "snd-soc-dummy-dai", +		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, +		.dpcm_playback = 1, +	}, +	{ +		.name = "Offload1", +		.stream_name = "Offload1 Playback", +		.cpu_dai_name = "Offload1 Pin", +		.platform_name = "haswell-pcm-audio", +		.dynamic = 1, +		.codec_name = "snd-soc-dummy", +		.codec_dai_name = "snd-soc-dummy-dai", +		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, +		.dpcm_playback = 1, +	}, +	{ +		.name = "Loopback", +		.stream_name = "Loopback", +		.cpu_dai_name = "Loopback Pin", +		.platform_name = "haswell-pcm-audio", +		.dynamic = 0, +		.codec_name = "snd-soc-dummy", +		.codec_dai_name = "snd-soc-dummy-dai", +		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, +		.dpcm_capture = 1, +	}, +	{ +		.name = "Capture", +		.stream_name = "Capture", +		.cpu_dai_name = "Capture Pin", +		.platform_name = "haswell-pcm-audio", +		.dynamic = 1, +		.codec_name = "snd-soc-dummy", +		.codec_dai_name = "snd-soc-dummy-dai", +		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, +		.dpcm_capture = 1, +	}, + +	/* Back End DAI links */ +	{ +		/* SSP0 - Codec */ +		.name = "Codec", +		.be_id = 0, +		.cpu_dai_name = "snd-soc-dummy-dai", +		.platform_name = "snd-soc-dummy", +		.no_pcm = 1, +		.codec_name = "i2c-INT33CA:00", +		.codec_dai_name = "rt5640-aif1", +		.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, +	}, +}; + +/* 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) +{ +	haswell_rt5640.dev = &pdev->dev; + +	return devm_snd_soc_register_card(&pdev->dev, &haswell_rt5640); +} + +static struct platform_driver haswell_audio = { +	.probe = haswell_audio_probe, +	.driver = { +		.name = "haswell-audio", +		.owner = THIS_MODULE, +	}, +}; + +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/mfld_machine.c b/sound/soc/intel/mfld_machine.c new file mode 100644 index 00000000000..031d78783fc --- /dev/null +++ b/sound/soc/intel/mfld_machine.c @@ -0,0 +1,435 @@ +/* + *  mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform + * + *  Copyright (C) 2010 Intel Corp + *  Author: Vinod Koul <vinod.koul@intel.com> + *  Author: Harsha Priya <priya.harsha@intel.com> + *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; version 2 of the License. + * + *  This program is distributed in the hope that it will be useful, but + *  WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  General Public License for more details. + * + *  You should have received a copy of the GNU General Public License along + *  with this program; if not, write to the Free Software Foundation, Inc., + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include "../codecs/sn95031.h" + +#define MID_MONO 1 +#define MID_STEREO 2 +#define MID_MAX_CAP 5 +#define MFLD_JACK_INSERT 0x04 + +enum soc_mic_bias_zones { +	MFLD_MV_START = 0, +	/* mic bias volutage range for Headphones*/ +	MFLD_MV_HP = 400, +	/* mic bias volutage range for American Headset*/ +	MFLD_MV_AM_HS = 650, +	/* mic bias volutage range for Headset*/ +	MFLD_MV_HS = 2000, +	MFLD_MV_UNDEFINED, +}; + +static unsigned int	hs_switch; +static unsigned int	lo_dac; +static struct snd_soc_codec *mfld_codec; + +struct mfld_mc_private { +	void __iomem *int_base; +	u8 interrupt_status; +}; + +struct snd_soc_jack mfld_jack; + +/*Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin mfld_jack_pins[] = { +	{ +		.pin = "Headphones", +		.mask = SND_JACK_HEADPHONE, +	}, +	{ +		.pin = "AMIC1", +		.mask = SND_JACK_MICROPHONE, +	}, +}; + +/* jack detection voltage zones */ +static struct snd_soc_jack_zone mfld_zones[] = { +	{MFLD_MV_START, MFLD_MV_AM_HS, SND_JACK_HEADPHONE}, +	{MFLD_MV_AM_HS, MFLD_MV_HS, SND_JACK_HEADSET}, +}; + +/* sound card controls */ +static const char *headset_switch_text[] = {"Earpiece", "Headset"}; + +static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"}; + +static const struct soc_enum headset_enum = +	SOC_ENUM_SINGLE_EXT(2, headset_switch_text); + +static const struct soc_enum lo_enum = +	SOC_ENUM_SINGLE_EXT(4, lo_text); + +static int headset_get_switch(struct snd_kcontrol *kcontrol, +	struct snd_ctl_elem_value *ucontrol) +{ +	ucontrol->value.integer.value[0] = hs_switch; +	return 0; +} + +static int headset_set_switch(struct snd_kcontrol *kcontrol, +	struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); +	struct snd_soc_dapm_context *dapm = &card->dapm; + +	if (ucontrol->value.integer.value[0] == hs_switch) +		return 0; + +	snd_soc_dapm_mutex_lock(dapm); + +	if (ucontrol->value.integer.value[0]) { +		pr_debug("hs_set HS path\n"); +		snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); +	} else { +		pr_debug("hs_set EP path\n"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); +		snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT"); +	} + +	snd_soc_dapm_sync_unlocked(dapm); + +	snd_soc_dapm_mutex_unlock(dapm); + +	hs_switch = ucontrol->value.integer.value[0]; + +	return 0; +} + +static void lo_enable_out_pins(struct snd_soc_dapm_context *dapm) +{ +	snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTL"); +	snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTR"); +	snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTL"); +	snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTR"); +	snd_soc_dapm_enable_pin_unlocked(dapm, "VIB1OUT"); +	snd_soc_dapm_enable_pin_unlocked(dapm, "VIB2OUT"); +	if (hs_switch) { +		snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); +	} else { +		snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); +		snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT"); +	} +} + +static int lo_get_switch(struct snd_kcontrol *kcontrol, +	struct snd_ctl_elem_value *ucontrol) +{ +	ucontrol->value.integer.value[0] = lo_dac; +	return 0; +} + +static int lo_set_switch(struct snd_kcontrol *kcontrol, +	struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); +	struct snd_soc_dapm_context *dapm = &card->dapm; + +	if (ucontrol->value.integer.value[0] == lo_dac) +		return 0; + +	snd_soc_dapm_mutex_lock(dapm); + +	/* we dont want to work with last state of lineout so just enable all +	 * pins and then disable pins not required +	 */ +	lo_enable_out_pins(dapm); + +	switch (ucontrol->value.integer.value[0]) { +	case 0: +		pr_debug("set vibra path\n"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "VIB1OUT"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "VIB2OUT"); +		snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0); +		break; + +	case 1: +		pr_debug("set hs  path\n"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT"); +		snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x22); +		break; + +	case 2: +		pr_debug("set spkr path\n"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTL"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTR"); +		snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x44); +		break; + +	case 3: +		pr_debug("set null path\n"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTL"); +		snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTR"); +		snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x66); +		break; +	} + +	snd_soc_dapm_sync_unlocked(dapm); + +	snd_soc_dapm_mutex_unlock(dapm); + +	lo_dac = ucontrol->value.integer.value[0]; +	return 0; +} + +static const struct snd_kcontrol_new mfld_snd_controls[] = { +	SOC_ENUM_EXT("Playback Switch", headset_enum, +			headset_get_switch, headset_set_switch), +	SOC_ENUM_EXT("Lineout Mux", lo_enum, +			lo_get_switch, lo_set_switch), +}; + +static const struct snd_soc_dapm_widget mfld_widgets[] = { +	SND_SOC_DAPM_HP("Headphones", NULL), +	SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route mfld_map[] = { +	{"Headphones", NULL, "HPOUTR"}, +	{"Headphones", NULL, "HPOUTL"}, +	{"Mic", NULL, "AMIC1"}, +}; + +static void mfld_jack_check(unsigned int intr_status) +{ +	struct mfld_jack_data jack_data; + +	jack_data.mfld_jack = &mfld_jack; +	jack_data.intr_id = intr_status; + +	sn95031_jack_detection(&jack_data); +	/* TODO: add american headset detection post gpiolib support */ +} + +static int mfld_init(struct snd_soc_pcm_runtime *runtime) +{ +	struct snd_soc_dapm_context *dapm = &runtime->card->dapm; +	int ret_val; + +	mfld_codec = runtime->codec; + +	/* default is earpiece pin, userspace sets it explcitly */ +	snd_soc_dapm_disable_pin(dapm, "Headphones"); +	/* default is lineout NC, userspace sets it explcitly */ +	snd_soc_dapm_disable_pin(dapm, "LINEOUTL"); +	snd_soc_dapm_disable_pin(dapm, "LINEOUTR"); +	lo_dac = 3; +	hs_switch = 0; +	/* we dont use linein in this so set to NC */ +	snd_soc_dapm_disable_pin(dapm, "LINEINL"); +	snd_soc_dapm_disable_pin(dapm, "LINEINR"); + +	/* Headset and button jack detection */ +	ret_val = snd_soc_jack_new(mfld_codec, "Intel(R) MID Audio Jack", +			SND_JACK_HEADSET | SND_JACK_BTN_0 | +			SND_JACK_BTN_1, &mfld_jack); +	if (ret_val) { +		pr_err("jack creation failed\n"); +		return ret_val; +	} + +	ret_val = snd_soc_jack_add_pins(&mfld_jack, +			ARRAY_SIZE(mfld_jack_pins), mfld_jack_pins); +	if (ret_val) { +		pr_err("adding jack pins failed\n"); +		return ret_val; +	} +	ret_val = snd_soc_jack_add_zones(&mfld_jack, +			ARRAY_SIZE(mfld_zones), mfld_zones); +	if (ret_val) { +		pr_err("adding jack zones failed\n"); +		return ret_val; +	} + +	/* we want to check if anything is inserted at boot, +	 * so send a fake event to codec and it will read adc +	 * to find if anything is there or not */ +	mfld_jack_check(MFLD_JACK_INSERT); +	return ret_val; +} + +static struct snd_soc_dai_link mfld_msic_dailink[] = { +	{ +		.name = "Medfield Headset", +		.stream_name = "Headset", +		.cpu_dai_name = "Headset-cpu-dai", +		.codec_dai_name = "SN95031 Headset", +		.codec_name = "sn95031", +		.platform_name = "sst-platform", +		.init = mfld_init, +	}, +	{ +		.name = "Medfield Speaker", +		.stream_name = "Speaker", +		.cpu_dai_name = "Speaker-cpu-dai", +		.codec_dai_name = "SN95031 Speaker", +		.codec_name = "sn95031", +		.platform_name = "sst-platform", +		.init = NULL, +	}, +	{ +		.name = "Medfield Vibra", +		.stream_name = "Vibra1", +		.cpu_dai_name = "Vibra1-cpu-dai", +		.codec_dai_name = "SN95031 Vibra1", +		.codec_name = "sn95031", +		.platform_name = "sst-platform", +		.init = NULL, +	}, +	{ +		.name = "Medfield Haptics", +		.stream_name = "Vibra2", +		.cpu_dai_name = "Vibra2-cpu-dai", +		.codec_dai_name = "SN95031 Vibra2", +		.codec_name = "sn95031", +		.platform_name = "sst-platform", +		.init = NULL, +	}, +	{ +		.name = "Medfield Compress", +		.stream_name = "Speaker", +		.cpu_dai_name = "Compress-cpu-dai", +		.codec_dai_name = "SN95031 Speaker", +		.codec_name = "sn95031", +		.platform_name = "sst-platform", +		.init = NULL, +	}, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_mfld = { +	.name = "medfield_audio", +	.owner = THIS_MODULE, +	.dai_link = mfld_msic_dailink, +	.num_links = ARRAY_SIZE(mfld_msic_dailink), + +	.controls = mfld_snd_controls, +	.num_controls = ARRAY_SIZE(mfld_snd_controls), +	.dapm_widgets = mfld_widgets, +	.num_dapm_widgets = ARRAY_SIZE(mfld_widgets), +	.dapm_routes = mfld_map, +	.num_dapm_routes = ARRAY_SIZE(mfld_map), +}; + +static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev) +{ +	struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev; + +	memcpy_fromio(&mc_private->interrupt_status, +			((void *)(mc_private->int_base)), +			sizeof(u8)); +	return IRQ_WAKE_THREAD; +} + +static irqreturn_t snd_mfld_jack_detection(int irq, void *data) +{ +	struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data; + +	if (mfld_jack.codec == NULL) +		return IRQ_HANDLED; +	mfld_jack_check(mc_drv_ctx->interrupt_status); + +	return IRQ_HANDLED; +} + +static int snd_mfld_mc_probe(struct platform_device *pdev) +{ +	int ret_val = 0, irq; +	struct mfld_mc_private *mc_drv_ctx; +	struct resource *irq_mem; + +	pr_debug("snd_mfld_mc_probe called\n"); + +	/* retrive the irq number */ +	irq = platform_get_irq(pdev, 0); + +	/* audio interrupt base of SRAM location where +	 * interrupts are stored by System FW */ +	mc_drv_ctx = devm_kzalloc(&pdev->dev, sizeof(*mc_drv_ctx), GFP_ATOMIC); +	if (!mc_drv_ctx) { +		pr_err("allocation failed\n"); +		return -ENOMEM; +	} + +	irq_mem = platform_get_resource_byname( +				pdev, IORESOURCE_MEM, "IRQ_BASE"); +	if (!irq_mem) { +		pr_err("no mem resource given\n"); +		return -ENODEV; +	} +	mc_drv_ctx->int_base = devm_ioremap_nocache(&pdev->dev, irq_mem->start, +						    resource_size(irq_mem)); +	if (!mc_drv_ctx->int_base) { +		pr_err("Mapping of cache failed\n"); +		return -ENOMEM; +	} +	/* register for interrupt */ +	ret_val = devm_request_threaded_irq(&pdev->dev, irq, +			snd_mfld_jack_intr_handler, +			snd_mfld_jack_detection, +			IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx); +	if (ret_val) { +		pr_err("cannot register IRQ\n"); +		return ret_val; +	} +	/* register the soc card */ +	snd_soc_card_mfld.dev = &pdev->dev; +	ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_mfld); +	if (ret_val) { +		pr_debug("snd_soc_register_card failed %d\n", ret_val); +		return ret_val; +	} +	platform_set_drvdata(pdev, mc_drv_ctx); +	pr_debug("successfully exited probe\n"); +	return 0; +} + +static struct platform_driver snd_mfld_mc_driver = { +	.driver = { +		.owner = THIS_MODULE, +		.name = "msic_audio", +	}, +	.probe = snd_mfld_mc_probe, +}; + +module_platform_driver(snd_mfld_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:msic-audio"); diff --git a/sound/soc/intel/sst-acpi.c b/sound/soc/intel/sst-acpi.c new file mode 100644 index 00000000000..42edc6f4fc4 --- /dev/null +++ b/sound/soc/intel/sst-acpi.c @@ -0,0 +1,286 @@ +/* + * Intel SST loader on ACPI systems + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#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" + +#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 SST ASoC machine driver */ +struct sst_acpi_mach { +	/* ACPI ID for the matching machine driver. Audio codec for instance */ +	const u8 id[ACPI_ID_LEN]; +	/* machine driver name */ +	const char *drv_name; +	/* firmware file name */ +	const char *fw_filename; +}; + +/* Descriptor for setting up SST platform data */ +struct sst_acpi_desc { +	const char *drv_name; +	struct sst_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 sst_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 sst_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 acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level, +				       void *context, void **ret) +{ +	*(bool *)context = true; +	return AE_OK; +} + +static struct sst_acpi_mach *sst_acpi_find_machine( +	struct sst_acpi_mach *machines) +{ +	struct sst_acpi_mach *mach; +	bool found = false; + +	for (mach = machines; mach->id[0]; mach++) +		if (ACPI_SUCCESS(acpi_get_devices(mach->id, +						  sst_acpi_mach_match, +						  &found, NULL)) && found) +			return mach; + +	return NULL; +} + +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 sst_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 = sst_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; + +	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); + +	/* register machine driver */ +	sst_acpi->pdev_mach = +		platform_device_register_data(dev, mach->drv_name, -1, +					      sst_pdata, sizeof(*sst_pdata)); +	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_mach haswell_machines[] = { +	{ "INT33CA", "haswell-audio", "intel/IntcSST1.bin" }, +	{} +}; + +static struct sst_acpi_desc sst_acpi_haswell_desc = { +	.drv_name = "haswell-pcm-audio", +	.machines = 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_mach broadwell_machines[] = { +	{ "INT343A", "broadwell-audio", "intel/IntcSST2.bin" }, +	{} +}; + +static struct sst_acpi_desc sst_acpi_broadwell_desc = { +	.drv_name = "haswell-pcm-audio", +	.machines = 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, +}; + +static struct sst_acpi_mach baytrail_machines[] = { +	{ "10EC5640", "byt-rt5640", "intel/fw_sst_0f28.bin-i2s_master" }, +	{ "193C9890", "byt-max98090", "intel/fw_sst_0f28.bin-i2s_master" }, +	{} +}; + +static struct sst_acpi_desc sst_acpi_baytrail_desc = { +	.drv_name = "baytrail-pcm-audio", +	.machines = baytrail_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, +}; + +static struct acpi_device_id sst_acpi_match[] = { +	{ "INT33C8", (unsigned long)&sst_acpi_haswell_desc }, +	{ "INT3438", (unsigned long)&sst_acpi_broadwell_desc }, +	{ "80860F28", (unsigned long)&sst_acpi_baytrail_desc }, +	{ } +}; +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", +		.owner = THIS_MODULE, +		.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/sst-baytrail-dsp.c b/sound/soc/intel/sst-baytrail-dsp.c new file mode 100644 index 00000000000..fc588764ffa --- /dev/null +++ b/sound/soc/intel/sst-baytrail-dsp.c @@ -0,0 +1,372 @@ +/* + * Intel Baytrail SST DSP driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + */ + +#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 "sst-dsp.h" +#include "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_data block_data; +	struct sst_module_template template; +	int count; + +	memset(&template, 0, sizeof(template)); +	template.id = module->type; +	template.entry = module->entry_point; +	template.p.type = SST_MEM_DRAM; +	template.p.data_type = SST_DATA_P; +	template.s.type = SST_MEM_DRAM; +	template.s.data_type = SST_DATA_S; + +	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: +			block_data.offset = block->ram_offset + +					    dsp->addr.iram_offset; +			block_data.type = SST_MEM_IRAM; +			break; +		case SST_BYT_DRAM: +			block_data.offset = block->ram_offset + +					    dsp->addr.dram_offset; +			block_data.type = SST_MEM_DRAM; +			break; +		case SST_BYT_CACHE: +			block_data.offset = block->ram_offset + +					    (dsp->addr.fw_ext - dsp->addr.lpe); +			block_data.type = SST_MEM_CACHE; +			break; +		default: +			dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n", +				block->type, count); +			return -EINVAL; +		} + +		block_data.size = block->size; +		block_data.data_type = SST_DATA_M; +		block_data.data = (void *)block + sizeof(*block); + +		sst_module_insert_fixed_block(mod, &block_data); + +		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/sst-baytrail-ipc.c b/sound/soc/intel/sst-baytrail-ipc.c new file mode 100644 index 00000000000..d207b22ea33 --- /dev/null +++ b/sound/soc/intel/sst-baytrail-ipc.c @@ -0,0 +1,961 @@ +/* + * Intel Baytrail SST IPC Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + */ + +#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/kthread.h> +#include <linux/firmware.h> +#include <linux/io.h> +#include <asm/div64.h> + +#include "sst-baytrail-ipc.h" +#include "sst-dsp.h" +#include "sst-dsp-priv.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; + +/* driver internal IPC message structure */ +struct ipc_message { +	struct list_head list; +	u64 header; + +	/* direction wrt host CPU */ +	char tx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; +	size_t tx_size; +	char rx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; +	size_t rx_size; + +	wait_queue_head_t waitq; +	bool complete; +	bool wait; +	int errno; +}; + +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 list_head tx_list; +	struct list_head rx_list; +	struct list_head empty_list; +	wait_queue_head_t wait_txq; +	struct task_struct *tx_thread; +	struct kthread_worker kworker; +	struct kthread_work kwork; +	struct ipc_message *msg; +}; + +static inline u64 sst_byt_header(int msg_id, int data, bool large, int str_id) +{ +	u64 header; + +	header = IPC_HEADER_MSG_ID(msg_id) | +		 IPC_HEADER_STR_ID(str_id) | +		 IPC_HEADER_LARGE(large) | +		 IPC_HEADER_DATA(data) | +		 SST_BYT_IPCX_BUSY; + +	return header; +} + +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_ipc_shim_dbg(struct sst_byt *byt, const char *text) +{ +	struct sst_dsp *sst = byt->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(byt->dev, +		"ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n", +		text, ipcx, isr, ipcd, imrx); +} + +/* locks held by caller */ +static struct ipc_message *sst_byt_msg_get_empty(struct sst_byt *byt) +{ +	struct ipc_message *msg = NULL; + +	if (!list_empty(&byt->empty_list)) { +		msg = list_first_entry(&byt->empty_list, +				       struct ipc_message, list); +		list_del(&msg->list); +	} + +	return msg; +} + +static void sst_byt_ipc_tx_msgs(struct kthread_work *work) +{ +	struct sst_byt *byt = +		container_of(work, struct sst_byt, kwork); +	struct ipc_message *msg; +	u64 ipcx; +	unsigned long flags; + +	spin_lock_irqsave(&byt->dsp->spinlock, flags); +	if (list_empty(&byt->tx_list)) { +		spin_unlock_irqrestore(&byt->dsp->spinlock, flags); +		return; +	} + +	/* if the DSP is busy we will TX messages after IRQ */ +	ipcx = sst_dsp_shim_read64_unlocked(byt->dsp, SST_IPCX); +	if (ipcx & SST_BYT_IPCX_BUSY) { +		spin_unlock_irqrestore(&byt->dsp->spinlock, flags); +		return; +	} + +	msg = list_first_entry(&byt->tx_list, struct ipc_message, list); + +	list_move(&msg->list, &byt->rx_list); + +	/* send the message */ +	if (msg->header & IPC_HEADER_LARGE(true)) +		sst_dsp_outbox_write(byt->dsp, msg->tx_data, msg->tx_size); +	sst_dsp_shim_write64_unlocked(byt->dsp, SST_IPCX, msg->header); + +	spin_unlock_irqrestore(&byt->dsp->spinlock, flags); +} + +static inline void sst_byt_tx_msg_reply_complete(struct sst_byt *byt, +						 struct ipc_message *msg) +{ +	msg->complete = true; + +	if (!msg->wait) +		list_add_tail(&msg->list, &byt->empty_list); +	else +		wake_up(&msg->waitq); +} + +static void sst_byt_drop_all(struct sst_byt *byt) +{ +	struct ipc_message *msg, *tmp; +	unsigned long flags; + +	/* drop all TX and Rx messages before we stall + reset DSP */ +	spin_lock_irqsave(&byt->dsp->spinlock, flags); +	list_for_each_entry_safe(msg, tmp, &byt->tx_list, list) { +		list_move(&msg->list, &byt->empty_list); +	} + +	list_for_each_entry_safe(msg, tmp, &byt->rx_list, list) { +		list_move(&msg->list, &byt->empty_list); +	} + +	spin_unlock_irqrestore(&byt->dsp->spinlock, flags); +} + +static int sst_byt_tx_wait_done(struct sst_byt *byt, struct ipc_message *msg, +				void *rx_data) +{ +	unsigned long flags; +	int ret; + +	/* wait for DSP completion */ +	ret = wait_event_timeout(msg->waitq, msg->complete, +				 msecs_to_jiffies(IPC_TIMEOUT_MSECS)); + +	spin_lock_irqsave(&byt->dsp->spinlock, flags); +	if (ret == 0) { +		list_del(&msg->list); +		sst_byt_ipc_shim_dbg(byt, "message timeout"); + +		ret = -ETIMEDOUT; +	} else { + +		/* copy the data returned from DSP */ +		if (msg->rx_size) +			memcpy(rx_data, msg->rx_data, msg->rx_size); +		ret = msg->errno; +	} + +	list_add_tail(&msg->list, &byt->empty_list); +	spin_unlock_irqrestore(&byt->dsp->spinlock, flags); +	return ret; +} + +static int sst_byt_ipc_tx_message(struct sst_byt *byt, u64 header, +				  void *tx_data, size_t tx_bytes, +				  void *rx_data, size_t rx_bytes, int wait) +{ +	unsigned long flags; +	struct ipc_message *msg; + +	spin_lock_irqsave(&byt->dsp->spinlock, flags); + +	msg = sst_byt_msg_get_empty(byt); +	if (msg == NULL) { +		spin_unlock_irqrestore(&byt->dsp->spinlock, flags); +		return -EBUSY; +	} + +	msg->header = header; +	msg->tx_size = tx_bytes; +	msg->rx_size = rx_bytes; +	msg->wait = wait; +	msg->errno = 0; +	msg->complete = false; + +	if (tx_bytes) { +		/* msg content = lower 32-bit of the header + data */ +		*(u32 *)msg->tx_data = (u32)(header & (u32)-1); +		memcpy(msg->tx_data + sizeof(u32), tx_data, tx_bytes); +		msg->tx_size += sizeof(u32); +	} + +	list_add_tail(&msg->list, &byt->tx_list); +	spin_unlock_irqrestore(&byt->dsp->spinlock, flags); + +	queue_kthread_work(&byt->kworker, &byt->kwork); + +	if (wait) +		return sst_byt_tx_wait_done(byt, msg, rx_data); +	else +		return 0; +} + +static inline int sst_byt_ipc_tx_msg_wait(struct sst_byt *byt, u64 header, +					  void *tx_data, size_t tx_bytes, +					  void *rx_data, size_t rx_bytes) +{ +	return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, +				      rx_data, rx_bytes, 1); +} + +static inline int sst_byt_ipc_tx_msg_nowait(struct sst_byt *byt, u64 header, +						void *tx_data, size_t tx_bytes) +{ +	return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, +				      NULL, 0, 0); +} + +static struct ipc_message *sst_byt_reply_find_msg(struct sst_byt *byt, +						  u64 header) +{ +	struct ipc_message *msg = NULL, *_msg; +	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; + +	if (list_empty(&byt->rx_list)) { +		dev_err(byt->dev, +			"ipc: rx list is empty but received 0x%llx\n", header); +		goto out; +	} + +	list_for_each_entry(_msg, &byt->rx_list, list) { +		if ((_msg->header & mask) == header) { +			msg = _msg; +			break; +		} +	} + +out: +	return msg; +} + +static void sst_byt_stream_update(struct sst_byt *byt, struct ipc_message *msg) +{ +	struct sst_byt_stream *stream; +	u64 header = msg->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_byt_reply_find_msg(byt, header); +	if (msg == NULL) +		return 1; + +	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_byt_tx_msg_reply_complete(byt, 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; +	int handled = 1; + +	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 handled; +} + +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); +	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... */ +	queue_kthread_work(&byt->kworker, &byt->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_byt_alloc_params *str_req = &stream->request; +	struct sst_byt_alloc_response *reply = &stream->reply; +	u64 header; +	int ret; + +	header = sst_byt_header(IPC_IA_ALLOC_STREAM, +				sizeof(*str_req) + sizeof(u32), +				true, stream->str_id); +	ret = sst_byt_ipc_tx_msg_wait(byt, header, str_req, sizeof(*str_req), +				      reply, sizeof(*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) +{ +	u64 header; +	int ret = 0; +	struct sst_dsp *sst = byt->dsp; +	unsigned long flags; + +	if (!stream->commited) +		goto out; + +	header = sst_byt_header(IPC_IA_FREE_STREAM, 0, false, stream->str_id); +	ret = sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); +	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) +{ +	u64 header; + +	header = sst_byt_header(type, 0, false, stream_id); +	if (wait) +		return sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); +	else +		return sst_byt_ipc_tx_msg_nowait(byt, header, NULL, 0); +} + +/* 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; +	void *tx_msg; +	size_t size; +	u64 header; +	int ret; + +	start_stream.byte_offset = start_offset; +	header = sst_byt_header(IPC_IA_START_STREAM, +				sizeof(start_stream) + sizeof(u32), +				true, stream->str_id); +	tx_msg = &start_stream; +	size = sizeof(start_stream); + +	ret = sst_byt_ipc_tx_msg_nowait(byt, header, tx_msg, size); +	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); +} + +static int msg_empty_list_init(struct sst_byt *byt) +{ +	struct ipc_message *msg; +	int i; + +	byt->msg = kzalloc(sizeof(*msg) * IPC_EMPTY_LIST_SIZE, GFP_KERNEL); +	if (byt->msg == NULL) +		return -ENOMEM; + +	for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { +		init_waitqueue_head(&byt->msg[i].waitq); +		list_add(&byt->msg[i].list, &byt->empty_list); +	} + +	return 0; +} + +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_noirq(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_byt_drop_all(byt); +	dev_dbg(byt->dev, "dsp in reset\n"); + +	return 0; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_suspend_noirq); + +int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata) +{ +	struct sst_byt *byt = pdata->dsp; + +	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); + +int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) +{ +	struct sst_byt *byt; +	struct sst_fw *byt_sst_fw; +	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; +	INIT_LIST_HEAD(&byt->stream_list); +	INIT_LIST_HEAD(&byt->tx_list); +	INIT_LIST_HEAD(&byt->rx_list); +	INIT_LIST_HEAD(&byt->empty_list); +	init_waitqueue_head(&byt->boot_wait); +	init_waitqueue_head(&byt->wait_txq); + +	err = msg_empty_list_init(byt); +	if (err < 0) +		return -ENOMEM; + +	/* start the IPC message thread */ +	init_kthread_worker(&byt->kworker); +	byt->tx_thread = kthread_run(kthread_worker_fn, +				     &byt->kworker, "%s", +				     dev_name(byt->dev)); +	if (IS_ERR(byt->tx_thread)) { +		err = PTR_ERR(byt->tx_thread); +		dev_err(byt->dev, "error failed to create message TX task\n"); +		goto err_free_msg; +	} +	init_kthread_work(&byt->kwork, sst_byt_ipc_tx_msgs); + +	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_err; +	} + +	/* 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; +	} + +	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_err: +	kthread_stop(byt->tx_thread); +err_free_msg: +	kfree(byt->msg); + +	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); +	kthread_stop(byt->tx_thread); +	kfree(byt->msg); +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_free); diff --git a/sound/soc/intel/sst-baytrail-ipc.h b/sound/soc/intel/sst-baytrail-ipc.h new file mode 100644 index 00000000000..06a4d202689 --- /dev/null +++ b/sound/soc/intel/sst-baytrail-ipc.h @@ -0,0 +1,74 @@ +/* + * Intel Baytrail SST IPC Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + */ + +#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_noirq(struct device *dev, struct sst_pdata *pdata); +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/sst-baytrail-pcm.c b/sound/soc/intel/sst-baytrail-pcm.c new file mode 100644 index 00000000000..8eab97368ea --- /dev/null +++ b/sound/soc/intel/sst-baytrail-pcm.c @@ -0,0 +1,525 @@ +/* + * Intel Baytrail SST PCM Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + */ + +#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 "sst-dsp-priv.h" +#include "sst-dsp.h" + +#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_FORMAT_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]; +}; + +/* this may get called several times by oss emulation */ +static int sst_byt_pcm_hw_params(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_platform_get_drvdata(rtd->platform); +	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; +	} + +	snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + +	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_hw_free(struct snd_pcm_substream *substream) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; + +	dev_dbg(rtd->dev, "PCM: hw_free\n"); +	snd_pcm_lib_free_pages(substream); + +	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 sst_byt_priv_data *pdata = +		snd_soc_platform_get_drvdata(rtd->platform); +	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_pcm_substream *substream, int cmd) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct sst_byt_priv_data *pdata = +		snd_soc_platform_get_drvdata(rtd->platform); +	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: +		schedule_work(&pcm_data->work); +		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: +	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 sst_byt_priv_data *pdata = +		snd_soc_platform_get_drvdata(rtd->platform); +	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_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_platform_get_drvdata(rtd->platform); +	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_pcm_substream *substream) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct sst_byt_priv_data *pdata = +		snd_soc_platform_get_drvdata(rtd->platform); +	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_pcm_substream *substream) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct sst_byt_priv_data *pdata = +		snd_soc_platform_get_drvdata(rtd->platform); +	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_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 struct snd_pcm_ops sst_byt_pcm_ops = { +	.open		= sst_byt_pcm_open, +	.close		= sst_byt_pcm_close, +	.ioctl		= snd_pcm_lib_ioctl, +	.hw_params	= sst_byt_pcm_hw_params, +	.hw_free	= sst_byt_pcm_hw_free, +	.trigger	= sst_byt_pcm_trigger, +	.pointer	= sst_byt_pcm_pointer, +	.mmap		= sst_byt_pcm_mmap, +}; + +static void sst_byt_pcm_free(struct snd_pcm *pcm) +{ +	snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int sst_byt_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ +	struct snd_pcm *pcm = rtd->pcm; +	size_t size; +	struct snd_soc_platform *platform = rtd->platform; +	struct sst_pdata *pdata = dev_get_platdata(platform->dev); +	int ret = 0; + +	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || +	    pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { +		size = sst_byt_pcm_hardware.buffer_bytes_max; +		ret = snd_pcm_lib_preallocate_pages_for_all(pcm, +							    SNDRV_DMA_TYPE_DEV, +							    pdata->dma_dev, +							    size, size); +		if (ret) { +			dev_err(rtd->dev, "dma buffer allocation failed %d\n", +				ret); +			return ret; +		} +	} + +	return ret; +} + +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_platform *platform) +{ +	struct sst_pdata *plat_data = dev_get_platdata(platform->dev); +	struct sst_byt_priv_data *priv_data; +	int i; + +	if (!plat_data) +		return -ENODEV; + +	priv_data = devm_kzalloc(platform->dev, sizeof(*priv_data), +				 GFP_KERNEL); +	priv_data->byt = plat_data->dsp; +	snd_soc_platform_set_drvdata(platform, 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 int sst_byt_pcm_remove(struct snd_soc_platform *platform) +{ +	return 0; +} + +static struct snd_soc_platform_driver byt_soc_platform = { +	.probe		= sst_byt_pcm_probe, +	.remove		= sst_byt_pcm_remove, +	.ops		= &sst_byt_pcm_ops, +	.pcm_new	= sst_byt_pcm_new, +	.pcm_free	= sst_byt_pcm_free, +}; + +static const struct snd_soc_component_driver byt_dai_component = { +	.name		= "byt-dai", +}; + +#ifdef CONFIG_PM +static int sst_byt_pcm_dev_suspend_noirq(struct device *dev) +{ +	struct sst_pdata *sst_pdata = dev_get_platdata(dev); +	int ret; + +	dev_dbg(dev, "suspending noirq\n"); + +	/* at this point all streams will be stopped and context saved */ +	ret = sst_byt_dsp_suspend_noirq(dev, sst_pdata); +	if (ret < 0) { +		dev_err(dev, "failed to suspend %d\n", ret); +		return ret; +	} + +	return ret; +} + +static int sst_byt_pcm_dev_suspend_late(struct device *dev) +{ +	struct sst_pdata *sst_pdata = dev_get_platdata(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; +	} + +	return ret; +} + +static int sst_byt_pcm_dev_resume_early(struct device *dev) +{ +	struct sst_pdata *sst_pdata = dev_get_platdata(dev); + +	dev_dbg(dev, "resume early\n"); + +	/* load fw and boot DSP */ +	return sst_byt_dsp_boot(dev, sst_pdata); +} + +static int sst_byt_pcm_dev_resume(struct device *dev) +{ +	struct sst_pdata *sst_pdata = dev_get_platdata(dev); + +	dev_dbg(dev, "resume\n"); + +	/* 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_noirq = sst_byt_pcm_dev_suspend_noirq, +	.suspend_late = sst_byt_pcm_dev_suspend_late, +	.resume_early = sst_byt_pcm_dev_resume_early, +	.resume = sst_byt_pcm_dev_resume, +}; + +#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 = snd_soc_register_platform(&pdev->dev, &byt_soc_platform); +	if (ret < 0) +		goto err_plat; + +	ret = snd_soc_register_component(&pdev->dev, &byt_dai_component, +					 byt_dais, ARRAY_SIZE(byt_dais)); +	if (ret < 0) +		goto err_comp; + +	return 0; + +err_comp: +	snd_soc_unregister_platform(&pdev->dev); +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); + +	snd_soc_unregister_platform(&pdev->dev); +	snd_soc_unregister_component(&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", +		.owner = THIS_MODULE, +		.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/sst-dsp-priv.h b/sound/soc/intel/sst-dsp-priv.h new file mode 100644 index 00000000000..ffb308bd81c --- /dev/null +++ b/sound/soc/intel/sst-dsp-priv.h @@ -0,0 +1,312 @@ +/* + * Intel Smart Sound Technology + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#ifndef __SOUND_SOC_SST_DSP_PRIV_H +#define __SOUND_SOC_SST_DSP_PRIV_H + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/firmware.h> + +struct sst_mem_block; +struct sst_module; +struct sst_fw; + +/* + * 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 *); + +	/* 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); +	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; +	void __iomem *lpe; +	void __iomem *shim; +	void __iomem *pci_cfg; +	void __iomem *fw_ext; +}; + +/* + * Audio DSP Mailbox configuration. + */ +struct sst_mailbox { +	void __iomem *in_base; +	void __iomem *out_base; +	size_t in_size; +	size_t out_size; +}; + +/* + * Audio DSP Firmware data types. + */ +enum sst_data_type { +	SST_DATA_M	= 0, /* module block data */ +	SST_DATA_P	= 1, /* peristant data (text, data) */ +	SST_DATA_S	= 2, /* scratch data (usually buffers) */ +}; + +/* + * 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 data. + * + * This is used to dsecribe any sections of persistent (text and data) and + * scratch (buffers) of module data in ADSP memory space. + */ +struct sst_module_data { + +	enum sst_mem_type type;		/* destination memory type */ +	enum sst_data_type data_type;	/* type of module data */ + +	u32 size;		/* size in bytes */ +	int32_t offset;		/* offset in FW file */ +	u32 data_offset;	/* offset in ADSP memory space */ +	void *data;		/* module data */ +}; + +/* + * 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 */ +	struct sst_module_data s;	/* scratch data */ +	struct sst_module_data p;	/* peristant data */ +}; + +/* + * 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. + */ +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 */ +	u32 offset;			/* module offset in firmware file */ +	u32 size;			/* module size */ +	struct sst_module_data s;	/* scratch data */ +	struct sst_module_data p;	/* peristant 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 */ +}; + +/* + * 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 */ +	struct sst_block_ops *ops;	/* block operations, if any */ + +	/* block status */ +	enum sst_data_type data_type;	/* data type held in this block */ +	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 { + +	/* runtime */ +	struct sst_dsp_device *sst_dev; +	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; + +	/* list of free and used ADSP memory blocks */ +	struct list_head used_block_list; +	struct list_head free_block_list; + +	/* operations */ +	struct sst_ops *ops; + +	/* debug FS */ +	struct dentry *debugfs_root; + +	/* base addresses */ +	struct sst_addr addr; + +	/* mailbox */ +	struct sst_mailbox mailbox; + +	/* SST FW files loaded and their modules */ +	struct list_head module_list; +	struct list_head fw_list; + +	/* platform data */ +	struct sst_pdata *pdata; + +	/* DMA FW loading */ +	struct sst_dma *dma; +	bool fw_use_dma; +}; + +/* 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 *sst_module); +int sst_module_insert(struct sst_module *sst_module); +int sst_module_remove(struct sst_module *sst_module); +int sst_module_insert_fixed_block(struct sst_module *module, +	struct sst_module_data *data); +struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id); + +/* allocate/free pesistent/scratch memory regions managed by drv */ +struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp); +void sst_mem_block_free_scratch(struct sst_dsp *dsp, +	struct sst_module *scratch); +int sst_block_module_remove(struct sst_module *module); + +/* 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, struct sst_block_ops *ops, u32 index, +	void *private); +void sst_mem_block_unregister_all(struct sst_dsp *dsp); + +#endif diff --git a/sound/soc/intel/sst-dsp.c b/sound/soc/intel/sst-dsp.c new file mode 100644 index 00000000000..0b715b20a2d --- /dev/null +++ b/sound/soc/intel/sst-dsp.c @@ -0,0 +1,386 @@ +/* + * Intel Smart Sound Technology (SST) DSP Core Driver + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" + +#define CREATE_TRACE_POINTS +#include <trace/events/intel-sst.h> + +/* Internal generic low-level SST IO functions - can be overidden */ +void sst_shim32_write(void __iomem *addr, u32 offset, u32 value) +{ +	writel(value, addr + offset); +} +EXPORT_SYMBOL_GPL(sst_shim32_write); + +u32 sst_shim32_read(void __iomem *addr, u32 offset) +{ +	return readl(addr + offset); +} +EXPORT_SYMBOL_GPL(sst_shim32_read); + +void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value) +{ +	memcpy_toio(addr + offset, &value, sizeof(value)); +} +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; +} +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) +{ +	unsigned long flags; + +	spin_lock_irqsave(&sst->spinlock, flags); +	sst->ops->write(sst->addr.shim, offset, value); +	spin_unlock_irqrestore(&sst->spinlock, flags); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write); + +u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset) +{ +	unsigned long flags; +	u32 val; + +	spin_lock_irqsave(&sst->spinlock, flags); +	val = sst->ops->read(sst->addr.shim, offset); +	spin_unlock_irqrestore(&sst->spinlock, flags); + +	return val; +} +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); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write_unlocked); + +u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset) +{ +	return sst->ops->read(sst->addr.shim, 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) +{ +	bool change; +	unsigned int old, new; +	u32 ret; + +	ret = sst_dsp_shim_read_unlocked(sst, offset); + +	old = ret; +	new = (old & (~mask)) | (value & mask); + +	change = (old != new); +	if (change) +		sst_dsp_shim_write_unlocked(sst, offset, new); + +	return change; +} +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); + +int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, +				u32 mask, u32 value) +{ +	unsigned long flags; +	bool change; + +	spin_lock_irqsave(&sst->spinlock, flags); +	change = sst_dsp_shim_update_bits_unlocked(sst, offset, mask, value); +	spin_unlock_irqrestore(&sst->spinlock, flags); +	return change; +} +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); + +void sst_dsp_dump(struct sst_dsp *sst) +{ +	sst->ops->dump(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_dump); + +void sst_dsp_reset(struct sst_dsp *sst) +{ +	sst->ops->reset(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_reset); + +int sst_dsp_boot(struct sst_dsp *sst) +{ +	sst->ops->boot(sst); +	return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_boot); + +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) +{ +	sst->mailbox.in_base = sst->addr.lpe + inbox_offset; +	sst->mailbox.out_base = sst->addr.lpe + outbox_offset; +	sst->mailbox.in_size = inbox_size; +	sst->mailbox.out_size = outbox_size; +	return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_mailbox_init); + +void sst_dsp_outbox_write(struct sst_dsp *sst, void *message, size_t bytes) +{ +	u32 i; + +	trace_sst_ipc_outbox_write(bytes); + +	memcpy_toio(sst->mailbox.out_base, message, bytes); + +	for (i = 0; i < bytes; i += 4) +		trace_sst_ipc_outbox_wdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_outbox_write); + +void sst_dsp_outbox_read(struct sst_dsp *sst, void *message, size_t bytes) +{ +	u32 i; + +	trace_sst_ipc_outbox_read(bytes); + +	memcpy_fromio(message, sst->mailbox.out_base, bytes); + +	for (i = 0; i < bytes; i += 4) +		trace_sst_ipc_outbox_rdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_outbox_read); + +void sst_dsp_inbox_write(struct sst_dsp *sst, void *message, size_t bytes) +{ +	u32 i; + +	trace_sst_ipc_inbox_write(bytes); + +	memcpy_toio(sst->mailbox.in_base, message, bytes); + +	for (i = 0; i < bytes; i += 4) +		trace_sst_ipc_inbox_wdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_inbox_write); + +void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes) +{ +	u32 i; + +	trace_sst_ipc_inbox_read(bytes); + +	memcpy_fromio(message, sst->mailbox.in_base, bytes); + +	for (i = 0; i < bytes; i += 4) +		trace_sst_ipc_inbox_rdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_inbox_read); + +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); + +	/* 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; + +	return 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); +} +EXPORT_SYMBOL_GPL(sst_dsp_free); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Intel SST Core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/sst-dsp.h b/sound/soc/intel/sst-dsp.h new file mode 100644 index 00000000000..e44423be66c --- /dev/null +++ b/sound/soc/intel/sst-dsp.h @@ -0,0 +1,234 @@ +/* + * Intel Smart Sound Technology (SST) Core + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#ifndef __SOUND_SOC_SST_DSP_H +#define __SOUND_SOC_SST_DSP_H + +#include <linux/kernel.h> +#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 +#define SST_DMA_TYPE_MID	2 + +/* 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_HDMC		0xE8 +#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_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) + +/*  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) + +/* HDMC */ +#define SST_HDMC_HDDA0(x)	(x << 0) +#define SST_HDMC_HDDA1(x)	(x << 7) + + +/* 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_DSRAMPGE_SHIFT	16 +#define SST_VDRTCL0_DSRAMPGE_MASK	(0xffff << SST_VDRTCL0_DSRAMPGE_SHIFT) +#define SST_VDRTCL0_ISRAMPGE_SHIFT	6 +#define SST_VDRTCL0_ISRAMPGE_MASK	(0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) + +struct sst_dsp; + +/* + * SST Device. + * + * This structure is populated by the SST core driver. + */ +struct sst_dsp_device { +	/* Mandatory fields */ +	struct sst_ops *ops; +	irqreturn_t (*thread)(int irq, void *context); +	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 */ +	u32 dma_base; +	u32 dma_size; +	int dma_engine; +	struct device *dma_dev; + +	/* DSP */ +	u32 id; +	void *dsp; +}; + +/* 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); + +/* 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); + +/* SHIM Read / Write Unlocked for callers already holding sst lock */ +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); + +/* Internal generic low-level SST IO functions - can be overidden */ +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); + +/* 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, +	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); + +/* Debug */ +void sst_dsp_dump(struct sst_dsp *sst); + +#endif diff --git a/sound/soc/intel/sst-firmware.c b/sound/soc/intel/sst-firmware.c new file mode 100644 index 00000000000..3bb43dac892 --- /dev/null +++ b/sound/soc/intel/sst-firmware.c @@ -0,0 +1,614 @@ +/* + * Intel SST Firmware Loader + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/firmware.h> +#include <linux/export.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/pci.h> + +#include <asm/page.h> +#include <asm/pgtable.h> + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" + +static void block_module_remove(struct sst_module *module); + +static void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) +{ +	u32 i; + +	/* copy one 32 bit word at a time as 64 bit access is not supported */ +	for (i = 0; i < bytes; i += 4) +		memcpy_toio(dest + i, src + i, 4); +} + +/* 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_DMA | 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); + +	/* 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; +	} + +	mutex_lock(&dsp->mutex); +	list_add(&sst_fw->list, &dsp->fw_list); +	mutex_unlock(&dsp->mutex); + +	return sst_fw; + +parse_err: +	dma_free_coherent(dsp->dev, sst_fw->size, +				sst_fw->dma_buf, +				sst_fw->dmable_fw_paddr); +	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, *tmp; + +        dev_dbg(dsp->dev, "unloading firmware\n"); + +        mutex_lock(&dsp->mutex); +        list_for_each_entry_safe(module, tmp, &dsp->module_list, list) { +                if (module->sst_fw == sst_fw) { +                        block_module_remove(module); +                        list_del(&module->list); +                        kfree(module); +                } +        } + +        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); + +	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; + +	memcpy(&sst_module->s, &template->s, sizeof(struct sst_module_data)); +	memcpy(&sst_module->p, &template->p, sizeof(struct sst_module_data)); + +	INIT_LIST_HEAD(&sst_module->block_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); + +static struct sst_mem_block *find_block(struct sst_dsp *dsp, int type, +	u32 offset) +{ +	struct sst_mem_block *block; + +	list_for_each_entry(block, &dsp->free_block_list, list) { +		if (block->type == type && block->offset == offset) +			return block; +	} + +	return NULL; +} + +static int block_alloc_contiguous(struct sst_module *module, +	struct sst_module_data *data, u32 offset, int size) +{ +	struct list_head tmp = LIST_HEAD_INIT(tmp); +	struct sst_dsp *dsp = module->dsp; +	struct sst_mem_block *block; + +	while (size > 0) { +		block = find_block(dsp, data->type, offset); +		if (!block) { +			list_splice(&tmp, &dsp->free_block_list); +			return -ENOMEM; +		} + +		list_move_tail(&block->list, &tmp); +		offset += block->size; +		size -= block->size; +	} + +	list_for_each_entry(block, &tmp, list) +		list_add(&block->module_list, &module->block_list); + +	list_splice(&tmp, &dsp->used_block_list); +	return 0; +} + +/* allocate free DSP blocks for module data - callers hold locks */ +static int block_alloc(struct sst_module *module, +	struct sst_module_data *data) +{ +	struct sst_dsp *dsp = module->dsp; +	struct sst_mem_block *block, *tmp; +	int ret = 0; + +	if (data->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 != data->type) +			continue; + +		if (data->size > block->size) +			continue; + +		data->offset = block->offset; +		block->data_type = data->data_type; +		block->bytes_used = data->size % block->size; +		list_add(&block->module_list, &module->block_list); +		list_move(&block->list, &dsp->used_block_list); +		dev_dbg(dsp->dev, " *module %d added block %d:%d\n", +			module->id, block->type, block->index); +		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 != data->type) +			continue; + +		/* do we span > 1 blocks */ +		if (data->size > block->size) { +			ret = block_alloc_contiguous(module, data, +				block->offset, data->size); +			if (ret == 0) +				return ret; +		} +	} + +	/* not enough free block space */ +	return -ENOMEM; +} + +/* remove module from memory - callers hold locks */ +static void block_module_remove(struct sst_module *module) +{ +	struct sst_mem_block *block, *tmp; +	struct sst_dsp *dsp = module->dsp; +	int err; + +	/* disable each block  */ +	list_for_each_entry(block, &module->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, &module->block_list, module_list) { +		list_del(&block->module_list); +		list_move(&block->list, &dsp->free_block_list); +	} +} + +/* prepare the memory block to receive data from host - callers hold locks */ +static int block_module_prepare(struct sst_module *module) +{ +	struct sst_mem_block *block; +	int ret = 0; + +	/* enable each block so that's it'e ready for module P/S data */ +	list_for_each_entry(block, &module->block_list, module_list) { + +		if (block->ops && block->ops->enable) { +			ret = block->ops->enable(block); +			if (ret < 0) { +				dev_err(module->dsp->dev, +					"error: cant disable block %d:%d\n", +					block->type, block->index); +				goto err; +			} +		} +	} +	return ret; + +err: +	list_for_each_entry(block, &module->block_list, module_list) { +		if (block->ops && block->ops->disable) +			block->ops->disable(block); +	} +	return ret; +} + +/* allocate memory blocks for static module addresses - callers hold locks */ +static int block_alloc_fixed(struct sst_module *module, +	struct sst_module_data *data) +{ +	struct sst_dsp *dsp = module->dsp; +	struct sst_mem_block *block, *tmp; +	u32 end = data->offset + data->size, block_end; +	int err; + +	/* only IRAM/DRAM blocks are managed */ +	if (data->type != SST_MEM_IRAM && data->type != SST_MEM_DRAM) +		return 0; + +	/* are blocks already attached to this module */ +	list_for_each_entry_safe(block, tmp, &module->block_list, module_list) { + +		/* force compacting mem blocks of the same data_type */ +		if (block->data_type != data->data_type) +			continue; + +		block_end = block->offset + block->size; + +		/* find block that holds section */ +		if (data->offset >= block->offset && end < block_end) +			return 0; + +		/* does block span more than 1 section */ +		if (data->offset >= block->offset && data->offset < block_end) { + +			err = block_alloc_contiguous(module, data, +				block->offset + block->size, +				data->size - block->size); +			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; + +		/* find block that holds section */ +		if (data->offset >= block->offset && end < block_end) { + +			/* add block */ +			block->data_type = data->data_type; +			list_move(&block->list, &dsp->used_block_list); +			list_add(&block->module_list, &module->block_list); +			return 0; +		} + +		/* does block span more than 1 section */ +		if (data->offset >= block->offset && data->offset < block_end) { + +			err = block_alloc_contiguous(module, data, +				block->offset, data->size); +			if (err < 0) +				return -ENOMEM; + +			return 0; +		} + +	} + +	return -ENOMEM; +} + +/* Load fixed module data into DSP memory blocks */ +int sst_module_insert_fixed_block(struct sst_module *module, +	struct sst_module_data *data) +{ +	struct sst_dsp *dsp = module->dsp; +	int ret; + +	mutex_lock(&dsp->mutex); + +	/* alloc blocks that includes this section */ +	ret = block_alloc_fixed(module, data); +	if (ret < 0) { +		dev_err(dsp->dev, +			"error: no free blocks for section at offset 0x%x size 0x%x\n", +			data->offset, data->size); +		mutex_unlock(&dsp->mutex); +		return -ENOMEM; +	} + +	/* prepare DSP blocks for module copy */ +	ret = block_module_prepare(module); +	if (ret < 0) { +		dev_err(dsp->dev, "error: fw module prepare failed\n"); +		goto err; +	} + +	/* copy partial module data to blocks */ +	sst_memcpy32(dsp->addr.lpe + data->offset, data->data, data->size); + +	mutex_unlock(&dsp->mutex); +	return ret; + +err: +	block_module_remove(module); +	mutex_unlock(&dsp->mutex); +	return ret; +} +EXPORT_SYMBOL_GPL(sst_module_insert_fixed_block); + +/* Unload entire module from DSP memory */ +int sst_block_module_remove(struct sst_module *module) +{ +	struct sst_dsp *dsp = module->dsp; + +	mutex_lock(&dsp->mutex); +	block_module_remove(module); +	mutex_unlock(&dsp->mutex); +	return 0; +} +EXPORT_SYMBOL_GPL(sst_block_module_remove); + +/* 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, 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 */ +struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp) +{ +	struct sst_module *sst_module, *scratch; +	struct sst_mem_block *block, *tmp; +	u32 block_size; +	int ret = 0; + +	scratch = kzalloc(sizeof(struct sst_module), GFP_KERNEL); +	if (scratch == NULL) +		return NULL; + +	mutex_lock(&dsp->mutex); + +	/* calculate required scratch size */ +	list_for_each_entry(sst_module, &dsp->module_list, list) { +		if (scratch->s.size < sst_module->s.size) +			scratch->s.size = sst_module->s.size; +	} + +	dev_dbg(dsp->dev, "scratch buffer required is %d bytes\n", +		scratch->s.size); + +	/* init scratch module */ +	scratch->dsp = dsp; +	scratch->s.type = SST_MEM_DRAM; +	scratch->s.data_type = SST_DATA_S; +	INIT_LIST_HEAD(&scratch->block_list); + +	/* check free blocks before looking at used blocks for space */ +	if (!list_empty(&dsp->free_block_list)) +		block = list_first_entry(&dsp->free_block_list, +			struct sst_mem_block, list); +	else +		block = list_first_entry(&dsp->used_block_list, +			struct sst_mem_block, list); +	block_size = block->size; + +	/* allocate blocks for module scratch buffers */ +	dev_dbg(dsp->dev, "allocating scratch blocks\n"); +	ret = block_alloc(scratch, &scratch->s); +	if (ret < 0) { +		dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); +		goto err; +	} + +	/* assign the same offset of scratch to each module */ +	list_for_each_entry(sst_module, &dsp->module_list, list) +		sst_module->s.offset = scratch->s.offset; + +	mutex_unlock(&dsp->mutex); +	return scratch; + +err: +	list_for_each_entry_safe(block, tmp, &scratch->block_list, module_list) +		list_del(&block->module_list); +	mutex_unlock(&dsp->mutex); +	return NULL; +} +EXPORT_SYMBOL_GPL(sst_mem_block_alloc_scratch); + +/* free all scratch blocks */ +void sst_mem_block_free_scratch(struct sst_dsp *dsp, +	struct sst_module *scratch) +{ +	struct sst_mem_block *block, *tmp; + +	mutex_lock(&dsp->mutex); + +	list_for_each_entry_safe(block, tmp, &scratch->block_list, module_list) +		list_del(&block->module_list); + +	mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_mem_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); diff --git a/sound/soc/intel/sst-haswell-dsp.c b/sound/soc/intel/sst-haswell-dsp.c new file mode 100644 index 00000000000..535f517629f --- /dev/null +++ b/sound/soc/intel/sst-haswell-dsp.c @@ -0,0 +1,517 @@ +/* + * Intel Haswell SST DSP driver + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#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 <linux/acpi.h> +#include <acpi/acpi_bus.h> + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" +#include "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_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_data block_data; +	struct sst_module_template template; +	int count; +	void __iomem *ram; + +	/* TODO: allowed module types need to be configurable */ +	if (module->type != SST_HSW_MODULE_BASE_FW +		&& module->type != SST_HSW_MODULE_PCM_SYSTEM +		&& module->type != SST_HSW_MODULE_PCM +		&& module->type != SST_HSW_MODULE_PCM_REFERENCE +		&& module->type != SST_HSW_MODULE_PCM_CAPTURE +		&& module->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, module->type); +	dev_dbg(dsp->dev, " entrypoint 0x%x\n", module->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 = module->type; +	template.entry = module->entry_point; +	template.p.size = module->info.persistent_size; +	template.p.type = SST_MEM_DRAM; +	template.p.data_type = SST_DATA_P; +	template.s.size = module->info.scratch_size; +	template.s.type = SST_MEM_DRAM; +	template.s.data_type = SST_DATA_S; + +	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, +				"error: block %d size invalid\n", count); +			sst_module_free(mod); +			return -EINVAL; +		} + +		switch (block->type) { +		case SST_HSW_IRAM: +			ram = dsp->addr.lpe; +			block_data.offset = +				block->ram_offset + dsp->addr.iram_offset; +			block_data.type = SST_MEM_IRAM; +			break; +		case SST_HSW_DRAM: +			ram = dsp->addr.lpe; +			block_data.offset = block->ram_offset; +			block_data.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; +		} + +		block_data.size = block->size; +		block_data.data_type = SST_DATA_M; +		block_data.data = (void *)block + sizeof(*block); +		block_data.data_offset = block_data.data - fw->dma_buf; + +		dev_dbg(dsp->dev, "copy firmware block %d type 0x%x " +			"size 0x%x ==> ram %p offset 0x%x\n", +			count, block->type, block->size, ram, +			block->ram_offset); + +		sst_module_insert_fixed_block(mod, &block_data); + +		block = (void *)block + sizeof(*block) + block->size; +	} +	return 0; +} + +static int hsw_parse_fw_image(struct sst_fw *sst_fw) +{ +	struct fw_header *header; +	struct sst_module *scratch; +	struct fw_module_header *module; +	struct sst_dsp *dsp = sst_fw->dsp; +	struct sst_hsw *hsw = sst_fw->private; +	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 != 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 < 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) + module->mod_size; +	} + +	/* allocate persistent/scratch mem regions */ +	scratch = sst_mem_block_alloc_scratch(dsp); +	if (scratch == NULL) +		return -ENOMEM; + +	sst_hsw_set_scratch_module(hsw, scratch); + +	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_boot(struct sst_dsp *sst) +{ +	/* 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); + +	/* disable DMA finish function for SSP0 & SSP1 */ +	sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1, +		SST_CSR2_SDFD_SSP1); + +	/* enable DMA engine 0,1 all channels to access host memory */ +	sst_dsp_shim_update_bits_unlocked(sst, SST_HDMC, +		SST_HDMC_HDDA1(0xff)  | SST_HDMC_HDDA0(0xff), +		SST_HDMC_HDDA1(0xff) | SST_HDMC_HDDA0(0xff)); + +	/* disable all clock gating */ +	writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL2); + +	/* set DSP to RUN */ +	sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0); +} + +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); +} + +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, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ +	{0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */ +	{0x80000, 0xA0000, 4, SST_MEM_DRAM}, /* D-SRAM2 - 4 * 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; +} + +static u32 hsw_block_get_bit(struct sst_mem_block *block) +{ +	u32 bit = 0, shift = 0; + +	switch (block->type) { +	case SST_MEM_DRAM: +		shift = 16; +		break; +	case SST_MEM_IRAM: +		shift = 6; +		break; +	default: +		return 0; +	} + +	bit = 1 << (block->index + shift); + +	return bit; +} + +/* 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); + +	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); + +	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); + +	val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); +	bit = hsw_block_get_bit(block); +	writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0); + +	return 0; +} + +static struct sst_block_ops sst_hsw_ops = { +	.enable = hsw_block_enable, +	.disable = hsw_block_disable, +}; + +static int hsw_enable_shim(struct sst_dsp *sst) +{ +	int tries = 10; +	u32 reg; + +	/* enable shim */ +	reg = readl(sst->addr.pci_cfg + SST_SHIM_PM_REG); +	writel(reg & ~0x3, sst->addr.pci_cfg + SST_SHIM_PM_REG); + +	/* check that ADSP shim is enabled */ +	while (tries--) { +		reg = sst_dsp_shim_read_unlocked(sst, SST_CSR); +		if (reg != 0xffffffff) +			return 0; + +		msleep(1); +	} + +	return -ENODEV; +} + +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; + +	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.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.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_enable_shim(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; + +	/* Enable Interrupt from both sides */ +	sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, 0x3, 0x0); +	sst_dsp_shim_update_bits_unlocked(sst, SST_IMRD, +		(0x3 | 0x1 << 16 | 0x3 << 21), 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, &sst_hsw_ops, j, sst); +			offset += size; +		} +	} + +	/* set default power gating mask */ +	writel(0x0, 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, +	.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/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c new file mode 100644 index 00000000000..434236343dd --- /dev/null +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -0,0 +1,1816 @@ +/* + *  Intel SST Haswell/Broadwell IPC Support + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#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/kthread.h> +#include <linux/firmware.h> +#include <linux/dma-mapping.h> +#include <linux/debugfs.h> + +#include "sst-haswell-ipc.h" +#include "sst-dsp.h" +#include "sst-dsp-priv.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) + +/* 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 + +/* 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_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. */ +}; + +/* 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[1]; +} __attribute__((packed)); + +struct ipc_message { +	struct list_head list; +	u32 header; + +	/* direction wrt host CPU */ +	char tx_data[IPC_MAX_MAILBOX_BYTES]; +	size_t tx_size; +	char rx_data[IPC_MAX_MAILBOX_BYTES]; +	size_t rx_size; + +	wait_queue_head_t waitq; +	bool pending; +	bool complete; +	bool wait; +	int errno; +}; + +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; + +	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; +	struct sst_module *scratch; +	bool fw_done; + +	/* 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; + +	/* boot */ +	wait_queue_head_t boot_wait; +	bool boot_complete; +	bool shutdown; + +	/* IPC messaging */ +	struct list_head tx_list; +	struct list_head rx_list; +	struct list_head empty_list; +	wait_queue_head_t wait_txq; +	struct task_struct *tx_thread; +	struct kthread_worker kworker; +	struct kthread_work kwork; +	bool pending; +	struct ipc_message *msg; + +	/* FW log stream */ +	struct sst_hsw_log_stream log_stream; +}; + +#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_stage_type(u32 msg) +{ +	return (msg & IPC_STG_TYPE_MASK) >>  IPC_STG_TYPE_SHIFT; +} + +static inline u32 msg_set_stage_type(u32 msg, u32 type) +{ +	return (msg & ~IPC_STG_TYPE_MASK) + +		(type << IPC_STG_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; +} + +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 ipc_shim_dbg(struct sst_hsw *hsw, const char *text) +{ +	struct sst_dsp *sst = hsw->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(hsw->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); +} + +/* locks held by caller */ +static struct ipc_message *msg_get_empty(struct sst_hsw *hsw) +{ +	struct ipc_message *msg = NULL; + +	if (!list_empty(&hsw->empty_list)) { +		msg = list_first_entry(&hsw->empty_list, struct ipc_message, +			list); +		list_del(&msg->list); +	} + +	return msg; +} + +static void ipc_tx_msgs(struct kthread_work *work) +{ +	struct sst_hsw *hsw = +		container_of(work, struct sst_hsw, kwork); +	struct ipc_message *msg; +	unsigned long flags; +	u32 ipcx; + +	spin_lock_irqsave(&hsw->dsp->spinlock, flags); + +	if (list_empty(&hsw->tx_list) || hsw->pending) { +		spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); +		return; +	} + +	/* if the DSP is busy we will TX messages after IRQ */ +	ipcx = sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX); +	if (ipcx & SST_IPCX_BUSY) { +		spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); +		return; +	} + +	msg = list_first_entry(&hsw->tx_list, struct ipc_message, list); + +	list_move(&msg->list, &hsw->rx_list); + +	/* send the message */ +	sst_dsp_outbox_write(hsw->dsp, msg->tx_data, msg->tx_size); +	sst_dsp_ipc_msg_tx(hsw->dsp, msg->header | SST_IPCX_BUSY); + +	spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); +} + +/* locks held by caller */ +static void tx_msg_reply_complete(struct sst_hsw *hsw, struct ipc_message *msg) +{ +	msg->complete = true; +	trace_ipc_reply("completed", msg->header); + +	if (!msg->wait) +		list_add_tail(&msg->list, &hsw->empty_list); +	else +		wake_up(&msg->waitq); +} + +static int tx_wait_done(struct sst_hsw *hsw, struct ipc_message *msg, +	void *rx_data) +{ +	unsigned long flags; +	int ret; + +	/* wait for DSP completion (in all cases atm inc pending) */ +	ret = wait_event_timeout(msg->waitq, msg->complete, +		msecs_to_jiffies(IPC_TIMEOUT_MSECS)); + +	spin_lock_irqsave(&hsw->dsp->spinlock, flags); +	if (ret == 0) { +		ipc_shim_dbg(hsw, "message timeout"); + +		trace_ipc_error("error message timeout for", msg->header); +		ret = -ETIMEDOUT; +	} else { + +		/* copy the data returned from DSP */ +		if (msg->rx_size) +			memcpy(rx_data, msg->rx_data, msg->rx_size); +		ret = msg->errno; +	} + +	list_add_tail(&msg->list, &hsw->empty_list); +	spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); +	return ret; +} + +static int ipc_tx_message(struct sst_hsw *hsw, u32 header, void *tx_data, +	size_t tx_bytes, void *rx_data, size_t rx_bytes, int wait) +{ +	struct ipc_message *msg; +	unsigned long flags; + +	spin_lock_irqsave(&hsw->dsp->spinlock, flags); + +	msg = msg_get_empty(hsw); +	if (msg == NULL) { +		spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); +		return -EBUSY; +	} + +	if (tx_bytes) +		memcpy(msg->tx_data, tx_data, tx_bytes); + +	msg->header = header; +	msg->tx_size = tx_bytes; +	msg->rx_size = rx_bytes; +	msg->wait = wait; +	msg->errno = 0; +	msg->pending = false; +	msg->complete = false; + +	list_add_tail(&msg->list, &hsw->tx_list); +	spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); + +	queue_kthread_work(&hsw->kworker, &hsw->kwork); + +	if (wait) +		return tx_wait_done(hsw, msg, rx_data); +	else +		return 0; +} + +static inline int ipc_tx_message_wait(struct sst_hsw *hsw, u32 header, +	void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) +{ +	return ipc_tx_message(hsw, header, tx_data, tx_bytes, rx_data, +		rx_bytes, 1); +} + +static inline int ipc_tx_message_nowait(struct sst_hsw *hsw, u32 header, +	void *tx_data, size_t tx_bytes) +{ +	return ipc_tx_message(hsw, header, tx_data, tx_bytes, NULL, 0, 0); +} + +static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) +{ +	struct sst_hsw_ipc_fw_ready fw_ready; +	u32 offset; + +	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); +} + +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_unlocked(hsw->dsp, SST_IPCD, +		SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); + +	/* unmask busy interrupt */ +	sst_dsp_shim_update_bits_unlocked(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); +} + +static struct ipc_message *reply_find_msg(struct sst_hsw *hsw, u32 header) +{ +	struct ipc_message *msg; + +	/* clear reply bits & status bits */ +	header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); + +	if (list_empty(&hsw->rx_list)) { +		dev_err(hsw->dev, "error: rx list empty but received 0x%x\n", +			header); +		return NULL; +	} + +	list_for_each_entry(msg, &hsw->rx_list, list) { +		if (msg->header == header) +			return msg; +	} + +	return NULL; +} + +static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) +{ +	struct sst_hsw_stream *stream; +	u32 header = msg->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: +	case IPC_STR_RESET: +		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 = reply_find_msg(hsw, header); +	if (msg == NULL) { +		trace_ipc_error("error: can't find message header", header); +		return -EIO; +	} + +	/* first process the header */ +	switch (reply) { +	case IPC_GLB_REPLY_PENDING: +		trace_ipc_pending_reply("received", header); +		msg->pending = true; +		hsw->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->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 */ +	hsw_stream_update(hsw, msg); + +	/* wake up and return the error if we have waiters on this message ? */ +	list_del(&msg->list); +	tx_msg_reply_complete(hsw, msg); + +	return 1; +} + +static int hsw_stream_message(struct sst_hsw *hsw, u32 header) +{ +	u32 stream_msg, stream_id, stage_type; +	struct sst_hsw_stream *stream; +	int handled = 0; + +	stream_msg = msg_get_stream_type(header); +	stream_id = msg_get_stream_id(header); +	stage_type = msg_get_stage_type(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; +	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); +	u32 ipcx, ipcd; +	int handled; +	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 */ +		handled = hsw_process_reply(hsw, ipcx); + +		if (handled > 0) { +			/* 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 */ +		handled = hsw_process_notification(hsw); + +		/* clear BUSY bit and set DONE bit - accept new messages */ +		if (handled > 0) { +			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... */ +	queue_kthread_work(&hsw->kworker, &hsw->kwork); + +	return IRQ_HANDLED; +} + +int sst_hsw_fw_get_version(struct sst_hsw *hsw, +	struct sst_hsw_ipc_fw_version *version) +{ +	int ret; + +	ret = ipc_tx_message_wait(hsw, IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION), +		NULL, 0, version, sizeof(*version)); +	if (ret < 0) +		dev_err(hsw->dev, "error: get version failed\n"); + +	return ret; +} + +/* Mixer Controls */ +int sst_hsw_stream_mute(struct sst_hsw *hsw, struct sst_hsw_stream *stream, +	u32 stage_id, u32 channel) +{ +	int ret; + +	ret = sst_hsw_stream_get_volume(hsw, stream, stage_id, channel, +		&stream->mute_volume[channel]); +	if (ret < 0) +		return ret; + +	ret = sst_hsw_stream_set_volume(hsw, stream, stage_id, channel, 0); +	if (ret < 0) { +		dev_err(hsw->dev, "error: can't unmute stream %d channel %d\n", +			stream->reply.stream_hw_id, channel); +		return ret; +	} + +	stream->mute[channel] = 1; +	return 0; +} + +int sst_hsw_stream_unmute(struct sst_hsw *hsw, struct sst_hsw_stream *stream, +	u32 stage_id, u32 channel) + +{ +	int ret; + +	stream->mute[channel] = 0; +	ret = sst_hsw_stream_set_volume(hsw, stream, stage_id, channel, +		stream->mute_volume[channel]); +	if (ret < 0) { +		dev_err(hsw->dev, "error: can't unmute stream %d channel %d\n", +			stream->reply.stream_hw_id, channel); +		return ret; +	} + +	return 0; +} + +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; +} + +int sst_hsw_stream_set_volume_curve(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u64 curve_duration, +	enum sst_hsw_volume_curve curve) +{ +	/* curve duration in steps of 100ns */ +	stream->vol_req.curve_duration = curve_duration; +	stream->vol_req.curve_type = curve; + +	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; +	u32 header; +	int ret; + +	trace_ipc_request("set stream volume", stream->reply.stream_hw_id); + +	if (channel > 1) +		return -EINVAL; + +	if (stream->mute[channel]) { +		stream->mute_volume[channel] = volume; +		return 0; +	} + +	header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | +		IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); +	header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); +	header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); +	header |= (stage_id << IPC_STG_ID_SHIFT); + +	req = &stream->vol_req; +	req->channel = channel; +	req->target_volume = volume; + +	ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0); +	if (ret < 0) { +		dev_err(hsw->dev, "error: set stream volume failed\n"); +		return ret; +	} + +	return 0; +} + +int sst_hsw_mixer_mute(struct sst_hsw *hsw, u32 stage_id, u32 channel) +{ +	int ret; + +	ret = sst_hsw_mixer_get_volume(hsw, stage_id, channel, +		&hsw->mute_volume[channel]); +	if (ret < 0) +		return ret; + +	ret = sst_hsw_mixer_set_volume(hsw, stage_id, channel, 0); +	if (ret < 0) { +		dev_err(hsw->dev, "error: failed to unmute mixer channel %d\n", +			channel); +		return ret; +	} + +	hsw->mute[channel] = 1; +	return 0; +} + +int sst_hsw_mixer_unmute(struct sst_hsw *hsw, u32 stage_id, u32 channel) +{ +	int ret; + +	ret = sst_hsw_mixer_set_volume(hsw, stage_id, channel, +		hsw->mixer_info.volume_register_address[channel]); +	if (ret < 0) { +		dev_err(hsw->dev, "error: failed to unmute mixer channel %d\n", +			channel); +		return ret; +	} + +	hsw->mute[channel] = 0; +	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; +} + +int sst_hsw_mixer_set_volume_curve(struct sst_hsw *hsw, +	 u64 curve_duration, enum sst_hsw_volume_curve curve) +{ +	/* curve duration in steps of 100ns */ +	hsw->curve_duration = curve_duration; +	hsw->curve_type = curve; + +	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; +	u32 header; +	int ret; + +	trace_ipc_request("set mixer volume", volume); + +	/* set both at same time ? */ +	if (channel == 2) { +		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 = 0xffffffff; +	} else { +		/* set only 1 channel */ +		if (hsw->mute[channel]) { +			hsw->mute_volume[channel] = volume; +			return 0; +		} +		req.channel = channel; +	} + +	header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | +		IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); +	header |= (hsw->mixer_info.mixer_hw_id << IPC_STR_ID_SHIFT); +	header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); +	header |= (stage_id << IPC_STG_ID_SHIFT); + +	req.curve_duration = hsw->curve_duration; +	req.curve_type = hsw->curve_type; +	req.target_volume = volume; + +	ret = ipc_tx_message_wait(hsw, header, &req, sizeof(req), NULL, 0); +	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); +	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) +{ +	u32 header; +	int ret = 0; +	struct sst_dsp *sst = hsw->dsp; +	unsigned long flags; + +	/* 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; +	header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM); + +	ret = ipc_tx_message_wait(hsw, header, &stream->free_req, +		sizeof(stream->free_req), NULL, 0); +	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; +	} + +	/* stereo is only supported atm */ +	if (channels != 2) +		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, enum sst_hsw_module_id module_id, +	u32 entry_point) +{ +	struct sst_hsw_module_map *map = &stream->request.map; + +	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 = entry_point; + +	return 0; +} + +int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u32 offset, u32 size) +{ +	if (stream->commited) { +		dev_err(hsw->dev, "error: stream committed for set pmem\n"); +		return -EINVAL; +	} + +	stream->request.persistent_mem.offset = offset; +	stream->request.persistent_mem.size = size; + +	return 0; +} + +int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u32 offset, u32 size) +{ +	if (stream->commited) { +		dev_err(hsw->dev, "error: stream committed for set smem\n"); +		return -EINVAL; +	} + +	stream->request.scratch_mem.offset = offset; +	stream->request.scratch_mem.size = size; + +	return 0; +} + +int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream) +{ +	struct sst_hsw_ipc_stream_alloc_req *str_req = &stream->request; +	struct sst_hsw_ipc_stream_alloc_reply *reply = &stream->reply; +	u32 header; +	int ret; + +	trace_ipc_request("stream alloc", stream->host_id); + +	header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM); + +	ret = ipc_tx_message_wait(hsw, header, str_req, sizeof(*str_req), +		reply, sizeof(*reply)); +	if (ret < 0) { +		dev_err(hsw->dev, "error: stream commit failed\n"); +		return ret; +	} + +	stream->commited = 1; +	trace_hsw_stream_alloc_reply(stream); + +	return 0; +} + +/* 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_stream_get_hw_id(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream) +{ +	return stream->reply.stream_hw_id; +} + +int sst_hsw_stream_get_mixer_id(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream) +{ +	return stream->reply.mixer_hw_id; +} + +u32 sst_hsw_stream_get_read_reg(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream) +{ +	return stream->reply.read_position_register_address; +} + +u32 sst_hsw_stream_get_pointer_reg(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream) +{ +	return stream->reply.presentation_position_register_address; +} + +u32 sst_hsw_stream_get_peak_reg(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u32 channel) +{ +	if (channel >= 2) +		return 0; + +	return stream->reply.peak_meter_register_address[channel]; +} + +u32 sst_hsw_stream_get_vol_reg(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u32 channel) +{ +	if (channel >= 2) +		return 0; + +	return stream->reply.volume_register_address[channel]; +} + +int sst_hsw_mixer_get_info(struct sst_hsw *hsw) +{ +	struct sst_hsw_ipc_stream_info_reply *reply; +	u32 header; +	int ret; + +	reply = &hsw->mixer_info; +	header = IPC_GLB_TYPE(IPC_GLB_GET_MIXER_STREAM_INFO); + +	trace_ipc_request("get global mixer info", 0); + +	ret = ipc_tx_message_wait(hsw, header, NULL, 0, reply, sizeof(*reply)); +	if (ret < 0) { +		dev_err(hsw->dev, "error: get stream info failed\n"); +		return ret; +	} + +	trace_hsw_mixer_info_reply(reply); + +	return 0; +} + +/* Send stream command */ +static int sst_hsw_stream_operations(struct sst_hsw *hsw, int type, +	int stream_id, int wait) +{ +	u32 header; + +	header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | IPC_STR_TYPE(type); +	header |= (stream_id << IPC_STR_ID_SHIFT); + +	if (wait) +		return ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); +	else +		return ipc_tx_message_nowait(hsw, header, NULL, 0); +} + +/* Stream ALSA trigger operations */ +int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, +	int wait) +{ +	int ret; + +	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; + +	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; + +	/* 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; +} + +int sst_hsw_stream_set_write_position(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u32 stage_id, u32 position) +{ +	u32 header; +	int ret; + +	trace_stream_write_position(stream->reply.stream_hw_id, position); + +	header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | +		IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); +	header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); +	header |= (IPC_STG_SET_WRITE_POSITION << IPC_STG_TYPE_SHIFT); +	header |= (stage_id << IPC_STG_ID_SHIFT); +	stream->wpos.position = position; + +	ret = ipc_tx_message_nowait(hsw, header, &stream->wpos, +		sizeof(stream->wpos)); +	if (ret < 0) +		dev_err(hsw->dev, "error: stream %d set position %d failed\n", +			stream->reply.stream_hw_id, position); + +	return ret; +} + +/* 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_hsw_ipc_device_config_req config; +	u32 header; +	int ret; + +	trace_ipc_request("set device config", dev); + +	config.ssp_interface = dev; +	config.clock_frequency = mclk; +	config.mode = mode; +	config.clock_divider = clock_divider; + +	trace_hsw_device_config_req(&config); + +	header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS); + +	ret = ipc_tx_message_wait(hsw, header, &config, sizeof(config), +		NULL, 0); +	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) +{ +	u32 header, state_; +	int ret; + +	header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE); +	state_ = state; + +	trace_ipc_request("PM enter Dx state", state); + +	ret = ipc_tx_message_wait(hsw, header, &state_, sizeof(state_), +		dx, sizeof(*dx)); +	if (ret < 0) { +		dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state); +		return ret; +	} + +	dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", +		dx->entries_no, state); + +	memcpy(&hsw->dx, dx, sizeof(*dx)); +	return 0; +} + +/* Used to save state into hsw->dx_reply */ +int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item, +	u32 *offset, u32 *size, u32 *source) +{ +	struct sst_hsw_ipc_dx_memory_item *dx_mem; +	struct sst_hsw_ipc_dx_reply *dx_reply; +	int entry_no; + +	dx_reply = &hsw->dx; +	entry_no = dx_reply->entries_no; + +	trace_ipc_request("PM get Dx state", entry_no); + +	if (item >= entry_no) +		return -EINVAL; + +	dx_mem = &dx_reply->mem_info[item]; +	*offset = dx_mem->offset; +	*size = dx_mem->size; +	*source = dx_mem->source; + +	return 0; +} + +static int msg_empty_list_init(struct sst_hsw *hsw) +{ +	int i; + +	hsw->msg = kzalloc(sizeof(struct ipc_message) * +		IPC_EMPTY_LIST_SIZE, GFP_KERNEL); +	if (hsw->msg == NULL) +		return -ENOMEM; + +	for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { +		init_waitqueue_head(&hsw->msg[i].waitq); +		list_add(&hsw->msg[i].list, &hsw->empty_list); +	} + +	return 0; +} + +void sst_hsw_set_scratch_module(struct sst_hsw *hsw, +	struct sst_module *scratch) +{ +	hsw->scratch = scratch; +} + +struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) +{ +	return hsw->dsp; +} + +static struct sst_dsp_device hsw_dev = { +	.thread = hsw_irq_thread, +	.ops = &haswell_ops, +}; + +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_fw *hsw_sst_fw; +	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; +	INIT_LIST_HEAD(&hsw->stream_list); +	INIT_LIST_HEAD(&hsw->tx_list); +	INIT_LIST_HEAD(&hsw->rx_list); +	INIT_LIST_HEAD(&hsw->empty_list); +	init_waitqueue_head(&hsw->boot_wait); +	init_waitqueue_head(&hsw->wait_txq); + +	ret = msg_empty_list_init(hsw); +	if (ret < 0) +		return -ENOMEM; + +	/* start the IPC message thread */ +	init_kthread_worker(&hsw->kworker); +	hsw->tx_thread = kthread_run(kthread_worker_fn, +					   &hsw->kworker, "%s", +					   dev_name(hsw->dev)); +	if (IS_ERR(hsw->tx_thread)) { +		ret = PTR_ERR(hsw->tx_thread); +		dev_err(hsw->dev, "error: failed to create message TX task\n"); +		goto err_free_msg; +	} +	init_kthread_work(&hsw->kwork, ipc_tx_msgs); + +	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_err; +	} + +	/* keep the DSP in reset state for base FW loading */ +	sst_dsp_reset(hsw->dsp); + +	hsw_sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw); + +	if (hsw_sst_fw == NULL) { +		ret = -ENODEV; +		dev_err(dev, "error: failed to load firmware\n"); +		goto fw_err; +	} + +	/* 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: ADSP boot timeout\n"); +		goto boot_err; +	} + +	/* get the FW version */ +	sst_hsw_fw_get_version(hsw, &version); +	dev_info(hsw->dev, "FW loaded: type %d - version: %d.%d build %d\n", +		version.type, version.major, version.minor, version.build); + +	/* 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(hsw_sst_fw); +fw_err: +	sst_dsp_free(hsw->dsp); +dsp_err: +	kthread_stop(hsw->tx_thread); +err_free_msg: +	kfree(hsw->msg); + +	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); +	sst_dsp_free(hsw->dsp); +	kfree(hsw->scratch); +	kthread_stop(hsw->tx_thread); +	kfree(hsw->msg); +} +EXPORT_SYMBOL_GPL(sst_hsw_dsp_free); diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h new file mode 100644 index 00000000000..2ac194a6d04 --- /dev/null +++ b/sound/soc/intel/sst-haswell-ipc.h @@ -0,0 +1,490 @@ +/* + * Intel SST Haswell/Broadwell IPC Support + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#ifndef __SST_HASWELL_IPC_H +#define __SST_HASWELL_IPC_H + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> + +#define SST_HSW_NO_CHANNELS		2 +#define SST_HSW_MAX_DX_REGIONS		14 + +#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 + +struct sst_hsw; +struct sst_hsw_stream; +struct sst_hsw_log_stream; +struct sst_pdata; +struct sst_module; +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, +}; + +/* 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, +}; + +/* 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_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; +	u16 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_mute(struct sst_hsw *hsw, struct sst_hsw_stream *stream, +	u32 stage_id, u32 channel); +int sst_hsw_stream_unmute(struct sst_hsw *hsw, struct sst_hsw_stream *stream, +	u32 stage_id, u32 channel); + +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); + +int sst_hsw_stream_set_volume_curve(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u64 curve_duration, +	enum sst_hsw_volume_curve curve); + +/* Global Mixer Controls - */ +int sst_hsw_mixer_mute(struct sst_hsw *hsw, u32 stage_id, u32 channel); +int sst_hsw_mixer_unmute(struct sst_hsw *hsw, u32 stage_id, u32 channel); + +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); + +int sst_hsw_mixer_set_volume_curve(struct sst_hsw *hsw, +	u64 curve_duration, enum sst_hsw_volume_curve curve); + +/* 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, enum sst_hsw_module_id module_id, +	u32 entry_point); +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); +int sst_hsw_stream_get_hw_id(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream); +int sst_hsw_stream_get_mixer_id(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream); +u32 sst_hsw_stream_get_read_reg(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream); +u32 sst_hsw_stream_get_pointer_reg(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream); +u32 sst_hsw_stream_get_peak_reg(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u32 channel); +u32 sst_hsw_stream_get_vol_reg(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u32 channel); +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); +int sst_hsw_stream_set_write_position(struct sst_hsw *hsw, +	struct sst_hsw_stream *stream, u32 stage_id, 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); +int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item, +	u32 *offset, u32 *size, u32 *source); + +/* 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); +void sst_hsw_set_scratch_module(struct sst_hsw *hsw, +	struct sst_module *scratch); + +#endif diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c new file mode 100644 index 00000000000..058efb17c56 --- /dev/null +++ b/sound/soc/intel/sst-haswell-pcm.c @@ -0,0 +1,906 @@ +/* + * Intel SST Haswell/Broadwell PCM Support + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/delay.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 "sst-haswell-ipc.h" +#include "sst-dsp-priv.h" +#include "sst-dsp.h" + +#define HSW_PCM_COUNT		6 +#define HSW_VOLUME_MAX		0x7FFFFFFF	/* 0dB */ + +/* 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 + +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, +	.formats		= SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_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, +}; + +/* private data for each PCM DSP stream */ +struct hsw_pcm_data { +	int dai_id; +	struct sst_hsw_stream *stream; +	u32 volume[2]; +	struct snd_pcm_substream *substream; +	struct snd_compr_stream *cstream; +	unsigned int wpos; +	struct mutex mutex; +	bool allocated; +}; + +/* private data for the driver */ +struct hsw_priv_data { +	/* runtime DSP */ +	struct sst_hsw *hsw; + +	/* page tables */ +	struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; + +	/* DAI data */ +	struct hsw_pcm_data pcm[HSW_PCM_COUNT]; +}; + +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_platform *platform = snd_soc_kcontrol_platform(kcontrol); +	struct soc_mixer_control *mc = +		(struct soc_mixer_control *)kcontrol->private_value; +	struct hsw_priv_data *pdata = +		snd_soc_platform_get_drvdata(platform); +	struct hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg]; +	struct sst_hsw *hsw = pdata->hsw; +	u32 volume; + +	mutex_lock(&pcm_data->mutex); + +	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]); +		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]); +		sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 2, 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); +	} + +	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_platform *platform = snd_soc_kcontrol_platform(kcontrol); +	struct soc_mixer_control *mc = +		(struct soc_mixer_control *)kcontrol->private_value; +	struct hsw_priv_data *pdata = +		snd_soc_platform_get_drvdata(platform); +	struct hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg]; +	struct sst_hsw *hsw = pdata->hsw; +	u32 volume; + +	mutex_lock(&pcm_data->mutex); + +	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]); +		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); +	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_platform *platform = snd_soc_kcontrol_platform(kcontrol); +	struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); +	struct sst_hsw *hsw = pdata->hsw; +	u32 volume; + +	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, 2, 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); +	} + +	return 0; +} + +static int hsw_volume_get(struct snd_kcontrol *kcontrol, +				struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); +	struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); +	struct sst_hsw *hsw = pdata->hsw; +	unsigned int volume = 0; + +	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); + +	return 0; +} + +/* 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), 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), 0, +		hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), +	/* Loopback volume */ +	SOC_DOUBLE_EXT_TLV("Loopback Capture Volume", 3, 0, 8, +		ARRAY_SIZE(volume_map), 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), 0, +		hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), +}; + +/* 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%zu 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_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_platform_get_drvdata(rtd->platform); +	struct hsw_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); +	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; + +	/* 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: +		stream_type = SST_HSW_STREAM_TYPE_SYSTEM; +		module_id = SST_HSW_MODULE_PCM_SYSTEM; +		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; +	case 4: +		stream_type = SST_HSW_STREAM_TYPE_CAPTURE; +		module_id = SST_HSW_MODULE_PCM_CAPTURE; +		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_24BIT; +		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; +	} + +	/* we only support stereo atm */ +	channels = params_channels(params); +	if (channels != 2) { +		dev_err(rtd->dev, "error: invalid channels %d\n", channels); +		return -EINVAL; +	} + +	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; +	} + +	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); +	if (ret < 0) { +		dev_err(rtd->dev, "error: could not allocate %d bytes for PCM %d\n", +			params_buffer_bytes(params), ret); +		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; +	} + +	/* we use hardcoded memory offsets atm, will be updated for new FW */ +	if (stream_type == SST_HSW_STREAM_TYPE_CAPTURE) { +		sst_hsw_stream_set_module_info(hsw, pcm_data->stream, +			SST_HSW_MODULE_PCM_CAPTURE, module_data->entry); +		sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, +			0x449400, 0x4000); +		sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, +			0x400000, 0); +	} else { /* stream_type == SST_HSW_STREAM_TYPE_SYSTEM */ +		sst_hsw_stream_set_module_info(hsw, pcm_data->stream, +			SST_HSW_MODULE_PCM_SYSTEM, module_data->entry); + +		sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, +			module_data->offset, module_data->size); +		sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, +			0x44d400, 0x3800); + +		sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, +			module_data->offset, module_data->size); +		sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, +			0x400000, 0); +	} + +	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; +	} +	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_hw_free(struct snd_pcm_substream *substream) +{ +	snd_pcm_lib_free_pages(substream); +	return 0; +} + +static int hsw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct hsw_priv_data *pdata = +		snd_soc_platform_get_drvdata(rtd->platform); +	struct hsw_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); +	struct sst_hsw *hsw = pdata->hsw; + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +	case SNDRV_PCM_TRIGGER_RESUME: +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +		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_pause(hsw, pcm_data->stream, 0); +		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; +	u32 pos; + +	pos = frames_to_bytes(runtime, +		(runtime->control->appl_ptr % runtime->buffer_size)); + +	dev_dbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); + +	/* 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_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_platform_get_drvdata(rtd->platform); +	struct hsw_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); +	struct sst_hsw *hsw = pdata->hsw; +	snd_pcm_uframes_t offset; +	uint64_t ppos; +	u32 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_dbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", +		position, ppos); +	return offset; +} + +static int hsw_pcm_open(struct snd_pcm_substream *substream) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct hsw_priv_data *pdata = +		snd_soc_platform_get_drvdata(rtd->platform); +	struct hsw_pcm_data *pcm_data; +	struct sst_hsw *hsw = pdata->hsw; + +	pcm_data = &pdata->pcm[rtd->cpu_dai->id]; + +	mutex_lock(&pcm_data->mutex); + +	snd_soc_pcm_set_drvdata(rtd, pcm_data); +	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"); +		mutex_unlock(&pcm_data->mutex); +		return -EINVAL; +	} + +	/* 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]); + +	mutex_unlock(&pcm_data->mutex); +	return 0; +} + +static int hsw_pcm_close(struct snd_pcm_substream *substream) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct hsw_priv_data *pdata = +		snd_soc_platform_get_drvdata(rtd->platform); +	struct hsw_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); +	struct sst_hsw *hsw = pdata->hsw; +	int ret; + +	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 = 0; +	pcm_data->stream = NULL; + +out: +	mutex_unlock(&pcm_data->mutex); +	return ret; +} + +static struct snd_pcm_ops hsw_pcm_ops = { +	.open		= hsw_pcm_open, +	.close		= hsw_pcm_close, +	.ioctl		= snd_pcm_lib_ioctl, +	.hw_params	= hsw_pcm_hw_params, +	.hw_free	= hsw_pcm_hw_free, +	.trigger	= hsw_pcm_trigger, +	.pointer	= hsw_pcm_pointer, +	.page		= snd_pcm_sgbuf_ops_page, +}; + +static void hsw_pcm_free(struct snd_pcm *pcm) +{ +	snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ +	struct snd_pcm *pcm = rtd->pcm; +	struct snd_soc_platform *platform = rtd->platform; +	struct sst_pdata *pdata = dev_get_platdata(platform->dev); +	struct device *dev = pdata->dma_dev; +	int ret = 0; + +	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || +			pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { +		ret = snd_pcm_lib_preallocate_pages_for_all(pcm, +			SNDRV_DMA_TYPE_DEV_SG, +			dev, +			hsw_pcm_hardware.buffer_bytes_max, +			hsw_pcm_hardware.buffer_bytes_max); +		if (ret) { +			dev_err(rtd->dev, "dma buffer allocation failed %d\n", +				ret); +			return ret; +		} +	} + +	return ret; +} + +#define HSW_FORMATS \ +	(SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE |\ +	 SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver hsw_dais[] = { +	{ +		.name  = "System Pin", +		.playback = { +			.stream_name = "System Playback", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_48000, +			.formats = SNDRV_PCM_FMTBIT_S16_LE, +		}, +	}, +	{ +		/* PCM */ +		.name  = "Offload0 Pin", +		.playback = { +			.stream_name = "Offload0 Playback", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_192000, +			.formats = HSW_FORMATS, +		}, +	}, +	{ +		/* PCM */ +		.name  = "Offload1 Pin", +		.playback = { +			.stream_name = "Offload1 Playback", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_192000, +			.formats = HSW_FORMATS, +		}, +	}, +	{ +		.name  = "Loopback Pin", +		.capture = { +			.stream_name = "Loopback Capture", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_192000, +			.formats = HSW_FORMATS, +		}, +	}, +	{ +		.name  = "Capture Pin", +		.capture = { +			.stream_name = "Analog Capture", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_192000, +			.formats = HSW_FORMATS, +		}, +	}, +}; + +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_platform *platform) +{ +	struct sst_pdata *pdata = dev_get_platdata(platform->dev); +	struct hsw_priv_data *priv_data; +	struct device *dma_dev; +	int i, ret = 0; + +	if (!pdata) +		return -ENODEV; + +	dma_dev = pdata->dma_dev; + +	priv_data = devm_kzalloc(platform->dev, sizeof(*priv_data), GFP_KERNEL); +	priv_data->hsw = pdata->dsp; +	snd_soc_platform_set_drvdata(platform, priv_data); + +	/* allocate DSP buffer page tables */ +	for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { + +		mutex_init(&priv_data->pcm[i].mutex); + +		/* playback */ +		if (hsw_dais[i].playback.channels_min) { +			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) { +			ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, +				PAGE_SIZE, &priv_data->dmab[i][1]); +			if (ret < 0) +				goto err; +		} +	} + +	return 0; + +err: +	for (;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 int hsw_pcm_remove(struct snd_soc_platform *platform) +{ +	struct hsw_priv_data *priv_data = +		snd_soc_platform_get_drvdata(platform); +	int i; + +	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]); +	} + +	return 0; +} + +static struct snd_soc_platform_driver hsw_soc_platform = { +	.probe		= hsw_pcm_probe, +	.remove		= hsw_pcm_remove, +	.ops		= &hsw_pcm_ops, +	.pcm_new	= hsw_pcm_new, +	.pcm_free	= hsw_pcm_free, +	.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 const struct snd_soc_component_driver hsw_dai_component = { +	.name		= "haswell-dai", +}; + +static int hsw_pcm_dev_probe(struct platform_device *pdev) +{ +	struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); +	int ret; + +	ret = sst_hsw_dsp_init(&pdev->dev, sst_pdata); +	if (ret < 0) +		return -ENODEV; + +	ret = snd_soc_register_platform(&pdev->dev, &hsw_soc_platform); +	if (ret < 0) +		goto err_plat; + +	ret = snd_soc_register_component(&pdev->dev, &hsw_dai_component, +		hsw_dais, ARRAY_SIZE(hsw_dais)); +	if (ret < 0) +		goto err_comp; + +	return 0; + +err_comp: +	snd_soc_unregister_platform(&pdev->dev); +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); + +	snd_soc_unregister_platform(&pdev->dev); +	snd_soc_unregister_component(&pdev->dev); +	sst_hsw_dsp_free(&pdev->dev, sst_pdata); + +	return 0; +} + +static struct platform_driver hsw_pcm_driver = { +	.driver = { +		.name = "haswell-pcm-audio", +		.owner = THIS_MODULE, +	}, + +	.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/sst-mfld-dsp.h b/sound/soc/intel/sst-mfld-dsp.h new file mode 100644 index 00000000000..8d482d76475 --- /dev/null +++ b/sound/soc/intel/sst-mfld-dsp.h @@ -0,0 +1,126 @@ +#ifndef __SST_MFLD_DSP_H__ +#define __SST_MFLD_DSP_H__ +/* + *  sst_mfld_dsp.h - Intel SST Driver for audio engine + * + *  Copyright (C) 2008-12 Intel Corporation + *  Authors:	Vinod Koul <vinod.koul@linux.intel.com> + *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; version 2 of the License. + * + *  This program is distributed in the hope that it will be useful, but + *  WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +enum sst_codec_types { +	/*  AUDIO/MUSIC	CODEC Type Definitions */ +	SST_CODEC_TYPE_UNKNOWN = 0, +	SST_CODEC_TYPE_PCM,	/* Pass through Audio codec */ +	SST_CODEC_TYPE_MP3, +	SST_CODEC_TYPE_MP24, +	SST_CODEC_TYPE_AAC, +	SST_CODEC_TYPE_AACP, +	SST_CODEC_TYPE_eAACP, +}; + +enum stream_type { +	SST_STREAM_TYPE_NONE = 0, +	SST_STREAM_TYPE_MUSIC = 1, +}; + +struct snd_pcm_params { +	u8 num_chan;	/* 1=Mono, 2=Stereo */ +	u8 pcm_wd_sz;	/* 16/24 - bit*/ +	u32 reserved;	/* Bitrate in bits per second */ +	u32 sfreq;	/* Sampling rate in Hz */ +	u8 use_offload_path; +	u8 reserved2; +	u16 reserved3; +	u8 channel_map[8]; +} __packed; + +/* MP3 Music Parameters Message */ +struct snd_mp3_params { +	u8  num_chan;	/* 1=Mono, 2=Stereo	*/ +	u8  pcm_wd_sz; /* 16/24 - bit*/ +	u8  crc_check; /* crc_check - disable (0) or enable (1) */ +	u8  reserved1; /* unused*/ +	u16 reserved2;	/* Unused */ +} __packed; + +#define AAC_BIT_STREAM_ADTS		0 +#define AAC_BIT_STREAM_ADIF		1 +#define AAC_BIT_STREAM_RAW		2 + +/* AAC Music Parameters Message */ +struct snd_aac_params { +	u8 num_chan; /* 1=Mono, 2=Stereo*/ +	u8 pcm_wd_sz; /* 16/24 - bit*/ +	u8 bdownsample; /*SBR downsampling 0 - disable 1 -enabled AAC+ only */ +	u8 bs_format; /* input bit stream format adts=0, adif=1, raw=2 */ +	u16  reser2; +	u32 externalsr; /*sampling rate of basic AAC raw bit stream*/ +	u8 sbr_signalling;/*disable/enable/set automode the SBR tool.AAC+*/ +	u8 reser1; +	u16  reser3; +} __packed; + +/* WMA Music Parameters Message */ +struct snd_wma_params { +	u8  num_chan;	/* 1=Mono, 2=Stereo */ +	u8  pcm_wd_sz;	/* 16/24 - bit*/ +	u32 brate;	/* Use the hard coded value. */ +	u32 sfreq;	/* Sampling freq eg. 8000, 441000, 48000 */ +	u32 channel_mask;  /* Channel Mask */ +	u16 format_tag;	/* Format Tag */ +	u16 block_align;	/* packet size */ +	u16 wma_encode_opt;/* Encoder option */ +	u8 op_align;	/* op align 0- 16 bit, 1- MSB, 2 LSB */ +	u8 reserved;	/* reserved */ +} __packed; + +/* Codec params struture */ +union  snd_sst_codec_params { +	struct snd_pcm_params pcm_params; +	struct snd_mp3_params mp3_params; +	struct snd_aac_params aac_params; +	struct snd_wma_params wma_params; +} __packed; + +/* Address and size info of a frame buffer */ +struct sst_address_info { +	u32 addr; /* Address at IA */ +	u32 size; /* Size of the buffer */ +}; + +struct snd_sst_alloc_params_ext { +	struct sst_address_info  ring_buf_info[8]; +	u8 sg_count; +	u8 reserved; +	u16 reserved2; +	u32 frag_size;	/*Number of samples after which period elapsed +				  message is sent valid only if path  = 0*/ +} __packed; + +struct snd_sst_stream_params { +	union snd_sst_codec_params uc; +} __packed; + +struct snd_sst_params { +	u32 stream_id; +	u8 codec; +	u8 ops; +	u8 stream_type; +	u8 device_type; +	struct snd_sst_stream_params sparams; +	struct snd_sst_alloc_params_ext aparams; +}; + +#endif /* __SST_MFLD_DSP_H__ */ diff --git a/sound/soc/intel/sst-mfld-platform-compress.c b/sound/soc/intel/sst-mfld-platform-compress.c new file mode 100644 index 00000000000..02abd19fce1 --- /dev/null +++ b/sound/soc/intel/sst-mfld-platform-compress.c @@ -0,0 +1,237 @@ +/* + *  sst_mfld_platform.c - Intel MID Platform driver + * + *  Copyright (C) 2010-2014 Intel Corp + *  Author: Vinod Koul <vinod.koul@intel.com> + *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; version 2 of the License. + * + *  This program is distributed in the hope that it will be useful, but + *  WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include "sst-mfld-platform.h" + +/* compress stream operations */ +static void sst_compr_fragment_elapsed(void *arg) +{ +	struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg; + +	pr_debug("fragment elapsed by driver\n"); +	if (cstream) +		snd_compr_fragment_elapsed(cstream); +} + +static void sst_drain_notify(void *arg) +{ +	struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg; + +	pr_debug("drain notify by driver\n"); +	if (cstream) +		snd_compr_drain_notify(cstream); +} + +static int sst_platform_compr_open(struct snd_compr_stream *cstream) +{ + +	int ret_val = 0; +	struct snd_compr_runtime *runtime = cstream->runtime; +	struct sst_runtime_stream *stream; + +	stream = kzalloc(sizeof(*stream), GFP_KERNEL); +	if (!stream) +		return -ENOMEM; + +	spin_lock_init(&stream->status_lock); + +	/* get the sst ops */ +	if (!sst || !try_module_get(sst->dev->driver->owner)) { +		pr_err("no device available to run\n"); +		ret_val = -ENODEV; +		goto out_ops; +	} +	stream->compr_ops = sst->compr_ops; + +	stream->id = 0; +	sst_set_stream_status(stream, SST_PLATFORM_INIT); +	runtime->private_data = stream; +	return 0; +out_ops: +	kfree(stream); +	return ret_val; +} + +static int sst_platform_compr_free(struct snd_compr_stream *cstream) +{ +	struct sst_runtime_stream *stream; +	int ret_val = 0, str_id; + +	stream = cstream->runtime->private_data; +	/*need to check*/ +	str_id = stream->id; +	if (str_id) +		ret_val = stream->compr_ops->close(str_id); +	module_put(sst->dev->driver->owner); +	kfree(stream); +	pr_debug("%s: %d\n", __func__, ret_val); +	return 0; +} + +static int sst_platform_compr_set_params(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; + +	stream = cstream->runtime->private_data; +	/* construct fw structure for this*/ +	memset(&str_params, 0, sizeof(str_params)); + +	str_params.ops = STREAM_OPS_PLAYBACK; +	str_params.stream_type = SST_STREAM_TYPE_MUSIC; +	str_params.device_type = SND_SST_DEVICE_COMPRESS; + +	switch (params->codec.id) { +	case SND_AUDIOCODEC_MP3: { +		str_params.codec = SST_CODEC_TYPE_MP3; +		str_params.sparams.uc.mp3_params.num_chan = params->codec.ch_in; +		str_params.sparams.uc.mp3_params.pcm_wd_sz = 16; +		break; +	} + +	case SND_AUDIOCODEC_AAC: { +		str_params.codec = SST_CODEC_TYPE_AAC; +		str_params.sparams.uc.aac_params.num_chan = params->codec.ch_in; +		str_params.sparams.uc.aac_params.pcm_wd_sz = 16; +		if (params->codec.format == SND_AUDIOSTREAMFORMAT_MP4ADTS) +			str_params.sparams.uc.aac_params.bs_format = +							AAC_BIT_STREAM_ADTS; +		else if (params->codec.format == SND_AUDIOSTREAMFORMAT_RAW) +			str_params.sparams.uc.aac_params.bs_format = +							AAC_BIT_STREAM_RAW; +		else { +			pr_err("Undefined format%d\n", params->codec.format); +			return -EINVAL; +		} +		str_params.sparams.uc.aac_params.externalsr = +						params->codec.sample_rate; +		break; +	} + +	default: +		pr_err("codec not supported, id =%d\n", params->codec.id); +		return -EINVAL; +	} + +	str_params.aparams.ring_buf_info[0].addr  = +					virt_to_phys(cstream->runtime->buffer); +	str_params.aparams.ring_buf_info[0].size = +					cstream->runtime->buffer_size; +	str_params.aparams.sg_count = 1; +	str_params.aparams.frag_size = cstream->runtime->fragment_size; + +	cb.param = cstream; +	cb.compr_cb = sst_compr_fragment_elapsed; +	cb.drain_cb_param = cstream; +	cb.drain_notify = sst_drain_notify; + +	retval = stream->compr_ops->open(&str_params, &cb); +	if (retval < 0) { +		pr_err("stream allocation failed %d\n", retval); +		return retval; +	} + +	stream->id = retval; +	return 0; +} + +static int sst_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd) +{ +	struct sst_runtime_stream *stream = +		cstream->runtime->private_data; + +	return stream->compr_ops->control(cmd, stream->id); +} + +static int sst_platform_compr_pointer(struct snd_compr_stream *cstream, +					struct snd_compr_tstamp *tstamp) +{ +	struct sst_runtime_stream *stream; + +	stream  = cstream->runtime->private_data; +	stream->compr_ops->tstamp(stream->id, tstamp); +	tstamp->byte_offset = tstamp->copied_total % +				 (u32)cstream->runtime->buffer_size; +	pr_debug("calc bytes offset/copied bytes as %d\n", tstamp->byte_offset); +	return 0; +} + +static int sst_platform_compr_ack(struct snd_compr_stream *cstream, +					size_t bytes) +{ +	struct sst_runtime_stream *stream; + +	stream  = cstream->runtime->private_data; +	stream->compr_ops->ack(stream->id, (unsigned long)bytes); +	stream->bytes_written += bytes; + +	return 0; +} + +static int sst_platform_compr_get_caps(struct snd_compr_stream *cstream, +					struct snd_compr_caps *caps) +{ +	struct sst_runtime_stream *stream = +		cstream->runtime->private_data; + +	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) +{ +	struct sst_runtime_stream *stream = +		cstream->runtime->private_data; + +	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) +{ +	struct sst_runtime_stream *stream  = +		 cstream->runtime->private_data; + +	return stream->compr_ops->set_metadata(stream->id, metadata); +} + +struct snd_compr_ops sst_platform_compr_ops = { + +	.open = sst_platform_compr_open, +	.free = sst_platform_compr_free, +	.set_params = sst_platform_compr_set_params, +	.set_metadata = sst_platform_compr_set_metadata, +	.trigger = sst_platform_compr_trigger, +	.pointer = sst_platform_compr_pointer, +	.ack = sst_platform_compr_ack, +	.get_caps = sst_platform_compr_get_caps, +	.get_codec_caps = sst_platform_compr_get_codec_caps, +}; diff --git a/sound/soc/intel/sst-mfld-platform-pcm.c b/sound/soc/intel/sst-mfld-platform-pcm.c new file mode 100644 index 00000000000..7c790f51d25 --- /dev/null +++ b/sound/soc/intel/sst-mfld-platform-pcm.c @@ -0,0 +1,491 @@ +/* + *  sst_mfld_platform.c - Intel MID Platform driver + * + *  Copyright (C) 2010-2013 Intel Corp + *  Author: Vinod Koul <vinod.koul@intel.com> + *  Author: Harsha Priya <priya.harsha@intel.com> + *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; version 2 of the License. + * + *  This program is distributed in the hope that it will be useful, but + *  WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include "sst-mfld-platform.h" + +struct sst_device *sst; +static DEFINE_MUTEX(sst_lock); +extern struct snd_compr_ops sst_platform_compr_ops; + +int sst_register_dsp(struct sst_device *dev) +{ +	if (WARN_ON(!dev)) +		return -EINVAL; +	if (!try_module_get(dev->dev->driver->owner)) +		return -ENODEV; +	mutex_lock(&sst_lock); +	if (sst) { +		pr_err("we already have a device %s\n", sst->name); +		module_put(dev->dev->driver->owner); +		mutex_unlock(&sst_lock); +		return -EEXIST; +	} +	pr_debug("registering device %s\n", dev->name); +	sst = dev; +	mutex_unlock(&sst_lock); +	return 0; +} +EXPORT_SYMBOL_GPL(sst_register_dsp); + +int sst_unregister_dsp(struct sst_device *dev) +{ +	if (WARN_ON(!dev)) +		return -EINVAL; +	if (dev != sst) +		return -EINVAL; + +	mutex_lock(&sst_lock); + +	if (!sst) { +		mutex_unlock(&sst_lock); +		return -EIO; +	} + +	module_put(sst->dev->driver->owner); +	pr_debug("unreg %s\n", sst->name); +	sst = NULL; +	mutex_unlock(&sst_lock); +	return 0; +} +EXPORT_SYMBOL_GPL(sst_unregister_dsp); + +static struct snd_pcm_hardware sst_platform_pcm_hw = { +	.info =	(SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_DOUBLE | +			SNDRV_PCM_INFO_PAUSE | +			SNDRV_PCM_INFO_RESUME | +			SNDRV_PCM_INFO_MMAP| +			SNDRV_PCM_INFO_MMAP_VALID | +			SNDRV_PCM_INFO_BLOCK_TRANSFER | +			SNDRV_PCM_INFO_SYNC_START), +	.buffer_bytes_max = SST_MAX_BUFFER, +	.period_bytes_min = SST_MIN_PERIOD_BYTES, +	.period_bytes_max = SST_MAX_PERIOD_BYTES, +	.periods_min = SST_MIN_PERIODS, +	.periods_max = SST_MAX_PERIODS, +	.fifo_size = SST_FIFO_SIZE, +}; + +/* MFLD - MSIC */ +static struct snd_soc_dai_driver sst_platform_dai[] = { +{ +	.name = "Headset-cpu-dai", +	.id = 0, +	.playback = { +		.channels_min = SST_STEREO, +		.channels_max = SST_STEREO, +		.rates = SNDRV_PCM_RATE_48000, +		.formats = SNDRV_PCM_FMTBIT_S24_LE, +	}, +	.capture = { +		.channels_min = 1, +		.channels_max = 5, +		.rates = SNDRV_PCM_RATE_48000, +		.formats = SNDRV_PCM_FMTBIT_S24_LE, +	}, +}, +{ +	.name = "Compress-cpu-dai", +	.compress_dai = 1, +	.playback = { +		.channels_min = SST_STEREO, +		.channels_max = SST_STEREO, +		.rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +	}, +}, +}; + +/* helper functions */ +void sst_set_stream_status(struct sst_runtime_stream *stream, +					int state) +{ +	unsigned long flags; +	spin_lock_irqsave(&stream->status_lock, flags); +	stream->stream_status = state; +	spin_unlock_irqrestore(&stream->status_lock, flags); +} + +static inline int sst_get_stream_status(struct sst_runtime_stream *stream) +{ +	int state; +	unsigned long flags; + +	spin_lock_irqsave(&stream->status_lock, flags); +	state = stream->stream_status; +	spin_unlock_irqrestore(&stream->status_lock, flags); +	return state; +} + +static void sst_fill_pcm_params(struct snd_pcm_substream *substream, +				struct sst_pcm_params *param) +{ + +	param->num_chan = (u8) substream->runtime->channels; +	param->pcm_wd_sz = substream->runtime->sample_bits; +	param->reserved = 0; +	param->sfreq = substream->runtime->rate; +	param->ring_buffer_size = snd_pcm_lib_buffer_bytes(substream); +	param->period_count = substream->runtime->period_size; +	param->ring_buffer_addr = virt_to_phys(substream->dma_buffer.area); +	pr_debug("period_cnt = %d\n", param->period_count); +	pr_debug("sfreq= %d, wd_sz = %d\n", param->sfreq, param->pcm_wd_sz); +} + +static int sst_platform_alloc_stream(struct snd_pcm_substream *substream) +{ +	struct sst_runtime_stream *stream = +			substream->runtime->private_data; +	struct sst_pcm_params param = {0}; +	struct sst_stream_params str_params = {0}; +	int ret_val; + +	/* set codec params and inform SST driver the same */ +	sst_fill_pcm_params(substream, ¶m); +	substream->runtime->dma_area = substream->dma_buffer.area; +	str_params.sparams = param; +	str_params.codec =  param.codec; +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +		str_params.ops = STREAM_OPS_PLAYBACK; +		str_params.device_type = substream->pcm->device + 1; +		pr_debug("Playbck stream,Device %d\n", +					substream->pcm->device); +	} else { +		str_params.ops = STREAM_OPS_CAPTURE; +		str_params.device_type = SND_SST_DEVICE_CAPTURE; +		pr_debug("Capture stream,Device %d\n", +					substream->pcm->device); +	} +	ret_val = stream->ops->open(&str_params); +	pr_debug("SST_SND_PLAY/CAPTURE ret_val = %x\n", ret_val); +	if (ret_val < 0) +		return ret_val; + +	stream->stream_info.str_id = ret_val; +	pr_debug("str id :  %d\n", stream->stream_info.str_id); +	return ret_val; +} + +static void sst_period_elapsed(void *mad_substream) +{ +	struct snd_pcm_substream *substream = mad_substream; +	struct sst_runtime_stream *stream; +	int status; + +	if (!substream || !substream->runtime) +		return; +	stream = substream->runtime->private_data; +	if (!stream) +		return; +	status = sst_get_stream_status(stream); +	if (status != SST_PLATFORM_RUNNING) +		return; +	snd_pcm_period_elapsed(substream); +} + +static int sst_platform_init_stream(struct snd_pcm_substream *substream) +{ +	struct sst_runtime_stream *stream = +			substream->runtime->private_data; +	int ret_val; + +	pr_debug("setting buffer ptr param\n"); +	sst_set_stream_status(stream, SST_PLATFORM_INIT); +	stream->stream_info.period_elapsed = sst_period_elapsed; +	stream->stream_info.mad_substream = substream; +	stream->stream_info.buffer_ptr = 0; +	stream->stream_info.sfreq = substream->runtime->rate; +	ret_val = stream->ops->device_control( +			SST_SND_STREAM_INIT, &stream->stream_info); +	if (ret_val) +		pr_err("control_set ret error %d\n", ret_val); +	return ret_val; + +} +/* end -- helper functions */ + +static int sst_platform_open(struct snd_pcm_substream *substream) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct sst_runtime_stream *stream; +	int ret_val; + +	pr_debug("sst_platform_open called\n"); + +	snd_soc_set_runtime_hwparams(substream, &sst_platform_pcm_hw); +	ret_val = snd_pcm_hw_constraint_integer(runtime, +						SNDRV_PCM_HW_PARAM_PERIODS); +	if (ret_val < 0) +		return ret_val; + +	stream = kzalloc(sizeof(*stream), GFP_KERNEL); +	if (!stream) +		return -ENOMEM; +	spin_lock_init(&stream->status_lock); + +	/* get the sst ops */ +	mutex_lock(&sst_lock); +	if (!sst) { +		pr_err("no device available to run\n"); +		mutex_unlock(&sst_lock); +		kfree(stream); +		return -ENODEV; +	} +	if (!try_module_get(sst->dev->driver->owner)) { +		mutex_unlock(&sst_lock); +		kfree(stream); +		return -ENODEV; +	} +	stream->ops = sst->ops; +	mutex_unlock(&sst_lock); + +	stream->stream_info.str_id = 0; +	sst_set_stream_status(stream, SST_PLATFORM_INIT); +	stream->stream_info.mad_substream = substream; +	/* allocate memory for SST API set */ +	runtime->private_data = stream; + +	return 0; +} + +static int sst_platform_close(struct snd_pcm_substream *substream) +{ +	struct sst_runtime_stream *stream; +	int ret_val = 0, str_id; + +	pr_debug("sst_platform_close called\n"); +	stream = substream->runtime->private_data; +	str_id = stream->stream_info.str_id; +	if (str_id) +		ret_val = stream->ops->close(str_id); +	module_put(sst->dev->driver->owner); +	kfree(stream); +	return ret_val; +} + +static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream) +{ +	struct sst_runtime_stream *stream; +	int ret_val = 0, str_id; + +	pr_debug("sst_platform_pcm_prepare called\n"); +	stream = substream->runtime->private_data; +	str_id = stream->stream_info.str_id; +	if (stream->stream_info.str_id) { +		ret_val = stream->ops->device_control( +				SST_SND_DROP, &str_id); +		return ret_val; +	} + +	ret_val = sst_platform_alloc_stream(substream); +	if (ret_val < 0) +		return ret_val; +	snprintf(substream->pcm->id, sizeof(substream->pcm->id), +			"%d", stream->stream_info.str_id); + +	ret_val = sst_platform_init_stream(substream); +	if (ret_val) +		return ret_val; +	substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER; +	return ret_val; +} + +static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, +					int cmd) +{ +	int ret_val = 0, str_id; +	struct sst_runtime_stream *stream; +	int str_cmd, status; + +	pr_debug("sst_platform_pcm_trigger called\n"); +	stream = substream->runtime->private_data; +	str_id = stream->stream_info.str_id; +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +		pr_debug("sst: Trigger Start\n"); +		str_cmd = SST_SND_START; +		status = SST_PLATFORM_RUNNING; +		stream->stream_info.mad_substream = substream; +		break; +	case SNDRV_PCM_TRIGGER_STOP: +		pr_debug("sst: in stop\n"); +		str_cmd = SST_SND_DROP; +		status = SST_PLATFORM_DROPPED; +		break; +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +		pr_debug("sst: in pause\n"); +		str_cmd = SST_SND_PAUSE; +		status = SST_PLATFORM_PAUSED; +		break; +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +		pr_debug("sst: in pause release\n"); +		str_cmd = SST_SND_RESUME; +		status = SST_PLATFORM_RUNNING; +		break; +	default: +		return -EINVAL; +	} +	ret_val = stream->ops->device_control(str_cmd, &str_id); +	if (!ret_val) +		sst_set_stream_status(stream, status); + +	return ret_val; +} + + +static snd_pcm_uframes_t sst_platform_pcm_pointer +			(struct snd_pcm_substream *substream) +{ +	struct sst_runtime_stream *stream; +	int ret_val, status; +	struct pcm_stream_info *str_info; + +	stream = substream->runtime->private_data; +	status = sst_get_stream_status(stream); +	if (status == SST_PLATFORM_INIT) +		return 0; +	str_info = &stream->stream_info; +	ret_val = stream->ops->device_control( +				SST_SND_BUFFER_POINTER, str_info); +	if (ret_val) { +		pr_err("sst: error code = %d\n", ret_val); +		return ret_val; +	} +	return stream->stream_info.buffer_ptr; +} + +static int sst_platform_pcm_hw_params(struct snd_pcm_substream *substream, +		struct snd_pcm_hw_params *params) +{ +	snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); +	memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); + +	return 0; +} + +static int sst_platform_pcm_hw_free(struct snd_pcm_substream *substream) +{ +	return snd_pcm_lib_free_pages(substream); +} + +static struct snd_pcm_ops sst_platform_ops = { +	.open = sst_platform_open, +	.close = sst_platform_close, +	.ioctl = snd_pcm_lib_ioctl, +	.prepare = sst_platform_pcm_prepare, +	.trigger = sst_platform_pcm_trigger, +	.pointer = sst_platform_pcm_pointer, +	.hw_params = sst_platform_pcm_hw_params, +	.hw_free = sst_platform_pcm_hw_free, +}; + +static void sst_pcm_free(struct snd_pcm *pcm) +{ +	pr_debug("sst_pcm_free called\n"); +	snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int sst_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ +	struct snd_pcm *pcm = rtd->pcm; +	int retval = 0; + +	pr_debug("sst_pcm_new called\n"); +	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || +			pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { +		retval =  snd_pcm_lib_preallocate_pages_for_all(pcm, +			SNDRV_DMA_TYPE_CONTINUOUS, +			snd_dma_continuous_data(GFP_KERNEL), +			SST_MIN_BUFFER, SST_MAX_BUFFER); +		if (retval) { +			pr_err("dma buffer allocationf fail\n"); +			return retval; +		} +	} +	return retval; +} + +static struct snd_soc_platform_driver sst_soc_platform_drv = { +	.ops		= &sst_platform_ops, +	.compr_ops	= &sst_platform_compr_ops, +	.pcm_new	= sst_pcm_new, +	.pcm_free	= sst_pcm_free, +}; + +static const struct snd_soc_component_driver sst_component = { +	.name		= "sst", +}; + + +static int sst_platform_probe(struct platform_device *pdev) +{ +	int ret; + +	pr_debug("sst_platform_probe called\n"); +	sst = NULL; +	ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv); +	if (ret) { +		pr_err("registering soc platform failed\n"); +		return ret; +	} + +	ret = snd_soc_register_component(&pdev->dev, &sst_component, +				sst_platform_dai, ARRAY_SIZE(sst_platform_dai)); +	if (ret) { +		pr_err("registering cpu dais failed\n"); +		snd_soc_unregister_platform(&pdev->dev); +	} +	return ret; +} + +static int sst_platform_remove(struct platform_device *pdev) +{ + +	snd_soc_unregister_component(&pdev->dev); +	snd_soc_unregister_platform(&pdev->dev); +	pr_debug("sst_platform_remove success\n"); +	return 0; +} + +static struct platform_driver sst_platform_driver = { +	.driver		= { +		.name		= "sst-mfld-platform", +		.owner		= THIS_MODULE, +	}, +	.probe		= sst_platform_probe, +	.remove		= sst_platform_remove, +}; + +module_platform_driver(sst_platform_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sst-mfld-platform"); diff --git a/sound/soc/intel/sst-mfld-platform.h b/sound/soc/intel/sst-mfld-platform.h new file mode 100644 index 00000000000..6c5e7dc49e3 --- /dev/null +++ b/sound/soc/intel/sst-mfld-platform.h @@ -0,0 +1,152 @@ +/* + *  sst_mfld_platform.h - Intel MID Platform driver header file + * + *  Copyright (C) 2010 Intel Corp + *  Author: Vinod Koul <vinod.koul@intel.com> + *  Author: Harsha Priya <priya.harsha@intel.com> + *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; version 2 of the License. + * + *  This program is distributed in the hope that it will be useful, but + *  WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#ifndef __SST_PLATFORMDRV_H__ +#define __SST_PLATFORMDRV_H__ + +#include "sst-mfld-dsp.h" + +extern struct sst_device *sst; + +#define SST_MONO		1 +#define SST_STEREO		2 +#define SST_MAX_CAP		5 + +#define SST_MAX_BUFFER		(800*1024) +#define SST_MIN_BUFFER		(800*1024) +#define SST_MIN_PERIOD_BYTES	32 +#define SST_MAX_PERIOD_BYTES	SST_MAX_BUFFER +#define SST_MIN_PERIODS		2 +#define SST_MAX_PERIODS		(1024*2) +#define SST_FIFO_SIZE		0 + +struct pcm_stream_info { +	int str_id; +	void *mad_substream; +	void (*period_elapsed) (void *mad_substream); +	unsigned long long buffer_ptr; +	int sfreq; +}; + +enum sst_drv_status { +	SST_PLATFORM_INIT = 1, +	SST_PLATFORM_STARTED, +	SST_PLATFORM_RUNNING, +	SST_PLATFORM_PAUSED, +	SST_PLATFORM_DROPPED, +}; + +enum sst_controls { +	SST_SND_ALLOC =			0x00, +	SST_SND_PAUSE =			0x01, +	SST_SND_RESUME =		0x02, +	SST_SND_DROP =			0x03, +	SST_SND_FREE =			0x04, +	SST_SND_BUFFER_POINTER =	0x05, +	SST_SND_STREAM_INIT =		0x06, +	SST_SND_START	 =		0x07, +	SST_MAX_CONTROLS =		0x07, +}; + +enum sst_stream_ops { +	STREAM_OPS_PLAYBACK = 0, +	STREAM_OPS_CAPTURE, +}; + +enum sst_audio_device_type { +	SND_SST_DEVICE_HEADSET = 1, +	SND_SST_DEVICE_IHF, +	SND_SST_DEVICE_VIBRA, +	SND_SST_DEVICE_HAPTIC, +	SND_SST_DEVICE_CAPTURE, +	SND_SST_DEVICE_COMPRESS, +}; + +/* PCM Parameters */ +struct sst_pcm_params { +	u16 codec;	/* codec type */ +	u8 num_chan;	/* 1=Mono, 2=Stereo */ +	u8 pcm_wd_sz;	/* 16/24 - bit*/ +	u32 reserved;	/* Bitrate in bits per second */ +	u32 sfreq;	/* Sampling rate in Hz */ +	u32 ring_buffer_size; +	u32 period_count;	/* period elapsed in samples*/ +	u32 ring_buffer_addr; +}; + +struct sst_stream_params { +	u32 result; +	u32 stream_id; +	u8 codec; +	u8 ops; +	u8 stream_type; +	u8 device_type; +	struct sst_pcm_params sparams; +}; + +struct sst_compress_cb { +	void *param; +	void (*compr_cb)(void *param); +	void *drain_cb_param; +	void (*drain_notify)(void *param); +}; + +struct compress_sst_ops { +	const char *name; +	int (*open) (struct snd_sst_params *str_params, +			struct sst_compress_cb *cb); +	int (*control) (unsigned int cmd, unsigned int str_id); +	int (*tstamp) (unsigned int str_id, struct snd_compr_tstamp *tstamp); +	int (*ack) (unsigned int str_id, unsigned long bytes); +	int (*close) (unsigned int str_id); +	int (*get_caps) (struct snd_compr_caps *caps); +	int (*get_codec_caps) (struct snd_compr_codec_caps *codec); +	int (*set_metadata) (unsigned int str_id, +			struct snd_compr_metadata *mdata); + +}; + +struct sst_ops { +	int (*open) (struct sst_stream_params *str_param); +	int (*device_control) (int cmd, void *arg); +	int (*close) (unsigned int str_id); +}; + +struct sst_runtime_stream { +	int     stream_status; +	unsigned int id; +	size_t bytes_written; +	struct pcm_stream_info stream_info; +	struct sst_ops *ops; +	struct compress_sst_ops *compr_ops; +	spinlock_t	status_lock; +}; + +struct sst_device { +	char *name; +	struct device *dev; +	struct sst_ops *ops; +	struct compress_sst_ops *compr_ops; +}; + +void sst_set_stream_status(struct sst_runtime_stream *stream, int state); +int sst_register_dsp(struct sst_device *sst); +int sst_unregister_dsp(struct sst_device *sst); +#endif  | 
