Commit addaeea9 authored by Mark Brown's avatar Mark Brown

Merge remote-tracking branches 'asoc/topic/hdmi', 'asoc/topic/intel',...

Merge remote-tracking branches 'asoc/topic/hdmi', 'asoc/topic/intel', 'asoc/topic/jack', 'asoc/topic/jz4740' and 'asoc/topic/lm49453' into asoc-next
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
#include <linux/sfi.h> #include <linux/sfi.h>
#define MAX_NUM_STREAMS_MRFLD 25
#define MAX_NUM_STREAMS MAX_NUM_STREAMS_MRFLD
enum sst_audio_task_id_mrfld { enum sst_audio_task_id_mrfld {
SST_TASK_ID_NONE = 0, SST_TASK_ID_NONE = 0,
SST_TASK_ID_SBA = 1, SST_TASK_ID_SBA = 1,
...@@ -73,6 +76,65 @@ struct sst_platform_data { ...@@ -73,6 +76,65 @@ struct sst_platform_data {
unsigned int strm_map_size; unsigned int strm_map_size;
}; };
struct sst_info {
u32 iram_start;
u32 iram_end;
bool iram_use;
u32 dram_start;
u32 dram_end;
bool dram_use;
u32 imr_start;
u32 imr_end;
bool imr_use;
u32 mailbox_start;
bool use_elf;
bool lpe_viewpt_rqd;
unsigned int max_streams;
u32 dma_max_len;
u8 num_probes;
};
struct sst_lib_dnld_info {
unsigned int mod_base;
unsigned int mod_end;
unsigned int mod_table_offset;
unsigned int mod_table_size;
bool mod_ddr_dnld;
};
struct sst_res_info {
unsigned int shim_offset;
unsigned int shim_size;
unsigned int shim_phy_addr;
unsigned int ssp0_offset;
unsigned int ssp0_size;
unsigned int dma0_offset;
unsigned int dma0_size;
unsigned int dma1_offset;
unsigned int dma1_size;
unsigned int iram_offset;
unsigned int iram_size;
unsigned int dram_offset;
unsigned int dram_size;
unsigned int mbox_offset;
unsigned int mbox_size;
unsigned int acpi_lpe_res_index;
unsigned int acpi_ddr_index;
unsigned int acpi_ipc_irq_index;
};
struct sst_ipc_info {
int ipc_offset;
unsigned int mbox_recv_off;
};
struct sst_platform_info {
const struct sst_info *probe_data;
const struct sst_ipc_info *ipc_info;
const struct sst_res_info *res_info;
const struct sst_lib_dnld_info *lib_info;
const char *platform;
};
int add_sst_platform_device(void); int add_sst_platform_device(void);
#endif #endif
...@@ -47,6 +47,7 @@ static struct snd_soc_dai_driver hdmi_codec_dai = { ...@@ -47,6 +47,7 @@ static struct snd_soc_dai_driver hdmi_codec_dai = {
SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | .formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
.sig_bits = 24,
}, },
.capture = { .capture = {
.stream_name = "Capture", .stream_name = "Capture",
...@@ -75,6 +76,7 @@ static struct snd_soc_codec_driver hdmi_codec = { ...@@ -75,6 +76,7 @@ static struct snd_soc_codec_driver hdmi_codec = {
.num_dapm_widgets = ARRAY_SIZE(hdmi_widgets), .num_dapm_widgets = ARRAY_SIZE(hdmi_widgets),
.dapm_routes = hdmi_routes, .dapm_routes = hdmi_routes,
.num_dapm_routes = ARRAY_SIZE(hdmi_routes), .num_dapm_routes = ARRAY_SIZE(hdmi_routes),
.ignore_pmdown_time = true,
}; };
static int hdmi_codec_probe(struct platform_device *pdev) static int hdmi_codec_probe(struct platform_device *pdev)
......
...@@ -1395,15 +1395,7 @@ static struct snd_soc_dai_driver lm49453_dai[] = { ...@@ -1395,15 +1395,7 @@ static struct snd_soc_dai_driver lm49453_dai[] = {
}, },
}; };
/* power down chip */
static int lm49453_remove(struct snd_soc_codec *codec)
{
lm49453_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
static struct snd_soc_codec_driver soc_codec_dev_lm49453 = { static struct snd_soc_codec_driver soc_codec_dev_lm49453 = {
.remove = lm49453_remove,
.set_bias_level = lm49453_set_bias_level, .set_bias_level = lm49453_set_bias_level,
.controls = lm49453_snd_controls, .controls = lm49453_snd_controls,
.num_controls = ARRAY_SIZE(lm49453_snd_controls), .num_controls = ARRAY_SIZE(lm49453_snd_controls),
......
...@@ -3,6 +3,7 @@ config SND_MFLD_MACHINE ...@@ -3,6 +3,7 @@ config SND_MFLD_MACHINE
depends on INTEL_SCU_IPC depends on INTEL_SCU_IPC
select SND_SOC_SN95031 select SND_SOC_SN95031
select SND_SST_MFLD_PLATFORM select SND_SST_MFLD_PLATFORM
select SND_SST_IPC_PCI
help help
This adds support for ASoC machine driver for Intel(R) MID Medfield platform 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 used as alsa device in audio substem in Intel(R) MID devices
...@@ -12,10 +13,23 @@ config SND_MFLD_MACHINE ...@@ -12,10 +13,23 @@ config SND_MFLD_MACHINE
config SND_SST_MFLD_PLATFORM config SND_SST_MFLD_PLATFORM
tristate tristate
config SND_SST_IPC
tristate
config SND_SST_IPC_PCI
tristate
select SND_SST_IPC
config SND_SST_IPC_ACPI
tristate
select SND_SST_IPC
depends on ACPI
config SND_SOC_INTEL_SST config SND_SOC_INTEL_SST
tristate "ASoC support for Intel(R) Smart Sound Technology" tristate "ASoC support for Intel(R) Smart Sound Technology"
select SND_SOC_INTEL_SST_ACPI if ACPI select SND_SOC_INTEL_SST_ACPI if ACPI
depends on (X86 || COMPILE_TEST) depends on (X86 || COMPILE_TEST)
depends on DW_DMAC_CORE
help help
This adds support for Intel(R) Smart Sound Technology (SST). This adds support for Intel(R) Smart Sound Technology (SST).
Say Y if you have such a device Say Y if you have such a device
...@@ -32,7 +46,8 @@ config SND_SOC_INTEL_BAYTRAIL ...@@ -32,7 +46,8 @@ config SND_SOC_INTEL_BAYTRAIL
config SND_SOC_INTEL_HASWELL_MACH config SND_SOC_INTEL_HASWELL_MACH
tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint" tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint"
depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C && \\
I2C_DESIGNWARE_PLATFORM
select SND_SOC_INTEL_HASWELL select SND_SOC_INTEL_HASWELL
select SND_SOC_RT5640 select SND_SOC_RT5640
help help
...@@ -61,7 +76,8 @@ config SND_SOC_INTEL_BYT_MAX98090_MACH ...@@ -61,7 +76,8 @@ config SND_SOC_INTEL_BYT_MAX98090_MACH
config SND_SOC_INTEL_BROADWELL_MACH config SND_SOC_INTEL_BROADWELL_MACH
tristate "ASoC Audio DSP support for Intel Broadwell Wildcatpoint" tristate "ASoC Audio DSP support for Intel Broadwell Wildcatpoint"
depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && DW_DMAC depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && DW_DMAC && \\
I2C_DESIGNWARE_PLATFORM
select SND_SOC_INTEL_HASWELL select SND_SOC_INTEL_HASWELL
select SND_COMPRESS_OFFLOAD select SND_COMPRESS_OFFLOAD
select SND_SOC_RT286 select SND_SOC_RT286
...@@ -70,3 +86,27 @@ config SND_SOC_INTEL_BROADWELL_MACH ...@@ -70,3 +86,27 @@ config SND_SOC_INTEL_BROADWELL_MACH
Ultrabook platforms. Ultrabook platforms.
Say Y if you have such a device Say Y if you have such a device
If unsure select "N". If unsure select "N".
config SND_SOC_INTEL_BYTCR_RT5640_MACH
tristate "ASoC Audio DSP Support for MID BYT Platform"
depends on X86
select SND_SOC_RT5640
select SND_SST_MFLD_PLATFORM
select SND_SST_IPC_ACPI
help
This adds support for ASoC machine driver for Intel(R) MID Baytrail 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_SOC_INTEL_CHT_BSW_RT5672_MACH
tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5672 codec"
depends on X86_INTEL_LPSS
select SND_SOC_RT5670
select SND_SST_MFLD_PLATFORM
select SND_SST_IPC_ACPI
help
This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell
platforms with RT5672 audio codec.
Say Y if you have such a device
If unsure select "N".
...@@ -26,8 +26,15 @@ snd-soc-sst-haswell-objs := haswell.o ...@@ -26,8 +26,15 @@ snd-soc-sst-haswell-objs := haswell.o
snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o
snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o
snd-soc-sst-broadwell-objs := broadwell.o snd-soc-sst-broadwell-objs := broadwell.o
snd-soc-sst-bytcr-dpcm-rt5640-objs := bytcr_dpcm_rt5640.o
snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o
obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.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_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o
obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o
obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o
obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-dpcm-rt5640.o
obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o
# DSP driver
obj-$(CONFIG_SND_SST_IPC) += sst/
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <sound/core.h> #include <sound/core.h>
#include <sound/pcm.h> #include <sound/pcm.h>
#include <sound/soc.h> #include <sound/soc.h>
#include <sound/jack.h>
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
#include "sst-dsp.h" #include "sst-dsp.h"
...@@ -26,8 +27,26 @@ ...@@ -26,8 +27,26 @@
#include "../codecs/rt286.h" #include "../codecs/rt286.h"
static struct snd_soc_jack broadwell_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin broadwell_headset_pins[] = {
{
.pin = "Mic Jack",
.mask = SND_JACK_MICROPHONE,
},
{
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
},
};
static const struct snd_kcontrol_new broadwell_controls[] = {
SOC_DAPM_PIN_SWITCH("Speaker"),
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
};
static const struct snd_soc_dapm_widget broadwell_widgets[] = { static const struct snd_soc_dapm_widget broadwell_widgets[] = {
SND_SOC_DAPM_HP("Headphones", NULL), SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Speaker", NULL), SND_SOC_DAPM_SPK("Speaker", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL), SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_MIC("DMIC1", NULL), SND_SOC_DAPM_MIC("DMIC1", NULL),
...@@ -42,7 +61,7 @@ static const struct snd_soc_dapm_route broadwell_rt286_map[] = { ...@@ -42,7 +61,7 @@ static const struct snd_soc_dapm_route broadwell_rt286_map[] = {
{"Speaker", NULL, "SPOL"}, {"Speaker", NULL, "SPOL"},
/* HP jack connectors - unknown if we have jack deteck */ /* HP jack connectors - unknown if we have jack deteck */
{"Headphones", NULL, "HPO Pin"}, {"Headphone Jack", NULL, "HPO Pin"},
/* other jacks */ /* other jacks */
{"MIC1", NULL, "Mic Jack"}, {"MIC1", NULL, "Mic Jack"},
...@@ -57,6 +76,27 @@ static const struct snd_soc_dapm_route broadwell_rt286_map[] = { ...@@ -57,6 +76,27 @@ static const struct snd_soc_dapm_route broadwell_rt286_map[] = {
{"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
}; };
static int broadwell_rt286_codec_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
int ret = 0;
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_HEADSET | SND_JACK_BTN_0, &broadwell_headset);
if (ret)
return ret;
ret = snd_soc_jack_add_pins(&broadwell_headset,
ARRAY_SIZE(broadwell_headset_pins),
broadwell_headset_pins);
if (ret)
return ret;
rt286_mic_detect(codec, &broadwell_headset);
return 0;
}
static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params) struct snd_pcm_hw_params *params)
{ {
...@@ -116,7 +156,7 @@ static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) ...@@ -116,7 +156,7 @@ static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd)
} }
/* always connected - check HP for jack detect */ /* always connected - check HP for jack detect */
snd_soc_dapm_enable_pin(dapm, "Headphones"); snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
snd_soc_dapm_enable_pin(dapm, "Speaker"); snd_soc_dapm_enable_pin(dapm, "Speaker");
snd_soc_dapm_enable_pin(dapm, "Mic Jack"); snd_soc_dapm_enable_pin(dapm, "Mic Jack");
snd_soc_dapm_enable_pin(dapm, "Line Jack"); snd_soc_dapm_enable_pin(dapm, "Line Jack");
...@@ -131,7 +171,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { ...@@ -131,7 +171,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = {
/* Front End DAI links */ /* Front End DAI links */
{ {
.name = "System PCM", .name = "System PCM",
.stream_name = "System Playback", .stream_name = "System Playback/Capture",
.cpu_dai_name = "System Pin", .cpu_dai_name = "System Pin",
.platform_name = "haswell-pcm-audio", .platform_name = "haswell-pcm-audio",
.dynamic = 1, .dynamic = 1,
...@@ -140,6 +180,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { ...@@ -140,6 +180,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = {
.init = broadwell_rtd_init, .init = broadwell_rtd_init,
.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dpcm_playback = 1, .dpcm_playback = 1,
.dpcm_capture = 1,
}, },
{ {
.name = "Offload0", .name = "Offload0",
...@@ -174,18 +215,6 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { ...@@ -174,18 +215,6 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = {
.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dpcm_capture = 1, .dpcm_capture = 1,
}, },
{
.name = "Capture PCM",
.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 */ /* Back End DAI links */
{ {
/* SSP0 - Codec */ /* SSP0 - Codec */
...@@ -196,6 +225,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { ...@@ -196,6 +225,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = {
.no_pcm = 1, .no_pcm = 1,
.codec_name = "i2c-INT343A:00", .codec_name = "i2c-INT343A:00",
.codec_dai_name = "rt286-aif1", .codec_dai_name = "rt286-aif1",
.init = broadwell_rt286_codec_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS, SND_SOC_DAIFMT_CBS_CFS,
.ignore_suspend = 1, .ignore_suspend = 1,
...@@ -213,6 +243,8 @@ static struct snd_soc_card broadwell_rt286 = { ...@@ -213,6 +243,8 @@ static struct snd_soc_card broadwell_rt286 = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.dai_link = broadwell_rt286_dais, .dai_link = broadwell_rt286_dais,
.num_links = ARRAY_SIZE(broadwell_rt286_dais), .num_links = ARRAY_SIZE(broadwell_rt286_dais),
.controls = broadwell_controls,
.num_controls = ARRAY_SIZE(broadwell_controls),
.dapm_widgets = broadwell_widgets, .dapm_widgets = broadwell_widgets,
.num_dapm_widgets = ARRAY_SIZE(broadwell_widgets), .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets),
.dapm_routes = broadwell_rt286_map, .dapm_routes = broadwell_rt286_map,
......
/*
* byt_cr_dpcm_rt5640.c - ASoc Machine driver for Intel Byt CR platform
*
* Copyright (C) 2014 Intel Corp
* Author: Subhransu S. Prusty <subhransu.s.prusty@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "../codecs/rt5640.h"
#include "sst-atom-controls.h"
static const struct snd_soc_dapm_widget byt_dapm_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_audio_map[] = {
{"IN2P", NULL, "Headset Mic"},
{"IN2N", NULL, "Headset Mic"},
{"Headset Mic", NULL, "MICBIAS1"},
{"IN1P", NULL, "MICBIAS1"},
{"LDO2", NULL, "Int Mic"},
{"Headphone", NULL, "HPOL"},
{"Headphone", NULL, "HPOR"},
{"Ext Spk", NULL, "SPOLP"},
{"Ext Spk", NULL, "SPOLN"},
{"Ext Spk", NULL, "SPORP"},
{"Ext Spk", NULL, "SPORN"},
{"AIF1 Playback", NULL, "ssp2 Tx"},
{"ssp2 Tx", NULL, "codec_out0"},
{"ssp2 Tx", NULL, "codec_out1"},
{"codec_in0", NULL, "ssp2 Rx"},
{"codec_in1", NULL, "ssp2 Rx"},
{"ssp2 Rx", NULL, "AIF1 Capture"},
};
static const struct snd_kcontrol_new byt_mc_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 int byt_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
snd_soc_dai_set_bclk_ratio(codec_dai, 50);
ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1,
params_rate(params) * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(rtd->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) * 50,
params_rate(params) * 512);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
return ret;
}
return 0;
}
static const struct snd_soc_pcm_stream byt_dai_params = {
.formats = SNDRV_PCM_FMTBIT_S24_LE,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
};
static int byt_codec_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
/* The DSP will covert the FE rate to 48k, stereo, 24bits */
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
/* set SSP2 to 24-bit */
snd_mask_set(&params->masks[SNDRV_PCM_HW_PARAM_FORMAT -
SNDRV_PCM_HW_PARAM_FIRST_MASK],
SNDRV_PCM_FORMAT_S24_LE);
return 0;
}
static unsigned int rates_48000[] = {
48000,
};
static struct snd_pcm_hw_constraint_list constraints_48000 = {
.count = ARRAY_SIZE(rates_48000),
.list = rates_48000,
};
static int byt_aif1_startup(struct snd_pcm_substream *substream)
{
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_48000);
}
static struct snd_soc_ops byt_aif1_ops = {
.startup = byt_aif1_startup,
};
static struct snd_soc_ops byt_be_ssp2_ops = {
.hw_params = byt_aif1_hw_params,
};
static struct snd_soc_dai_link byt_dailink[] = {
[MERR_DPCM_AUDIO] = {
.name = "Baytrail Audio Port",
.stream_name = "Baytrail Audio",
.cpu_dai_name = "media-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
.ignore_suspend = 1,
.dynamic = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &byt_aif1_ops,
},
[MERR_DPCM_COMPR] = {
.name = "Baytrail Compressed Port",
.stream_name = "Baytrail Compress",
.cpu_dai_name = "compress-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
},
/* back ends */
{
.name = "SSP2-Codec",
.be_id = 1,
.cpu_dai_name = "ssp2-port",
.platform_name = "sst-mfld-platform",
.no_pcm = 1,
.codec_dai_name = "rt5640-aif1",
.codec_name = "i2c-10EC5640:00",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.be_hw_params_fixup = byt_codec_fixup,
.ignore_suspend = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &byt_be_ssp2_ops,
},
};
/* SoC card */
static struct snd_soc_card snd_soc_card_byt = {
.name = "baytrailcraudio",
.dai_link = byt_dailink,
.num_links = ARRAY_SIZE(byt_dailink),
.dapm_widgets = byt_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(byt_dapm_widgets),
.dapm_routes = byt_audio_map,
.num_dapm_routes = ARRAY_SIZE(byt_audio_map),
.controls = byt_mc_controls,
.num_controls = ARRAY_SIZE(byt_mc_controls),
};
static int snd_byt_mc_probe(struct platform_device *pdev)
{
int ret_val = 0;
/* register the soc card */
snd_soc_card_byt.dev = &pdev->dev;
ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_byt);
if (ret_val) {
dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", ret_val);
return ret_val;
}
platform_set_drvdata(pdev, &snd_soc_card_byt);
return ret_val;
}
static struct platform_driver snd_byt_mc_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "bytt100_rt5640",
.pm = &snd_soc_pm_ops,
},
.probe = snd_byt_mc_probe,
};
module_platform_driver(snd_byt_mc_driver);
MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver");
MODULE_AUTHOR("Subhransu S. Prusty <subhransu.s.prusty@intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:bytrt5640-audio");
/*
* cht_bsw_rt5672.c - ASoc Machine driver for Intel Cherryview-based platforms
* Cherrytrail and Braswell, with RT5672 codec.
*
* Copyright (C) 2014 Intel Corp
* Author: Subhransu S. Prusty <subhransu.s.prusty@intel.com>
* Mengdong Lin <mengdong.lin@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.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "../codecs/rt5670.h"
#include "sst-atom-controls.h"
/* The platform clock #3 outputs 19.2Mhz clock to codec as I2S MCLK */
#define CHT_PLAT_CLK_3_HZ 19200000
#define CHT_CODEC_DAI "rt5670-aif1"
static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card)
{
int i;
for (i = 0; i < card->num_rtd; i++) {
struct snd_soc_pcm_runtime *rtd;
rtd = card->rtd + i;
if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI,
strlen(CHT_CODEC_DAI)))
return rtd->codec_dai;
}
return NULL;
}
static int platform_clock_control(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
struct snd_soc_dapm_context *dapm = w->dapm;
struct snd_soc_card *card = dapm->card;
struct snd_soc_dai *codec_dai;
codec_dai = cht_get_codec_dai(card);
if (!codec_dai) {
dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n");
return -EIO;
}
if (!SND_SOC_DAPM_EVENT_OFF(event))
return 0;
/* Set codec sysclk source to its internal clock because codec PLL will
* be off when idle and MCLK will also be off by ACPI when codec is
* runtime suspended. Codec needs clock for jack detection and button
* press.
*/
snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_RCCLK,
0, SND_SOC_CLOCK_IN);
return 0;
}
static const struct snd_soc_dapm_widget cht_dapm_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),
SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
platform_clock_control, SND_SOC_DAPM_POST_PMD),
};
static const struct snd_soc_dapm_route cht_audio_map[] = {
{"IN1P", NULL, "Headset Mic"},
{"IN1N", NULL, "Headset Mic"},
{"DMIC L1", NULL, "Int Mic"},
{"DMIC R1", NULL, "Int Mic"},
{"Headphone", NULL, "HPOL"},
{"Headphone", NULL, "HPOR"},
{"Ext Spk", NULL, "SPOLP"},
{"Ext Spk", NULL, "SPOLN"},
{"Ext Spk", NULL, "SPORP"},
{"Ext Spk", NULL, "SPORN"},
{"AIF1 Playback", NULL, "ssp2 Tx"},
{"ssp2 Tx", NULL, "codec_out0"},
{"ssp2 Tx", NULL, "codec_out1"},
{"codec_in0", NULL, "ssp2 Rx"},
{"codec_in1", NULL, "ssp2 Rx"},
{"ssp2 Rx", NULL, "AIF1 Capture"},
{"Headphone", NULL, "Platform Clock"},
{"Headset Mic", NULL, "Platform Clock"},
{"Int Mic", NULL, "Platform Clock"},
{"Ext Spk", NULL, "Platform Clock"},
};
static const struct snd_kcontrol_new cht_mc_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
SOC_DAPM_PIN_SWITCH("Ext Spk"),
};
static int cht_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
/* set codec PLL source to the 19.2MHz platform clock (MCLK) */
ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK,
CHT_PLAT_CLK_3_HZ, params_rate(params) * 512);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
return ret;
}
/* set codec sysclk source to PLL */
ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1,
params_rate(params) * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
return ret;
}
return 0;
}
static int cht_codec_init(struct snd_soc_pcm_runtime *runtime)
{
int ret;
struct snd_soc_dai *codec_dai = runtime->codec_dai;
/* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */
ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24);
if (ret < 0) {
dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret);
return ret;
}
return 0;
}
static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
/* The DSP will covert the FE rate to 48k, stereo, 24bits */
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
/* set SSP2 to 24-bit */
snd_mask_set(&params->masks[SNDRV_PCM_HW_PARAM_FORMAT -
SNDRV_PCM_HW_PARAM_FIRST_MASK],
SNDRV_PCM_FORMAT_S24_LE);
return 0;
}
static unsigned int rates_48000[] = {
48000,
};
static struct snd_pcm_hw_constraint_list constraints_48000 = {
.count = ARRAY_SIZE(rates_48000),
.list = rates_48000,
};
static int cht_aif1_startup(struct snd_pcm_substream *substream)
{
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_48000);
}
static struct snd_soc_ops cht_aif1_ops = {
.startup = cht_aif1_startup,
};
static struct snd_soc_ops cht_be_ssp2_ops = {
.hw_params = cht_aif1_hw_params,
};
static struct snd_soc_dai_link cht_dailink[] = {
/* Front End DAI links */
[MERR_DPCM_AUDIO] = {
.name = "Audio Port",
.stream_name = "Audio",
.cpu_dai_name = "media-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
.ignore_suspend = 1,
.dynamic = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &cht_aif1_ops,
},
[MERR_DPCM_COMPR] = {
.name = "Compressed Port",
.stream_name = "Compress",
.cpu_dai_name = "compress-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
},
/* Back End DAI links */
{
/* SSP2 - Codec */
.name = "SSP2-Codec",
.be_id = 1,
.cpu_dai_name = "ssp2-port",
.platform_name = "sst-mfld-platform",
.no_pcm = 1,
.codec_dai_name = "rt5670-aif1",
.codec_name = "i2c-10EC5670:00",
.dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.init = cht_codec_init,
.be_hw_params_fixup = cht_codec_fixup,
.ignore_suspend = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &cht_be_ssp2_ops,
},
};
/* SoC card */
static struct snd_soc_card snd_soc_card_cht = {
.name = "cherrytrailcraudio",
.dai_link = cht_dailink,
.num_links = ARRAY_SIZE(cht_dailink),
.dapm_widgets = cht_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets),
.dapm_routes = cht_audio_map,
.num_dapm_routes = ARRAY_SIZE(cht_audio_map),
.controls = cht_mc_controls,
.num_controls = ARRAY_SIZE(cht_mc_controls),
};
static int snd_cht_mc_probe(struct platform_device *pdev)
{
int ret_val = 0;
/* register the soc card */
snd_soc_card_cht.dev = &pdev->dev;
ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht);
if (ret_val) {
dev_err(&pdev->dev,
"snd_soc_register_card failed %d\n", ret_val);
return ret_val;
}
platform_set_drvdata(pdev, &snd_soc_card_cht);
return ret_val;
}
static struct platform_driver snd_cht_mc_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "cht-bsw-rt5672",
.pm = &snd_soc_pm_ops,
},
.probe = snd_cht_mc_probe,
};
module_platform_driver(snd_cht_mc_driver);
MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver");
MODULE_AUTHOR("Subhransu S. Prusty, Mengdong Lin");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:cht-bsw-rt5672");
...@@ -109,7 +109,7 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = { ...@@ -109,7 +109,7 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = {
/* Front End DAI links */ /* Front End DAI links */
{ {
.name = "System", .name = "System",
.stream_name = "System Playback", .stream_name = "System Playback/Capture",
.cpu_dai_name = "System Pin", .cpu_dai_name = "System Pin",
.platform_name = "haswell-pcm-audio", .platform_name = "haswell-pcm-audio",
.dynamic = 1, .dynamic = 1,
...@@ -118,6 +118,7 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = { ...@@ -118,6 +118,7 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = {
.init = haswell_rtd_init, .init = haswell_rtd_init,
.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dpcm_playback = 1, .dpcm_playback = 1,
.dpcm_capture = 1,
}, },
{ {
.name = "Offload0", .name = "Offload0",
...@@ -152,17 +153,6 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = { ...@@ -152,17 +153,6 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = {
.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dpcm_capture = 1, .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 */ /* Back End DAI links */
{ {
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details. * General Public License for more details.
* *
* In the dpcm driver modelling when a particular FE/BE/Mixer/Pipe is active
* we forward the settings and parameters, rest we keep the values in
* driver and forward when DAPM enables them
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
...@@ -81,6 +84,183 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, ...@@ -81,6 +84,183 @@ static int sst_fill_and_send_cmd(struct sst_data *drv,
return ret; return ret;
} }
/**
* tx map value is a bitfield where each bit represents a FW channel
*
* 3 2 1 0 # 0 = codec0, 1 = codec1
* RLRLRLRL # 3, 4 = reserved
*
* e.g. slot 0 rx map = 00001100b -> data from slot 0 goes into codec_in1 L,R
*/
static u8 sst_ssp_tx_map[SST_MAX_TDM_SLOTS] = {
0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default rx map */
};
/**
* rx map value is a bitfield where each bit represents a slot
*
* 76543210 # 0 = slot 0, 1 = slot 1
*
* e.g. codec1_0 tx map = 00000101b -> data from codec_out1_0 goes into slot 0, 2
*/
static u8 sst_ssp_rx_map[SST_MAX_TDM_SLOTS] = {
0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default tx map */
};
/**
* NOTE: this is invoked with lock held
*/
static int sst_send_slot_map(struct sst_data *drv)
{
struct sst_param_sba_ssp_slot_map cmd;
SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
cmd.header.command_id = SBA_SET_SSP_SLOT_MAP;
cmd.header.length = sizeof(struct sst_param_sba_ssp_slot_map)
- sizeof(struct sst_dsp_header);
cmd.param_id = SBA_SET_SSP_SLOT_MAP;
cmd.param_len = sizeof(cmd.rx_slot_map) + sizeof(cmd.tx_slot_map)
+ sizeof(cmd.ssp_index);
cmd.ssp_index = SSP_CODEC;
memcpy(cmd.rx_slot_map, &sst_ssp_tx_map[0], sizeof(cmd.rx_slot_map));
memcpy(cmd.tx_slot_map, &sst_ssp_rx_map[0], sizeof(cmd.tx_slot_map));
return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS,
SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd,
sizeof(cmd.header) + cmd.header.length);
}
int sst_slot_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct sst_enum *e = (struct sst_enum *)kcontrol->private_value;
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = e->max;
if (uinfo->value.enumerated.item > e->max - 1)
uinfo->value.enumerated.item = e->max - 1;
strcpy(uinfo->value.enumerated.name,
e->texts[uinfo->value.enumerated.item]);
return 0;
}
/**
* sst_slot_get - get the status of the interleaver/deinterleaver control
*
* Searches the map where the control status is stored, and gets the
* channel/slot which is currently set for this enumerated control. Since it is
* an enumerated control, there is only one possible value.
*/
static int sst_slot_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sst_enum *e = (void *)kcontrol->private_value;
struct snd_soc_component *c = snd_kcontrol_chip(kcontrol);
struct sst_data *drv = snd_soc_component_get_drvdata(c);
unsigned int ctl_no = e->reg;
unsigned int is_tx = e->tx;
unsigned int val, mux;
u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map;
mutex_lock(&drv->lock);
val = 1 << ctl_no;
/* search which slot/channel has this bit set - there should be only one */
for (mux = e->max; mux > 0; mux--)
if (map[mux - 1] & val)
break;
ucontrol->value.enumerated.item[0] = mux;
mutex_unlock(&drv->lock);
dev_dbg(c->dev, "%s - %s map = %#x\n",
is_tx ? "tx channel" : "rx slot",
e->texts[mux], mux ? map[mux - 1] : -1);
return 0;
}
/* sst_check_and_send_slot_map - helper for checking power state and sending
* slot map cmd
*
* called with lock held
*/
static int sst_check_and_send_slot_map(struct sst_data *drv, struct snd_kcontrol *kcontrol)
{
struct sst_enum *e = (void *)kcontrol->private_value;
int ret = 0;
if (e->w && e->w->power)
ret = sst_send_slot_map(drv);
else
dev_err(&drv->pdev->dev, "Slot control: %s doesn't have DAPM widget!!!\n",
kcontrol->id.name);
return ret;
}
/**
* sst_slot_put - set the status of interleaver/deinterleaver control
*
* (de)interleaver controls are defined in opposite sense to be user-friendly
*
* Instead of the enum value being the value written to the register, it is the
* register address; and the kcontrol number (register num) is the value written
* to the register. This is so that there can be only one value for each
* slot/channel since there is only one control for each slot/channel.
*
* This means that whenever an enum is set, we need to clear the bit
* for that kcontrol_no for all the interleaver OR deinterleaver registers
*/
static int sst_slot_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol);
struct sst_data *drv = snd_soc_component_get_drvdata(c);
struct sst_enum *e = (void *)kcontrol->private_value;
int i, ret = 0;
unsigned int ctl_no = e->reg;
unsigned int is_tx = e->tx;
unsigned int slot_channel_no;
unsigned int val, mux;
u8 *map;
map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map;
val = 1 << ctl_no;
mux = ucontrol->value.enumerated.item[0];
if (mux > e->max - 1)
return -EINVAL;
mutex_lock(&drv->lock);
/* first clear all registers of this bit */
for (i = 0; i < e->max; i++)
map[i] &= ~val;
if (mux == 0) {
/* kctl set to 'none' and we reset the bits so send IPC */
ret = sst_check_and_send_slot_map(drv, kcontrol);
mutex_unlock(&drv->lock);
return ret;
}
/* offset by one to take "None" into account */
slot_channel_no = mux - 1;
map[slot_channel_no] |= val;
dev_dbg(c->dev, "%s %s map = %#x\n",
is_tx ? "tx channel" : "rx slot",
e->texts[mux], map[slot_channel_no]);
ret = sst_check_and_send_slot_map(drv, kcontrol);
mutex_unlock(&drv->lock);
return ret;
}
static int sst_send_algo_cmd(struct sst_data *drv, static int sst_send_algo_cmd(struct sst_data *drv,
struct sst_algo_control *bc) struct sst_algo_control *bc)
{ {
...@@ -104,6 +284,34 @@ static int sst_send_algo_cmd(struct sst_data *drv, ...@@ -104,6 +284,34 @@ static int sst_send_algo_cmd(struct sst_data *drv,
return ret; return ret;
} }
/**
* sst_find_and_send_pipe_algo - send all the algo parameters for a pipe
*
* The algos which are in each pipeline are sent to the firmware one by one
*
* Called with lock held
*/
static int sst_find_and_send_pipe_algo(struct sst_data *drv,
const char *pipe, struct sst_ids *ids)
{
int ret = 0;
struct sst_algo_control *bc;
struct sst_module *algo = NULL;
dev_dbg(&drv->pdev->dev, "Enter: widget=%s\n", pipe);
list_for_each_entry(algo, &ids->algo_list, node) {
bc = (void *)algo->kctl->private_value;
dev_dbg(&drv->pdev->dev, "Found algo control name=%s pipe=%s\n",
algo->kctl->id.name, pipe);
ret = sst_send_algo_cmd(drv, bc);
if (ret)
return ret;
}
return ret;
}
static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol, static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo) struct snd_ctl_elem_info *uinfo)
{ {
...@@ -162,6 +370,743 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, ...@@ -162,6 +370,743 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol,
return ret; return ret;
} }
static int sst_gain_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = mc->stereo ? 2 : 1;
uinfo->value.integer.min = mc->min;
uinfo->value.integer.max = mc->max;
return 0;
}
/**
* sst_send_gain_cmd - send the gain algorithm IPC to the FW
* @gv: the stored value of gain (also contains rampduration)
* @mute: flag that indicates whether this was called from the
* digital_mute callback or directly. If called from the
* digital_mute callback, module will be muted/unmuted based on this
* flag. The flag is always 0 if called directly.
*
* Called with sst_data.lock held
*
* The user-set gain value is sent only if the user-controllable 'mute' control
* is OFF (indicated by gv->mute). Otherwise, the mute value (MIN value) is
* sent.
*/
static int sst_send_gain_cmd(struct sst_data *drv, struct sst_gain_value *gv,
u16 task_id, u16 loc_id, u16 module_id, int mute)
{
struct sst_cmd_set_gain_dual cmd;
dev_dbg(&drv->pdev->dev, "Enter\n");
cmd.header.command_id = MMX_SET_GAIN;
SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
cmd.gain_cell_num = 1;
if (mute || gv->mute) {
cmd.cell_gains[0].cell_gain_left = SST_GAIN_MIN_VALUE;
cmd.cell_gains[0].cell_gain_right = SST_GAIN_MIN_VALUE;
} else {
cmd.cell_gains[0].cell_gain_left = gv->l_gain;
cmd.cell_gains[0].cell_gain_right = gv->r_gain;
}
SST_FILL_DESTINATION(2, cmd.cell_gains[0].dest,
loc_id, module_id);
cmd.cell_gains[0].gain_time_constant = gv->ramp_duration;
cmd.header.length = sizeof(struct sst_cmd_set_gain_dual)
- sizeof(struct sst_dsp_header);
/* we are with lock held, so call the unlocked api to send */
return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS,
SST_FLAG_BLOCKED, task_id, 0, &cmd,
sizeof(cmd.header) + cmd.header.length);
}
static int sst_gain_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value;
struct sst_gain_value *gv = mc->gain_val;
switch (mc->type) {
case SST_GAIN_TLV:
ucontrol->value.integer.value[0] = gv->l_gain;
ucontrol->value.integer.value[1] = gv->r_gain;
break;
case SST_GAIN_MUTE:
ucontrol->value.integer.value[0] = gv->mute ? 1 : 0;
break;
case SST_GAIN_RAMP_DURATION:
ucontrol->value.integer.value[0] = gv->ramp_duration;
break;
default:
dev_err(component->dev, "Invalid Input- gain type:%d\n",
mc->type);
return -EINVAL;
}
return 0;
}
static int sst_gain_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int ret = 0;
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt);
struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value;
struct sst_gain_value *gv = mc->gain_val;
mutex_lock(&drv->lock);
switch (mc->type) {
case SST_GAIN_TLV:
gv->l_gain = ucontrol->value.integer.value[0];
gv->r_gain = ucontrol->value.integer.value[1];
dev_dbg(cmpnt->dev, "%s: Volume %d, %d\n",
mc->pname, gv->l_gain, gv->r_gain);
break;
case SST_GAIN_MUTE:
gv->mute = !!ucontrol->value.integer.value[0];
dev_dbg(cmpnt->dev, "%s: Mute %d\n", mc->pname, gv->mute);
break;
case SST_GAIN_RAMP_DURATION:
gv->ramp_duration = ucontrol->value.integer.value[0];
dev_dbg(cmpnt->dev, "%s: Ramp Delay%d\n",
mc->pname, gv->ramp_duration);
break;
default:
mutex_unlock(&drv->lock);
dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n",
mc->type);
return -EINVAL;
}
if (mc->w && mc->w->power)
ret = sst_send_gain_cmd(drv, gv, mc->task_id,
mc->pipe_id | mc->instance_id, mc->module_id, 0);
mutex_unlock(&drv->lock);
return ret;
}
static int sst_set_pipe_gain(struct sst_ids *ids,
struct sst_data *drv, int mute);
static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol)
{
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct sst_data *drv = snd_soc_component_get_drvdata(c);
struct sst_ids *ids = w->priv;
mutex_lock(&drv->lock);
sst_find_and_send_pipe_algo(drv, w->name, ids);
sst_set_pipe_gain(ids, drv, 0);
mutex_unlock(&drv->lock);
return 0;
}
static int sst_generic_modules_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
if (SND_SOC_DAPM_EVENT_ON(event))
return sst_send_pipe_module_params(w, k);
return 0;
}
static const DECLARE_TLV_DB_SCALE(sst_gain_tlv_common, SST_GAIN_MIN_VALUE * 10, 10, 0);
/* Look up table to convert MIXER SW bit regs to SWM inputs */
static const uint swm_mixer_input_ids[SST_SWM_INPUT_COUNT] = {
[SST_IP_CODEC0] = SST_SWM_IN_CODEC0,
[SST_IP_CODEC1] = SST_SWM_IN_CODEC1,
[SST_IP_LOOP0] = SST_SWM_IN_SPROT_LOOP,
[SST_IP_LOOP1] = SST_SWM_IN_MEDIA_LOOP1,
[SST_IP_LOOP2] = SST_SWM_IN_MEDIA_LOOP2,
[SST_IP_PCM0] = SST_SWM_IN_PCM0,
[SST_IP_PCM1] = SST_SWM_IN_PCM1,
[SST_IP_MEDIA0] = SST_SWM_IN_MEDIA0,
[SST_IP_MEDIA1] = SST_SWM_IN_MEDIA1,
[SST_IP_MEDIA2] = SST_SWM_IN_MEDIA2,
[SST_IP_MEDIA3] = SST_SWM_IN_MEDIA3,
};
/**
* fill_swm_input - fill in the SWM input ids given the register
*
* The register value is a bit-field inicated which mixer inputs are ON. Use the
* lookup table to get the input-id and fill it in the structure.
*/
static int fill_swm_input(struct snd_soc_component *cmpnt,
struct swm_input_ids *swm_input, unsigned int reg)
{
uint i, is_set, nb_inputs = 0;
u16 input_loc_id;
dev_dbg(cmpnt->dev, "reg: %#x\n", reg);
for (i = 0; i < SST_SWM_INPUT_COUNT; i++) {
is_set = reg & BIT(i);
if (!is_set)
continue;
input_loc_id = swm_mixer_input_ids[i];
SST_FILL_DESTINATION(2, swm_input->input_id,
input_loc_id, SST_DEFAULT_MODULE_ID);
nb_inputs++;
swm_input++;
dev_dbg(cmpnt->dev, "input id: %#x, nb_inputs: %d\n",
input_loc_id, nb_inputs);
if (nb_inputs == SST_CMD_SWM_MAX_INPUTS) {
dev_warn(cmpnt->dev, "SET_SWM cmd max inputs reached");
break;
}
}
return nb_inputs;
}
/**
* called with lock held
*/
static int sst_set_pipe_gain(struct sst_ids *ids,
struct sst_data *drv, int mute)
{
int ret = 0;
struct sst_gain_mixer_control *mc;
struct sst_gain_value *gv;
struct sst_module *gain = NULL;
list_for_each_entry(gain, &ids->gain_list, node) {
struct snd_kcontrol *kctl = gain->kctl;
dev_dbg(&drv->pdev->dev, "control name=%s\n", kctl->id.name);
mc = (void *)kctl->private_value;
gv = mc->gain_val;
ret = sst_send_gain_cmd(drv, gv, mc->task_id,
mc->pipe_id | mc->instance_id, mc->module_id, mute);
if (ret)
return ret;
}
return ret;
}
static int sst_swm_mixer_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
struct sst_cmd_set_swm cmd;
struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt);
struct sst_ids *ids = w->priv;
bool set_mixer = false;
struct soc_mixer_control *mc;
int val = 0;
int i = 0;
dev_dbg(cmpnt->dev, "widget = %s\n", w->name);
/*
* Identify which mixer input is on and send the bitmap of the
* inputs as an IPC to the DSP.
*/
for (i = 0; i < w->num_kcontrols; i++) {
if (dapm_kcontrol_get_value(w->kcontrols[i])) {
mc = (struct soc_mixer_control *)(w->kcontrols[i])->private_value;
val |= 1 << mc->shift;
}
}
dev_dbg(cmpnt->dev, "val = %#x\n", val);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
case SND_SOC_DAPM_POST_PMD:
set_mixer = true;
break;
case SND_SOC_DAPM_POST_REG:
if (w->power)
set_mixer = true;
break;
default:
set_mixer = false;
}
if (set_mixer == false)
return 0;
if (SND_SOC_DAPM_EVENT_ON(event) ||
event == SND_SOC_DAPM_POST_REG)
cmd.switch_state = SST_SWM_ON;
else
cmd.switch_state = SST_SWM_OFF;
SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
/* MMX_SET_SWM == SBA_SET_SWM */
cmd.header.command_id = SBA_SET_SWM;
SST_FILL_DESTINATION(2, cmd.output_id,
ids->location_id, SST_DEFAULT_MODULE_ID);
cmd.nb_inputs = fill_swm_input(cmpnt, &cmd.input[0], val);
cmd.header.length = offsetof(struct sst_cmd_set_swm, input)
- sizeof(struct sst_dsp_header)
+ (cmd.nb_inputs * sizeof(cmd.input[0]));
return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
ids->task_id, 0, &cmd,
sizeof(cmd.header) + cmd.header.length);
}
/* SBA mixers - 16 inputs */
#define SST_SBA_DECLARE_MIX_CONTROLS(kctl_name) \
static const struct snd_kcontrol_new kctl_name[] = { \
SOC_DAPM_SINGLE("codec_in0 Switch", SND_SOC_NOPM, SST_IP_CODEC0, 1, 0), \
SOC_DAPM_SINGLE("codec_in1 Switch", SND_SOC_NOPM, SST_IP_CODEC1, 1, 0), \
SOC_DAPM_SINGLE("sprot_loop_in Switch", SND_SOC_NOPM, SST_IP_LOOP0, 1, 0), \
SOC_DAPM_SINGLE("media_loop1_in Switch", SND_SOC_NOPM, SST_IP_LOOP1, 1, 0), \
SOC_DAPM_SINGLE("media_loop2_in Switch", SND_SOC_NOPM, SST_IP_LOOP2, 1, 0), \
SOC_DAPM_SINGLE("pcm0_in Switch", SND_SOC_NOPM, SST_IP_PCM0, 1, 0), \
SOC_DAPM_SINGLE("pcm1_in Switch", SND_SOC_NOPM, SST_IP_PCM1, 1, 0), \
}
#define SST_SBA_MIXER_GRAPH_MAP(mix_name) \
{ mix_name, "codec_in0 Switch", "codec_in0" }, \
{ mix_name, "codec_in1 Switch", "codec_in1" }, \
{ mix_name, "sprot_loop_in Switch", "sprot_loop_in" }, \
{ mix_name, "media_loop1_in Switch", "media_loop1_in" }, \
{ mix_name, "media_loop2_in Switch", "media_loop2_in" }, \
{ mix_name, "pcm0_in Switch", "pcm0_in" }, \
{ mix_name, "pcm1_in Switch", "pcm1_in" }
#define SST_MMX_DECLARE_MIX_CONTROLS(kctl_name) \
static const struct snd_kcontrol_new kctl_name[] = { \
SOC_DAPM_SINGLE("media0_in Switch", SND_SOC_NOPM, SST_IP_MEDIA0, 1, 0), \
SOC_DAPM_SINGLE("media1_in Switch", SND_SOC_NOPM, SST_IP_MEDIA1, 1, 0), \
SOC_DAPM_SINGLE("media2_in Switch", SND_SOC_NOPM, SST_IP_MEDIA2, 1, 0), \
SOC_DAPM_SINGLE("media3_in Switch", SND_SOC_NOPM, SST_IP_MEDIA3, 1, 0), \
}
SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media0_controls);
SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media1_controls);
/* 18 SBA mixers */
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm0_controls);
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm1_controls);
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm2_controls);
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_sprot_l0_controls);
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l1_controls);
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l2_controls);
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_voip_controls);
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls);
SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_controls);
/*
* sst_handle_vb_timer - Start/Stop the DSP scheduler
*
* The DSP expects first cmd to be SBA_VB_START, so at first startup send
* that.
* DSP expects last cmd to be SBA_VB_IDLE, so at last shutdown send that.
*
* Do refcount internally so that we send command only at first start
* and last end. Since SST driver does its own ref count, invoke sst's
* power ops always!
*/
int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable)
{
int ret = 0;
struct sst_cmd_generic cmd;
struct sst_data *drv = snd_soc_dai_get_drvdata(dai);
static int timer_usage;
if (enable)
cmd.header.command_id = SBA_VB_START;
else
cmd.header.command_id = SBA_IDLE;
dev_dbg(dai->dev, "enable=%u, usage=%d\n", enable, timer_usage);
SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
cmd.header.length = 0;
if (enable) {
ret = sst->ops->power(sst->dev, true);
if (ret < 0)
return ret;
}
mutex_lock(&drv->lock);
if (enable)
timer_usage++;
else
timer_usage--;
/*
* Send the command only if this call is the first enable or last
* disable
*/
if ((enable && (timer_usage == 1)) ||
(!enable && (timer_usage == 0))) {
ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD,
SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd,
sizeof(cmd.header) + cmd.header.length);
if (ret && enable) {
timer_usage--;
enable = false;
}
}
mutex_unlock(&drv->lock);
if (!enable)
sst->ops->power(sst->dev, false);
return ret;
}
/**
* sst_ssp_config - contains SSP configuration for media UC
*/
static const struct sst_ssp_config sst_ssp_configs = {
.ssp_id = SSP_CODEC,
.bits_per_slot = 24,
.slots = 4,
.ssp_mode = SSP_MODE_MASTER,
.pcm_mode = SSP_PCM_MODE_NETWORK,
.duplex = SSP_DUPLEX,
.ssp_protocol = SSP_MODE_PCM,
.fs_width = 1,
.fs_frequency = SSP_FS_48_KHZ,
.active_slot_map = 0xF,
.start_delay = 0,
};
int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable)
{
struct sst_cmd_sba_hw_set_ssp cmd;
struct sst_data *drv = snd_soc_dai_get_drvdata(dai);
const struct sst_ssp_config *config;
dev_info(dai->dev, "Enter: enable=%d port_name=%s\n", enable, id);
SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
cmd.header.command_id = SBA_HW_SET_SSP;
cmd.header.length = sizeof(struct sst_cmd_sba_hw_set_ssp)
- sizeof(struct sst_dsp_header);
config = &sst_ssp_configs;
dev_dbg(dai->dev, "ssp_id: %u\n", config->ssp_id);
if (enable)
cmd.switch_state = SST_SWITCH_ON;
else
cmd.switch_state = SST_SWITCH_OFF;
cmd.selection = config->ssp_id;
cmd.nb_bits_per_slots = config->bits_per_slot;
cmd.nb_slots = config->slots;
cmd.mode = config->ssp_mode | (config->pcm_mode << 1);
cmd.duplex = config->duplex;
cmd.active_tx_slot_map = config->active_slot_map;
cmd.active_rx_slot_map = config->active_slot_map;
cmd.frame_sync_frequency = config->fs_frequency;
cmd.frame_sync_polarity = SSP_FS_ACTIVE_HIGH;
cmd.data_polarity = 1;
cmd.frame_sync_width = config->fs_width;
cmd.ssp_protocol = config->ssp_protocol;
cmd.start_delay = config->start_delay;
cmd.reserved1 = cmd.reserved2 = 0xFF;
return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
SST_TASK_SBA, 0, &cmd,
sizeof(cmd.header) + cmd.header.length);
}
static int sst_set_be_modules(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
int ret = 0;
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct sst_data *drv = snd_soc_component_get_drvdata(c);
dev_dbg(c->dev, "Enter: widget=%s\n", w->name);
if (SND_SOC_DAPM_EVENT_ON(event)) {
ret = sst_send_slot_map(drv);
if (ret)
return ret;
ret = sst_send_pipe_module_params(w, k);
}
return ret;
}
static int sst_set_media_path(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
int ret = 0;
struct sst_cmd_set_media_path cmd;
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct sst_data *drv = snd_soc_component_get_drvdata(c);
struct sst_ids *ids = w->priv;
dev_dbg(c->dev, "widget=%s\n", w->name);
dev_dbg(c->dev, "task=%u, location=%#x\n",
ids->task_id, ids->location_id);
if (SND_SOC_DAPM_EVENT_ON(event))
cmd.switch_state = SST_PATH_ON;
else
cmd.switch_state = SST_PATH_OFF;
SST_FILL_DESTINATION(2, cmd.header.dst,
ids->location_id, SST_DEFAULT_MODULE_ID);
/* MMX_SET_MEDIA_PATH == SBA_SET_MEDIA_PATH */
cmd.header.command_id = MMX_SET_MEDIA_PATH;
cmd.header.length = sizeof(struct sst_cmd_set_media_path)
- sizeof(struct sst_dsp_header);
ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
ids->task_id, 0, &cmd,
sizeof(cmd.header) + cmd.header.length);
if (ret)
return ret;
if (SND_SOC_DAPM_EVENT_ON(event))
ret = sst_send_pipe_module_params(w, k);
return ret;
}
static int sst_set_media_loop(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
int ret = 0;
struct sst_cmd_sba_set_media_loop_map cmd;
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct sst_data *drv = snd_soc_component_get_drvdata(c);
struct sst_ids *ids = w->priv;
dev_dbg(c->dev, "Enter:widget=%s\n", w->name);
if (SND_SOC_DAPM_EVENT_ON(event))
cmd.switch_state = SST_SWITCH_ON;
else
cmd.switch_state = SST_SWITCH_OFF;
SST_FILL_DESTINATION(2, cmd.header.dst,
ids->location_id, SST_DEFAULT_MODULE_ID);
cmd.header.command_id = SBA_SET_MEDIA_LOOP_MAP;
cmd.header.length = sizeof(struct sst_cmd_sba_set_media_loop_map)
- sizeof(struct sst_dsp_header);
cmd.param.part.cfg.rate = 2; /* 48khz */
cmd.param.part.cfg.format = ids->format; /* stereo/Mono */
cmd.param.part.cfg.s_length = 1; /* 24bit left justified */
cmd.map = 0; /* Algo sequence: Gain - DRP - FIR - IIR */
ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
SST_TASK_SBA, 0, &cmd,
sizeof(cmd.header) + cmd.header.length);
if (ret)
return ret;
if (SND_SOC_DAPM_EVENT_ON(event))
ret = sst_send_pipe_module_params(w, k);
return ret;
}
static const struct snd_soc_dapm_widget sst_dapm_widgets[] = {
SST_AIF_IN("codec_in0", sst_set_be_modules),
SST_AIF_IN("codec_in1", sst_set_be_modules),
SST_AIF_OUT("codec_out0", sst_set_be_modules),
SST_AIF_OUT("codec_out1", sst_set_be_modules),
/* Media Paths */
/* MediaX IN paths are set via ALLOC, so no SET_MEDIA_PATH command */
SST_PATH_INPUT("media0_in", SST_TASK_MMX, SST_SWM_IN_MEDIA0, sst_generic_modules_event),
SST_PATH_INPUT("media1_in", SST_TASK_MMX, SST_SWM_IN_MEDIA1, NULL),
SST_PATH_INPUT("media2_in", SST_TASK_MMX, SST_SWM_IN_MEDIA2, sst_set_media_path),
SST_PATH_INPUT("media3_in", SST_TASK_MMX, SST_SWM_IN_MEDIA3, NULL),
SST_PATH_OUTPUT("media0_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA0, sst_set_media_path),
SST_PATH_OUTPUT("media1_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA1, sst_set_media_path),
/* SBA PCM Paths */
SST_PATH_INPUT("pcm0_in", SST_TASK_SBA, SST_SWM_IN_PCM0, sst_set_media_path),
SST_PATH_INPUT("pcm1_in", SST_TASK_SBA, SST_SWM_IN_PCM1, sst_set_media_path),
SST_PATH_OUTPUT("pcm0_out", SST_TASK_SBA, SST_SWM_OUT_PCM0, sst_set_media_path),
SST_PATH_OUTPUT("pcm1_out", SST_TASK_SBA, SST_SWM_OUT_PCM1, sst_set_media_path),
SST_PATH_OUTPUT("pcm2_out", SST_TASK_SBA, SST_SWM_OUT_PCM2, sst_set_media_path),
/* SBA Loops */
SST_PATH_INPUT("sprot_loop_in", SST_TASK_SBA, SST_SWM_IN_SPROT_LOOP, NULL),
SST_PATH_INPUT("media_loop1_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP1, NULL),
SST_PATH_INPUT("media_loop2_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP2, NULL),
SST_PATH_MEDIA_LOOP_OUTPUT("sprot_loop_out", SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, SST_FMT_MONO, sst_set_media_loop),
SST_PATH_MEDIA_LOOP_OUTPUT("media_loop1_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, SST_FMT_MONO, sst_set_media_loop),
SST_PATH_MEDIA_LOOP_OUTPUT("media_loop2_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, SST_FMT_STEREO, sst_set_media_loop),
/* Media Mixers */
SST_SWM_MIXER("media0_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA0,
sst_mix_media0_controls, sst_swm_mixer_event),
SST_SWM_MIXER("media1_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA1,
sst_mix_media1_controls, sst_swm_mixer_event),
/* SBA PCM mixers */
SST_SWM_MIXER("pcm0_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM0,
sst_mix_pcm0_controls, sst_swm_mixer_event),
SST_SWM_MIXER("pcm1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM1,
sst_mix_pcm1_controls, sst_swm_mixer_event),
SST_SWM_MIXER("pcm2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM2,
sst_mix_pcm2_controls, sst_swm_mixer_event),
/* SBA Loop mixers */
SST_SWM_MIXER("sprot_loop_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP,
sst_mix_sprot_l0_controls, sst_swm_mixer_event),
SST_SWM_MIXER("media_loop1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1,
sst_mix_media_l1_controls, sst_swm_mixer_event),
SST_SWM_MIXER("media_loop2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2,
sst_mix_media_l2_controls, sst_swm_mixer_event),
/* SBA Backend mixers */
SST_SWM_MIXER("codec_out0 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC0,
sst_mix_codec0_controls, sst_swm_mixer_event),
SST_SWM_MIXER("codec_out1 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC1,
sst_mix_codec1_controls, sst_swm_mixer_event),
};
static const struct snd_soc_dapm_route intercon[] = {
{"media0_in", NULL, "Compress Playback"},
{"media1_in", NULL, "Headset Playback"},
{"media2_in", NULL, "pcm0_out"},
{"media0_out mix 0", "media0_in Switch", "media0_in"},
{"media0_out mix 0", "media1_in Switch", "media1_in"},
{"media0_out mix 0", "media2_in Switch", "media2_in"},
{"media0_out mix 0", "media3_in Switch", "media3_in"},
{"media1_out mix 0", "media0_in Switch", "media0_in"},
{"media1_out mix 0", "media1_in Switch", "media1_in"},
{"media1_out mix 0", "media2_in Switch", "media2_in"},
{"media1_out mix 0", "media3_in Switch", "media3_in"},
{"media0_out", NULL, "media0_out mix 0"},
{"media1_out", NULL, "media1_out mix 0"},
{"pcm0_in", NULL, "media0_out"},
{"pcm1_in", NULL, "media1_out"},
{"Headset Capture", NULL, "pcm1_out"},
{"Headset Capture", NULL, "pcm2_out"},
{"pcm0_out", NULL, "pcm0_out mix 0"},
SST_SBA_MIXER_GRAPH_MAP("pcm0_out mix 0"),
{"pcm1_out", NULL, "pcm1_out mix 0"},
SST_SBA_MIXER_GRAPH_MAP("pcm1_out mix 0"),
{"pcm2_out", NULL, "pcm2_out mix 0"},
SST_SBA_MIXER_GRAPH_MAP("pcm2_out mix 0"),
{"media_loop1_in", NULL, "media_loop1_out"},
{"media_loop1_out", NULL, "media_loop1_out mix 0"},
SST_SBA_MIXER_GRAPH_MAP("media_loop1_out mix 0"),
{"media_loop2_in", NULL, "media_loop2_out"},
{"media_loop2_out", NULL, "media_loop2_out mix 0"},
SST_SBA_MIXER_GRAPH_MAP("media_loop2_out mix 0"),
{"sprot_loop_in", NULL, "sprot_loop_out"},
{"sprot_loop_out", NULL, "sprot_loop_out mix 0"},
SST_SBA_MIXER_GRAPH_MAP("sprot_loop_out mix 0"),
{"codec_out0", NULL, "codec_out0 mix 0"},
SST_SBA_MIXER_GRAPH_MAP("codec_out0 mix 0"),
{"codec_out1", NULL, "codec_out1 mix 0"},
SST_SBA_MIXER_GRAPH_MAP("codec_out1 mix 0"),
};
static const char * const slot_names[] = {
"none",
"slot 0", "slot 1", "slot 2", "slot 3",
"slot 4", "slot 5", "slot 6", "slot 7", /* not supported by FW */
};
static const char * const channel_names[] = {
"none",
"codec_out0_0", "codec_out0_1", "codec_out1_0", "codec_out1_1",
"codec_out2_0", "codec_out2_1", "codec_out3_0", "codec_out3_1", /* not supported by FW */
};
#define SST_INTERLEAVER(xpname, slot_name, slotno) \
SST_SSP_SLOT_CTL(xpname, "tx interleaver", slot_name, slotno, true, \
channel_names, sst_slot_get, sst_slot_put)
#define SST_DEINTERLEAVER(xpname, channel_name, channel_no) \
SST_SSP_SLOT_CTL(xpname, "rx deinterleaver", channel_name, channel_no, false, \
slot_names, sst_slot_get, sst_slot_put)
static const struct snd_kcontrol_new sst_slot_controls[] = {
SST_INTERLEAVER("codec_out", "slot 0", 0),
SST_INTERLEAVER("codec_out", "slot 1", 1),
SST_INTERLEAVER("codec_out", "slot 2", 2),
SST_INTERLEAVER("codec_out", "slot 3", 3),
SST_DEINTERLEAVER("codec_in", "codec_in0_0", 0),
SST_DEINTERLEAVER("codec_in", "codec_in0_1", 1),
SST_DEINTERLEAVER("codec_in", "codec_in1_0", 2),
SST_DEINTERLEAVER("codec_in", "codec_in1_1", 3),
};
/* Gain helper with min/max set */
#define SST_GAIN(name, path_id, task_id, instance, gain_var) \
SST_GAIN_KCONTROLS(name, "Gain", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \
SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \
sst_gain_get, sst_gain_put, \
SST_MODULE_ID_GAIN_CELL, path_id, instance, task_id, \
sst_gain_tlv_common, gain_var)
#define SST_VOLUME(name, path_id, task_id, instance, gain_var) \
SST_GAIN_KCONTROLS(name, "Volume", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \
SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \
sst_gain_get, sst_gain_put, \
SST_MODULE_ID_VOLUME, path_id, instance, task_id, \
sst_gain_tlv_common, gain_var)
static struct sst_gain_value sst_gains[];
static const struct snd_kcontrol_new sst_gain_controls[] = {
SST_GAIN("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[0]),
SST_GAIN("media1_in", SST_PATH_INDEX_MEDIA1_IN, SST_TASK_MMX, 0, &sst_gains[1]),
SST_GAIN("media2_in", SST_PATH_INDEX_MEDIA2_IN, SST_TASK_MMX, 0, &sst_gains[2]),
SST_GAIN("media3_in", SST_PATH_INDEX_MEDIA3_IN, SST_TASK_MMX, 0, &sst_gains[3]),
SST_GAIN("pcm0_in", SST_PATH_INDEX_PCM0_IN, SST_TASK_SBA, 0, &sst_gains[4]),
SST_GAIN("pcm1_in", SST_PATH_INDEX_PCM1_IN, SST_TASK_SBA, 0, &sst_gains[5]),
SST_GAIN("pcm1_out", SST_PATH_INDEX_PCM1_OUT, SST_TASK_SBA, 0, &sst_gains[6]),
SST_GAIN("pcm2_out", SST_PATH_INDEX_PCM2_OUT, SST_TASK_SBA, 0, &sst_gains[7]),
SST_GAIN("codec_in0", SST_PATH_INDEX_CODEC_IN0, SST_TASK_SBA, 0, &sst_gains[8]),
SST_GAIN("codec_in1", SST_PATH_INDEX_CODEC_IN1, SST_TASK_SBA, 0, &sst_gains[9]),
SST_GAIN("codec_out0", SST_PATH_INDEX_CODEC_OUT0, SST_TASK_SBA, 0, &sst_gains[10]),
SST_GAIN("codec_out1", SST_PATH_INDEX_CODEC_OUT1, SST_TASK_SBA, 0, &sst_gains[11]),
SST_GAIN("media_loop1_out", SST_PATH_INDEX_MEDIA_LOOP1_OUT, SST_TASK_SBA, 0, &sst_gains[12]),
SST_GAIN("media_loop2_out", SST_PATH_INDEX_MEDIA_LOOP2_OUT, SST_TASK_SBA, 0, &sst_gains[13]),
SST_GAIN("sprot_loop_out", SST_PATH_INDEX_SPROT_LOOP_OUT, SST_TASK_SBA, 0, &sst_gains[14]),
SST_VOLUME("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[15]),
};
#define SST_GAIN_NUM_CONTROLS 3
/* the SST_GAIN macro above will create three alsa controls for each
* instance invoked, gain, mute and ramp duration, which use the same gain
* cell sst_gain to keep track of data
* To calculate number of gain cell instances we need to device by 3 in
* below caulcation for gain cell memory.
* This gets rid of static number and issues while adding new controls
*/
static struct sst_gain_value sst_gains[ARRAY_SIZE(sst_gain_controls)/SST_GAIN_NUM_CONTROLS];
static const struct snd_kcontrol_new sst_algo_controls[] = { static const struct snd_kcontrol_new sst_algo_controls[] = {
SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24, SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24,
SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR),
...@@ -198,21 +1143,280 @@ static int sst_algo_control_init(struct device *dev) ...@@ -198,21 +1143,280 @@ static int sst_algo_control_init(struct device *dev)
return 0; return 0;
} }
int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform) static bool is_sst_dapm_widget(struct snd_soc_dapm_widget *w)
{
switch (w->id) {
case snd_soc_dapm_pga:
case snd_soc_dapm_aif_in:
case snd_soc_dapm_aif_out:
case snd_soc_dapm_input:
case snd_soc_dapm_output:
case snd_soc_dapm_mixer:
return true;
default:
return false;
}
}
/**
* sst_send_pipe_gains - send gains for the front-end DAIs
*
* The gains in the pipes connected to the front-ends are muted/unmuted
* automatically via the digital_mute() DAPM callback. This function sends the
* gains for the front-end pipes.
*/
int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute)
{
struct sst_data *drv = snd_soc_dai_get_drvdata(dai);
struct snd_soc_dapm_widget *w;
struct snd_soc_dapm_path *p = NULL;
dev_dbg(dai->dev, "enter, dai-name=%s dir=%d\n", dai->name, stream);
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
dev_dbg(dai->dev, "Stream name=%s\n",
dai->playback_widget->name);
w = dai->playback_widget;
list_for_each_entry(p, &w->sinks, list_source) {
if (p->connected && !p->connected(w, p->sink))
continue;
if (p->connect && p->sink->power &&
is_sst_dapm_widget(p->sink)) {
struct sst_ids *ids = p->sink->priv;
dev_dbg(dai->dev, "send gains for widget=%s\n",
p->sink->name);
mutex_lock(&drv->lock);
sst_set_pipe_gain(ids, drv, mute);
mutex_unlock(&drv->lock);
}
}
} else {
dev_dbg(dai->dev, "Stream name=%s\n",
dai->capture_widget->name);
w = dai->capture_widget;
list_for_each_entry(p, &w->sources, list_sink) {
if (p->connected && !p->connected(w, p->sink))
continue;
if (p->connect && p->source->power &&
is_sst_dapm_widget(p->source)) {
struct sst_ids *ids = p->source->priv;
dev_dbg(dai->dev, "send gain for widget=%s\n",
p->source->name);
mutex_lock(&drv->lock);
sst_set_pipe_gain(ids, drv, mute);
mutex_unlock(&drv->lock);
}
}
}
return 0;
}
/**
* sst_fill_module_list - populate the list of modules/gains for a pipe
*
*
* Fills the widget pointer in the kcontrol private data, and also fills the
* kcontrol pointer in the widget private data.
*
* Widget pointer is used to send the algo/gain in the .put() handler if the
* widget is powerd on.
*
* Kcontrol pointer is used to send the algo/gain in the widget power ON/OFF
* event handler. Each widget (pipe) has multiple algos stored in the algo_list.
*/
static int sst_fill_module_list(struct snd_kcontrol *kctl,
struct snd_soc_dapm_widget *w, int type)
{ {
struct sst_module *module = NULL;
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct sst_ids *ids = w->priv;
int ret = 0; int ret = 0;
module = devm_kzalloc(c->dev, sizeof(*module), GFP_KERNEL);
if (!module)
return -ENOMEM;
if (type == SST_MODULE_GAIN) {
struct sst_gain_mixer_control *mc = (void *)kctl->private_value;
mc->w = w;
module->kctl = kctl;
list_add_tail(&module->node, &ids->gain_list);
} else if (type == SST_MODULE_ALGO) {
struct sst_algo_control *bc = (void *)kctl->private_value;
bc->w = w;
module->kctl = kctl;
list_add_tail(&module->node, &ids->algo_list);
} else {
dev_err(c->dev, "invoked for unknown type %d module %s",
type, kctl->id.name);
ret = -EINVAL;
}
return ret;
}
/**
* sst_fill_widget_module_info - fill list of gains/algos for the pipe
* @widget: pipe modelled as a DAPM widget
*
* Fill the list of gains/algos for the widget by looking at all the card
* controls and comparing the name of the widget with the first part of control
* name. First part of control name contains the pipe name (widget name).
*/
static int sst_fill_widget_module_info(struct snd_soc_dapm_widget *w,
struct snd_soc_platform *platform)
{
struct snd_kcontrol *kctl;
int index, ret = 0;
struct snd_card *card = platform->component.card->snd_card;
char *idx;
down_read(&card->controls_rwsem);
list_for_each_entry(kctl, &card->controls, list) {
idx = strstr(kctl->id.name, " ");
if (idx == NULL)
continue;
index = strlen(kctl->id.name) - strlen(idx);
if (strstr(kctl->id.name, "Volume") &&
!strncmp(kctl->id.name, w->name, index))
ret = sst_fill_module_list(kctl, w, SST_MODULE_GAIN);
else if (strstr(kctl->id.name, "params") &&
!strncmp(kctl->id.name, w->name, index))
ret = sst_fill_module_list(kctl, w, SST_MODULE_ALGO);
else if (strstr(kctl->id.name, "Switch") &&
!strncmp(kctl->id.name, w->name, index) &&
strstr(kctl->id.name, "Gain")) {
struct sst_gain_mixer_control *mc =
(void *)kctl->private_value;
mc->w = w;
} else if (strstr(kctl->id.name, "interleaver") &&
!strncmp(kctl->id.name, w->name, index)) {
struct sst_enum *e = (void *)kctl->private_value;
e->w = w;
} else if (strstr(kctl->id.name, "deinterleaver") &&
!strncmp(kctl->id.name, w->name, index)) {
struct sst_enum *e = (void *)kctl->private_value;
e->w = w;
}
if (ret < 0) {
up_read(&card->controls_rwsem);
return ret;
}
}
up_read(&card->controls_rwsem);
return 0;
}
/**
* sst_fill_linked_widgets - fill the parent pointer for the linked widget
*/
static void sst_fill_linked_widgets(struct snd_soc_platform *platform,
struct sst_ids *ids)
{
struct snd_soc_dapm_widget *w;
unsigned int len = strlen(ids->parent_wname);
list_for_each_entry(w, &platform->component.card->widgets, list) {
if (!strncmp(ids->parent_wname, w->name, len)) {
ids->parent_w = w;
break;
}
}
}
/**
* sst_map_modules_to_pipe - fill algo/gains list for all pipes
*/
static int sst_map_modules_to_pipe(struct snd_soc_platform *platform)
{
struct snd_soc_dapm_widget *w;
int ret = 0;
list_for_each_entry(w, &platform->component.card->widgets, list) {
if (is_sst_dapm_widget(w) && (w->priv)) {
struct sst_ids *ids = w->priv;
dev_dbg(platform->dev, "widget type=%d name=%s\n",
w->id, w->name);
INIT_LIST_HEAD(&ids->algo_list);
INIT_LIST_HEAD(&ids->gain_list);
ret = sst_fill_widget_module_info(w, platform);
if (ret < 0)
return ret;
/* fill linked widgets */
if (ids->parent_wname != NULL)
sst_fill_linked_widgets(platform, ids);
}
}
return 0;
}
int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform)
{
int i, ret = 0;
struct snd_soc_dapm_context *dapm =
snd_soc_component_get_dapm(&platform->component);
struct sst_data *drv = snd_soc_platform_get_drvdata(platform); struct sst_data *drv = snd_soc_platform_get_drvdata(platform);
unsigned int gains = ARRAY_SIZE(sst_gain_controls)/3;
drv->byte_stream = devm_kzalloc(platform->dev, drv->byte_stream = devm_kzalloc(platform->dev,
SST_MAX_BIN_BYTES, GFP_KERNEL); SST_MAX_BIN_BYTES, GFP_KERNEL);
if (!drv->byte_stream) if (!drv->byte_stream)
return -ENOMEM; return -ENOMEM;
/*Initialize algo control params*/ snd_soc_dapm_new_controls(dapm, sst_dapm_widgets,
ARRAY_SIZE(sst_dapm_widgets));
snd_soc_dapm_add_routes(dapm, intercon,
ARRAY_SIZE(intercon));
snd_soc_dapm_new_widgets(dapm->card);
for (i = 0; i < gains; i++) {
sst_gains[i].mute = SST_GAIN_MUTE_DEFAULT;
sst_gains[i].l_gain = SST_GAIN_VOLUME_DEFAULT;
sst_gains[i].r_gain = SST_GAIN_VOLUME_DEFAULT;
sst_gains[i].ramp_duration = SST_GAIN_RAMP_DURATION_DEFAULT;
}
ret = snd_soc_add_platform_controls(platform, sst_gain_controls,
ARRAY_SIZE(sst_gain_controls));
if (ret)
return ret;
/* Initialize algo control params */
ret = sst_algo_control_init(platform->dev); ret = sst_algo_control_init(platform->dev);
if (ret) if (ret)
return ret; return ret;
ret = snd_soc_add_platform_controls(platform, sst_algo_controls, ret = snd_soc_add_platform_controls(platform, sst_algo_controls,
ARRAY_SIZE(sst_algo_controls)); ARRAY_SIZE(sst_algo_controls));
if (ret)
return ret;
ret = snd_soc_add_platform_controls(platform, sst_slot_controls,
ARRAY_SIZE(sst_slot_controls));
if (ret)
return ret;
ret = sst_map_modules_to_pipe(platform);
return ret; return ret;
} }
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
#ifndef __SST_ATOM_CONTROLS_H__ #ifndef __SST_ATOM_CONTROLS_H__
#define __SST_ATOM_CONTROLS_H__ #define __SST_ATOM_CONTROLS_H__
#include <sound/soc.h>
#include <sound/tlv.h>
enum { enum {
MERR_DPCM_AUDIO = 0, MERR_DPCM_AUDIO = 0,
MERR_DPCM_COMPR, MERR_DPCM_COMPR,
...@@ -360,16 +363,416 @@ struct sst_dsp_header { ...@@ -360,16 +363,416 @@ struct sst_dsp_header {
struct sst_cmd_generic { struct sst_cmd_generic {
struct sst_dsp_header header; struct sst_dsp_header header;
} __packed; } __packed;
struct swm_input_ids {
struct sst_destination_id input_id;
} __packed;
struct sst_cmd_set_swm {
struct sst_dsp_header header;
struct sst_destination_id output_id;
u16 switch_state;
u16 nb_inputs;
struct swm_input_ids input[SST_CMD_SWM_MAX_INPUTS];
} __packed;
struct sst_cmd_set_media_path {
struct sst_dsp_header header;
u16 switch_state;
} __packed;
struct pcm_cfg {
u8 s_length:2;
u8 rate:3;
u8 format:3;
} __packed;
struct sst_cmd_set_speech_path {
struct sst_dsp_header header;
u16 switch_state;
struct {
u16 rsvd:8;
struct pcm_cfg cfg;
} config;
} __packed;
struct gain_cell {
struct sst_destination_id dest;
s16 cell_gain_left;
s16 cell_gain_right;
u16 gain_time_constant;
} __packed;
#define NUM_GAIN_CELLS 1
struct sst_cmd_set_gain_dual {
struct sst_dsp_header header;
u16 gain_cell_num;
struct gain_cell cell_gains[NUM_GAIN_CELLS];
} __packed;
struct sst_cmd_set_params { struct sst_cmd_set_params {
struct sst_destination_id dst; struct sst_destination_id dst;
u16 command_id; u16 command_id;
char params[0]; char params[0];
} __packed; } __packed;
struct sst_cmd_sba_vb_start {
struct sst_dsp_header header;
} __packed;
union sba_media_loop_params {
struct {
u16 rsvd:8;
struct pcm_cfg cfg;
} part;
u16 full;
} __packed;
struct sst_cmd_sba_set_media_loop_map {
struct sst_dsp_header header;
u16 switch_state;
union sba_media_loop_params param;
u16 map;
} __packed;
struct sst_cmd_tone_stop {
struct sst_dsp_header header;
u16 switch_state;
} __packed;
enum sst_ssp_mode {
SSP_MODE_MASTER = 0,
SSP_MODE_SLAVE = 1,
};
enum sst_ssp_pcm_mode {
SSP_PCM_MODE_NORMAL = 0,
SSP_PCM_MODE_NETWORK = 1,
};
enum sst_ssp_duplex {
SSP_DUPLEX = 0,
SSP_RX = 1,
SSP_TX = 2,
};
enum sst_ssp_fs_frequency {
SSP_FS_8_KHZ = 0,
SSP_FS_16_KHZ = 1,
SSP_FS_44_1_KHZ = 2,
SSP_FS_48_KHZ = 3,
};
enum sst_ssp_fs_polarity {
SSP_FS_ACTIVE_LOW = 0,
SSP_FS_ACTIVE_HIGH = 1,
};
enum sst_ssp_protocol {
SSP_MODE_PCM = 0,
SSP_MODE_I2S = 1,
};
enum sst_ssp_port_id {
SSP_MODEM = 0,
SSP_BT = 1,
SSP_FM = 2,
SSP_CODEC = 3,
};
struct sst_cmd_sba_hw_set_ssp {
struct sst_dsp_header header;
u16 selection; /* 0:SSP0(def), 1:SSP1, 2:SSP2 */
u16 switch_state;
u16 nb_bits_per_slots:6; /* 0-32 bits, 24 (def) */
u16 nb_slots:4; /* 0-8: slots per frame */
u16 mode:3; /* 0:Master, 1: Slave */
u16 duplex:3;
u16 active_tx_slot_map:8; /* Bit map, 0:off, 1:on */
u16 reserved1:8;
u16 active_rx_slot_map:8; /* Bit map 0: Off, 1:On */
u16 reserved2:8;
u16 frame_sync_frequency;
u16 frame_sync_polarity:8;
u16 data_polarity:8;
u16 frame_sync_width; /* 1 to N clocks */
u16 ssp_protocol:8;
u16 start_delay:8; /* Start delay in terms of clock ticks */
} __packed;
#define SST_MAX_TDM_SLOTS 8
struct sst_param_sba_ssp_slot_map {
struct sst_dsp_header header;
u16 param_id;
u16 param_len;
u16 ssp_index;
u8 rx_slot_map[SST_MAX_TDM_SLOTS];
u8 tx_slot_map[SST_MAX_TDM_SLOTS];
} __packed;
enum {
SST_PROBE_EXTRACTOR = 0,
SST_PROBE_INJECTOR = 1,
};
/**** widget defines *****/
#define SST_MODULE_GAIN 1
#define SST_MODULE_ALGO 2
#define SST_FMT_MONO 0
#define SST_FMT_STEREO 3
/* physical SSP numbers */
enum {
SST_SSP0 = 0,
SST_SSP1,
SST_SSP2,
SST_SSP_LAST = SST_SSP2,
};
#define SST_NUM_SSPS (SST_SSP_LAST + 1) /* physical SSPs */
#define SST_MAX_SSP_MUX 2 /* single SSP muxed between pipes */
#define SST_MAX_SSP_DOMAINS 2 /* domains present in each pipe */
struct sst_module {
struct snd_kcontrol *kctl;
struct list_head node;
};
struct sst_ssp_config {
u8 ssp_id;
u8 bits_per_slot;
u8 slots;
u8 ssp_mode;
u8 pcm_mode;
u8 duplex;
u8 ssp_protocol;
u8 fs_frequency;
u8 active_slot_map;
u8 start_delay;
u16 fs_width;
};
struct sst_ssp_cfg {
const u8 ssp_number;
const int *mux_shift;
const int (*domain_shift)[SST_MAX_SSP_MUX];
const struct sst_ssp_config (*ssp_config)[SST_MAX_SSP_MUX][SST_MAX_SSP_DOMAINS];
};
struct sst_ids {
u16 location_id;
u16 module_id;
u8 task_id;
u8 format;
u8 reg;
const char *parent_wname;
struct snd_soc_dapm_widget *parent_w;
struct list_head algo_list;
struct list_head gain_list;
const struct sst_pcm_format *pcm_fmt;
};
#define SST_AIF_IN(wname, wevent) \
{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = NULL, \
.reg = SND_SOC_NOPM, .shift = 0, \
.on_val = 1, .off_val = 0, \
.event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
.priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \
}
#define SST_AIF_OUT(wname, wevent) \
{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = NULL, \
.reg = SND_SOC_NOPM, .shift = 0, \
.on_val = 1, .off_val = 0, \
.event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
.priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \
}
#define SST_INPUT(wname, wevent) \
{ .id = snd_soc_dapm_input, .name = wname, .sname = NULL, \
.reg = SND_SOC_NOPM, .shift = 0, \
.on_val = 1, .off_val = 0, \
.event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
.priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \
}
#define SST_OUTPUT(wname, wevent) \
{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \
.reg = SND_SOC_NOPM, .shift = 0, \
.on_val = 1, .off_val = 0, \
.event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
.priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \
}
#define SST_DAPM_OUTPUT(wname, wloc_id, wtask_id, wformat, wevent) \
{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \
.reg = SND_SOC_NOPM, .shift = 0, \
.on_val = 1, .off_val = 0, \
.event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
.priv = (void *)&(struct sst_ids) { .location_id = wloc_id, .task_id = wtask_id,\
.pcm_fmt = wformat, } \
}
#define SST_PATH(wname, wtask, wloc_id, wevent, wflags) \
{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \
.kcontrol_news = NULL, .num_kcontrols = 0, \
.on_val = 1, .off_val = 0, \
.event = wevent, .event_flags = wflags, \
.priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, } \
}
#define SST_LINKED_PATH(wname, wtask, wloc_id, linked_wname, wevent, wflags) \
{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \
.kcontrol_news = NULL, .num_kcontrols = 0, \
.on_val = 1, .off_val = 0, \
.event = wevent, .event_flags = wflags, \
.priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \
.parent_wname = linked_wname} \
}
#define SST_PATH_MEDIA_LOOP(wname, wtask, wloc_id, wformat, wevent, wflags) \
{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \
.kcontrol_news = NULL, .num_kcontrols = 0, \
.event = wevent, .event_flags = wflags, \
.priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \
.format = wformat,} \
}
/* output is triggered before input */
#define SST_PATH_INPUT(name, task_id, loc_id, event) \
SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)
#define SST_PATH_LINKED_INPUT(name, task_id, loc_id, linked_wname, event) \
SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)
#define SST_PATH_OUTPUT(name, task_id, loc_id, event) \
SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)
#define SST_PATH_LINKED_OUTPUT(name, task_id, loc_id, linked_wname, event) \
SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)
#define SST_PATH_MEDIA_LOOP_OUTPUT(name, task_id, loc_id, format, event) \
SST_PATH_MEDIA_LOOP(name, task_id, loc_id, format, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)
#define SST_SWM_MIXER(wname, wreg, wtask, wloc_id, wcontrols, wevent) \
{ .id = snd_soc_dapm_mixer, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \
.kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols),\
.event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD | \
SND_SOC_DAPM_POST_REG, \
.priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \
.reg = wreg } \
}
enum sst_gain_kcontrol_type {
SST_GAIN_TLV,
SST_GAIN_MUTE,
SST_GAIN_RAMP_DURATION,
};
struct sst_gain_mixer_control {
bool stereo;
enum sst_gain_kcontrol_type type;
struct sst_gain_value *gain_val;
int max;
int min;
u16 instance_id;
u16 module_id;
u16 pipe_id;
u16 task_id;
char pname[44];
struct snd_soc_dapm_widget *w;
};
struct sst_gain_value {
u16 ramp_duration;
s16 l_gain;
s16 r_gain;
bool mute;
};
#define SST_GAIN_VOLUME_DEFAULT (-1440)
#define SST_GAIN_RAMP_DURATION_DEFAULT 5 /* timeconstant */
#define SST_GAIN_MUTE_DEFAULT true
#define SST_GAIN_KCONTROL_TLV(xname, xhandler_get, xhandler_put, \
xmod, xpipe, xinstance, xtask, tlv_array, xgain_val, \
xmin, xmax, xpname) \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.tlv.p = (tlv_array), \
.info = sst_gain_ctl_info,\
.get = xhandler_get, .put = xhandler_put, \
.private_value = (unsigned long)&(struct sst_gain_mixer_control) \
{ .stereo = true, .max = xmax, .min = xmin, .type = SST_GAIN_TLV, \
.module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\
.instance_id = xinstance, .gain_val = xgain_val, .pname = xpname}
#define SST_GAIN_KCONTROL_INT(xname, xhandler_get, xhandler_put, \
xmod, xpipe, xinstance, xtask, xtype, xgain_val, \
xmin, xmax, xpname) \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = sst_gain_ctl_info, \
.get = xhandler_get, .put = xhandler_put, \
.private_value = (unsigned long)&(struct sst_gain_mixer_control) \
{ .stereo = false, .max = xmax, .min = xmin, .type = xtype, \
.module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\
.instance_id = xinstance, .gain_val = xgain_val, .pname = xpname}
#define SST_GAIN_KCONTROL_BOOL(xname, xhandler_get, xhandler_put,\
xmod, xpipe, xinstance, xtask, xgain_val, xpname) \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_bool_ext, \
.get = xhandler_get, .put = xhandler_put, \
.private_value = (unsigned long)&(struct sst_gain_mixer_control) \
{ .stereo = false, .type = SST_GAIN_MUTE, \
.module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\
.instance_id = xinstance, .gain_val = xgain_val, .pname = xpname}
#define SST_CONTROL_NAME(xpname, xmname, xinstance, xtype) \ #define SST_CONTROL_NAME(xpname, xmname, xinstance, xtype) \
xpname " " xmname " " #xinstance " " xtype xpname " " xmname " " #xinstance " " xtype
#define SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, xtype, xsubmodule) \ #define SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, xtype, xsubmodule) \
xpname " " xmname " " #xinstance " " xtype " " xsubmodule xpname " " xmname " " #xinstance " " xtype " " xsubmodule
/*
* 3 Controls for each Gain module
* e.g. - pcm0_in Gain 0 Volume
* - pcm0_in Gain 0 Ramp Delay
* - pcm0_in Gain 0 Switch
*/
#define SST_GAIN_KCONTROLS(xpname, xmname, xmin_gain, xmax_gain, xmin_tc, xmax_tc, \
xhandler_get, xhandler_put, \
xmod, xpipe, xinstance, xtask, tlv_array, xgain_val) \
{ SST_GAIN_KCONTROL_INT(SST_CONTROL_NAME(xpname, xmname, xinstance, "Ramp Delay"), \
xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, SST_GAIN_RAMP_DURATION, \
xgain_val, xmin_tc, xmax_tc, xpname) }, \
{ SST_GAIN_KCONTROL_BOOL(SST_CONTROL_NAME(xpname, xmname, xinstance, "Switch"), \
xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, \
xgain_val, xpname) } ,\
{ SST_GAIN_KCONTROL_TLV(SST_CONTROL_NAME(xpname, xmname, xinstance, "Volume"), \
xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, tlv_array, \
xgain_val, xmin_gain, xmax_gain, xpname) }
#define SST_GAIN_TC_MIN 5
#define SST_GAIN_TC_MAX 5000
#define SST_GAIN_MIN_VALUE -1440 /* in 0.1 DB units */
#define SST_GAIN_MAX_VALUE 360
enum sst_algo_kcontrol_type { enum sst_algo_kcontrol_type {
SST_ALGO_PARAMS, SST_ALGO_PARAMS,
SST_ALGO_BYPASS, SST_ALGO_BYPASS,
...@@ -439,4 +842,29 @@ struct sst_enum { ...@@ -439,4 +842,29 @@ struct sst_enum {
struct snd_soc_dapm_widget *w; struct snd_soc_dapm_widget *w;
}; };
/* only 4 slots/channels supported atm */
#define SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts) \
(struct sst_enum){ .reg = s_ch_no, .tx = is_tx, .max = 4+1, .texts = xtexts, }
#define SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name) \
xpname " " xmname " " s_ch_name
#define SST_SSP_SLOT_CTL(xpname, xmname, s_ch_name, s_ch_no, is_tx, xtexts, xget, xput) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name), \
.info = sst_slot_enum_info, \
.get = xget, .put = xput, \
.private_value = (unsigned long)&SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts), \
}
#define SST_MUX_CTL_NAME(xpname, xinstance) \
xpname " " #xinstance
#define SST_SSP_MUX_ENUM(xreg, xshift, xtexts) \
(struct soc_enum) SOC_ENUM_DOUBLE(xreg, xshift, xshift, ARRAY_SIZE(xtexts), xtexts)
#define SST_SSP_MUX_CTL(xpname, xinstance, xreg, xshift, xtexts) \
SOC_DAPM_ENUM(SST_MUX_CTL_NAME(xpname, xinstance), \
SST_SSP_MUX_ENUM(xreg, xshift, xtexts))
#endif #endif
...@@ -67,17 +67,12 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ...@@ -67,17 +67,12 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
{ {
struct dma_block_info *block; struct dma_block_info *block;
struct sst_module *mod; struct sst_module *mod;
struct sst_module_data block_data;
struct sst_module_template template; struct sst_module_template template;
int count; int count;
memset(&template, 0, sizeof(template)); memset(&template, 0, sizeof(template));
template.id = module->type; template.id = module->type;
template.entry = module->entry_point; 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); mod = sst_module_new(fw, &template, NULL);
if (mod == NULL) if (mod == NULL)
...@@ -94,19 +89,19 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ...@@ -94,19 +89,19 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
switch (block->type) { switch (block->type) {
case SST_BYT_IRAM: case SST_BYT_IRAM:
block_data.offset = block->ram_offset + mod->offset = block->ram_offset +
dsp->addr.iram_offset; dsp->addr.iram_offset;
block_data.type = SST_MEM_IRAM; mod->type = SST_MEM_IRAM;
break; break;
case SST_BYT_DRAM: case SST_BYT_DRAM:
block_data.offset = block->ram_offset + mod->offset = block->ram_offset +
dsp->addr.dram_offset; dsp->addr.dram_offset;
block_data.type = SST_MEM_DRAM; mod->type = SST_MEM_DRAM;
break; break;
case SST_BYT_CACHE: case SST_BYT_CACHE:
block_data.offset = block->ram_offset + mod->offset = block->ram_offset +
(dsp->addr.fw_ext - dsp->addr.lpe); (dsp->addr.fw_ext - dsp->addr.lpe);
block_data.type = SST_MEM_CACHE; mod->type = SST_MEM_CACHE;
break; break;
default: default:
dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n", dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n",
...@@ -114,11 +109,10 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ...@@ -114,11 +109,10 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
return -EINVAL; return -EINVAL;
} }
block_data.size = block->size; mod->size = block->size;
block_data.data_type = SST_DATA_M; mod->data = (void *)block + sizeof(*block);
block_data.data = (void *)block + sizeof(*block);
sst_module_insert_fixed_block(mod, &block_data); sst_module_alloc_blocks(mod);
block = (void *)block + sizeof(*block) + block->size; block = (void *)block + sizeof(*block) + block->size;
} }
......
...@@ -26,6 +26,9 @@ struct sst_mem_block; ...@@ -26,6 +26,9 @@ struct sst_mem_block;
struct sst_module; struct sst_module;
struct sst_fw; struct sst_fw;
/* do we need to remove or keep */
#define DSP_DRAM_ADDR_OFFSET 0x400000
/* /*
* DSP Operations exported by platform Audio DSP driver. * DSP Operations exported by platform Audio DSP driver.
*/ */
...@@ -33,6 +36,9 @@ struct sst_ops { ...@@ -33,6 +36,9 @@ struct sst_ops {
/* DSP core boot / reset */ /* DSP core boot / reset */
void (*boot)(struct sst_dsp *); void (*boot)(struct sst_dsp *);
void (*reset)(struct sst_dsp *); void (*reset)(struct sst_dsp *);
int (*wake)(struct sst_dsp *);
void (*sleep)(struct sst_dsp *);
void (*stall)(struct sst_dsp *);
/* Shim IO */ /* Shim IO */
void (*write)(void __iomem *addr, u32 offset, u32 value); void (*write)(void __iomem *addr, u32 offset, u32 value);
...@@ -67,6 +73,8 @@ struct sst_addr { ...@@ -67,6 +73,8 @@ struct sst_addr {
u32 shim_offset; u32 shim_offset;
u32 iram_offset; u32 iram_offset;
u32 dram_offset; u32 dram_offset;
u32 dsp_iram_offset;
u32 dsp_dram_offset;
void __iomem *lpe; void __iomem *lpe;
void __iomem *shim; void __iomem *shim;
void __iomem *pci_cfg; void __iomem *pci_cfg;
...@@ -83,15 +91,6 @@ struct sst_mailbox { ...@@ -83,15 +91,6 @@ struct sst_mailbox {
size_t out_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. * Audio DSP memory block types.
*/ */
...@@ -124,23 +123,6 @@ struct sst_fw { ...@@ -124,23 +123,6 @@ struct sst_fw {
void *private; /* core doesn't touch this */ 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. * Audio DSP Generic Module Template.
* *
...@@ -150,15 +132,52 @@ struct sst_module_data { ...@@ -150,15 +132,52 @@ struct sst_module_data {
struct sst_module_template { struct sst_module_template {
u32 id; u32 id;
u32 entry; /* entry point */ u32 entry; /* entry point */
struct sst_module_data s; /* scratch data */ u32 scratch_size;
struct sst_module_data p; /* peristant data */ u32 persistent_size;
};
/*
* Block Allocator - Used to allocate blocks of DSP memory.
*/
struct sst_block_allocator {
u32 id;
u32 offset;
int size;
enum sst_mem_type type;
};
/*
* Runtime Module Instance - A module object can be instanciated multiple
* times within the DSP FW.
*/
struct sst_module_runtime {
struct sst_dsp *dsp;
int id;
struct sst_module *module; /* parent module we belong too */
u32 persistent_offset; /* private memory offset */
void *private;
struct list_head list;
struct list_head block_list; /* list of blocks used */
};
/*
* Runtime Module Context - The runtime context must be manually stored by the
* driver prior to enter S3 and restored after leaving S3. This should really be
* part of the memory context saved by the enter D3 message IPC ???
*/
struct sst_module_runtime_context {
dma_addr_t dma_buffer;
u32 *buffer;
}; };
/* /*
* Audio DSP Generic Module. * Audio DSP Generic Module.
* *
* Each Firmware file can consist of 1..N modules. A module can span multiple * 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. * ADSP memory blocks. The simplest FW will be a file with 1 module. A module
* can be instanciated multiple times in the DSP.
*/ */
struct sst_module { struct sst_module {
struct sst_dsp *dsp; struct sst_dsp *dsp;
...@@ -167,10 +186,13 @@ struct sst_module { ...@@ -167,10 +186,13 @@ struct sst_module {
/* module configuration */ /* module configuration */
u32 id; u32 id;
u32 entry; /* module entry point */ u32 entry; /* module entry point */
u32 offset; /* module offset in firmware file */ s32 offset; /* module offset in firmware file */
u32 size; /* module size */ u32 size; /* module size */
struct sst_module_data s; /* scratch data */ u32 scratch_size; /* global scratch memory required */
struct sst_module_data p; /* peristant data */ u32 persistent_size; /* private memory required */
enum sst_mem_type type; /* destination memory type */
u32 data_offset; /* offset in ADSP memory space */
void *data; /* module data */
/* runtime */ /* runtime */
u32 usage_count; /* can be unloaded if count == 0 */ u32 usage_count; /* can be unloaded if count == 0 */
...@@ -180,6 +202,7 @@ struct sst_module { ...@@ -180,6 +202,7 @@ struct sst_module {
struct list_head block_list; /* Module list of blocks in use */ struct list_head block_list; /* Module list of blocks in use */
struct list_head list; /* DSP list of modules */ struct list_head list; /* DSP list of modules */
struct list_head list_fw; /* FW list of modules */ struct list_head list_fw; /* FW list of modules */
struct list_head runtime_list; /* list of runtime module objects*/
}; };
/* /*
...@@ -208,7 +231,6 @@ struct sst_mem_block { ...@@ -208,7 +231,6 @@ struct sst_mem_block {
struct sst_block_ops *ops; /* block operations, if any */ struct sst_block_ops *ops; /* block operations, if any */
/* block status */ /* block status */
enum sst_data_type data_type; /* data type held in this block */
u32 bytes_used; /* bytes in use by modules */ u32 bytes_used; /* bytes in use by modules */
void *private; /* generic core does not touch this */ void *private; /* generic core does not touch this */
int users; /* number of modules using this block */ int users; /* number of modules using this block */
...@@ -253,6 +275,11 @@ struct sst_dsp { ...@@ -253,6 +275,11 @@ struct sst_dsp {
struct list_head module_list; struct list_head module_list;
struct list_head fw_list; struct list_head fw_list;
/* scratch buffer */
struct list_head scratch_block_list;
u32 scratch_offset;
u32 scratch_size;
/* platform data */ /* platform data */
struct sst_pdata *pdata; struct sst_pdata *pdata;
...@@ -290,18 +317,33 @@ void sst_fw_unload(struct sst_fw *sst_fw); ...@@ -290,18 +317,33 @@ void sst_fw_unload(struct sst_fw *sst_fw);
/* Create/Free firmware modules */ /* Create/Free firmware modules */
struct sst_module *sst_module_new(struct sst_fw *sst_fw, struct sst_module *sst_module_new(struct sst_fw *sst_fw,
struct sst_module_template *template, void *private); struct sst_module_template *template, void *private);
void sst_module_free(struct sst_module *sst_module); void sst_module_free(struct sst_module *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); struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id);
int sst_module_alloc_blocks(struct sst_module *module);
/* allocate/free pesistent/scratch memory regions managed by drv */ int sst_module_free_blocks(struct sst_module *module);
struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp);
void sst_mem_block_free_scratch(struct sst_dsp *dsp, /* Create/Free firmware module runtime instances */
struct sst_module *scratch); struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module,
int sst_block_module_remove(struct sst_module *module); int id, void *private);
void sst_module_runtime_free(struct sst_module_runtime *runtime);
struct sst_module_runtime *sst_module_runtime_get_from_id(
struct sst_module *module, u32 id);
int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime,
int offset);
int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime);
int sst_module_runtime_save(struct sst_module_runtime *runtime,
struct sst_module_runtime_context *context);
int sst_module_runtime_restore(struct sst_module_runtime *runtime,
struct sst_module_runtime_context *context);
/* generic block allocation */
int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba,
struct list_head *block_list);
int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list);
/* scratch allocation */
int sst_block_alloc_scratch(struct sst_dsp *dsp);
void sst_block_free_scratch(struct sst_dsp *dsp);
/* Register the DSPs memory blocks - would be nice to read from ACPI */ /* 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, struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset,
...@@ -309,4 +351,10 @@ struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, ...@@ -309,4 +351,10 @@ struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset,
void *private); void *private);
void sst_mem_block_unregister_all(struct sst_dsp *dsp); void sst_mem_block_unregister_all(struct sst_dsp *dsp);
/* Create/Free DMA resources */
int sst_dma_new(struct sst_dsp *sst);
void sst_dma_free(struct sst_dma *dma);
u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset,
enum sst_mem_type type);
#endif #endif
...@@ -245,6 +245,29 @@ int sst_dsp_boot(struct sst_dsp *sst) ...@@ -245,6 +245,29 @@ int sst_dsp_boot(struct sst_dsp *sst)
} }
EXPORT_SYMBOL_GPL(sst_dsp_boot); EXPORT_SYMBOL_GPL(sst_dsp_boot);
int sst_dsp_wake(struct sst_dsp *sst)
{
if (sst->ops->wake)
return sst->ops->wake(sst);
return 0;
}
EXPORT_SYMBOL_GPL(sst_dsp_wake);
void sst_dsp_sleep(struct sst_dsp *sst)
{
if (sst->ops->sleep)
sst->ops->sleep(sst);
}
EXPORT_SYMBOL_GPL(sst_dsp_sleep);
void sst_dsp_stall(struct sst_dsp *sst)
{
if (sst->ops->stall)
sst->ops->stall(sst);
}
EXPORT_SYMBOL_GPL(sst_dsp_stall);
void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg) void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg)
{ {
sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY); sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY);
...@@ -352,6 +375,7 @@ struct sst_dsp *sst_dsp_new(struct device *dev, ...@@ -352,6 +375,7 @@ struct sst_dsp *sst_dsp_new(struct device *dev,
INIT_LIST_HEAD(&sst->free_block_list); INIT_LIST_HEAD(&sst->free_block_list);
INIT_LIST_HEAD(&sst->module_list); INIT_LIST_HEAD(&sst->module_list);
INIT_LIST_HEAD(&sst->fw_list); INIT_LIST_HEAD(&sst->fw_list);
INIT_LIST_HEAD(&sst->scratch_block_list);
/* Initialise SST Audio DSP */ /* Initialise SST Audio DSP */
if (sst->ops->init) { if (sst->ops->init) {
...@@ -366,6 +390,10 @@ struct sst_dsp *sst_dsp_new(struct device *dev, ...@@ -366,6 +390,10 @@ struct sst_dsp *sst_dsp_new(struct device *dev,
if (err) if (err)
goto irq_err; goto irq_err;
err = sst_dma_new(sst);
if (err)
dev_warn(dev, "sst_dma_new failed %d\n", err);
return sst; return sst;
irq_err: irq_err:
...@@ -381,6 +409,9 @@ void sst_dsp_free(struct sst_dsp *sst) ...@@ -381,6 +409,9 @@ void sst_dsp_free(struct sst_dsp *sst)
free_irq(sst->irq, sst); free_irq(sst->irq, sst);
if (sst->ops->free) if (sst->ops->free)
sst->ops->free(sst); sst->ops->free(sst);
if (sst->dma)
sst_dma_free(sst->dma);
} }
EXPORT_SYMBOL_GPL(sst_dsp_free); EXPORT_SYMBOL_GPL(sst_dsp_free);
......
...@@ -30,6 +30,9 @@ ...@@ -30,6 +30,9 @@
#define SST_DMA_TYPE_DW 1 #define SST_DMA_TYPE_DW 1
#define SST_DMA_TYPE_MID 2 #define SST_DMA_TYPE_MID 2
/* autosuspend delay 5s*/
#define SST_RUNTIME_SUSPEND_DELAY (5 * 1000)
/* SST Shim register map /* SST Shim register map
* The register naming can differ between products. Some products also * The register naming can differ between products. Some products also
* contain extra functionality. * contain extra functionality.
...@@ -156,12 +159,18 @@ ...@@ -156,12 +159,18 @@
#define SST_VDRTCTL3 0xaC #define SST_VDRTCTL3 0xaC
/* VDRTCTL0 */ /* VDRTCTL0 */
#define SST_VDRTCL0_APLLSE_MASK 1 #define SST_VDRTCL0_D3PGD (1 << 0)
#define SST_VDRTCL0_DSRAMPGE_SHIFT 16 #define SST_VDRTCL0_D3SRAMPGD (1 << 1)
#define SST_VDRTCL0_DSRAMPGE_MASK (0xffff << SST_VDRTCL0_DSRAMPGE_SHIFT) #define SST_VDRTCL0_DSRAMPGE_SHIFT 12
#define SST_VDRTCL0_ISRAMPGE_SHIFT 6 #define SST_VDRTCL0_DSRAMPGE_MASK (0xfffff << SST_VDRTCL0_DSRAMPGE_SHIFT)
#define SST_VDRTCL0_ISRAMPGE_SHIFT 2
#define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) #define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT)
/* VDRTCTL2 */
#define SST_VDRTCL2_DCLCGE (1 << 1)
#define SST_VDRTCL2_DTCGE (1 << 10)
#define SST_VDRTCL2_APLLSE_MASK (1 << 31)
/* PMCS */ /* PMCS */
#define SST_PMCS 0x84 #define SST_PMCS 0x84
#define SST_PMCS_PS_MASK 0x3 #define SST_PMCS_PS_MASK 0x3
...@@ -245,6 +254,17 @@ void sst_memcpy_fromio_32(struct sst_dsp *sst, ...@@ -245,6 +254,17 @@ void sst_memcpy_fromio_32(struct sst_dsp *sst,
/* DSP reset & boot */ /* DSP reset & boot */
void sst_dsp_reset(struct sst_dsp *sst); void sst_dsp_reset(struct sst_dsp *sst);
int sst_dsp_boot(struct sst_dsp *sst); int sst_dsp_boot(struct sst_dsp *sst);
int sst_dsp_wake(struct sst_dsp *sst);
void sst_dsp_sleep(struct sst_dsp *sst);
void sst_dsp_stall(struct sst_dsp *sst);
/* DMA */
int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id);
void sst_dsp_dma_put_channel(struct sst_dsp *dsp);
int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr,
dma_addr_t src_addr, size_t size);
int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr,
dma_addr_t src_addr, size_t size);
/* Msg IO */ /* Msg IO */
void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg); void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg);
......
...@@ -23,6 +23,11 @@ ...@@ -23,6 +23,11 @@
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/dmaengine.h> #include <linux/dmaengine.h>
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/acpi.h>
/* supported DMA engine drivers */
#include <linux/platform_data/dma-dw.h>
#include <linux/dma/dw.h>
#include <asm/page.h> #include <asm/page.h>
#include <asm/pgtable.h> #include <asm/pgtable.h>
...@@ -30,16 +35,301 @@ ...@@ -30,16 +35,301 @@
#include "sst-dsp.h" #include "sst-dsp.h"
#include "sst-dsp-priv.h" #include "sst-dsp-priv.h"
static void block_module_remove(struct sst_module *module); #define SST_DMA_RESOURCES 2
#define SST_DSP_DMA_MAX_BURST 0x3
#define SST_HSW_BLOCK_ANY 0xffffffff
#define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000
struct sst_dma {
struct sst_dsp *sst;
struct dw_dma_chip *chip;
struct dma_async_tx_descriptor *desc;
struct dma_chan *ch;
};
static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes)
{
/* __iowrite32_copy use 32bit size values so divide by 4 */
__iowrite32_copy((void *)dest, src, bytes/4);
}
static void sst_dma_transfer_complete(void *arg)
{
struct sst_dsp *sst = (struct sst_dsp *)arg;
dev_dbg(sst->dev, "DMA: callback\n");
}
static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr,
dma_addr_t src_addr, size_t size)
{
struct dma_async_tx_descriptor *desc;
struct sst_dma *dma = sst->dma;
if (dma->ch == NULL) {
dev_err(sst->dev, "error: no DMA channel\n");
return -ENODEV;
}
dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n",
(unsigned long)src_addr, (unsigned long)dest_addr, size);
desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr,
src_addr, size, DMA_CTRL_ACK);
if (!desc){
dev_err(sst->dev, "error: dma prep memcpy failed\n");
return -EINVAL;
}
desc->callback = sst_dma_transfer_complete;
desc->callback_param = sst;
desc->tx_submit(desc);
dma_wait_for_async_tx(desc);
return 0;
}
/* copy to DSP */
int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr,
dma_addr_t src_addr, size_t size)
{
return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP,
src_addr, size);
}
EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto);
/* copy from DSP */
int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr,
dma_addr_t src_addr, size_t size)
{
return sst_dsp_dma_copy(sst, dest_addr,
src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size);
}
EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom);
/* remove module from memory - callers hold locks */
static void block_list_remove(struct sst_dsp *dsp,
struct list_head *block_list)
{
struct sst_mem_block *block, *tmp;
int err;
/* disable each block */
list_for_each_entry(block, block_list, module_list) {
if (block->ops && block->ops->disable) {
err = block->ops->disable(block);
if (err < 0)
dev_err(dsp->dev,
"error: cant disable block %d:%d\n",
block->type, block->index);
}
}
/* mark each block as free */
list_for_each_entry_safe(block, tmp, block_list, module_list) {
list_del(&block->module_list);
list_move(&block->list, &dsp->free_block_list);
dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n",
block->type, block->index, block->offset);
}
}
/* prepare the memory block to receive data from host - callers hold locks */
static int block_list_prepare(struct sst_dsp *dsp,
struct list_head *block_list)
{
struct sst_mem_block *block;
int ret = 0;
/* enable each block so that's it'e ready for data */
list_for_each_entry(block, block_list, module_list) {
if (block->ops && block->ops->enable && !block->users) {
ret = block->ops->enable(block);
if (ret < 0) {
dev_err(dsp->dev,
"error: cant disable block %d:%d\n",
block->type, block->index);
goto err;
}
}
}
return ret;
err:
list_for_each_entry(block, block_list, module_list) {
if (block->ops && block->ops->disable)
block->ops->disable(block);
}
return ret;
}
static struct dw_dma_platform_data dw_pdata = {
.is_private = 1,
.chan_allocation_order = CHAN_ALLOCATION_ASCENDING,
.chan_priority = CHAN_PRIORITY_ASCENDING,
};
static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem,
int irq)
{
struct dw_dma_chip *chip;
int err;
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return ERR_PTR(-ENOMEM);
chip->irq = irq;
chip->regs = devm_ioremap_resource(dev, mem);
if (IS_ERR(chip->regs))
return ERR_CAST(chip->regs);
err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31));
if (err)
return ERR_PTR(err);
chip->dev = dev;
err = dw_dma_probe(chip, &dw_pdata);
if (err)
return ERR_PTR(err);
return chip;
}
static void dw_remove(struct dw_dma_chip *chip)
{
dw_dma_remove(chip);
}
static bool dma_chan_filter(struct dma_chan *chan, void *param)
{
struct sst_dsp *dsp = (struct sst_dsp *)param;
return chan->device->dev == dsp->dma_dev;
}
static void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id)
{ {
u32 i; struct sst_dma *dma = dsp->dma;
struct dma_slave_config slave;
dma_cap_mask_t mask;
int ret;
/* The Intel MID DMA engine driver needs the slave config set but
* Synopsis DMA engine driver safely ignores the slave config */
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
dma_cap_set(DMA_MEMCPY, mask);
dma->ch = dma_request_channel(mask, dma_chan_filter, dsp);
if (dma->ch == NULL) {
dev_err(dsp->dev, "error: DMA request channel failed\n");
return -EIO;
}
memset(&slave, 0, sizeof(slave));
slave.direction = DMA_MEM_TO_DEV;
slave.src_addr_width =
slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST;
ret = dmaengine_slave_config(dma->ch, &slave);
if (ret) {
dev_err(dsp->dev, "error: unable to set DMA slave config %d\n",
ret);
dma_release_channel(dma->ch);
dma->ch = NULL;
}
/* copy one 32 bit word at a time as 64 bit access is not supported */ return ret;
for (i = 0; i < bytes; i += 4)
memcpy_toio(dest + i, src + i, 4);
} }
EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel);
void sst_dsp_dma_put_channel(struct sst_dsp *dsp)
{
struct sst_dma *dma = dsp->dma;
if (!dma->ch)
return;
dma_release_channel(dma->ch);
dma->ch = NULL;
}
EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel);
int sst_dma_new(struct sst_dsp *sst)
{
struct sst_pdata *sst_pdata = sst->pdata;
struct sst_dma *dma;
struct resource mem;
const char *dma_dev_name;
int ret = 0;
/* configure the correct platform data for whatever DMA engine
* is attached to the ADSP IP. */
switch (sst->pdata->dma_engine) {
case SST_DMA_TYPE_DW:
dma_dev_name = "dw_dmac";
break;
case SST_DMA_TYPE_MID:
dma_dev_name = "Intel MID DMA";
break;
default:
dev_err(sst->dev, "error: invalid DMA engine %d\n",
sst->pdata->dma_engine);
return -EINVAL;
}
dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL);
if (!dma)
return -ENOMEM;
dma->sst = sst;
memset(&mem, 0, sizeof(mem));
mem.start = sst->addr.lpe_base + sst_pdata->dma_base;
mem.end = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1;
mem.flags = IORESOURCE_MEM;
/* now register DMA engine device */
dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq);
if (IS_ERR(dma->chip)) {
dev_err(sst->dev, "error: DMA device register failed\n");
ret = PTR_ERR(dma->chip);
goto err_dma_dev;
}
sst->dma = dma;
sst->fw_use_dma = true;
return 0;
err_dma_dev:
devm_kfree(sst->dev, dma);
return ret;
}
EXPORT_SYMBOL(sst_dma_new);
void sst_dma_free(struct sst_dma *dma)
{
if (dma == NULL)
return;
if (dma->ch)
dma_release_channel(dma->ch);
if (dma->chip)
dw_remove(dma->chip);
}
EXPORT_SYMBOL(sst_dma_free);
/* create new generic firmware object */ /* create new generic firmware object */
struct sst_fw *sst_fw_new(struct sst_dsp *dsp, struct sst_fw *sst_fw_new(struct sst_dsp *dsp,
...@@ -71,6 +361,12 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp, ...@@ -71,6 +361,12 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp,
/* copy FW data to DMA-able memory */ /* copy FW data to DMA-able memory */
memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size); memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size);
if (dsp->fw_use_dma) {
err = sst_dsp_dma_get_channel(dsp, 0);
if (err < 0)
goto chan_err;
}
/* call core specific FW paser to load FW data into DSP */ /* call core specific FW paser to load FW data into DSP */
err = dsp->ops->parse_fw(sst_fw); err = dsp->ops->parse_fw(sst_fw);
if (err < 0) { if (err < 0) {
...@@ -78,6 +374,9 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp, ...@@ -78,6 +374,9 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp,
goto parse_err; goto parse_err;
} }
if (dsp->fw_use_dma)
sst_dsp_dma_put_channel(dsp);
mutex_lock(&dsp->mutex); mutex_lock(&dsp->mutex);
list_add(&sst_fw->list, &dsp->fw_list); list_add(&sst_fw->list, &dsp->fw_list);
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
...@@ -85,9 +384,13 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp, ...@@ -85,9 +384,13 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp,
return sst_fw; return sst_fw;
parse_err: parse_err:
dma_free_coherent(dsp->dev, sst_fw->size, if (dsp->fw_use_dma)
sst_dsp_dma_put_channel(dsp);
chan_err:
dma_free_coherent(dsp->dma_dev, sst_fw->size,
sst_fw->dma_buf, sst_fw->dma_buf,
sst_fw->dmable_fw_paddr); sst_fw->dmable_fw_paddr);
sst_fw->dma_buf = NULL;
kfree(sst_fw); kfree(sst_fw);
return NULL; return NULL;
} }
...@@ -111,21 +414,37 @@ EXPORT_SYMBOL_GPL(sst_fw_reload); ...@@ -111,21 +414,37 @@ EXPORT_SYMBOL_GPL(sst_fw_reload);
void sst_fw_unload(struct sst_fw *sst_fw) void sst_fw_unload(struct sst_fw *sst_fw)
{ {
struct sst_dsp *dsp = sst_fw->dsp; struct sst_dsp *dsp = sst_fw->dsp;
struct sst_module *module, *tmp; struct sst_module *module, *mtmp;
struct sst_module_runtime *runtime, *rtmp;
dev_dbg(dsp->dev, "unloading firmware\n");
mutex_lock(&dsp->mutex);
/* check module by module */
list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) {
if (module->sst_fw == sst_fw) {
/* remove runtime modules */
list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) {
dev_dbg(dsp->dev, "unloading firmware\n"); block_list_remove(dsp, &runtime->block_list);
list_del(&runtime->list);
kfree(runtime);
}
/* now remove the module */
block_list_remove(dsp, &module->block_list);
list_del(&module->list);
kfree(module);
}
}
mutex_lock(&dsp->mutex); /* remove all scratch blocks */
list_for_each_entry_safe(module, tmp, &dsp->module_list, list) { block_list_remove(dsp, &dsp->scratch_block_list);
if (module->sst_fw == sst_fw) {
block_module_remove(module);
list_del(&module->list);
kfree(module);
}
}
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
} }
EXPORT_SYMBOL_GPL(sst_fw_unload); EXPORT_SYMBOL_GPL(sst_fw_unload);
...@@ -138,7 +457,8 @@ void sst_fw_free(struct sst_fw *sst_fw) ...@@ -138,7 +457,8 @@ void sst_fw_free(struct sst_fw *sst_fw)
list_del(&sst_fw->list); list_del(&sst_fw->list);
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, if (sst_fw->dma_buf)
dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf,
sst_fw->dmable_fw_paddr); sst_fw->dmable_fw_paddr);
kfree(sst_fw); kfree(sst_fw);
} }
...@@ -175,11 +495,11 @@ struct sst_module *sst_module_new(struct sst_fw *sst_fw, ...@@ -175,11 +495,11 @@ struct sst_module *sst_module_new(struct sst_fw *sst_fw,
sst_module->id = template->id; sst_module->id = template->id;
sst_module->dsp = dsp; sst_module->dsp = dsp;
sst_module->sst_fw = sst_fw; sst_module->sst_fw = sst_fw;
sst_module->scratch_size = template->scratch_size;
memcpy(&sst_module->s, &template->s, sizeof(struct sst_module_data)); sst_module->persistent_size = template->persistent_size;
memcpy(&sst_module->p, &template->p, sizeof(struct sst_module_data));
INIT_LIST_HEAD(&sst_module->block_list); INIT_LIST_HEAD(&sst_module->block_list);
INIT_LIST_HEAD(&sst_module->runtime_list);
mutex_lock(&dsp->mutex); mutex_lock(&dsp->mutex);
list_add(&sst_module->list, &dsp->module_list); list_add(&sst_module->list, &dsp->module_list);
...@@ -202,73 +522,122 @@ void sst_module_free(struct sst_module *sst_module) ...@@ -202,73 +522,122 @@ void sst_module_free(struct sst_module *sst_module)
} }
EXPORT_SYMBOL_GPL(sst_module_free); EXPORT_SYMBOL_GPL(sst_module_free);
static struct sst_mem_block *find_block(struct sst_dsp *dsp, int type, struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module,
u32 offset) int id, void *private)
{
struct sst_dsp *dsp = module->dsp;
struct sst_module_runtime *runtime;
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
if (runtime == NULL)
return NULL;
runtime->id = id;
runtime->dsp = dsp;
runtime->module = module;
INIT_LIST_HEAD(&runtime->block_list);
mutex_lock(&dsp->mutex);
list_add(&runtime->list, &module->runtime_list);
mutex_unlock(&dsp->mutex);
return runtime;
}
EXPORT_SYMBOL_GPL(sst_module_runtime_new);
void sst_module_runtime_free(struct sst_module_runtime *runtime)
{
struct sst_dsp *dsp = runtime->dsp;
mutex_lock(&dsp->mutex);
list_del(&runtime->list);
mutex_unlock(&dsp->mutex);
kfree(runtime);
}
EXPORT_SYMBOL_GPL(sst_module_runtime_free);
static struct sst_mem_block *find_block(struct sst_dsp *dsp,
struct sst_block_allocator *ba)
{ {
struct sst_mem_block *block; struct sst_mem_block *block;
list_for_each_entry(block, &dsp->free_block_list, list) { list_for_each_entry(block, &dsp->free_block_list, list) {
if (block->type == type && block->offset == offset) if (block->type == ba->type && block->offset == ba->offset)
return block; return block;
} }
return NULL; return NULL;
} }
static int block_alloc_contiguous(struct sst_module *module, /* Block allocator must be on block boundary */
struct sst_module_data *data, u32 offset, int size) static int block_alloc_contiguous(struct sst_dsp *dsp,
struct sst_block_allocator *ba, struct list_head *block_list)
{ {
struct list_head tmp = LIST_HEAD_INIT(tmp); struct list_head tmp = LIST_HEAD_INIT(tmp);
struct sst_dsp *dsp = module->dsp;
struct sst_mem_block *block; struct sst_mem_block *block;
u32 block_start = SST_HSW_BLOCK_ANY;
int size = ba->size, offset = ba->offset;
while (ba->size > 0) {
while (size > 0) { block = find_block(dsp, ba);
block = find_block(dsp, data->type, offset);
if (!block) { if (!block) {
list_splice(&tmp, &dsp->free_block_list); list_splice(&tmp, &dsp->free_block_list);
ba->size = size;
ba->offset = offset;
return -ENOMEM; return -ENOMEM;
} }
list_move_tail(&block->list, &tmp); list_move_tail(&block->list, &tmp);
offset += block->size; ba->offset += block->size;
size -= block->size; ba->size -= block->size;
} }
ba->size = size;
ba->offset = offset;
list_for_each_entry(block, &tmp, list) {
if (block->offset < block_start)
block_start = block->offset;
list_for_each_entry(block, &tmp, list) list_add(&block->module_list, block_list);
list_add(&block->module_list, &module->block_list);
dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
block->type, block->index, block->offset);
}
list_splice(&tmp, &dsp->used_block_list); list_splice(&tmp, &dsp->used_block_list);
return 0; return 0;
} }
/* allocate free DSP blocks for module data - callers hold locks */ /* allocate first free DSP blocks for data - callers hold locks */
static int block_alloc(struct sst_module *module, static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba,
struct sst_module_data *data) struct list_head *block_list)
{ {
struct sst_dsp *dsp = module->dsp;
struct sst_mem_block *block, *tmp; struct sst_mem_block *block, *tmp;
int ret = 0; int ret = 0;
if (data->size == 0) if (ba->size == 0)
return 0; return 0;
/* find first free whole blocks that can hold module */ /* find first free whole blocks that can hold module */
list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
/* ignore blocks with wrong type */ /* ignore blocks with wrong type */
if (block->type != data->type) if (block->type != ba->type)
continue; continue;
if (data->size > block->size) if (ba->size > block->size)
continue; continue;
data->offset = block->offset; ba->offset = block->offset;
block->data_type = data->data_type; block->bytes_used = ba->size % block->size;
block->bytes_used = data->size % block->size; list_add(&block->module_list, block_list);
list_add(&block->module_list, &module->block_list);
list_move(&block->list, &dsp->used_block_list); list_move(&block->list, &dsp->used_block_list);
dev_dbg(dsp->dev, " *module %d added block %d:%d\n", dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
module->id, block->type, block->index); block->type, block->index, block->offset);
return 0; return 0;
} }
...@@ -276,15 +645,19 @@ static int block_alloc(struct sst_module *module, ...@@ -276,15 +645,19 @@ static int block_alloc(struct sst_module *module,
list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
/* ignore blocks with wrong type */ /* ignore blocks with wrong type */
if (block->type != data->type) if (block->type != ba->type)
continue; continue;
/* do we span > 1 blocks */ /* do we span > 1 blocks */
if (data->size > block->size) { if (ba->size > block->size) {
ret = block_alloc_contiguous(module, data,
block->offset, data->size); /* align ba to block boundary */
ba->offset = block->offset;
ret = block_alloc_contiguous(dsp, ba, block_list);
if (ret == 0) if (ret == 0)
return ret; return ret;
} }
} }
...@@ -292,93 +665,74 @@ static int block_alloc(struct sst_module *module, ...@@ -292,93 +665,74 @@ static int block_alloc(struct sst_module *module,
return -ENOMEM; return -ENOMEM;
} }
/* remove module from memory - callers hold locks */ int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba,
static void block_module_remove(struct sst_module *module) struct list_head *block_list)
{ {
struct sst_mem_block *block, *tmp; int ret;
struct sst_dsp *dsp = module->dsp;
int err;
/* disable each block */ dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n",
list_for_each_entry(block, &module->block_list, module_list) { ba->size, ba->offset, ba->type);
if (block->ops && block->ops->disable) { mutex_lock(&dsp->mutex);
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 */ ret = block_alloc(dsp, ba, block_list);
list_for_each_entry_safe(block, tmp, &module->block_list, module_list) { if (ret < 0) {
list_del(&block->module_list); dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret);
list_move(&block->list, &dsp->free_block_list); goto out;
} }
}
/* 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 */ /* prepare DSP blocks for module usage */
list_for_each_entry(block, &module->block_list, module_list) { ret = block_list_prepare(dsp, block_list);
if (ret < 0)
dev_err(dsp->dev, "error: prepare failed\n");
if (block->ops && block->ops->enable) { out:
ret = block->ops->enable(block); mutex_unlock(&dsp->mutex);
if (ret < 0) {
dev_err(module->dsp->dev,
"error: cant disable block %d:%d\n",
block->type, block->index);
goto err;
}
}
}
return ret; return ret;
}
EXPORT_SYMBOL_GPL(sst_alloc_blocks);
err: int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list)
list_for_each_entry(block, &module->block_list, module_list) { {
if (block->ops && block->ops->disable) mutex_lock(&dsp->mutex);
block->ops->disable(block); block_list_remove(dsp, block_list);
} mutex_unlock(&dsp->mutex);
return ret; return 0;
} }
EXPORT_SYMBOL_GPL(sst_free_blocks);
/* allocate memory blocks for static module addresses - callers hold locks */ /* allocate memory blocks for static module addresses - callers hold locks */
static int block_alloc_fixed(struct sst_module *module, static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba,
struct sst_module_data *data) struct list_head *block_list)
{ {
struct sst_dsp *dsp = module->dsp;
struct sst_mem_block *block, *tmp; struct sst_mem_block *block, *tmp;
u32 end = data->offset + data->size, block_end; u32 end = ba->offset + ba->size, block_end;
int err; int err;
/* only IRAM/DRAM blocks are managed */ /* only IRAM/DRAM blocks are managed */
if (data->type != SST_MEM_IRAM && data->type != SST_MEM_DRAM) if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM)
return 0; return 0;
/* are blocks already attached to this module */ /* are blocks already attached to this module */
list_for_each_entry_safe(block, tmp, &module->block_list, module_list) { list_for_each_entry_safe(block, tmp, block_list, module_list) {
/* force compacting mem blocks of the same data_type */ /* ignore blocks with wrong type */
if (block->data_type != data->data_type) if (block->type != ba->type)
continue; continue;
block_end = block->offset + block->size; block_end = block->offset + block->size;
/* find block that holds section */ /* find block that holds section */
if (data->offset >= block->offset && end < block_end) if (ba->offset >= block->offset && end <= block_end)
return 0; return 0;
/* does block span more than 1 section */ /* does block span more than 1 section */
if (data->offset >= block->offset && data->offset < block_end) { if (ba->offset >= block->offset && ba->offset < block_end) {
err = block_alloc_contiguous(module, data, /* align ba to block boundary */
block->offset + block->size, ba->size -= block_end - ba->offset;
data->size - block->size); ba->offset = block_end;
err = block_alloc_contiguous(dsp, ba, block_list);
if (err < 0) if (err < 0)
return -ENOMEM; return -ENOMEM;
...@@ -391,82 +745,270 @@ static int block_alloc_fixed(struct sst_module *module, ...@@ -391,82 +745,270 @@ static int block_alloc_fixed(struct sst_module *module,
list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
block_end = block->offset + block->size; block_end = block->offset + block->size;
/* ignore blocks with wrong type */
if (block->type != ba->type)
continue;
/* find block that holds section */ /* find block that holds section */
if (data->offset >= block->offset && end < block_end) { if (ba->offset >= block->offset && end <= block_end) {
/* add block */ /* add block */
block->data_type = data->data_type;
list_move(&block->list, &dsp->used_block_list); list_move(&block->list, &dsp->used_block_list);
list_add(&block->module_list, &module->block_list); list_add(&block->module_list, block_list);
dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
block->type, block->index, block->offset);
return 0; return 0;
} }
/* does block span more than 1 section */ /* does block span more than 1 section */
if (data->offset >= block->offset && data->offset < block_end) { if (ba->offset >= block->offset && ba->offset < block_end) {
err = block_alloc_contiguous(module, data, /* align ba to block boundary */
block->offset, data->size); ba->offset = block->offset;
err = block_alloc_contiguous(dsp, ba, block_list);
if (err < 0) if (err < 0)
return -ENOMEM; return -ENOMEM;
return 0; return 0;
} }
} }
return -ENOMEM; return -ENOMEM;
} }
/* Load fixed module data into DSP memory blocks */ /* Load fixed module data into DSP memory blocks */
int sst_module_insert_fixed_block(struct sst_module *module, int sst_module_alloc_blocks(struct sst_module *module)
struct sst_module_data *data)
{ {
struct sst_dsp *dsp = module->dsp; struct sst_dsp *dsp = module->dsp;
struct sst_fw *sst_fw = module->sst_fw;
struct sst_block_allocator ba;
int ret; int ret;
ba.size = module->size;
ba.type = module->type;
ba.offset = module->offset;
dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n",
ba.size, ba.offset, ba.type);
mutex_lock(&dsp->mutex); mutex_lock(&dsp->mutex);
/* alloc blocks that includes this section */ /* alloc blocks that includes this section */
ret = block_alloc_fixed(module, data); ret = block_alloc_fixed(dsp, &ba, &module->block_list);
if (ret < 0) { if (ret < 0) {
dev_err(dsp->dev, dev_err(dsp->dev,
"error: no free blocks for section at offset 0x%x size 0x%x\n", "error: no free blocks for section at offset 0x%x size 0x%x\n",
data->offset, data->size); module->offset, module->size);
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
return -ENOMEM; return -ENOMEM;
} }
/* prepare DSP blocks for module copy */ /* prepare DSP blocks for module copy */
ret = block_module_prepare(module); ret = block_list_prepare(dsp, &module->block_list);
if (ret < 0) { if (ret < 0) {
dev_err(dsp->dev, "error: fw module prepare failed\n"); dev_err(dsp->dev, "error: fw module prepare failed\n");
goto err; goto err;
} }
/* copy partial module data to blocks */ /* copy partial module data to blocks */
sst_memcpy32(dsp->addr.lpe + data->offset, data->data, data->size); if (dsp->fw_use_dma) {
ret = sst_dsp_dma_copyto(dsp,
dsp->addr.lpe_base + module->offset,
sst_fw->dmable_fw_paddr + module->data_offset,
module->size);
if (ret < 0) {
dev_err(dsp->dev, "error: module copy failed\n");
goto err;
}
} else
sst_memcpy32(dsp->addr.lpe + module->offset, module->data,
module->size);
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
return ret; return ret;
err: err:
block_module_remove(module); block_list_remove(dsp, &module->block_list);
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(sst_module_insert_fixed_block); EXPORT_SYMBOL_GPL(sst_module_alloc_blocks);
/* Unload entire module from DSP memory */ /* Unload entire module from DSP memory */
int sst_block_module_remove(struct sst_module *module) int sst_module_free_blocks(struct sst_module *module)
{ {
struct sst_dsp *dsp = module->dsp; struct sst_dsp *dsp = module->dsp;
mutex_lock(&dsp->mutex); mutex_lock(&dsp->mutex);
block_module_remove(module); block_list_remove(dsp, &module->block_list);
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(sst_block_module_remove); EXPORT_SYMBOL_GPL(sst_module_free_blocks);
int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime,
int offset)
{
struct sst_dsp *dsp = runtime->dsp;
struct sst_module *module = runtime->module;
struct sst_block_allocator ba;
int ret;
if (module->persistent_size == 0)
return 0;
ba.size = module->persistent_size;
ba.type = SST_MEM_DRAM;
mutex_lock(&dsp->mutex);
/* do we need to allocate at a fixed address ? */
if (offset != 0) {
ba.offset = offset;
dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n",
ba.size, ba.type, ba.offset);
/* alloc blocks that includes this section */
ret = block_alloc_fixed(dsp, &ba, &runtime->block_list);
} else {
dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n",
ba.size, ba.type);
/* alloc blocks that includes this section */
ret = block_alloc(dsp, &ba, &runtime->block_list);
}
if (ret < 0) {
dev_err(dsp->dev,
"error: no free blocks for runtime module size 0x%x\n",
module->persistent_size);
mutex_unlock(&dsp->mutex);
return -ENOMEM;
}
runtime->persistent_offset = ba.offset;
/* prepare DSP blocks for module copy */
ret = block_list_prepare(dsp, &runtime->block_list);
if (ret < 0) {
dev_err(dsp->dev, "error: runtime block prepare failed\n");
goto err;
}
mutex_unlock(&dsp->mutex);
return ret;
err:
block_list_remove(dsp, &module->block_list);
mutex_unlock(&dsp->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks);
int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime)
{
struct sst_dsp *dsp = runtime->dsp;
mutex_lock(&dsp->mutex);
block_list_remove(dsp, &runtime->block_list);
mutex_unlock(&dsp->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks);
int sst_module_runtime_save(struct sst_module_runtime *runtime,
struct sst_module_runtime_context *context)
{
struct sst_dsp *dsp = runtime->dsp;
struct sst_module *module = runtime->module;
int ret = 0;
dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n",
runtime->id, runtime->persistent_offset,
module->persistent_size);
context->buffer = dma_alloc_coherent(dsp->dma_dev,
module->persistent_size,
&context->dma_buffer, GFP_DMA | GFP_KERNEL);
if (!context->buffer) {
dev_err(dsp->dev, "error: DMA context alloc failed\n");
return -ENOMEM;
}
mutex_lock(&dsp->mutex);
if (dsp->fw_use_dma) {
ret = sst_dsp_dma_get_channel(dsp, 0);
if (ret < 0)
goto err;
ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer,
dsp->addr.lpe_base + runtime->persistent_offset,
module->persistent_size);
sst_dsp_dma_put_channel(dsp);
if (ret < 0) {
dev_err(dsp->dev, "error: context copy failed\n");
goto err;
}
} else
sst_memcpy32(context->buffer, dsp->addr.lpe +
runtime->persistent_offset,
module->persistent_size);
err:
mutex_unlock(&dsp->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(sst_module_runtime_save);
int sst_module_runtime_restore(struct sst_module_runtime *runtime,
struct sst_module_runtime_context *context)
{
struct sst_dsp *dsp = runtime->dsp;
struct sst_module *module = runtime->module;
int ret = 0;
dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n",
runtime->id, runtime->persistent_offset,
module->persistent_size);
mutex_lock(&dsp->mutex);
if (!context->buffer) {
dev_info(dsp->dev, "no context buffer need to restore!\n");
goto err;
}
if (dsp->fw_use_dma) {
ret = sst_dsp_dma_get_channel(dsp, 0);
if (ret < 0)
goto err;
ret = sst_dsp_dma_copyto(dsp,
dsp->addr.lpe_base + runtime->persistent_offset,
context->dma_buffer, module->persistent_size);
sst_dsp_dma_put_channel(dsp);
if (ret < 0) {
dev_err(dsp->dev, "error: module copy failed\n");
goto err;
}
} else
sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset,
context->buffer, module->persistent_size);
dma_free_coherent(dsp->dma_dev, module->persistent_size,
context->buffer, context->dma_buffer);
context->buffer = NULL;
err:
mutex_unlock(&dsp->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(sst_module_runtime_restore);
/* register a DSP memory block for use with FW based modules */ /* 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, struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset,
...@@ -519,80 +1061,84 @@ void sst_mem_block_unregister_all(struct sst_dsp *dsp) ...@@ -519,80 +1061,84 @@ void sst_mem_block_unregister_all(struct sst_dsp *dsp)
EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all); EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all);
/* allocate scratch buffer blocks */ /* allocate scratch buffer blocks */
struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp) int sst_block_alloc_scratch(struct sst_dsp *dsp)
{ {
struct sst_module *sst_module, *scratch; struct sst_module *module;
struct sst_mem_block *block, *tmp; struct sst_block_allocator ba;
u32 block_size; int ret;
int ret = 0;
scratch = kzalloc(sizeof(struct sst_module), GFP_KERNEL);
if (scratch == NULL)
return NULL;
mutex_lock(&dsp->mutex); mutex_lock(&dsp->mutex);
/* calculate required scratch size */ /* calculate required scratch size */
list_for_each_entry(sst_module, &dsp->module_list, list) { dsp->scratch_size = 0;
if (scratch->s.size < sst_module->s.size) list_for_each_entry(module, &dsp->module_list, list) {
scratch->s.size = sst_module->s.size; dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n",
module->id, module->scratch_size);
if (dsp->scratch_size < module->scratch_size)
dsp->scratch_size = module->scratch_size;
} }
dev_dbg(dsp->dev, "scratch buffer required is %d bytes\n", dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n",
scratch->s.size); dsp->scratch_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 (dsp->scratch_size == 0) {
if (!list_empty(&dsp->free_block_list)) dev_info(dsp->dev, "no modules need scratch buffer\n");
block = list_first_entry(&dsp->free_block_list, mutex_unlock(&dsp->mutex);
struct sst_mem_block, list); return 0;
else }
block = list_first_entry(&dsp->used_block_list,
struct sst_mem_block, list);
block_size = block->size;
/* allocate blocks for module scratch buffers */ /* allocate blocks for module scratch buffers */
dev_dbg(dsp->dev, "allocating scratch blocks\n"); dev_dbg(dsp->dev, "allocating scratch blocks\n");
ret = block_alloc(scratch, &scratch->s);
ba.size = dsp->scratch_size;
ba.type = SST_MEM_DRAM;
/* do we need to allocate at fixed offset */
if (dsp->scratch_offset != 0) {
dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n",
ba.size, ba.type, ba.offset);
ba.offset = dsp->scratch_offset;
/* alloc blocks that includes this section */
ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list);
} else {
dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n",
ba.size, ba.type);
ba.offset = 0;
ret = block_alloc(dsp, &ba, &dsp->scratch_block_list);
}
if (ret < 0) { if (ret < 0) {
dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); dev_err(dsp->dev, "error: can't alloc scratch blocks\n");
goto err; mutex_unlock(&dsp->mutex);
return ret;
} }
/* assign the same offset of scratch to each module */ ret = block_list_prepare(dsp, &dsp->scratch_block_list);
list_for_each_entry(sst_module, &dsp->module_list, list) if (ret < 0) {
sst_module->s.offset = scratch->s.offset; dev_err(dsp->dev, "error: scratch block prepare failed\n");
mutex_unlock(&dsp->mutex);
mutex_unlock(&dsp->mutex); return ret;
return scratch; }
err: /* assign the same offset of scratch to each module */
list_for_each_entry_safe(block, tmp, &scratch->block_list, module_list) dsp->scratch_offset = ba.offset;
list_del(&block->module_list);
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
return NULL; return dsp->scratch_size;
} }
EXPORT_SYMBOL_GPL(sst_mem_block_alloc_scratch); EXPORT_SYMBOL_GPL(sst_block_alloc_scratch);
/* free all scratch blocks */ /* free all scratch blocks */
void sst_mem_block_free_scratch(struct sst_dsp *dsp, void sst_block_free_scratch(struct sst_dsp *dsp)
struct sst_module *scratch)
{ {
struct sst_mem_block *block, *tmp;
mutex_lock(&dsp->mutex); mutex_lock(&dsp->mutex);
block_list_remove(dsp, &dsp->scratch_block_list);
list_for_each_entry_safe(block, tmp, &scratch->block_list, module_list)
list_del(&block->module_list);
mutex_unlock(&dsp->mutex); mutex_unlock(&dsp->mutex);
} }
EXPORT_SYMBOL_GPL(sst_mem_block_free_scratch); EXPORT_SYMBOL_GPL(sst_block_free_scratch);
/* get a module from it's unique ID */ /* 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 *sst_module_get_from_id(struct sst_dsp *dsp, u32 id)
...@@ -612,3 +1158,40 @@ struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) ...@@ -612,3 +1158,40 @@ struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id)
return NULL; return NULL;
} }
EXPORT_SYMBOL_GPL(sst_module_get_from_id); EXPORT_SYMBOL_GPL(sst_module_get_from_id);
struct sst_module_runtime *sst_module_runtime_get_from_id(
struct sst_module *module, u32 id)
{
struct sst_module_runtime *runtime;
struct sst_dsp *dsp = module->dsp;
mutex_lock(&dsp->mutex);
list_for_each_entry(runtime, &module->runtime_list, list) {
if (runtime->id == id) {
mutex_unlock(&dsp->mutex);
return runtime;
}
}
mutex_unlock(&dsp->mutex);
return NULL;
}
EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id);
/* returns block address in DSP address space */
u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset,
enum sst_mem_type type)
{
switch (type) {
case SST_MEM_IRAM:
return offset - dsp->addr.iram_offset +
dsp->addr.dsp_iram_offset;
case SST_MEM_DRAM:
return offset - dsp->addr.dram_offset +
dsp->addr.dsp_dram_offset;
default:
return 0;
}
}
EXPORT_SYMBOL_GPL(sst_dsp_get_offset);
...@@ -42,6 +42,10 @@ ...@@ -42,6 +42,10 @@
#define SST_LP_SHIM_OFFSET 0xE7000 #define SST_LP_SHIM_OFFSET 0xE7000
#define SST_WPT_IRAM_OFFSET 0xA0000 #define SST_WPT_IRAM_OFFSET 0xA0000
#define SST_LP_IRAM_OFFSET 0x80000 #define SST_LP_IRAM_OFFSET 0x80000
#define SST_WPT_DSP_DRAM_OFFSET 0x400000
#define SST_WPT_DSP_IRAM_OFFSET 0x00000
#define SST_LPT_DSP_DRAM_OFFSET 0x400000
#define SST_LPT_DSP_IRAM_OFFSET 0x00000
#define SST_SHIM_PM_REG 0x84 #define SST_SHIM_PM_REG 0x84
...@@ -86,9 +90,8 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ...@@ -86,9 +90,8 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
{ {
struct dma_block_info *block; struct dma_block_info *block;
struct sst_module *mod; struct sst_module *mod;
struct sst_module_data block_data;
struct sst_module_template template; struct sst_module_template template;
int count; int count, ret;
void __iomem *ram; void __iomem *ram;
/* TODO: allowed module types need to be configurable */ /* TODO: allowed module types need to be configurable */
...@@ -109,13 +112,9 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ...@@ -109,13 +112,9 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
memset(&template, 0, sizeof(template)); memset(&template, 0, sizeof(template));
template.id = module->type; template.id = module->type;
template.entry = module->entry_point; template.entry = module->entry_point - 4;
template.p.size = module->info.persistent_size; template.persistent_size = module->info.persistent_size;
template.p.type = SST_MEM_DRAM; template.scratch_size = module->info.scratch_size;
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); mod = sst_module_new(fw, &template, NULL);
if (mod == NULL) if (mod == NULL)
...@@ -135,14 +134,14 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ...@@ -135,14 +134,14 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
switch (block->type) { switch (block->type) {
case SST_HSW_IRAM: case SST_HSW_IRAM:
ram = dsp->addr.lpe; ram = dsp->addr.lpe;
block_data.offset = mod->offset =
block->ram_offset + dsp->addr.iram_offset; block->ram_offset + dsp->addr.iram_offset;
block_data.type = SST_MEM_IRAM; mod->type = SST_MEM_IRAM;
break; break;
case SST_HSW_DRAM: case SST_HSW_DRAM:
ram = dsp->addr.lpe; ram = dsp->addr.lpe;
block_data.offset = block->ram_offset; mod->offset = block->ram_offset;
block_data.type = SST_MEM_DRAM; mod->type = SST_MEM_DRAM;
break; break;
default: default:
dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n", dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n",
...@@ -151,30 +150,34 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ...@@ -151,30 +150,34 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
return -EINVAL; return -EINVAL;
} }
block_data.size = block->size; mod->size = block->size;
block_data.data_type = SST_DATA_M; mod->data = (void *)block + sizeof(*block);
block_data.data = (void *)block + sizeof(*block); mod->data_offset = mod->data - fw->dma_buf;
block_data.data_offset = block_data.data - fw->dma_buf;
dev_dbg(dsp->dev, "copy firmware block %d type 0x%x " dev_dbg(dsp->dev, "module block %d type 0x%x "
"size 0x%x ==> ram %p offset 0x%x\n", "size 0x%x ==> ram %p offset 0x%x\n",
count, block->type, block->size, ram, count, mod->type, block->size, ram,
block->ram_offset); block->ram_offset);
sst_module_insert_fixed_block(mod, &block_data); ret = sst_module_alloc_blocks(mod);
if (ret < 0) {
dev_err(dsp->dev, "error: could not allocate blocks for module %d\n",
count);
sst_module_free(mod);
return ret;
}
block = (void *)block + sizeof(*block) + block->size; block = (void *)block + sizeof(*block) + block->size;
} }
return 0; return 0;
} }
static int hsw_parse_fw_image(struct sst_fw *sst_fw) static int hsw_parse_fw_image(struct sst_fw *sst_fw)
{ {
struct fw_header *header; struct fw_header *header;
struct sst_module *scratch;
struct fw_module_header *module; struct fw_module_header *module;
struct sst_dsp *dsp = sst_fw->dsp; struct sst_dsp *dsp = sst_fw->dsp;
struct sst_hsw *hsw = sst_fw->private;
int ret, count; int ret, count;
/* Read the header information from the data pointer */ /* Read the header information from the data pointer */
...@@ -204,12 +207,8 @@ static int hsw_parse_fw_image(struct sst_fw *sst_fw) ...@@ -204,12 +207,8 @@ static int hsw_parse_fw_image(struct sst_fw *sst_fw)
module = (void *)module + sizeof(*module) + module->mod_size; module = (void *)module + sizeof(*module) + module->mod_size;
} }
/* allocate persistent/scratch mem regions */ /* allocate scratch mem regions */
scratch = sst_mem_block_alloc_scratch(dsp); sst_block_alloc_scratch(dsp);
if (scratch == NULL)
return -ENOMEM;
sst_hsw_set_scratch_module(hsw, scratch);
return 0; return 0;
} }
...@@ -248,8 +247,94 @@ static irqreturn_t hsw_irq(int irq, void *context) ...@@ -248,8 +247,94 @@ static irqreturn_t hsw_irq(int irq, void *context)
return ret; return ret;
} }
static void hsw_boot(struct sst_dsp *sst) static void hsw_set_dsp_D3(struct sst_dsp *sst)
{
u32 val;
u32 reg;
/* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE);
writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
/* enable power gating and switch off DRAM & IRAM blocks */
val = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
val |= SST_VDRTCL0_DSRAMPGE_MASK |
SST_VDRTCL0_ISRAMPGE_MASK;
val &= ~(SST_VDRTCL0_D3PGD | SST_VDRTCL0_D3SRAMPGD);
writel(val, sst->addr.pci_cfg + SST_VDRTCTL0);
/* switch off audio PLL */
val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
val |= SST_VDRTCL2_APLLSE_MASK;
writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
/* disable MCLK(clkctl.smos = 0) */
sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL,
SST_CLKCTL_MASK, 0);
/* Set D3 state, delay 50 us */
val = readl(sst->addr.pci_cfg + SST_PMCS);
val |= SST_PMCS_PS_MASK;
writel(val, sst->addr.pci_cfg + SST_PMCS);
udelay(50);
/* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE;
writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
udelay(50);
}
static void hsw_reset(struct sst_dsp *sst)
{ {
/* put DSP into reset and stall */
sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
SST_CSR_RST | SST_CSR_STALL,
SST_CSR_RST | SST_CSR_STALL);
/* keep in reset for 10ms */
mdelay(10);
/* take DSP out of reset and keep stalled for FW loading */
sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL);
}
static int hsw_set_dsp_D0(struct sst_dsp *sst)
{
int tries = 10;
u32 reg;
/* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE);
writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
/* Disable D3PG (VDRTCTL0.D3PGD = 1) */
reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
reg |= SST_VDRTCL0_D3PGD;
writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0);
/* Set D0 state */
reg = readl(sst->addr.pci_cfg + SST_PMCS);
reg &= ~SST_PMCS_PS_MASK;
writel(reg, sst->addr.pci_cfg + SST_PMCS);
/* check that ADSP shim is enabled */
while (tries--) {
reg = readl(sst->addr.pci_cfg + SST_PMCS) & SST_PMCS_PS_MASK;
if (reg == 0)
goto finish;
msleep(1);
}
return -ENODEV;
finish:
/* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */ /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */
sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0); SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0);
...@@ -264,34 +349,96 @@ static void hsw_boot(struct sst_dsp *sst) ...@@ -264,34 +349,96 @@ static void hsw_boot(struct sst_dsp *sst)
SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0, SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0,
SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0); SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0);
/* Stall and reset core, set CSR */
hsw_reset(sst);
/* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE;
writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
udelay(50);
/* switch on audio PLL */
reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
reg &= ~SST_VDRTCL2_APLLSE_MASK;
writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
/* set default power gating control, enable power gating control for all blocks. that is,
can't be accessed, please enable each block before accessing. */
reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
reg |= SST_VDRTCL0_DSRAMPGE_MASK | SST_VDRTCL0_ISRAMPGE_MASK;
writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0);
/* disable DMA finish function for SSP0 & SSP1 */ /* disable DMA finish function for SSP0 & SSP1 */
sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1, sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1,
SST_CSR2_SDFD_SSP1); SST_CSR2_SDFD_SSP1);
/* enable DMA engine 0,1 all channels to access host memory */ /* set on-demond mode on engine 0,1 for all channels */
sst_dsp_shim_update_bits_unlocked(sst, SST_HMDC, sst_dsp_shim_update_bits(sst, SST_HMDC,
SST_HMDC_HDDA1(0xff) | SST_HMDC_HDDA0(0xff), SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH,
SST_HMDC_HDDA1(0xff) | SST_HMDC_HDDA0(0xff)); SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH);
/* Enable Interrupt from both sides */
sst_dsp_shim_update_bits(sst, SST_IMRX, (SST_IMRX_BUSY | SST_IMRX_DONE),
0x0);
sst_dsp_shim_update_bits(sst, SST_IMRD, (SST_IMRD_DONE | SST_IMRD_BUSY |
SST_IMRD_SSP0 | SST_IMRD_DMAC), 0x0);
/* clear IPC registers */
sst_dsp_shim_write(sst, SST_IPCX, 0x0);
sst_dsp_shim_write(sst, SST_IPCD, 0x0);
sst_dsp_shim_write(sst, 0x80, 0x6);
sst_dsp_shim_write(sst, 0xe0, 0x300a);
return 0;
}
/* disable all clock gating */ static void hsw_boot(struct sst_dsp *sst)
writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL2); {
/* set oportunistic mode on engine 0,1 for all channels */
sst_dsp_shim_update_bits(sst, SST_HMDC,
SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, 0);
/* set DSP to RUN */ /* set DSP to RUN */
sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0); sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0);
} }
static void hsw_reset(struct sst_dsp *sst) static void hsw_stall(struct sst_dsp *sst)
{
/* stall DSP */
sst_dsp_shim_update_bits(sst, SST_CSR,
SST_CSR_24MHZ_LPCS | SST_CSR_STALL,
SST_CSR_STALL | SST_CSR_24MHZ_LPCS);
}
static void hsw_sleep(struct sst_dsp *sst)
{ {
dev_dbg(sst->dev, "HSW_PM dsp runtime suspend\n");
/* put DSP into reset and stall */ /* put DSP into reset and stall */
sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, sst_dsp_shim_update_bits(sst, SST_CSR,
SST_CSR_RST | SST_CSR_STALL, SST_CSR_RST | SST_CSR_STALL); SST_CSR_24MHZ_LPCS | SST_CSR_RST | SST_CSR_STALL,
SST_CSR_RST | SST_CSR_STALL | SST_CSR_24MHZ_LPCS);
/* keep in reset for 10ms */ hsw_set_dsp_D3(sst);
mdelay(10); dev_dbg(sst->dev, "HSW_PM dsp runtime suspend exit\n");
}
/* take DSP out of reset and keep stalled for FW loading */ static int hsw_wake(struct sst_dsp *sst)
sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, {
SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL); int ret;
dev_dbg(sst->dev, "HSW_PM dsp runtime resume\n");
ret = hsw_set_dsp_D0(sst);
if (ret < 0)
return ret;
dev_dbg(sst->dev, "HSW_PM dsp runtime resume exit\n");
return 0;
} }
struct sst_adsp_memregion { struct sst_adsp_memregion {
...@@ -396,6 +543,11 @@ static int hsw_block_enable(struct sst_mem_block *block) ...@@ -396,6 +543,11 @@ static int hsw_block_enable(struct sst_mem_block *block)
dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n", dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n",
block->type, block->index, block->offset); block->type, block->index, block->offset);
/* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
val &= ~SST_VDRTCL2_DCLCGE;
writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); val = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
bit = hsw_block_get_bit(block); bit = hsw_block_get_bit(block);
writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0); writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0);
...@@ -403,6 +555,13 @@ static int hsw_block_enable(struct sst_mem_block *block) ...@@ -403,6 +555,13 @@ static int hsw_block_enable(struct sst_mem_block *block)
/* wait 18 DSP clock ticks */ /* wait 18 DSP clock ticks */
udelay(10); udelay(10);
/* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
val |= SST_VDRTCL2_DCLCGE;
writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
udelay(50);
/*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/ /*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/
sst_mem_block_dummy_read(block); sst_mem_block_dummy_read(block);
return 0; return 0;
...@@ -420,10 +579,26 @@ static int hsw_block_disable(struct sst_mem_block *block) ...@@ -420,10 +579,26 @@ static int hsw_block_disable(struct sst_mem_block *block)
dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n", dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n",
block->type, block->index, block->offset); block->type, block->index, block->offset);
/* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
val &= ~SST_VDRTCL2_DCLCGE;
writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); val = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
bit = hsw_block_get_bit(block); bit = hsw_block_get_bit(block);
writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0); writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0);
/* wait 18 DSP clock ticks */
udelay(10);
/* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
val |= SST_VDRTCL2_DCLCGE;
writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
udelay(50);
return 0; return 0;
} }
...@@ -432,27 +607,6 @@ static struct sst_block_ops sst_hsw_ops = { ...@@ -432,27 +607,6 @@ static struct sst_block_ops sst_hsw_ops = {
.disable = hsw_block_disable, .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) static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata)
{ {
const struct sst_adsp_memregion *region; const struct sst_adsp_memregion *region;
...@@ -467,12 +621,16 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) ...@@ -467,12 +621,16 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata)
region = lp_region; region = lp_region;
region_count = ARRAY_SIZE(lp_region); region_count = ARRAY_SIZE(lp_region);
sst->addr.iram_offset = SST_LP_IRAM_OFFSET; sst->addr.iram_offset = SST_LP_IRAM_OFFSET;
sst->addr.dsp_iram_offset = SST_LPT_DSP_IRAM_OFFSET;
sst->addr.dsp_dram_offset = SST_LPT_DSP_DRAM_OFFSET;
sst->addr.shim_offset = SST_LP_SHIM_OFFSET; sst->addr.shim_offset = SST_LP_SHIM_OFFSET;
break; break;
case SST_DEV_ID_WILDCAT_POINT: case SST_DEV_ID_WILDCAT_POINT:
region = wpt_region; region = wpt_region;
region_count = ARRAY_SIZE(wpt_region); region_count = ARRAY_SIZE(wpt_region);
sst->addr.iram_offset = SST_WPT_IRAM_OFFSET; sst->addr.iram_offset = SST_WPT_IRAM_OFFSET;
sst->addr.dsp_iram_offset = SST_WPT_DSP_IRAM_OFFSET;
sst->addr.dsp_dram_offset = SST_WPT_DSP_DRAM_OFFSET;
sst->addr.shim_offset = SST_WPT_SHIM_OFFSET; sst->addr.shim_offset = SST_WPT_SHIM_OFFSET;
break; break;
default: default:
...@@ -487,7 +645,7 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) ...@@ -487,7 +645,7 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata)
} }
/* enable the DSP SHIM */ /* enable the DSP SHIM */
ret = hsw_enable_shim(sst); ret = hsw_set_dsp_D0(sst);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n"); dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n");
return ret; return ret;
...@@ -497,10 +655,6 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) ...@@ -497,10 +655,6 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata)
if (ret) if (ret)
return 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 */ /* register DSP memory blocks - ideally we should get this from ACPI */
for (i = 0; i < region_count; i++) { for (i = 0; i < region_count; i++) {
...@@ -532,6 +686,9 @@ static void hsw_free(struct sst_dsp *sst) ...@@ -532,6 +686,9 @@ static void hsw_free(struct sst_dsp *sst)
struct sst_ops haswell_ops = { struct sst_ops haswell_ops = {
.reset = hsw_reset, .reset = hsw_reset,
.boot = hsw_boot, .boot = hsw_boot,
.stall = hsw_stall,
.wake = hsw_wake,
.sleep = hsw_sleep,
.write = sst_shim32_write, .write = sst_shim32_write,
.read = sst_shim32_read, .read = sst_shim32_read,
.write64 = sst_shim32_write64, .write64 = sst_shim32_write64,
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <linux/firmware.h> #include <linux/firmware.h>
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/debugfs.h> #include <linux/debugfs.h>
#include <linux/pm_runtime.h>
#include "sst-haswell-ipc.h" #include "sst-haswell-ipc.h"
#include "sst-dsp.h" #include "sst-dsp.h"
...@@ -276,6 +277,7 @@ struct sst_hsw { ...@@ -276,6 +277,7 @@ struct sst_hsw {
struct sst_hsw_ipc_fw_version version; struct sst_hsw_ipc_fw_version version;
struct sst_module *scratch; struct sst_module *scratch;
bool fw_done; bool fw_done;
struct sst_fw *sst_fw;
/* stream */ /* stream */
struct list_head stream_list; struct list_head stream_list;
...@@ -289,6 +291,8 @@ struct sst_hsw { ...@@ -289,6 +291,8 @@ struct sst_hsw {
/* DX */ /* DX */
struct sst_hsw_ipc_dx_reply dx; struct sst_hsw_ipc_dx_reply dx;
void *dx_context;
dma_addr_t dx_context_paddr;
/* boot */ /* boot */
wait_queue_head_t boot_wait; wait_queue_head_t boot_wait;
...@@ -1038,14 +1042,9 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw, ...@@ -1038,14 +1042,9 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw,
trace_ipc_request("set stream volume", stream->reply.stream_hw_id); trace_ipc_request("set stream volume", stream->reply.stream_hw_id);
if (channel > 1) if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL)
return -EINVAL; return -EINVAL;
if (stream->mute[channel]) {
stream->mute_volume[channel] = volume;
return 0;
}
header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) |
IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE);
header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT);
...@@ -1053,9 +1052,28 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw, ...@@ -1053,9 +1052,28 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw,
header |= (stage_id << IPC_STG_ID_SHIFT); header |= (stage_id << IPC_STG_ID_SHIFT);
req = &stream->vol_req; req = &stream->vol_req;
req->channel = channel;
req->target_volume = volume; req->target_volume = volume;
/* set both at same time ? */
if (channel == SST_HSW_CHANNELS_ALL) {
if (hsw->mute[0] && hsw->mute[1]) {
hsw->mute_volume[0] = hsw->mute_volume[1] = volume;
return 0;
} else if (hsw->mute[0])
req->channel = 1;
else if (hsw->mute[1])
req->channel = 0;
else
req->channel = SST_HSW_CHANNELS_ALL;
} else {
/* set only 1 channel */
if (hsw->mute[channel]) {
hsw->mute_volume[channel] = volume;
return 0;
}
req->channel = channel;
}
ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0); ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0);
if (ret < 0) { if (ret < 0) {
dev_err(hsw->dev, "error: set stream volume failed\n"); dev_err(hsw->dev, "error: set stream volume failed\n");
...@@ -1134,8 +1152,11 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, ...@@ -1134,8 +1152,11 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel,
trace_ipc_request("set mixer volume", volume); trace_ipc_request("set mixer volume", volume);
if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL)
return -EINVAL;
/* set both at same time ? */ /* set both at same time ? */
if (channel == 2) { if (channel == SST_HSW_CHANNELS_ALL) {
if (hsw->mute[0] && hsw->mute[1]) { if (hsw->mute[0] && hsw->mute[1]) {
hsw->mute_volume[0] = hsw->mute_volume[1] = volume; hsw->mute_volume[0] = hsw->mute_volume[1] = volume;
return 0; return 0;
...@@ -1144,7 +1165,7 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, ...@@ -1144,7 +1165,7 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel,
else if (hsw->mute[1]) else if (hsw->mute[1])
req.channel = 0; req.channel = 0;
else else
req.channel = 0xffffffff; req.channel = SST_HSW_CHANNELS_ALL;
} else { } else {
/* set only 1 channel */ /* set only 1 channel */
if (hsw->mute[channel]) { if (hsw->mute[channel]) {
...@@ -1256,10 +1277,6 @@ int sst_hsw_stream_set_channels(struct sst_hsw *hsw, ...@@ -1256,10 +1277,6 @@ int sst_hsw_stream_set_channels(struct sst_hsw *hsw,
return -EINVAL; return -EINVAL;
} }
/* stereo is only supported atm */
if (channels != 2)
return -EINVAL;
stream->request.format.ch_num = channels; stream->request.format.ch_num = channels;
return 0; return 0;
} }
...@@ -1355,10 +1372,11 @@ int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ...@@ -1355,10 +1372,11 @@ int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
} }
int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, int sst_hsw_stream_set_module_info(struct sst_hsw *hsw,
struct sst_hsw_stream *stream, enum sst_hsw_module_id module_id, struct sst_hsw_stream *stream, struct sst_module_runtime *runtime)
u32 entry_point)
{ {
struct sst_hsw_module_map *map = &stream->request.map; struct sst_hsw_module_map *map = &stream->request.map;
struct sst_dsp *dsp = sst_hsw_get_dsp(hsw);
struct sst_module *module = runtime->module;
if (stream->commited) { if (stream->commited) {
dev_err(hsw->dev, "error: stream committed for set module\n"); dev_err(hsw->dev, "error: stream committed for set module\n");
...@@ -1367,36 +1385,25 @@ int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, ...@@ -1367,36 +1385,25 @@ int sst_hsw_stream_set_module_info(struct sst_hsw *hsw,
/* only support initial module atm */ /* only support initial module atm */
map->module_entries_count = 1; map->module_entries_count = 1;
map->module_entries[0].module_id = module_id; map->module_entries[0].module_id = module->id;
map->module_entries[0].entry_point = entry_point; map->module_entries[0].entry_point = module->entry;
return 0; stream->request.persistent_mem.offset =
} sst_dsp_get_offset(dsp, runtime->persistent_offset, SST_MEM_DRAM);
stream->request.persistent_mem.size = module->persistent_size;
int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw,
struct sst_hsw_stream *stream, u32 offset, u32 size) stream->request.scratch_mem.offset =
{ sst_dsp_get_offset(dsp, dsp->scratch_offset, SST_MEM_DRAM);
if (stream->commited) { stream->request.scratch_mem.size = dsp->scratch_size;
dev_err(hsw->dev, "error: stream committed for set pmem\n");
return -EINVAL; dev_dbg(hsw->dev, "module %d runtime %d using:\n", module->id,
} runtime->id);
dev_dbg(hsw->dev, " persistent offset 0x%x bytes 0x%x\n",
stream->request.persistent_mem.offset = offset; stream->request.persistent_mem.offset,
stream->request.persistent_mem.size = size; stream->request.persistent_mem.size);
dev_dbg(hsw->dev, " scratch offset 0x%x bytes 0x%x\n",
return 0; stream->request.scratch_mem.offset,
} stream->request.scratch_mem.size);
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; return 0;
} }
...@@ -1630,6 +1637,10 @@ int sst_hsw_device_set_config(struct sst_hsw *hsw, ...@@ -1630,6 +1637,10 @@ int sst_hsw_device_set_config(struct sst_hsw *hsw,
config.clock_frequency = mclk; config.clock_frequency = mclk;
config.mode = mode; config.mode = mode;
config.clock_divider = clock_divider; config.clock_divider = clock_divider;
if (mode == SST_HSW_DEVICE_TDM_CLOCK_MASTER)
config.channels = 4;
else
config.channels = 2;
trace_hsw_device_config_req(&config); trace_hsw_device_config_req(&config);
...@@ -1673,34 +1684,283 @@ int sst_hsw_dx_set_state(struct sst_hsw *hsw, ...@@ -1673,34 +1684,283 @@ int sst_hsw_dx_set_state(struct sst_hsw *hsw,
dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n",
dx->entries_no, state); dx->entries_no, state);
memcpy(&hsw->dx, dx, sizeof(*dx)); return ret;
return 0;
} }
/* Used to save state into hsw->dx_reply */ struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw,
int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item, int mod_id, int offset)
u32 *offset, u32 *size, u32 *source)
{ {
struct sst_hsw_ipc_dx_memory_item *dx_mem; struct sst_dsp *dsp = hsw->dsp;
struct sst_hsw_ipc_dx_reply *dx_reply; struct sst_module *module;
int entry_no; struct sst_module_runtime *runtime;
int err;
dx_reply = &hsw->dx; module = sst_module_get_from_id(dsp, mod_id);
entry_no = dx_reply->entries_no; if (module == NULL) {
dev_err(dsp->dev, "error: failed to get module %d for pcm\n",
mod_id);
return NULL;
}
runtime = sst_module_runtime_new(module, mod_id, NULL);
if (runtime == NULL) {
dev_err(dsp->dev, "error: failed to create module %d runtime\n",
mod_id);
return NULL;
}
err = sst_module_runtime_alloc_blocks(runtime, offset);
if (err < 0) {
dev_err(dsp->dev, "error: failed to alloc blocks for module %d runtime\n",
mod_id);
sst_module_runtime_free(runtime);
return NULL;
}
dev_dbg(dsp->dev, "runtime id %d created for module %d\n", runtime->id,
mod_id);
return runtime;
}
void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime)
{
sst_module_runtime_free_blocks(runtime);
sst_module_runtime_free(runtime);
}
#ifdef CONFIG_PM
static int sst_hsw_dx_state_dump(struct sst_hsw *hsw)
{
struct sst_dsp *sst = hsw->dsp;
u32 item, offset, size;
int ret = 0;
trace_ipc_request("PM get Dx state", entry_no); trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS);
if (item >= entry_no) if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) {
dev_err(hsw->dev,
"error: number of FW context regions greater than %d\n",
SST_HSW_MAX_DX_REGIONS);
memset(&hsw->dx, 0, sizeof(hsw->dx));
return -EINVAL; return -EINVAL;
}
ret = sst_dsp_dma_get_channel(sst, 0);
if (ret < 0) {
dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
return ret;
}
/* set on-demond mode on engine 0 channel 3 */
sst_dsp_shim_update_bits(sst, SST_HMDC,
SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH,
SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH);
for (item = 0; item < hsw->dx.entries_no; item++) {
if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP
&& hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET
&& hsw->dx.mem_info[item].offset <
DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) {
offset = hsw->dx.mem_info[item].offset
- DSP_DRAM_ADDR_OFFSET;
size = (hsw->dx.mem_info[item].size + 3) & (~3);
ret = sst_dsp_dma_copyfrom(sst, hsw->dx_context_paddr + offset,
sst->addr.lpe_base + offset, size);
if (ret < 0) {
dev_err(hsw->dev,
"error: FW context dump failed\n");
memset(&hsw->dx, 0, sizeof(hsw->dx));
goto out;
}
}
}
out:
sst_dsp_dma_put_channel(sst);
return ret;
}
static int sst_hsw_dx_state_restore(struct sst_hsw *hsw)
{
struct sst_dsp *sst = hsw->dsp;
u32 item, offset, size;
int ret;
for (item = 0; item < hsw->dx.entries_no; item++) {
if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP
&& hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET
&& hsw->dx.mem_info[item].offset <
DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) {
offset = hsw->dx.mem_info[item].offset
- DSP_DRAM_ADDR_OFFSET;
size = (hsw->dx.mem_info[item].size + 3) & (~3);
ret = sst_dsp_dma_copyto(sst, sst->addr.lpe_base + offset,
hsw->dx_context_paddr + offset, size);
if (ret < 0) {
dev_err(hsw->dev,
"error: FW context restore failed\n");
return ret;
}
}
}
return 0;
}
static void sst_hsw_drop_all(struct sst_hsw *hsw)
{
struct ipc_message *msg, *tmp;
unsigned long flags;
int tx_drop_cnt = 0, rx_drop_cnt = 0;
dx_mem = &dx_reply->mem_info[item]; /* drop all TX and Rx messages before we stall + reset DSP */
*offset = dx_mem->offset; spin_lock_irqsave(&hsw->dsp->spinlock, flags);
*size = dx_mem->size;
*source = dx_mem->source; list_for_each_entry_safe(msg, tmp, &hsw->tx_list, list) {
list_move(&msg->list, &hsw->empty_list);
tx_drop_cnt++;
}
list_for_each_entry_safe(msg, tmp, &hsw->rx_list, list) {
list_move(&msg->list, &hsw->empty_list);
rx_drop_cnt++;
}
spin_unlock_irqrestore(&hsw->dsp->spinlock, flags);
if (tx_drop_cnt || rx_drop_cnt)
dev_err(hsw->dev, "dropped IPC msg RX=%d, TX=%d\n",
tx_drop_cnt, rx_drop_cnt);
}
int sst_hsw_dsp_load(struct sst_hsw *hsw)
{
struct sst_dsp *dsp = hsw->dsp;
int ret;
dev_dbg(hsw->dev, "loading audio DSP....");
ret = sst_dsp_wake(dsp);
if (ret < 0) {
dev_err(hsw->dev, "error: failed to wake audio DSP\n");
return -ENODEV;
}
ret = sst_dsp_dma_get_channel(dsp, 0);
if (ret < 0) {
dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
return ret;
}
ret = sst_fw_reload(hsw->sst_fw);
if (ret < 0) {
dev_err(hsw->dev, "error: SST FW reload failed\n");
sst_dsp_dma_put_channel(dsp);
return -ENOMEM;
}
sst_dsp_dma_put_channel(dsp);
return 0; return 0;
} }
static int sst_hsw_dsp_restore(struct sst_hsw *hsw)
{
struct sst_dsp *dsp = hsw->dsp;
int ret;
dev_dbg(hsw->dev, "restoring audio DSP....");
ret = sst_dsp_dma_get_channel(dsp, 0);
if (ret < 0) {
dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
return ret;
}
ret = sst_hsw_dx_state_restore(hsw);
if (ret < 0) {
dev_err(hsw->dev, "error: SST FW context restore failed\n");
sst_dsp_dma_put_channel(dsp);
return -ENOMEM;
}
sst_dsp_dma_put_channel(dsp);
/* wait for DSP boot completion */
sst_dsp_boot(dsp);
return ret;
}
int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw)
{
int ret;
dev_dbg(hsw->dev, "audio dsp runtime suspend\n");
ret = sst_hsw_dx_set_state(hsw, SST_HSW_DX_STATE_D3, &hsw->dx);
if (ret < 0)
return ret;
sst_dsp_stall(hsw->dsp);
ret = sst_hsw_dx_state_dump(hsw);
if (ret < 0)
return ret;
sst_hsw_drop_all(hsw);
return 0;
}
int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw)
{
sst_fw_unload(hsw->sst_fw);
sst_block_free_scratch(hsw->dsp);
hsw->boot_complete = false;
sst_dsp_sleep(hsw->dsp);
return 0;
}
int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw)
{
struct device *dev = hsw->dev;
int ret;
dev_dbg(dev, "audio dsp runtime resume\n");
if (hsw->boot_complete)
return 1; /* tell caller no action is required */
ret = sst_hsw_dsp_restore(hsw);
if (ret < 0)
dev_err(dev, "error: audio DSP boot failure\n");
ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete,
msecs_to_jiffies(IPC_BOOT_MSECS));
if (ret == 0) {
dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n",
sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD),
sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX));
return -EIO;
}
/* Set ADSP SSP port settings */
ret = sst_hsw_device_set_config(hsw, SST_HSW_DEVICE_SSP_0,
SST_HSW_DEVICE_MCLK_FREQ_24_MHZ,
SST_HSW_DEVICE_CLOCK_MASTER, 9);
if (ret < 0)
dev_err(dev, "error: SSP re-initialization failed\n");
return ret;
}
#endif
static int msg_empty_list_init(struct sst_hsw *hsw) static int msg_empty_list_init(struct sst_hsw *hsw)
{ {
int i; int i;
...@@ -1718,12 +1978,6 @@ static int msg_empty_list_init(struct sst_hsw *hsw) ...@@ -1718,12 +1978,6 @@ static int msg_empty_list_init(struct sst_hsw *hsw)
return 0; 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) struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw)
{ {
return hsw->dsp; return hsw->dsp;
...@@ -1738,7 +1992,6 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) ...@@ -1738,7 +1992,6 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
{ {
struct sst_hsw_ipc_fw_version version; struct sst_hsw_ipc_fw_version version;
struct sst_hsw *hsw; struct sst_hsw *hsw;
struct sst_fw *hsw_sst_fw;
int ret; int ret;
dev_dbg(dev, "initialising Audio DSP IPC\n"); dev_dbg(dev, "initialising Audio DSP IPC\n");
...@@ -1780,12 +2033,19 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) ...@@ -1780,12 +2033,19 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
goto dsp_err; goto dsp_err;
} }
/* allocate DMA buffer for context storage */
hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev,
SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL);
if (hsw->dx_context == NULL) {
ret = -ENOMEM;
goto dma_err;
}
/* keep the DSP in reset state for base FW loading */ /* keep the DSP in reset state for base FW loading */
sst_dsp_reset(hsw->dsp); sst_dsp_reset(hsw->dsp);
hsw_sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw); hsw->sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw);
if (hsw->sst_fw == NULL) {
if (hsw_sst_fw == NULL) {
ret = -ENODEV; ret = -ENODEV;
dev_err(dev, "error: failed to load firmware\n"); dev_err(dev, "error: failed to load firmware\n");
goto fw_err; goto fw_err;
...@@ -1797,7 +2057,9 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) ...@@ -1797,7 +2057,9 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
msecs_to_jiffies(IPC_BOOT_MSECS)); msecs_to_jiffies(IPC_BOOT_MSECS));
if (ret == 0) { if (ret == 0) {
ret = -EIO; ret = -EIO;
dev_err(hsw->dev, "error: ADSP boot timeout\n"); dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n",
sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD),
sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX));
goto boot_err; goto boot_err;
} }
...@@ -1816,8 +2078,11 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) ...@@ -1816,8 +2078,11 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
boot_err: boot_err:
sst_dsp_reset(hsw->dsp); sst_dsp_reset(hsw->dsp);
sst_fw_free(hsw_sst_fw); sst_fw_free(hsw->sst_fw);
fw_err: fw_err:
dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE,
hsw->dx_context, hsw->dx_context_paddr);
dma_err:
sst_dsp_free(hsw->dsp); sst_dsp_free(hsw->dsp);
dsp_err: dsp_err:
kthread_stop(hsw->tx_thread); kthread_stop(hsw->tx_thread);
...@@ -1834,6 +2099,8 @@ void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) ...@@ -1834,6 +2099,8 @@ void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata)
sst_dsp_reset(hsw->dsp); sst_dsp_reset(hsw->dsp);
sst_fw_free_all(hsw->dsp); sst_fw_free_all(hsw->dsp);
dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE,
hsw->dx_context, hsw->dx_context_paddr);
sst_dsp_free(hsw->dsp); sst_dsp_free(hsw->dsp);
kfree(hsw->scratch); kfree(hsw->scratch);
kthread_stop(hsw->tx_thread); kthread_stop(hsw->tx_thread);
......
...@@ -21,8 +21,10 @@ ...@@ -21,8 +21,10 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#define SST_HSW_NO_CHANNELS 2 #define SST_HSW_NO_CHANNELS 4
#define SST_HSW_MAX_DX_REGIONS 14 #define SST_HSW_MAX_DX_REGIONS 14
#define SST_HSW_DX_CONTEXT_SIZE (640 * 1024)
#define SST_HSW_CHANNELS_ALL 0xffffffff
#define SST_HSW_FW_LOG_CONFIG_DWORDS 12 #define SST_HSW_FW_LOG_CONFIG_DWORDS 12
#define SST_HSW_GLOBAL_LOG 15 #define SST_HSW_GLOBAL_LOG 15
...@@ -40,6 +42,7 @@ struct sst_hsw_stream; ...@@ -40,6 +42,7 @@ struct sst_hsw_stream;
struct sst_hsw_log_stream; struct sst_hsw_log_stream;
struct sst_pdata; struct sst_pdata;
struct sst_module; struct sst_module;
struct sst_module_runtime;
extern struct sst_ops haswell_ops; extern struct sst_ops haswell_ops;
/* Stream Allocate Path ID */ /* Stream Allocate Path ID */
...@@ -84,6 +87,7 @@ enum sst_hsw_device_mclk { ...@@ -84,6 +87,7 @@ enum sst_hsw_device_mclk {
enum sst_hsw_device_mode { enum sst_hsw_device_mode {
SST_HSW_DEVICE_CLOCK_SLAVE = 0, SST_HSW_DEVICE_CLOCK_SLAVE = 0,
SST_HSW_DEVICE_CLOCK_MASTER = 1, SST_HSW_DEVICE_CLOCK_MASTER = 1,
SST_HSW_DEVICE_TDM_CLOCK_MASTER = 2,
}; };
/* DX Power State */ /* DX Power State */
...@@ -295,7 +299,8 @@ struct sst_hsw_ipc_device_config_req { ...@@ -295,7 +299,8 @@ struct sst_hsw_ipc_device_config_req {
u32 clock_frequency; u32 clock_frequency;
u32 mode; u32 mode;
u16 clock_divider; u16 clock_divider;
u16 reserved; u8 channels;
u8 reserved;
} __attribute__((packed)); } __attribute__((packed));
/* Audio Data formats */ /* Audio Data formats */
...@@ -430,8 +435,7 @@ int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, ...@@ -430,8 +435,7 @@ int sst_hsw_stream_set_map_config(struct sst_hsw *hsw,
int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream, int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
enum sst_hsw_interleaving style); enum sst_hsw_interleaving style);
int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, int sst_hsw_stream_set_module_info(struct sst_hsw *hsw,
struct sst_hsw_stream *stream, enum sst_hsw_module_id module_id, struct sst_hsw_stream *stream, struct sst_module_runtime *runtime);
u32 entry_point);
int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw,
struct sst_hsw_stream *stream, u32 offset, u32 size); struct sst_hsw_stream *stream, u32 offset, u32 size);
int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw,
...@@ -484,7 +488,16 @@ int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item, ...@@ -484,7 +488,16 @@ int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item,
int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata); int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata);
void sst_hsw_dsp_free(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); 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); /* runtime module management */
struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw,
int mod_id, int offset);
void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime);
/* PM */
int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw);
int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw);
int sst_hsw_dsp_load(struct sst_hsw *hsw);
int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw);
#endif #endif
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <asm/page.h> #include <asm/page.h>
#include <asm/pgtable.h> #include <asm/pgtable.h>
#include <sound/core.h> #include <sound/core.h>
...@@ -73,6 +74,13 @@ static const u32 volume_map[] = { ...@@ -73,6 +74,13 @@ static const u32 volume_map[] = {
#define HSW_PCM_PERIODS_MAX 64 #define HSW_PCM_PERIODS_MAX 64
#define HSW_PCM_PERIODS_MIN 2 #define HSW_PCM_PERIODS_MIN 2
#define HSW_PCM_DAI_ID_SYSTEM 0
#define HSW_PCM_DAI_ID_OFFLOAD0 1
#define HSW_PCM_DAI_ID_OFFLOAD1 2
#define HSW_PCM_DAI_ID_LOOPBACK 3
#define HSW_PCM_DAI_ID_CAPTURE 4
static const struct snd_pcm_hardware hsw_pcm_hardware = { static const struct snd_pcm_hardware hsw_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP | .info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_MMAP_VALID |
...@@ -89,22 +97,39 @@ static const struct snd_pcm_hardware hsw_pcm_hardware = { ...@@ -89,22 +97,39 @@ static const struct snd_pcm_hardware hsw_pcm_hardware = {
.buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE, .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE,
}; };
struct hsw_pcm_module_map {
int dai_id;
enum sst_hsw_module_id mod_id;
};
/* private data for each PCM DSP stream */ /* private data for each PCM DSP stream */
struct hsw_pcm_data { struct hsw_pcm_data {
int dai_id; int dai_id;
struct sst_hsw_stream *stream; struct sst_hsw_stream *stream;
struct sst_module_runtime *runtime;
struct sst_module_runtime_context context;
struct snd_pcm *hsw_pcm;
u32 volume[2]; u32 volume[2];
struct snd_pcm_substream *substream; struct snd_pcm_substream *substream;
struct snd_compr_stream *cstream; struct snd_compr_stream *cstream;
unsigned int wpos; unsigned int wpos;
struct mutex mutex; struct mutex mutex;
bool allocated; bool allocated;
int persistent_offset;
};
enum hsw_pm_state {
HSW_PM_STATE_D3 = 0,
HSW_PM_STATE_D0 = 1,
}; };
/* private data for the driver */ /* private data for the driver */
struct hsw_priv_data { struct hsw_priv_data {
/* runtime DSP */ /* runtime DSP */
struct sst_hsw *hsw; struct sst_hsw *hsw;
struct device *dev;
enum hsw_pm_state pm_state;
struct snd_soc_card *soc_card;
/* page tables */ /* page tables */
struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; struct snd_dma_buffer dmab[HSW_PCM_COUNT][2];
...@@ -138,21 +163,25 @@ static inline unsigned int hsw_ipc_to_mixer(u32 value) ...@@ -138,21 +163,25 @@ static inline unsigned int hsw_ipc_to_mixer(u32 value)
static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc = struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; (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 hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg];
struct sst_hsw *hsw = pdata->hsw; struct sst_hsw *hsw = pdata->hsw;
u32 volume; u32 volume;
mutex_lock(&pcm_data->mutex); mutex_lock(&pcm_data->mutex);
pm_runtime_get_sync(pdata->dev);
if (!pcm_data->stream) { if (!pcm_data->stream) {
pcm_data->volume[0] = pcm_data->volume[0] =
hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
pcm_data->volume[1] = pcm_data->volume[1] =
hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); hsw_mixer_to_ipc(ucontrol->value.integer.value[1]);
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
mutex_unlock(&pcm_data->mutex); mutex_unlock(&pcm_data->mutex);
return 0; return 0;
} }
...@@ -160,7 +189,8 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, ...@@ -160,7 +189,8 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol,
if (ucontrol->value.integer.value[0] == if (ucontrol->value.integer.value[0] ==
ucontrol->value.integer.value[1]) { ucontrol->value.integer.value[1]) {
volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 2, volume); /* apply volume value to all channels */
sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, SST_HSW_CHANNELS_ALL, volume);
} else { } else {
volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume); sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume);
...@@ -168,6 +198,8 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, ...@@ -168,6 +198,8 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol,
sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume); sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume);
} }
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
mutex_unlock(&pcm_data->mutex); mutex_unlock(&pcm_data->mutex);
return 0; return 0;
} }
...@@ -175,21 +207,25 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, ...@@ -175,21 +207,25 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol,
static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc = struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; (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 hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg];
struct sst_hsw *hsw = pdata->hsw; struct sst_hsw *hsw = pdata->hsw;
u32 volume; u32 volume;
mutex_lock(&pcm_data->mutex); mutex_lock(&pcm_data->mutex);
pm_runtime_get_sync(pdata->dev);
if (!pcm_data->stream) { if (!pcm_data->stream) {
ucontrol->value.integer.value[0] = ucontrol->value.integer.value[0] =
hsw_ipc_to_mixer(pcm_data->volume[0]); hsw_ipc_to_mixer(pcm_data->volume[0]);
ucontrol->value.integer.value[1] = ucontrol->value.integer.value[1] =
hsw_ipc_to_mixer(pcm_data->volume[1]); hsw_ipc_to_mixer(pcm_data->volume[1]);
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
mutex_unlock(&pcm_data->mutex); mutex_unlock(&pcm_data->mutex);
return 0; return 0;
} }
...@@ -198,6 +234,9 @@ static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, ...@@ -198,6 +234,9 @@ static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol,
ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume);
sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume); sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume);
ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume);
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
mutex_unlock(&pcm_data->mutex); mutex_unlock(&pcm_data->mutex);
return 0; return 0;
...@@ -206,16 +245,18 @@ static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, ...@@ -206,16 +245,18 @@ static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol,
static int hsw_volume_put(struct snd_kcontrol *kcontrol, static int hsw_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt); struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform);
struct sst_hsw *hsw = pdata->hsw; struct sst_hsw *hsw = pdata->hsw;
u32 volume; u32 volume;
pm_runtime_get_sync(pdata->dev);
if (ucontrol->value.integer.value[0] == if (ucontrol->value.integer.value[0] ==
ucontrol->value.integer.value[1]) { ucontrol->value.integer.value[1]) {
volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
sst_hsw_mixer_set_volume(hsw, 0, 2, volume); sst_hsw_mixer_set_volume(hsw, 0, SST_HSW_CHANNELS_ALL, volume);
} else { } else {
volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
...@@ -225,23 +266,28 @@ static int hsw_volume_put(struct snd_kcontrol *kcontrol, ...@@ -225,23 +266,28 @@ static int hsw_volume_put(struct snd_kcontrol *kcontrol,
sst_hsw_mixer_set_volume(hsw, 0, 1, volume); sst_hsw_mixer_set_volume(hsw, 0, 1, volume);
} }
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
return 0; return 0;
} }
static int hsw_volume_get(struct snd_kcontrol *kcontrol, static int hsw_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt); struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform);
struct sst_hsw *hsw = pdata->hsw; struct sst_hsw *hsw = pdata->hsw;
unsigned int volume = 0; unsigned int volume = 0;
pm_runtime_get_sync(pdata->dev);
sst_hsw_mixer_get_volume(hsw, 0, 0, &volume); sst_hsw_mixer_get_volume(hsw, 0, 0, &volume);
ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume);
sst_hsw_mixer_get_volume(hsw, 0, 1, &volume); sst_hsw_mixer_get_volume(hsw, 0, 1, &volume);
ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume);
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
return 0; return 0;
} }
...@@ -252,23 +298,19 @@ static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); ...@@ -252,23 +298,19 @@ static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1);
static const struct snd_kcontrol_new hsw_volume_controls[] = { static const struct snd_kcontrol_new hsw_volume_controls[] = {
/* Global DSP volume */ /* Global DSP volume */
SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8, SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8,
ARRAY_SIZE(volume_map) -1, 0, ARRAY_SIZE(volume_map) - 1, 0,
hsw_volume_get, hsw_volume_put, hsw_vol_tlv), hsw_volume_get, hsw_volume_put, hsw_vol_tlv),
/* Offload 0 volume */ /* Offload 0 volume */
SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8, SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8,
ARRAY_SIZE(volume_map), 0, ARRAY_SIZE(volume_map) - 1, 0,
hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv),
/* Offload 1 volume */ /* Offload 1 volume */
SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8, SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8,
ARRAY_SIZE(volume_map), 0, ARRAY_SIZE(volume_map) - 1, 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), hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv),
/* Mic Capture volume */ /* Mic Capture volume */
SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 0, 0, 8,
ARRAY_SIZE(volume_map), 0, ARRAY_SIZE(volume_map) - 1, 0,
hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv),
}; };
...@@ -354,8 +396,14 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, ...@@ -354,8 +396,14 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream,
/* DSP stream type depends on DAI ID */ /* DSP stream type depends on DAI ID */
switch (rtd->cpu_dai->id) { switch (rtd->cpu_dai->id) {
case 0: case 0:
stream_type = SST_HSW_STREAM_TYPE_SYSTEM; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
module_id = SST_HSW_MODULE_PCM_SYSTEM; stream_type = SST_HSW_STREAM_TYPE_SYSTEM;
module_id = SST_HSW_MODULE_PCM_SYSTEM;
}
else {
stream_type = SST_HSW_STREAM_TYPE_CAPTURE;
module_id = SST_HSW_MODULE_PCM_CAPTURE;
}
break; break;
case 1: case 1:
case 2: case 2:
...@@ -368,10 +416,6 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, ...@@ -368,10 +416,6 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream,
path_id = SST_HSW_STREAM_PATH_SSP0_OUT; path_id = SST_HSW_STREAM_PATH_SSP0_OUT;
module_id = SST_HSW_MODULE_PCM_REFERENCE; module_id = SST_HSW_MODULE_PCM_REFERENCE;
break; break;
case 4:
stream_type = SST_HSW_STREAM_TYPE_CAPTURE;
module_id = SST_HSW_MODULE_PCM_CAPTURE;
break;
default: default:
dev_err(rtd->dev, "error: invalid DAI ID %d\n", dev_err(rtd->dev, "error: invalid DAI ID %d\n",
rtd->cpu_dai->id); rtd->cpu_dai->id);
...@@ -421,13 +465,7 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, ...@@ -421,13 +465,7 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream,
return ret; return ret;
} }
/* we only support stereo atm */
channels = params_channels(params); 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); map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO);
sst_hsw_stream_set_map_config(hsw, pcm_data->stream, sst_hsw_stream_set_map_config(hsw, pcm_data->stream,
map, SST_HSW_CHANNEL_CONFIG_STEREO); map, SST_HSW_CHANNEL_CONFIG_STEREO);
...@@ -478,35 +516,23 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, ...@@ -478,35 +516,23 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream,
return -EINVAL; return -EINVAL;
} }
/* we use hardcoded memory offsets atm, will be updated for new FW */ sst_hsw_stream_set_module_info(hsw, pcm_data->stream,
if (stream_type == SST_HSW_STREAM_TYPE_CAPTURE) { pcm_data->runtime);
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); ret = sst_hsw_stream_commit(hsw, pcm_data->stream);
if (ret < 0) { if (ret < 0) {
dev_err(rtd->dev, "error: failed to commit stream %d\n", ret); dev_err(rtd->dev, "error: failed to commit stream %d\n", ret);
return ret; return ret;
} }
pcm_data->allocated = true;
if (!pcm_data->allocated) {
/* Set previous saved volume */
sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0,
0, pcm_data->volume[0]);
sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0,
1, pcm_data->volume[1]);
pcm_data->allocated = true;
}
ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1); ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1);
if (ret < 0) if (ret < 0)
...@@ -558,7 +584,7 @@ static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data) ...@@ -558,7 +584,7 @@ static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data)
pos = frames_to_bytes(runtime, pos = frames_to_bytes(runtime,
(runtime->control->appl_ptr % runtime->buffer_size)); (runtime->control->appl_ptr % runtime->buffer_size));
dev_dbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); dev_vdbg(rtd->dev, "PCM: App pointer %d bytes\n", pos);
/* let alsa know we have play a period */ /* let alsa know we have play a period */
snd_pcm_period_elapsed(substream); snd_pcm_period_elapsed(substream);
...@@ -580,7 +606,7 @@ static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream) ...@@ -580,7 +606,7 @@ static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream)
offset = bytes_to_frames(runtime, position); offset = bytes_to_frames(runtime, position);
ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream); ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream);
dev_dbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", dev_vdbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n",
position, ppos); position, ppos);
return offset; return offset;
} }
...@@ -596,6 +622,7 @@ static int hsw_pcm_open(struct snd_pcm_substream *substream) ...@@ -596,6 +622,7 @@ static int hsw_pcm_open(struct snd_pcm_substream *substream)
pcm_data = &pdata->pcm[rtd->cpu_dai->id]; pcm_data = &pdata->pcm[rtd->cpu_dai->id];
mutex_lock(&pcm_data->mutex); mutex_lock(&pcm_data->mutex);
pm_runtime_get_sync(pdata->dev);
snd_soc_pcm_set_drvdata(rtd, pcm_data); snd_soc_pcm_set_drvdata(rtd, pcm_data);
pcm_data->substream = substream; pcm_data->substream = substream;
...@@ -606,16 +633,12 @@ static int hsw_pcm_open(struct snd_pcm_substream *substream) ...@@ -606,16 +633,12 @@ static int hsw_pcm_open(struct snd_pcm_substream *substream)
hsw_notify_pointer, pcm_data); hsw_notify_pointer, pcm_data);
if (pcm_data->stream == NULL) { if (pcm_data->stream == NULL) {
dev_err(rtd->dev, "error: failed to create stream\n"); dev_err(rtd->dev, "error: failed to create stream\n");
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
mutex_unlock(&pcm_data->mutex); mutex_unlock(&pcm_data->mutex);
return -EINVAL; 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); mutex_unlock(&pcm_data->mutex);
return 0; return 0;
} }
...@@ -645,6 +668,8 @@ static int hsw_pcm_close(struct snd_pcm_substream *substream) ...@@ -645,6 +668,8 @@ static int hsw_pcm_close(struct snd_pcm_substream *substream)
pcm_data->stream = NULL; pcm_data->stream = NULL;
out: out:
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
mutex_unlock(&pcm_data->mutex); mutex_unlock(&pcm_data->mutex);
return ret; return ret;
} }
...@@ -660,6 +685,56 @@ static struct snd_pcm_ops hsw_pcm_ops = { ...@@ -660,6 +685,56 @@ static struct snd_pcm_ops hsw_pcm_ops = {
.page = snd_pcm_sgbuf_ops_page, .page = snd_pcm_sgbuf_ops_page,
}; };
/* static mappings between PCMs and modules - may be dynamic in future */
static struct hsw_pcm_module_map mod_map[] = {
{HSW_PCM_DAI_ID_SYSTEM, SST_HSW_MODULE_PCM_SYSTEM},
{HSW_PCM_DAI_ID_OFFLOAD0, SST_HSW_MODULE_PCM},
{HSW_PCM_DAI_ID_OFFLOAD1, SST_HSW_MODULE_PCM},
{HSW_PCM_DAI_ID_LOOPBACK, SST_HSW_MODULE_PCM_REFERENCE},
{HSW_PCM_DAI_ID_CAPTURE, SST_HSW_MODULE_PCM_CAPTURE},
};
static int hsw_pcm_create_modules(struct hsw_priv_data *pdata)
{
struct sst_hsw *hsw = pdata->hsw;
struct hsw_pcm_data *pcm_data;
int i;
for (i = 0; i < ARRAY_SIZE(mod_map); i++) {
pcm_data = &pdata->pcm[i];
/* create new runtime module, use same offset if recreated */
pcm_data->runtime = sst_hsw_runtime_module_create(hsw,
mod_map[i].mod_id, pcm_data->persistent_offset);
if (pcm_data->runtime == NULL)
goto err;
pcm_data->persistent_offset =
pcm_data->runtime->persistent_offset;
}
return 0;
err:
for (--i; i >= 0; i--) {
pcm_data = &pdata->pcm[i];
sst_hsw_runtime_module_free(pcm_data->runtime);
}
return -ENODEV;
}
static void hsw_pcm_free_modules(struct hsw_priv_data *pdata)
{
struct hsw_pcm_data *pcm_data;
int i;
for (i = 0; i < ARRAY_SIZE(mod_map); i++) {
pcm_data = &pdata->pcm[i];
sst_hsw_runtime_module_free(pcm_data->runtime);
}
}
static void hsw_pcm_free(struct snd_pcm *pcm) static void hsw_pcm_free(struct snd_pcm *pcm)
{ {
snd_pcm_lib_preallocate_free_for_all(pcm); snd_pcm_lib_preallocate_free_for_all(pcm);
...@@ -670,6 +745,7 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) ...@@ -670,6 +745,7 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd)
struct snd_pcm *pcm = rtd->pcm; struct snd_pcm *pcm = rtd->pcm;
struct snd_soc_platform *platform = rtd->platform; struct snd_soc_platform *platform = rtd->platform;
struct sst_pdata *pdata = dev_get_platdata(platform->dev); struct sst_pdata *pdata = dev_get_platdata(platform->dev);
struct hsw_priv_data *priv_data = dev_get_drvdata(platform->dev);
struct device *dev = pdata->dma_dev; struct device *dev = pdata->dma_dev;
int ret = 0; int ret = 0;
...@@ -686,6 +762,7 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) ...@@ -686,6 +762,7 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd)
return ret; return ret;
} }
} }
priv_data->pcm[rtd->cpu_dai->id].hsw_pcm = pcm;
return ret; return ret;
} }
...@@ -696,6 +773,7 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) ...@@ -696,6 +773,7 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd)
static struct snd_soc_dai_driver hsw_dais[] = { static struct snd_soc_dai_driver hsw_dais[] = {
{ {
.name = "System Pin", .name = "System Pin",
.id = HSW_PCM_DAI_ID_SYSTEM,
.playback = { .playback = {
.stream_name = "System Playback", .stream_name = "System Playback",
.channels_min = 2, .channels_min = 2,
...@@ -703,10 +781,18 @@ static struct snd_soc_dai_driver hsw_dais[] = { ...@@ -703,10 +781,18 @@ static struct snd_soc_dai_driver hsw_dais[] = {
.rates = SNDRV_PCM_RATE_48000, .rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
}, },
.capture = {
.stream_name = "Analog Capture",
.channels_min = 2,
.channels_max = 4,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
},
}, },
{ {
/* PCM */ /* PCM */
.name = "Offload0 Pin", .name = "Offload0 Pin",
.id = HSW_PCM_DAI_ID_OFFLOAD0,
.playback = { .playback = {
.stream_name = "Offload0 Playback", .stream_name = "Offload0 Playback",
.channels_min = 2, .channels_min = 2,
...@@ -718,6 +804,7 @@ static struct snd_soc_dai_driver hsw_dais[] = { ...@@ -718,6 +804,7 @@ static struct snd_soc_dai_driver hsw_dais[] = {
{ {
/* PCM */ /* PCM */
.name = "Offload1 Pin", .name = "Offload1 Pin",
.id = HSW_PCM_DAI_ID_OFFLOAD1,
.playback = { .playback = {
.stream_name = "Offload1 Playback", .stream_name = "Offload1 Playback",
.channels_min = 2, .channels_min = 2,
...@@ -728,6 +815,7 @@ static struct snd_soc_dai_driver hsw_dais[] = { ...@@ -728,6 +815,7 @@ static struct snd_soc_dai_driver hsw_dais[] = {
}, },
{ {
.name = "Loopback Pin", .name = "Loopback Pin",
.id = HSW_PCM_DAI_ID_LOOPBACK,
.capture = { .capture = {
.stream_name = "Loopback Capture", .stream_name = "Loopback Capture",
.channels_min = 2, .channels_min = 2,
...@@ -736,16 +824,6 @@ static struct snd_soc_dai_driver hsw_dais[] = { ...@@ -736,16 +824,6 @@ static struct snd_soc_dai_driver hsw_dais[] = {
.formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
}, },
}, },
{
.name = "Capture Pin",
.capture = {
.stream_name = "Analog Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
},
},
}; };
static const struct snd_soc_dapm_widget widgets[] = { static const struct snd_soc_dapm_widget widgets[] = {
...@@ -776,9 +854,20 @@ static int hsw_pcm_probe(struct snd_soc_platform *platform) ...@@ -776,9 +854,20 @@ static int hsw_pcm_probe(struct snd_soc_platform *platform)
{ {
struct hsw_priv_data *priv_data = snd_soc_platform_get_drvdata(platform); struct hsw_priv_data *priv_data = snd_soc_platform_get_drvdata(platform);
struct sst_pdata *pdata = dev_get_platdata(platform->dev); struct sst_pdata *pdata = dev_get_platdata(platform->dev);
struct device *dma_dev = pdata->dma_dev; struct device *dma_dev, *dev;
int i, ret = 0; int i, ret = 0;
if (!pdata)
return -ENODEV;
dev = platform->dev;
dma_dev = pdata->dma_dev;
priv_data->hsw = pdata->dsp;
priv_data->dev = platform->dev;
priv_data->pm_state = HSW_PM_STATE_D0;
priv_data->soc_card = platform->component.card;
/* allocate DSP buffer page tables */ /* allocate DSP buffer page tables */
for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) {
...@@ -801,6 +890,16 @@ static int hsw_pcm_probe(struct snd_soc_platform *platform) ...@@ -801,6 +890,16 @@ static int hsw_pcm_probe(struct snd_soc_platform *platform)
} }
} }
/* allocate runtime modules */
hsw_pcm_create_modules(priv_data);
/* enable runtime PM with auto suspend */
pm_runtime_set_autosuspend_delay(platform->dev,
SST_RUNTIME_SUSPEND_DELAY);
pm_runtime_use_autosuspend(platform->dev);
pm_runtime_enable(platform->dev);
pm_runtime_idle(platform->dev);
return 0; return 0;
err: err:
...@@ -819,6 +918,9 @@ static int hsw_pcm_remove(struct snd_soc_platform *platform) ...@@ -819,6 +918,9 @@ static int hsw_pcm_remove(struct snd_soc_platform *platform)
snd_soc_platform_get_drvdata(platform); snd_soc_platform_get_drvdata(platform);
int i; int i;
pm_runtime_disable(platform->dev);
hsw_pcm_free_modules(priv_data);
for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) {
if (hsw_dais[i].playback.channels_min) if (hsw_dais[i].playback.channels_min)
snd_dma_free_pages(&priv_data->dmab[i][0]); snd_dma_free_pages(&priv_data->dmab[i][0]);
...@@ -896,10 +998,181 @@ static int hsw_pcm_dev_remove(struct platform_device *pdev) ...@@ -896,10 +998,181 @@ static int hsw_pcm_dev_remove(struct platform_device *pdev)
return 0; return 0;
} }
#ifdef CONFIG_PM_RUNTIME
static int hsw_pcm_runtime_idle(struct device *dev)
{
return 0;
}
static int hsw_pcm_runtime_suspend(struct device *dev)
{
struct hsw_priv_data *pdata = dev_get_drvdata(dev);
struct sst_hsw *hsw = pdata->hsw;
if (pdata->pm_state == HSW_PM_STATE_D3)
return 0;
sst_hsw_dsp_runtime_suspend(hsw);
sst_hsw_dsp_runtime_sleep(hsw);
pdata->pm_state = HSW_PM_STATE_D3;
return 0;
}
static int hsw_pcm_runtime_resume(struct device *dev)
{
struct hsw_priv_data *pdata = dev_get_drvdata(dev);
struct sst_hsw *hsw = pdata->hsw;
int ret;
if (pdata->pm_state == HSW_PM_STATE_D0)
return 0;
ret = sst_hsw_dsp_load(hsw);
if (ret < 0) {
dev_err(dev, "failed to reload %d\n", ret);
return ret;
}
ret = hsw_pcm_create_modules(pdata);
if (ret < 0) {
dev_err(dev, "failed to create modules %d\n", ret);
return ret;
}
ret = sst_hsw_dsp_runtime_resume(hsw);
if (ret < 0)
return ret;
else if (ret == 1) /* no action required */
return 0;
pdata->pm_state = HSW_PM_STATE_D0;
return ret;
}
#else
#define hsw_pcm_runtime_idle NULL
#define hsw_pcm_runtime_suspend NULL
#define hsw_pcm_runtime_resume NULL
#endif
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_RUNTIME)
static void hsw_pcm_complete(struct device *dev)
{
struct hsw_priv_data *pdata = dev_get_drvdata(dev);
struct sst_hsw *hsw = pdata->hsw;
struct hsw_pcm_data *pcm_data;
int i, err;
if (pdata->pm_state == HSW_PM_STATE_D0)
return;
err = sst_hsw_dsp_load(hsw);
if (err < 0) {
dev_err(dev, "failed to reload %d\n", err);
return;
}
err = hsw_pcm_create_modules(pdata);
if (err < 0) {
dev_err(dev, "failed to create modules %d\n", err);
return;
}
for (i = 0; i < HSW_PCM_DAI_ID_CAPTURE + 1; i++) {
pcm_data = &pdata->pcm[i];
if (!pcm_data->substream)
continue;
err = sst_module_runtime_restore(pcm_data->runtime,
&pcm_data->context);
if (err < 0)
dev_err(dev, "failed to restore context for PCM %d\n", i);
}
snd_soc_resume(pdata->soc_card->dev);
err = sst_hsw_dsp_runtime_resume(hsw);
if (err < 0)
return;
else if (err == 1) /* no action required */
return;
pdata->pm_state = HSW_PM_STATE_D0;
return;
}
static int hsw_pcm_prepare(struct device *dev)
{
struct hsw_priv_data *pdata = dev_get_drvdata(dev);
struct sst_hsw *hsw = pdata->hsw;
struct hsw_pcm_data *pcm_data;
int i, err;
if (pdata->pm_state == HSW_PM_STATE_D3)
return 0;
/* suspend all active streams */
for (i = 0; i < HSW_PCM_DAI_ID_CAPTURE + 1; i++) {
pcm_data = &pdata->pcm[i];
if (!pcm_data->substream)
continue;
dev_dbg(dev, "suspending pcm %d\n", i);
snd_pcm_suspend_all(pcm_data->hsw_pcm);
/* We need to wait until the DSP FW stops the streams */
msleep(2);
}
snd_soc_suspend(pdata->soc_card->dev);
snd_soc_poweroff(pdata->soc_card->dev);
/* enter D3 state and stall */
sst_hsw_dsp_runtime_suspend(hsw);
/* preserve persistent memory */
for (i = 0; i < HSW_PCM_DAI_ID_CAPTURE + 1; i++) {
pcm_data = &pdata->pcm[i];
if (!pcm_data->substream)
continue;
dev_dbg(dev, "saving context pcm %d\n", i);
err = sst_module_runtime_save(pcm_data->runtime,
&pcm_data->context);
if (err < 0)
dev_err(dev, "failed to save context for PCM %d\n", i);
}
/* put the DSP to sleep */
sst_hsw_dsp_runtime_sleep(hsw);
pdata->pm_state = HSW_PM_STATE_D3;
return 0;
}
#else
#define hsw_pcm_prepare NULL
#define hsw_pcm_complete NULL
#endif
static const struct dev_pm_ops hsw_pcm_pm = {
.runtime_idle = hsw_pcm_runtime_idle,
.runtime_suspend = hsw_pcm_runtime_suspend,
.runtime_resume = hsw_pcm_runtime_resume,
.prepare = hsw_pcm_prepare,
.complete = hsw_pcm_complete,
};
static struct platform_driver hsw_pcm_driver = { static struct platform_driver hsw_pcm_driver = {
.driver = { .driver = {
.name = "haswell-pcm-audio", .name = "haswell-pcm-audio",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.pm = &hsw_pcm_pm,
}, },
.probe = hsw_pcm_dev_probe, .probe = hsw_pcm_dev_probe,
......
...@@ -67,8 +67,11 @@ static int sst_platform_compr_open(struct snd_compr_stream *cstream) ...@@ -67,8 +67,11 @@ static int sst_platform_compr_open(struct snd_compr_stream *cstream)
goto out_ops; goto out_ops;
} }
stream->compr_ops = sst->compr_ops; stream->compr_ops = sst->compr_ops;
stream->id = 0; stream->id = 0;
/* Turn on LPE */
sst->compr_ops->power(sst->dev, true);
sst_set_stream_status(stream, SST_PLATFORM_INIT); sst_set_stream_status(stream, SST_PLATFORM_INIT);
runtime->private_data = stream; runtime->private_data = stream;
return 0; return 0;
...@@ -83,6 +86,9 @@ static int sst_platform_compr_free(struct snd_compr_stream *cstream) ...@@ -83,6 +86,9 @@ static int sst_platform_compr_free(struct snd_compr_stream *cstream)
int ret_val = 0, str_id; int ret_val = 0, str_id;
stream = cstream->runtime->private_data; stream = cstream->runtime->private_data;
/* Turn off LPE */
sst->compr_ops->power(sst->dev, false);
/*need to check*/ /*need to check*/
str_id = stream->id; str_id = stream->id;
if (str_id) if (str_id)
......
...@@ -101,35 +101,11 @@ static struct sst_dev_stream_map dpcm_strm_map[] = { ...@@ -101,35 +101,11 @@ static struct sst_dev_stream_map dpcm_strm_map[] = {
{MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0}, {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0},
}; };
/* MFLD - MSIC */ static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream)
static struct snd_soc_dai_driver sst_platform_dai[] = {
{ {
.name = "Headset-cpu-dai",
.id = 0, return sst_send_pipe_gains(dai, stream, mute);
.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 */ /* helper functions */
void sst_set_stream_status(struct sst_runtime_stream *stream, void sst_set_stream_status(struct sst_runtime_stream *stream,
...@@ -451,12 +427,133 @@ static int sst_media_hw_free(struct snd_pcm_substream *substream, ...@@ -451,12 +427,133 @@ static int sst_media_hw_free(struct snd_pcm_substream *substream,
return snd_pcm_lib_free_pages(substream); return snd_pcm_lib_free_pages(substream);
} }
static int sst_enable_ssp(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret = 0;
if (!dai->active) {
ret = sst_handle_vb_timer(dai, true);
if (ret)
return ret;
ret = send_ssp_cmd(dai, dai->name, 1);
}
return ret;
}
static void sst_disable_ssp(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
if (!dai->active) {
send_ssp_cmd(dai, dai->name, 0);
sst_handle_vb_timer(dai, false);
}
}
static struct snd_soc_dai_ops sst_media_dai_ops = { static struct snd_soc_dai_ops sst_media_dai_ops = {
.startup = sst_media_open, .startup = sst_media_open,
.shutdown = sst_media_close, .shutdown = sst_media_close,
.prepare = sst_media_prepare, .prepare = sst_media_prepare,
.hw_params = sst_media_hw_params, .hw_params = sst_media_hw_params,
.hw_free = sst_media_hw_free, .hw_free = sst_media_hw_free,
.mute_stream = sst_media_digital_mute,
};
static struct snd_soc_dai_ops sst_compr_dai_ops = {
.mute_stream = sst_media_digital_mute,
};
static struct snd_soc_dai_ops sst_be_dai_ops = {
.startup = sst_enable_ssp,
.shutdown = sst_disable_ssp,
};
static struct snd_soc_dai_driver sst_platform_dai[] = {
{
.name = "media-cpu-dai",
.ops = &sst_media_dai_ops,
.playback = {
.stream_name = "Headset Playback",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "Headset Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
{
.name = "compress-cpu-dai",
.compress_dai = 1,
.ops = &sst_compr_dai_ops,
.playback = {
.stream_name = "Compress Playback",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
/* BE CPU Dais */
{
.name = "ssp0-port",
.ops = &sst_be_dai_ops,
.playback = {
.stream_name = "ssp0 Tx",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "ssp0 Rx",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
{
.name = "ssp1-port",
.ops = &sst_be_dai_ops,
.playback = {
.stream_name = "ssp1 Tx",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "ssp1 Rx",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
{
.name = "ssp2-port",
.ops = &sst_be_dai_ops,
.playback = {
.stream_name = "ssp2 Tx",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "ssp2 Rx",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
}; };
static int sst_platform_open(struct snd_pcm_substream *substream) static int sst_platform_open(struct snd_pcm_substream *substream)
...@@ -609,6 +706,7 @@ static int sst_platform_probe(struct platform_device *pdev) ...@@ -609,6 +706,7 @@ static int sst_platform_probe(struct platform_device *pdev)
pdata->pdev_strm_map = dpcm_strm_map; pdata->pdev_strm_map = dpcm_strm_map;
pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map); pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map);
drv->pdata = pdata; drv->pdata = pdata;
drv->pdev = pdev;
mutex_init(&drv->lock); mutex_init(&drv->lock);
dev_set_drvdata(&pdev->dev, drv); dev_set_drvdata(&pdev->dev, drv);
......
...@@ -117,6 +117,7 @@ struct compress_sst_ops { ...@@ -117,6 +117,7 @@ struct compress_sst_ops {
int (*get_codec_caps)(struct snd_compr_codec_caps *codec); int (*get_codec_caps)(struct snd_compr_codec_caps *codec);
int (*set_metadata)(struct device *dev, unsigned int str_id, int (*set_metadata)(struct device *dev, unsigned int str_id,
struct snd_compr_metadata *mdata); struct snd_compr_metadata *mdata);
int (*power)(struct device *dev, bool state);
}; };
struct sst_ops { struct sst_ops {
...@@ -153,6 +154,10 @@ struct sst_device { ...@@ -153,6 +154,10 @@ struct sst_device {
struct sst_data; struct sst_data;
int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform); int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform);
int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute);
int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable);
int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable);
void sst_set_stream_status(struct sst_runtime_stream *stream, int state); void sst_set_stream_status(struct sst_runtime_stream *stream, int state);
int sst_fill_stream_params(void *substream, const struct sst_data *ctx, int sst_fill_stream_params(void *substream, const struct sst_data *ctx,
struct snd_sst_params *str_params, bool is_compress); struct snd_sst_params *str_params, bool is_compress);
......
snd-intel-sst-core-objs := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o
snd-intel-sst-pci-objs += sst_pci.o
snd-intel-sst-acpi-objs += sst_acpi.o
obj-$(CONFIG_SND_SST_IPC) += snd-intel-sst-core.o
obj-$(CONFIG_SND_SST_IPC_PCI) += snd-intel-sst-pci.o
obj-$(CONFIG_SND_SST_IPC_ACPI) += snd-intel-sst-acpi.o
/*
* sst.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/async.h>
#include <linux/acpi.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
#include "../sst-dsp.h"
MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver");
MODULE_LICENSE("GPL v2");
static inline bool sst_is_process_reply(u32 msg_id)
{
return ((msg_id & PROCESS_MSG) ? true : false);
}
static inline bool sst_validate_mailbox_size(unsigned int size)
{
return ((size <= SST_MAILBOX_SIZE) ? true : false);
}
static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context)
{
union interrupt_reg_mrfld isr;
union ipc_header_mrfld header;
union sst_imr_reg_mrfld imr;
struct ipc_post *msg = NULL;
unsigned int size = 0;
struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
irqreturn_t retval = IRQ_HANDLED;
/* Interrupt arrived, check src */
isr.full = sst_shim_read64(drv->shim, SST_ISRX);
if (isr.part.done_interrupt) {
/* Clear done bit */
spin_lock(&drv->ipc_spin_lock);
header.full = sst_shim_read64(drv->shim,
drv->ipc_reg.ipcx);
header.p.header_high.part.done = 0;
sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full);
/* write 1 to clear status register */;
isr.part.done_interrupt = 1;
sst_shim_write64(drv->shim, SST_ISRX, isr.full);
spin_unlock(&drv->ipc_spin_lock);
/* we can send more messages to DSP so trigger work */
queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq);
retval = IRQ_HANDLED;
}
if (isr.part.busy_interrupt) {
/* message from dsp so copy that */
spin_lock(&drv->ipc_spin_lock);
imr.full = sst_shim_read64(drv->shim, SST_IMRX);
imr.part.busy_interrupt = 1;
sst_shim_write64(drv->shim, SST_IMRX, imr.full);
spin_unlock(&drv->ipc_spin_lock);
header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd);
if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) {
drv->ops->clear_interrupt(drv);
return IRQ_HANDLED;
}
if (header.p.header_high.part.large) {
size = header.p.header_low_payload;
if (sst_validate_mailbox_size(size)) {
memcpy_fromio(msg->mailbox_data,
drv->mailbox + drv->mailbox_recv_offset, size);
} else {
dev_err(drv->dev,
"Mailbox not copied, payload size is: %u\n", size);
header.p.header_low_payload = 0;
}
}
msg->mrfld_header = header;
msg->is_process_reply =
sst_is_process_reply(header.p.header_high.part.msg_id);
spin_lock(&drv->rx_msg_lock);
list_add_tail(&msg->node, &drv->rx_list);
spin_unlock(&drv->rx_msg_lock);
drv->ops->clear_interrupt(drv);
retval = IRQ_WAKE_THREAD;
}
return retval;
}
static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context)
{
struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
struct ipc_post *__msg, *msg = NULL;
unsigned long irq_flags;
spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
if (list_empty(&drv->rx_list)) {
spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
return IRQ_HANDLED;
}
list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) {
list_del(&msg->node);
spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
if (msg->is_process_reply)
drv->ops->process_message(msg);
else
drv->ops->process_reply(drv, msg);
if (msg->is_large)
kfree(msg->mailbox_data);
kfree(msg);
spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
}
spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
return IRQ_HANDLED;
}
static int sst_save_dsp_context_v2(struct intel_sst_drv *sst)
{
int ret = 0;
ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD,
IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL,
true, true, false, true);
if (ret < 0) {
dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret);
return -EIO;
}
return 0;
}
static struct intel_sst_ops mrfld_ops = {
.interrupt = intel_sst_interrupt_mrfld,
.irq_thread = intel_sst_irq_thread_mrfld,
.clear_interrupt = intel_sst_clear_intr_mrfld,
.start = sst_start_mrfld,
.reset = intel_sst_reset_dsp_mrfld,
.post_message = sst_post_message_mrfld,
.process_reply = sst_process_reply_mrfld,
.save_dsp_context = sst_save_dsp_context_v2,
.alloc_stream = sst_alloc_stream_mrfld,
.post_download = sst_post_download_mrfld,
};
int sst_driver_ops(struct intel_sst_drv *sst)
{
switch (sst->dev_id) {
case SST_MRFLD_PCI_ID:
case SST_BYT_ACPI_ID:
case SST_CHV_ACPI_ID:
sst->tstamp = SST_TIME_STAMP_MRFLD;
sst->ops = &mrfld_ops;
return 0;
default:
dev_err(sst->dev,
"SST Driver capablities missing for dev_id: %x", sst->dev_id);
return -EINVAL;
};
}
void sst_process_pending_msg(struct work_struct *work)
{
struct intel_sst_drv *ctx = container_of(work,
struct intel_sst_drv, ipc_post_msg_wq);
ctx->ops->post_message(ctx, NULL, false);
}
static int sst_workqueue_init(struct intel_sst_drv *ctx)
{
INIT_LIST_HEAD(&ctx->memcpy_list);
INIT_LIST_HEAD(&ctx->rx_list);
INIT_LIST_HEAD(&ctx->ipc_dispatch_list);
INIT_LIST_HEAD(&ctx->block_list);
INIT_WORK(&ctx->ipc_post_msg_wq, sst_process_pending_msg);
init_waitqueue_head(&ctx->wait_queue);
ctx->post_msg_wq =
create_singlethread_workqueue("sst_post_msg_wq");
if (!ctx->post_msg_wq)
return -EBUSY;
return 0;
}
static void sst_init_locks(struct intel_sst_drv *ctx)
{
mutex_init(&ctx->sst_lock);
spin_lock_init(&ctx->rx_msg_lock);
spin_lock_init(&ctx->ipc_spin_lock);
spin_lock_init(&ctx->block_lock);
}
int sst_alloc_drv_context(struct intel_sst_drv **ctx,
struct device *dev, unsigned int dev_id)
{
*ctx = devm_kzalloc(dev, sizeof(struct intel_sst_drv), GFP_KERNEL);
if (!(*ctx))
return -ENOMEM;
(*ctx)->dev = dev;
(*ctx)->dev_id = dev_id;
return 0;
}
EXPORT_SYMBOL_GPL(sst_alloc_drv_context);
int sst_context_init(struct intel_sst_drv *ctx)
{
int ret = 0, i;
if (!ctx->pdata)
return -EINVAL;
if (!ctx->pdata->probe_data)
return -EINVAL;
memcpy(&ctx->info, ctx->pdata->probe_data, sizeof(ctx->info));
ret = sst_driver_ops(ctx);
if (ret != 0)
return -EINVAL;
sst_init_locks(ctx);
sst_set_fw_state_locked(ctx, SST_RESET);
/* pvt_id 0 reserved for async messages */
ctx->pvt_id = 1;
ctx->stream_cnt = 0;
ctx->fw_in_mem = NULL;
/* we use memcpy, so set to 0 */
ctx->use_dma = 0;
ctx->use_lli = 0;
if (sst_workqueue_init(ctx))
return -EINVAL;
ctx->mailbox_recv_offset = ctx->pdata->ipc_info->mbox_recv_off;
ctx->ipc_reg.ipcx = SST_IPCX + ctx->pdata->ipc_info->ipc_offset;
ctx->ipc_reg.ipcd = SST_IPCD + ctx->pdata->ipc_info->ipc_offset;
dev_info(ctx->dev, "Got drv data max stream %d\n",
ctx->info.max_streams);
for (i = 1; i <= ctx->info.max_streams; i++) {
struct stream_info *stream = &ctx->streams[i];
memset(stream, 0, sizeof(*stream));
stream->pipe_id = PIPE_RSVD;
mutex_init(&stream->lock);
}
/* Register the ISR */
ret = devm_request_threaded_irq(ctx->dev, ctx->irq_num, ctx->ops->interrupt,
ctx->ops->irq_thread, 0, SST_DRV_NAME,
ctx);
if (ret)
goto do_free_mem;
dev_dbg(ctx->dev, "Registered IRQ %#x\n", ctx->irq_num);
/* default intr are unmasked so set this as masked */
sst_shim_write64(ctx->shim, SST_IMRX, 0xFFFF0038);
ctx->qos = devm_kzalloc(ctx->dev,
sizeof(struct pm_qos_request), GFP_KERNEL);
if (!ctx->qos) {
ret = -ENOMEM;
goto do_free_mem;
}
pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY,
PM_QOS_DEFAULT_VALUE);
dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name);
ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name,
ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb);
if (ret) {
dev_err(ctx->dev, "Firmware download failed:%d\n", ret);
goto do_free_mem;
}
sst_register(ctx->dev);
return 0;
do_free_mem:
destroy_workqueue(ctx->post_msg_wq);
return ret;
}
EXPORT_SYMBOL_GPL(sst_context_init);
void sst_context_cleanup(struct intel_sst_drv *ctx)
{
pm_runtime_get_noresume(ctx->dev);
pm_runtime_disable(ctx->dev);
sst_unregister(ctx->dev);
sst_set_fw_state_locked(ctx, SST_SHUTDOWN);
flush_scheduled_work();
destroy_workqueue(ctx->post_msg_wq);
pm_qos_remove_request(ctx->qos);
kfree(ctx->fw_sg_list.src);
kfree(ctx->fw_sg_list.dst);
ctx->fw_sg_list.list_len = 0;
kfree(ctx->fw_in_mem);
ctx->fw_in_mem = NULL;
sst_memcpy_free_resources(ctx);
ctx = NULL;
}
EXPORT_SYMBOL_GPL(sst_context_cleanup);
static inline void sst_save_shim64(struct intel_sst_drv *ctx,
void __iomem *shim,
struct sst_shim_regs64 *shim_regs)
{
unsigned long irq_flags;
spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags);
shim_regs->imrx = sst_shim_read64(shim, SST_IMRX),
spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags);
}
static inline void sst_restore_shim64(struct intel_sst_drv *ctx,
void __iomem *shim,
struct sst_shim_regs64 *shim_regs)
{
unsigned long irq_flags;
/*
* we only need to restore IMRX for this case, rest will be
* initialize by FW or driver when firmware is loaded
*/
spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags);
sst_shim_write64(shim, SST_IMRX, shim_regs->imrx),
spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags);
}
void sst_configure_runtime_pm(struct intel_sst_drv *ctx)
{
pm_runtime_set_autosuspend_delay(ctx->dev, SST_SUSPEND_DELAY);
pm_runtime_use_autosuspend(ctx->dev);
/*
* For acpi devices, the actual physical device state is
* initially active. So change the state to active before
* enabling the pm
*/
pm_runtime_enable(ctx->dev);
if (acpi_disabled)
pm_runtime_set_active(ctx->dev);
else
pm_runtime_put_noidle(ctx->dev);
sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64);
}
EXPORT_SYMBOL_GPL(sst_configure_runtime_pm);
static int intel_sst_runtime_suspend(struct device *dev)
{
int ret = 0;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state == SST_RESET) {
dev_dbg(dev, "LPE is already in RESET state, No action\n");
return 0;
}
/* save fw context */
if (ctx->ops->save_dsp_context(ctx))
return -EBUSY;
/* Move the SST state to Reset */
sst_set_fw_state_locked(ctx, SST_RESET);
synchronize_irq(ctx->irq_num);
flush_workqueue(ctx->post_msg_wq);
/* save the shim registers because PMC doesn't save state */
sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64);
return ret;
}
static int intel_sst_runtime_resume(struct device *dev)
{
int ret = 0;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state == SST_RESET) {
ret = sst_load_fw(ctx);
if (ret) {
dev_err(dev, "FW download fail %d\n", ret);
sst_set_fw_state_locked(ctx, SST_RESET);
}
}
return ret;
}
const struct dev_pm_ops intel_sst_pm = {
.runtime_suspend = intel_sst_runtime_suspend,
.runtime_resume = intel_sst_runtime_resume,
};
EXPORT_SYMBOL_GPL(intel_sst_pm);
/*
* sst.h - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corporation
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Common private declarations for SST
*/
#ifndef __SST_H__
#define __SST_H__
#include <linux/firmware.h>
/* driver names */
#define SST_DRV_NAME "intel_sst_driver"
#define SST_MRFLD_PCI_ID 0x119A
#define SST_BYT_ACPI_ID 0x80860F28
#define SST_CHV_ACPI_ID 0x808622A8
#define SST_SUSPEND_DELAY 2000
#define FW_CONTEXT_MEM (64*1024)
#define SST_ICCM_BOUNDARY 4
#define SST_CONFIG_SSP_SIGN 0x7ffe8001
#define MRFLD_FW_VIRTUAL_BASE 0xC0000000
#define MRFLD_FW_DDR_BASE_OFFSET 0x0
#define MRFLD_FW_FEATURE_BASE_OFFSET 0x4
#define MRFLD_FW_BSS_RESET_BIT 0
extern const struct dev_pm_ops intel_sst_pm;
enum sst_states {
SST_FW_LOADING = 1,
SST_FW_RUNNING,
SST_RESET,
SST_SHUTDOWN,
};
enum sst_algo_ops {
SST_SET_ALGO = 0,
SST_GET_ALGO = 1,
};
#define SST_BLOCK_TIMEOUT 1000
#define FW_SIGNATURE_SIZE 4
/* stream states */
enum sst_stream_states {
STREAM_UN_INIT = 0, /* Freed/Not used stream */
STREAM_RUNNING = 1, /* Running */
STREAM_PAUSED = 2, /* Paused stream */
STREAM_DECODE = 3, /* stream is in decoding only state */
STREAM_INIT = 4, /* stream init, waiting for data */
STREAM_RESET = 5, /* force reset on recovery */
};
enum sst_ram_type {
SST_IRAM = 1,
SST_DRAM = 2,
SST_DDR = 5,
SST_CUSTOM_INFO = 7, /* consists of FW binary information */
};
/* SST shim registers to structure mapping */
union interrupt_reg {
struct {
u64 done_interrupt:1;
u64 busy_interrupt:1;
u64 rsvd:62;
} part;
u64 full;
};
union sst_pisr_reg {
struct {
u32 pssp0:1;
u32 pssp1:1;
u32 rsvd0:3;
u32 dmac:1;
u32 rsvd1:26;
} part;
u32 full;
};
union sst_pimr_reg {
struct {
u32 ssp0:1;
u32 ssp1:1;
u32 rsvd0:3;
u32 dmac:1;
u32 rsvd1:10;
u32 ssp0_sc:1;
u32 ssp1_sc:1;
u32 rsvd2:3;
u32 dmac_sc:1;
u32 rsvd3:10;
} part;
u32 full;
};
union config_status_reg_mrfld {
struct {
u64 lpe_reset:1;
u64 lpe_reset_vector:1;
u64 runstall:1;
u64 pwaitmode:1;
u64 clk_sel:3;
u64 rsvd2:1;
u64 sst_clk:3;
u64 xt_snoop:1;
u64 rsvd3:4;
u64 clk_sel1:6;
u64 clk_enable:3;
u64 rsvd4:6;
u64 slim0baseclk:1;
u64 rsvd:32;
} part;
u64 full;
};
union interrupt_reg_mrfld {
struct {
u64 done_interrupt:1;
u64 busy_interrupt:1;
u64 rsvd:62;
} part;
u64 full;
};
union sst_imr_reg_mrfld {
struct {
u64 done_interrupt:1;
u64 busy_interrupt:1;
u64 rsvd:62;
} part;
u64 full;
};
/**
* struct sst_block - This structure is used to block a user/fw data call to another
* fw/user call
*
* @condition: condition for blocking check
* @ret_code: ret code when block is released
* @data: data ptr
* @size: size of data
* @on: block condition
* @msg_id: msg_id = msgid in mfld/ctp, mrfld = NULL
* @drv_id: str_id in mfld/ctp, = drv_id in mrfld
* @node: list head node
*/
struct sst_block {
bool condition;
int ret_code;
void *data;
u32 size;
bool on;
u32 msg_id;
u32 drv_id;
struct list_head node;
};
/**
* struct stream_info - structure that holds the stream information
*
* @status : stream current state
* @prev : stream prev state
* @ops : stream operation pb/cp/drm...
* @bufs: stream buffer list
* @lock : stream mutex for protecting state
* @pcm_substream : PCM substream
* @period_elapsed : PCM period elapsed callback
* @sfreq : stream sampling freq
* @str_type : stream type
* @cumm_bytes : cummulative bytes decoded
* @str_type : stream type
* @src : stream source
*/
struct stream_info {
unsigned int status;
unsigned int prev;
unsigned int ops;
struct mutex lock;
void *pcm_substream;
void (*period_elapsed)(void *pcm_substream);
unsigned int sfreq;
u32 cumm_bytes;
void *compr_cb_param;
void (*compr_cb)(void *compr_cb_param);
void *drain_cb_param;
void (*drain_notify)(void *drain_cb_param);
unsigned int num_ch;
unsigned int pipe_id;
unsigned int str_id;
unsigned int task_id;
};
#define SST_FW_SIGN "$SST"
#define SST_FW_LIB_SIGN "$LIB"
/**
* struct sst_fw_header - FW file headers
*
* @signature : FW signature
* @file_size: size of fw image
* @modules : # of modules
* @file_format : version of header format
* @reserved : reserved fields
*/
struct sst_fw_header {
unsigned char signature[FW_SIGNATURE_SIZE];
u32 file_size;
u32 modules;
u32 file_format;
u32 reserved[4];
};
/**
* struct fw_module_header - module header in FW
*
* @signature: module signature
* @mod_size: size of module
* @blocks: block count
* @type: block type
* @entry_point: module netry point
*/
struct fw_module_header {
unsigned char signature[FW_SIGNATURE_SIZE];
u32 mod_size;
u32 blocks;
u32 type;
u32 entry_point;
};
/**
* struct fw_block_info - block header for FW
*
* @type: block ram type I/D
* @size: size of block
* @ram_offset: offset in ram
*/
struct fw_block_info {
enum sst_ram_type type;
u32 size;
u32 ram_offset;
u32 rsvd;
};
struct sst_runtime_param {
struct snd_sst_runtime_params param;
};
struct sst_sg_list {
struct scatterlist *src;
struct scatterlist *dst;
int list_len;
unsigned int sg_idx;
};
struct sst_memcpy_list {
struct list_head memcpylist;
void *dstn;
const void *src;
u32 size;
bool is_io;
};
/*Firmware Module Information*/
enum sst_lib_dwnld_status {
SST_LIB_NOT_FOUND = 0,
SST_LIB_FOUND,
SST_LIB_DOWNLOADED,
};
struct sst_module_info {
const char *name; /*Library name*/
u32 id; /*Module ID*/
u32 entry_pt; /*Module entry point*/
u8 status; /*module status*/
u8 rsvd1;
u16 rsvd2;
};
/*
* Structure for managing the Library Region(1.5MB)
* in DDR in Merrifield
*/
struct sst_mem_mgr {
phys_addr_t current_base;
int avail;
unsigned int count;
};
struct sst_ipc_reg {
int ipcx;
int ipcd;
};
struct sst_shim_regs64 {
u64 csr;
u64 pisr;
u64 pimr;
u64 isrx;
u64 isrd;
u64 imrx;
u64 imrd;
u64 ipcx;
u64 ipcd;
u64 isrsc;
u64 isrlpesc;
u64 imrsc;
u64 imrlpesc;
u64 ipcsc;
u64 ipclpesc;
u64 clkctl;
u64 csr2;
};
/**
* struct intel_sst_drv - driver ops
*
* @sst_state : current sst device state
* @dev_id : device identifier, pci_id for pci devices and acpi_id for acpi
* devices
* @shim : SST shim pointer
* @mailbox : SST mailbox pointer
* @iram : SST IRAM pointer
* @dram : SST DRAM pointer
* @pdata : SST info passed as a part of pci platform data
* @shim_phy_add : SST shim phy addr
* @shim_regs64: Struct to save shim registers
* @ipc_dispatch_list : ipc messages dispatched
* @rx_list : to copy the process_reply/process_msg from DSP
* @ipc_post_msg_wq : wq to post IPC messages context
* @mad_ops : MAD driver operations registered
* @mad_wq : MAD driver wq
* @post_msg_wq : wq to post IPC messages
* @streams : sst stream contexts
* @list_lock : sst driver list lock (deprecated)
* @ipc_spin_lock : spin lock to handle audio shim access and ipc queue
* @block_lock : spin lock to add block to block_list and assign pvt_id
* @rx_msg_lock : spin lock to handle the rx messages from the DSP
* @scard_ops : sst card ops
* @pci : sst pci device struture
* @dev : pointer to current device struct
* @sst_lock : sst device lock
* @pvt_id : sst private id
* @stream_cnt : total sst active stream count
* @pb_streams : total active pb streams
* @cp_streams : total active cp streams
* @audio_start : audio status
* @qos : PM Qos struct
* firmware_name : Firmware / Library name
*/
struct intel_sst_drv {
int sst_state;
int irq_num;
unsigned int dev_id;
void __iomem *ddr;
void __iomem *shim;
void __iomem *mailbox;
void __iomem *iram;
void __iomem *dram;
unsigned int mailbox_add;
unsigned int iram_base;
unsigned int dram_base;
unsigned int shim_phy_add;
unsigned int iram_end;
unsigned int dram_end;
unsigned int ddr_end;
unsigned int ddr_base;
unsigned int mailbox_recv_offset;
struct sst_shim_regs64 *shim_regs64;
struct list_head block_list;
struct list_head ipc_dispatch_list;
struct sst_platform_info *pdata;
struct list_head rx_list;
struct work_struct ipc_post_msg_wq;
wait_queue_head_t wait_queue;
struct workqueue_struct *post_msg_wq;
unsigned int tstamp;
/* str_id 0 is not used */
struct stream_info streams[MAX_NUM_STREAMS+1];
spinlock_t ipc_spin_lock;
spinlock_t block_lock;
spinlock_t rx_msg_lock;
struct pci_dev *pci;
struct device *dev;
volatile long unsigned pvt_id;
struct mutex sst_lock;
unsigned int stream_cnt;
unsigned int csr_value;
void *fw_in_mem;
struct sst_sg_list fw_sg_list, library_list;
struct intel_sst_ops *ops;
struct sst_info info;
struct pm_qos_request *qos;
unsigned int use_dma;
unsigned int use_lli;
atomic_t fw_clear_context;
bool lib_dwnld_reqd;
struct list_head memcpy_list;
struct sst_ipc_reg ipc_reg;
struct sst_mem_mgr lib_mem_mgr;
/*
* Holder for firmware name. Due to async call it needs to be
* persistent till worker thread gets called
*/
char firmware_name[20];
};
/* misc definitions */
#define FW_DWNL_ID 0x01
struct intel_sst_ops {
irqreturn_t (*interrupt)(int, void *);
irqreturn_t (*irq_thread)(int, void *);
void (*clear_interrupt)(struct intel_sst_drv *ctx);
int (*start)(struct intel_sst_drv *ctx);
int (*reset)(struct intel_sst_drv *ctx);
void (*process_reply)(struct intel_sst_drv *ctx, struct ipc_post *msg);
int (*post_message)(struct intel_sst_drv *ctx,
struct ipc_post *msg, bool sync);
void (*process_message)(struct ipc_post *msg);
void (*set_bypass)(bool set);
int (*save_dsp_context)(struct intel_sst_drv *sst);
void (*restore_dsp_context)(void);
int (*alloc_stream)(struct intel_sst_drv *ctx, void *params);
void (*post_download)(struct intel_sst_drv *sst);
};
int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int id);
int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int id);
int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int id);
int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int id);
int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
int sst_send_byte_stream_mrfld(struct intel_sst_drv *ctx,
struct snd_sst_bytes_v2 *sbytes);
int sst_set_stream_param(int str_id, struct snd_sst_params *str_param);
int sst_set_metadata(int str_id, char *params);
int sst_get_stream(struct intel_sst_drv *sst_drv_ctx,
struct snd_sst_params *str_param);
int sst_get_stream_allocated(struct intel_sst_drv *ctx,
struct snd_sst_params *str_param,
struct snd_sst_lib_download **lib_dnld);
int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx,
int str_id, bool partial_drain);
int sst_post_message_mrfld(struct intel_sst_drv *ctx,
struct ipc_post *msg, bool sync);
void sst_process_reply_mrfld(struct intel_sst_drv *ctx, struct ipc_post *msg);
int sst_start_mrfld(struct intel_sst_drv *ctx);
int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *ctx);
void intel_sst_clear_intr_mrfld(struct intel_sst_drv *ctx);
int sst_load_fw(struct intel_sst_drv *ctx);
int sst_load_library(struct snd_sst_lib_download *lib, u8 ops);
void sst_post_download_mrfld(struct intel_sst_drv *ctx);
int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx);
void sst_memcpy_free_resources(struct intel_sst_drv *ctx);
int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx,
struct sst_block *block);
int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx,
struct sst_block *block);
int sst_create_ipc_msg(struct ipc_post **arg, bool large);
int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id);
void sst_clean_stream(struct stream_info *stream);
int intel_sst_register_compress(struct intel_sst_drv *sst);
int intel_sst_remove_compress(struct intel_sst_drv *sst);
void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id);
int sst_send_sync_msg(int ipc, int str_id);
int sst_get_num_channel(struct snd_sst_params *str_param);
int sst_get_sfreq(struct snd_sst_params *str_param);
int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params);
void sst_restore_fw_context(void);
struct sst_block *sst_create_block(struct intel_sst_drv *ctx,
u32 msg_id, u32 drv_id);
int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large,
struct intel_sst_drv *sst_drv_ctx, struct sst_block **block,
u32 msg_id, u32 drv_id);
int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed);
int sst_wake_up_block(struct intel_sst_drv *ctx, int result,
u32 drv_id, u32 ipc, void *data, u32 size);
int sst_request_firmware_async(struct intel_sst_drv *ctx);
int sst_driver_ops(struct intel_sst_drv *sst);
struct sst_platform_info *sst_get_acpi_driver_data(const char *hid);
void sst_firmware_load_cb(const struct firmware *fw, void *context);
int sst_prepare_and_post_msg(struct intel_sst_drv *sst,
int task_id, int ipc_msg, int cmd_id, int pipe_id,
size_t mbox_data_len, const void *mbox_data, void **data,
bool large, bool fill_dsp, bool sync, bool response);
void sst_process_pending_msg(struct work_struct *work);
int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx);
void sst_init_stream(struct stream_info *stream,
int codec, int sst_id, int ops, u8 slot);
int sst_validate_strid(struct intel_sst_drv *sst_drv_ctx, int str_id);
struct stream_info *get_stream_info(struct intel_sst_drv *sst_drv_ctx,
int str_id);
int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx,
u32 pipe_id);
u32 relocate_imr_addr_mrfld(u32 base_addr);
void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst,
struct ipc_post *msg);
int sst_pm_runtime_put(struct intel_sst_drv *sst_drv);
int sst_shim_write(void __iomem *addr, int offset, int value);
u32 sst_shim_read(void __iomem *addr, int offset);
u64 sst_reg_read64(void __iomem *addr, int offset);
int sst_shim_write64(void __iomem *addr, int offset, u64 value);
u64 sst_shim_read64(void __iomem *addr, int offset);
void sst_set_fw_state_locked(
struct intel_sst_drv *sst_drv_ctx, int sst_state);
void sst_fill_header_mrfld(union ipc_header_mrfld *header,
int msg, int task_id, int large, int drv_id);
void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg,
int pipe_id, int len);
int sst_register(struct device *);
int sst_unregister(struct device *);
int sst_alloc_drv_context(struct intel_sst_drv **ctx,
struct device *dev, unsigned int dev_id);
int sst_context_init(struct intel_sst_drv *ctx);
void sst_context_cleanup(struct intel_sst_drv *ctx);
void sst_configure_runtime_pm(struct intel_sst_drv *ctx);
#endif
/*
* sst_acpi.c - SST (LPE) driver init file for ACPI enumeration.
*
* Copyright (c) 2013, Intel Corporation.
*
* Authors: Ramesh Babu K V <Ramesh.Babu@intel.com>
* Authors: Omair Mohammed Abdullah <omair.m.abdullah@intel.com>
*
* 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/fs.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/acpi.h>
#include <asm/platform_sst_audio.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <acpi/acbuffer.h>
#include <acpi/platform/acenv.h>
#include <acpi/platform/aclinux.h>
#include <acpi/actypes.h>
#include <acpi/acpi_bus.h>
#include "../sst-mfld-platform.h"
#include "../sst-dsp.h"
#include "sst.h"
struct sst_machines {
char codec_id[32];
char board[32];
char machine[32];
void (*machine_quirk)(void);
char firmware[32];
struct sst_platform_info *pdata;
};
/* LPE viewpoint addresses */
#define SST_BYT_IRAM_PHY_START 0xff2c0000
#define SST_BYT_IRAM_PHY_END 0xff2d4000
#define SST_BYT_DRAM_PHY_START 0xff300000
#define SST_BYT_DRAM_PHY_END 0xff320000
#define SST_BYT_IMR_VIRT_START 0xc0000000 /* virtual addr in LPE */
#define SST_BYT_IMR_VIRT_END 0xc01fffff
#define SST_BYT_SHIM_PHY_ADDR 0xff340000
#define SST_BYT_MBOX_PHY_ADDR 0xff344000
#define SST_BYT_DMA0_PHY_ADDR 0xff298000
#define SST_BYT_DMA1_PHY_ADDR 0xff29c000
#define SST_BYT_SSP0_PHY_ADDR 0xff2a0000
#define SST_BYT_SSP2_PHY_ADDR 0xff2a2000
#define BYT_FW_MOD_TABLE_OFFSET 0x80000
#define BYT_FW_MOD_TABLE_SIZE 0x100
#define BYT_FW_MOD_OFFSET (BYT_FW_MOD_TABLE_OFFSET + BYT_FW_MOD_TABLE_SIZE)
static const struct sst_info byt_fwparse_info = {
.use_elf = false,
.max_streams = 25,
.iram_start = SST_BYT_IRAM_PHY_START,
.iram_end = SST_BYT_IRAM_PHY_END,
.iram_use = true,
.dram_start = SST_BYT_DRAM_PHY_START,
.dram_end = SST_BYT_DRAM_PHY_END,
.dram_use = true,
.imr_start = SST_BYT_IMR_VIRT_START,
.imr_end = SST_BYT_IMR_VIRT_END,
.imr_use = true,
.mailbox_start = SST_BYT_MBOX_PHY_ADDR,
.num_probes = 0,
.lpe_viewpt_rqd = true,
};
static const struct sst_ipc_info byt_ipc_info = {
.ipc_offset = 0,
.mbox_recv_off = 0x400,
};
static const struct sst_lib_dnld_info byt_lib_dnld_info = {
.mod_base = SST_BYT_IMR_VIRT_START,
.mod_end = SST_BYT_IMR_VIRT_END,
.mod_table_offset = BYT_FW_MOD_TABLE_OFFSET,
.mod_table_size = BYT_FW_MOD_TABLE_SIZE,
.mod_ddr_dnld = false,
};
static const struct sst_res_info byt_rvp_res_info = {
.shim_offset = 0x140000,
.shim_size = 0x000100,
.shim_phy_addr = SST_BYT_SHIM_PHY_ADDR,
.ssp0_offset = 0xa0000,
.ssp0_size = 0x1000,
.dma0_offset = 0x98000,
.dma0_size = 0x4000,
.dma1_offset = 0x9c000,
.dma1_size = 0x4000,
.iram_offset = 0x0c0000,
.iram_size = 0x14000,
.dram_offset = 0x100000,
.dram_size = 0x28000,
.mbox_offset = 0x144000,
.mbox_size = 0x1000,
.acpi_lpe_res_index = 0,
.acpi_ddr_index = 2,
.acpi_ipc_irq_index = 5,
};
static struct sst_platform_info byt_rvp_platform_data = {
.probe_data = &byt_fwparse_info,
.ipc_info = &byt_ipc_info,
.lib_info = &byt_lib_dnld_info,
.res_info = &byt_rvp_res_info,
.platform = "sst-mfld-platform",
};
/* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail,
* so pdata is same as Baytrail.
*/
static struct sst_platform_info chv_platform_data = {
.probe_data = &byt_fwparse_info,
.ipc_info = &byt_ipc_info,
.lib_info = &byt_lib_dnld_info,
.res_info = &byt_rvp_res_info,
.platform = "sst-mfld-platform",
};
static int sst_platform_get_resources(struct intel_sst_drv *ctx)
{
struct resource *rsrc;
struct platform_device *pdev = to_platform_device(ctx->dev);
/* All ACPI resource request here */
/* Get Shim addr */
rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
ctx->pdata->res_info->acpi_lpe_res_index);
if (!rsrc) {
dev_err(ctx->dev, "Invalid SHIM base from IFWI");
return -EIO;
}
dev_info(ctx->dev, "LPE base: %#x size:%#x", (unsigned int) rsrc->start,
(unsigned int)resource_size(rsrc));
ctx->iram_base = rsrc->start + ctx->pdata->res_info->iram_offset;
ctx->iram_end = ctx->iram_base + ctx->pdata->res_info->iram_size - 1;
dev_info(ctx->dev, "IRAM base: %#x", ctx->iram_base);
ctx->iram = devm_ioremap_nocache(ctx->dev, ctx->iram_base,
ctx->pdata->res_info->iram_size);
if (!ctx->iram) {
dev_err(ctx->dev, "unable to map IRAM");
return -EIO;
}
ctx->dram_base = rsrc->start + ctx->pdata->res_info->dram_offset;
ctx->dram_end = ctx->dram_base + ctx->pdata->res_info->dram_size - 1;
dev_info(ctx->dev, "DRAM base: %#x", ctx->dram_base);
ctx->dram = devm_ioremap_nocache(ctx->dev, ctx->dram_base,
ctx->pdata->res_info->dram_size);
if (!ctx->dram) {
dev_err(ctx->dev, "unable to map DRAM");
return -EIO;
}
ctx->shim_phy_add = rsrc->start + ctx->pdata->res_info->shim_offset;
dev_info(ctx->dev, "SHIM base: %#x", ctx->shim_phy_add);
ctx->shim = devm_ioremap_nocache(ctx->dev, ctx->shim_phy_add,
ctx->pdata->res_info->shim_size);
if (!ctx->shim) {
dev_err(ctx->dev, "unable to map SHIM");
return -EIO;
}
/* reassign physical address to LPE viewpoint address */
ctx->shim_phy_add = ctx->pdata->res_info->shim_phy_addr;
/* Get mailbox addr */
ctx->mailbox_add = rsrc->start + ctx->pdata->res_info->mbox_offset;
dev_info(ctx->dev, "Mailbox base: %#x", ctx->mailbox_add);
ctx->mailbox = devm_ioremap_nocache(ctx->dev, ctx->mailbox_add,
ctx->pdata->res_info->mbox_size);
if (!ctx->mailbox) {
dev_err(ctx->dev, "unable to map mailbox");
return -EIO;
}
/* reassign physical address to LPE viewpoint address */
ctx->mailbox_add = ctx->info.mailbox_start;
rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
ctx->pdata->res_info->acpi_ddr_index);
if (!rsrc) {
dev_err(ctx->dev, "Invalid DDR base from IFWI");
return -EIO;
}
ctx->ddr_base = rsrc->start;
ctx->ddr_end = rsrc->end;
dev_info(ctx->dev, "DDR base: %#x", ctx->ddr_base);
ctx->ddr = devm_ioremap_nocache(ctx->dev, ctx->ddr_base,
resource_size(rsrc));
if (!ctx->ddr) {
dev_err(ctx->dev, "unable to map DDR");
return -EIO;
}
/* Find the IRQ */
ctx->irq_num = platform_get_irq(pdev,
ctx->pdata->res_info->acpi_ipc_irq_index);
return 0;
}
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_machines *sst_acpi_find_machine(
struct sst_machines *machines)
{
struct sst_machines *mach;
bool found = false;
for (mach = machines; mach->codec_id; mach++)
if (ACPI_SUCCESS(acpi_get_devices(mach->codec_id,
sst_acpi_mach_match,
&found, NULL)) && found)
return mach;
return NULL;
}
int sst_acpi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0;
struct intel_sst_drv *ctx;
const struct acpi_device_id *id;
struct sst_machines *mach;
struct platform_device *mdev;
struct platform_device *plat_dev;
unsigned int dev_id;
id = acpi_match_device(dev->driver->acpi_match_table, dev);
if (!id)
return -ENODEV;
dev_dbg(dev, "for %s", id->id);
mach = (struct sst_machines *)id->driver_data;
mach = sst_acpi_find_machine(mach);
if (mach == NULL) {
dev_err(dev, "No matching machine driver found\n");
return -ENODEV;
}
ret = kstrtouint(id->id, 16, &dev_id);
if (ret < 0) {
dev_err(dev, "Unique device id conversion error: %d\n", ret);
return ret;
}
dev_dbg(dev, "ACPI device id: %x\n", dev_id);
plat_dev = platform_device_register_data(dev, mach->pdata->platform, -1, NULL, 0);
if (plat_dev == NULL) {
dev_err(dev, "Failed to create machine device: %s\n", mach->pdata->platform);
return -ENODEV;
}
/* Create platform device for sst machine driver */
mdev = platform_device_register_data(dev, mach->machine, -1, NULL, 0);
if (mdev == NULL) {
dev_err(dev, "Failed to create machine device: %s\n", mach->machine);
return -ENODEV;
}
ret = sst_alloc_drv_context(&ctx, dev, dev_id);
if (ret < 0)
return ret;
/* Fill sst platform data */
ctx->pdata = mach->pdata;
strcpy(ctx->firmware_name, mach->firmware);
ret = sst_platform_get_resources(ctx);
if (ret)
return ret;
ret = sst_context_init(ctx);
if (ret < 0)
return ret;
/* need to save shim registers in BYT */
ctx->shim_regs64 = devm_kzalloc(ctx->dev, sizeof(*ctx->shim_regs64),
GFP_KERNEL);
if (!ctx->shim_regs64) {
return -ENOMEM;
goto do_sst_cleanup;
}
sst_configure_runtime_pm(ctx);
platform_set_drvdata(pdev, ctx);
return ret;
do_sst_cleanup:
sst_context_cleanup(ctx);
platform_set_drvdata(pdev, NULL);
dev_err(ctx->dev, "failed with %d\n", ret);
return ret;
}
/**
* intel_sst_remove - remove function
*
* @pdev: platform device structure
*
* This function is called by OS when a device is unloaded
* This frees the interrupt etc
*/
int sst_acpi_remove(struct platform_device *pdev)
{
struct intel_sst_drv *ctx;
ctx = platform_get_drvdata(pdev);
sst_context_cleanup(ctx);
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct sst_machines sst_acpi_bytcr[] = {
{"10EC5640", "T100", "bytt100_rt5640", NULL, "fw_sst_0f28.bin",
&byt_rvp_platform_data },
{},
};
/* Cherryview-based platforms: CherryTrail and Braswell */
static struct sst_machines sst_acpi_chv[] = {
{"10EC5670", "cht-bsw", "cht-bsw-rt5672", NULL, "fw_sst_22a8.bin",
&chv_platform_data },
{},
};
static const struct acpi_device_id sst_acpi_ids[] = {
{ "80860F28", (unsigned long)&sst_acpi_bytcr},
{ "808622A8", (unsigned long) &sst_acpi_chv},
{ },
};
MODULE_DEVICE_TABLE(acpi, sst_acpi_ids);
static struct platform_driver sst_acpi_driver = {
.driver = {
.name = "intel_sst_acpi",
.owner = THIS_MODULE,
.acpi_match_table = ACPI_PTR(sst_acpi_ids),
.pm = &intel_sst_pm,
},
.probe = sst_acpi_probe,
.remove = sst_acpi_remove,
};
module_platform_driver(sst_acpi_driver);
MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine ACPI Driver");
MODULE_AUTHOR("Ramesh Babu K V");
MODULE_AUTHOR("Omair Mohammed Abdullah");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("sst");
/*
* sst_drv_interface.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/math64.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
#include "../sst-dsp.h"
#define NUM_CODEC 2
#define MIN_FRAGMENT 2
#define MAX_FRAGMENT 4
#define MIN_FRAGMENT_SIZE (50 * 1024)
#define MAX_FRAGMENT_SIZE (1024 * 1024)
#define SST_GET_BYTES_PER_SAMPLE(pcm_wd_sz) (((pcm_wd_sz + 15) >> 4) << 1)
int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id)
{
struct stream_info *stream;
int ret = 0;
stream = get_stream_info(ctx, str_id);
if (stream) {
/* str_id is valid, so stream is alloacted */
ret = sst_free_stream(ctx, str_id);
if (ret)
sst_clean_stream(&ctx->streams[str_id]);
return ret;
} else {
dev_err(ctx->dev, "we tried to free stream context %d which was freed!!!\n", str_id);
}
return ret;
}
int sst_get_stream_allocated(struct intel_sst_drv *ctx,
struct snd_sst_params *str_param,
struct snd_sst_lib_download **lib_dnld)
{
int retval;
retval = ctx->ops->alloc_stream(ctx, str_param);
if (retval > 0)
dev_dbg(ctx->dev, "Stream allocated %d\n", retval);
return retval;
}
/*
* sst_get_sfreq - this function returns the frequency of the stream
*
* @str_param : stream params
*/
int sst_get_sfreq(struct snd_sst_params *str_param)
{
switch (str_param->codec) {
case SST_CODEC_TYPE_PCM:
return str_param->sparams.uc.pcm_params.sfreq;
case SST_CODEC_TYPE_AAC:
return str_param->sparams.uc.aac_params.externalsr;
case SST_CODEC_TYPE_MP3:
return 0;
default:
return -EINVAL;
}
}
/*
* sst_get_num_channel - get number of channels for the stream
*
* @str_param : stream params
*/
int sst_get_num_channel(struct snd_sst_params *str_param)
{
switch (str_param->codec) {
case SST_CODEC_TYPE_PCM:
return str_param->sparams.uc.pcm_params.num_chan;
case SST_CODEC_TYPE_MP3:
return str_param->sparams.uc.mp3_params.num_chan;
case SST_CODEC_TYPE_AAC:
return str_param->sparams.uc.aac_params.num_chan;
default:
return -EINVAL;
}
}
/*
* sst_get_stream - this function prepares for stream allocation
*
* @str_param : stream param
*/
int sst_get_stream(struct intel_sst_drv *ctx,
struct snd_sst_params *str_param)
{
int retval;
struct stream_info *str_info;
/* stream is not allocated, we are allocating */
retval = ctx->ops->alloc_stream(ctx, str_param);
if (retval <= 0) {
return -EIO;
}
/* store sampling freq */
str_info = &ctx->streams[retval];
str_info->sfreq = sst_get_sfreq(str_param);
return retval;
}
static int sst_power_control(struct device *dev, bool state)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
dev_dbg(ctx->dev, "state:%d", state);
if (state == true)
return pm_runtime_get_sync(dev);
else
return sst_pm_runtime_put(ctx);
}
/*
* sst_open_pcm_stream - Open PCM interface
*
* @str_param: parameters of pcm stream
*
* This function is called by MID sound card driver to open
* a new pcm interface
*/
static int sst_open_pcm_stream(struct device *dev,
struct snd_sst_params *str_param)
{
int retval;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (!str_param)
return -EINVAL;
retval = sst_get_stream(ctx, str_param);
if (retval > 0)
ctx->stream_cnt++;
else
dev_err(ctx->dev, "sst_get_stream returned err %d\n", retval);
return retval;
}
static int sst_cdev_open(struct device *dev,
struct snd_sst_params *str_params, struct sst_compress_cb *cb)
{
int str_id, retval;
struct stream_info *stream;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
retval = pm_runtime_get_sync(ctx->dev);
if (retval < 0)
return retval;
str_id = sst_get_stream(ctx, str_params);
if (str_id > 0) {
dev_dbg(dev, "stream allocated in sst_cdev_open %d\n", str_id);
stream = &ctx->streams[str_id];
stream->compr_cb = cb->compr_cb;
stream->compr_cb_param = cb->param;
stream->drain_notify = cb->drain_notify;
stream->drain_cb_param = cb->drain_cb_param;
} else {
dev_err(dev, "stream encountered error during alloc %d\n", str_id);
str_id = -EINVAL;
sst_pm_runtime_put(ctx);
}
return str_id;
}
static int sst_cdev_close(struct device *dev, unsigned int str_id)
{
int retval;
struct stream_info *stream;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
stream = get_stream_info(ctx, str_id);
if (!stream) {
dev_err(dev, "stream info is NULL for str %d!!!\n", str_id);
return -EINVAL;
}
if (stream->status == STREAM_RESET) {
dev_dbg(dev, "stream in reset state...\n");
stream->status = STREAM_UN_INIT;
retval = 0;
goto put;
}
retval = sst_free_stream(ctx, str_id);
put:
stream->compr_cb_param = NULL;
stream->compr_cb = NULL;
if (retval)
dev_err(dev, "free stream returned err %d\n", retval);
dev_dbg(dev, "End\n");
return retval;
}
static int sst_cdev_ack(struct device *dev, unsigned int str_id,
unsigned long bytes)
{
struct stream_info *stream;
struct snd_sst_tstamp fw_tstamp = {0,};
int offset;
void __iomem *addr;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
stream = get_stream_info(ctx, str_id);
if (!stream)
return -EINVAL;
/* update bytes sent */
stream->cumm_bytes += bytes;
dev_dbg(dev, "bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes);
memcpy_fromio(&fw_tstamp,
((void *)(ctx->mailbox + ctx->tstamp)
+(str_id * sizeof(fw_tstamp))),
sizeof(fw_tstamp));
fw_tstamp.bytes_copied = stream->cumm_bytes;
dev_dbg(dev, "bytes sent to fw %llu inc by %ld\n",
fw_tstamp.bytes_copied, bytes);
addr = ((void *)(ctx->mailbox + ctx->tstamp)) +
(str_id * sizeof(fw_tstamp));
offset = offsetof(struct snd_sst_tstamp, bytes_copied);
sst_shim_write(addr, offset, fw_tstamp.bytes_copied);
return 0;
}
static int sst_cdev_set_metadata(struct device *dev,
unsigned int str_id, struct snd_compr_metadata *metadata)
{
int retval = 0;
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
dev_dbg(dev, "set metadata for stream %d\n", str_id);
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
dev_dbg(dev, "pipe id = %d\n", str_info->pipe_id);
retval = sst_prepare_and_post_msg(ctx, str_info->task_id, IPC_CMD,
IPC_IA_SET_STREAM_PARAMS_MRFLD, str_info->pipe_id,
sizeof(*metadata), metadata, NULL,
true, true, true, false);
return retval;
}
static int sst_cdev_stream_pause(struct device *dev, unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_pause_stream(ctx, str_id);
}
static int sst_cdev_stream_pause_release(struct device *dev,
unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_resume_stream(ctx, str_id);
}
static int sst_cdev_stream_start(struct device *dev, unsigned int str_id)
{
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
str_info->prev = str_info->status;
str_info->status = STREAM_RUNNING;
return sst_start_stream(ctx, str_id);
}
static int sst_cdev_stream_drop(struct device *dev, unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_drop_stream(ctx, str_id);
}
static int sst_cdev_stream_drain(struct device *dev, unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_drain_stream(ctx, str_id, false);
}
static int sst_cdev_stream_partial_drain(struct device *dev,
unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_drain_stream(ctx, str_id, true);
}
static int sst_cdev_tstamp(struct device *dev, unsigned int str_id,
struct snd_compr_tstamp *tstamp)
{
struct snd_sst_tstamp fw_tstamp = {0,};
struct stream_info *stream;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
memcpy_fromio(&fw_tstamp,
((void *)(ctx->mailbox + ctx->tstamp)
+(str_id * sizeof(fw_tstamp))),
sizeof(fw_tstamp));
stream = get_stream_info(ctx, str_id);
if (!stream)
return -EINVAL;
dev_dbg(dev, "rb_counter %llu in bytes\n", fw_tstamp.ring_buffer_counter);
tstamp->copied_total = fw_tstamp.ring_buffer_counter;
tstamp->pcm_frames = fw_tstamp.frames_decoded;
tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter,
(u64)((stream->num_ch) * SST_GET_BYTES_PER_SAMPLE(24)));
tstamp->sampling_rate = fw_tstamp.sampling_frequency;
dev_dbg(dev, "PCM = %u\n", tstamp->pcm_io_frames);
dev_dbg(dev, "Ptr Query on strid = %d copied_total %d, decodec %d\n",
str_id, tstamp->copied_total, tstamp->pcm_frames);
dev_dbg(dev, "rendered %d\n", tstamp->pcm_io_frames);
return 0;
}
static int sst_cdev_caps(struct snd_compr_caps *caps)
{
caps->num_codecs = NUM_CODEC;
caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */
caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */
caps->min_fragments = MIN_FRAGMENT;
caps->max_fragments = MAX_FRAGMENT;
caps->codecs[0] = SND_AUDIOCODEC_MP3;
caps->codecs[1] = SND_AUDIOCODEC_AAC;
return 0;
}
static struct snd_compr_codec_caps caps_mp3 = {
.num_descriptors = 1,
.descriptor[0].max_ch = 2,
.descriptor[0].sample_rates[0] = 48000,
.descriptor[0].sample_rates[1] = 44100,
.descriptor[0].sample_rates[2] = 32000,
.descriptor[0].sample_rates[3] = 16000,
.descriptor[0].sample_rates[4] = 8000,
.descriptor[0].num_sample_rates = 5,
.descriptor[0].bit_rate[0] = 320,
.descriptor[0].bit_rate[1] = 192,
.descriptor[0].num_bitrates = 2,
.descriptor[0].profiles = 0,
.descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO,
.descriptor[0].formats = 0,
};
static struct snd_compr_codec_caps caps_aac = {
.num_descriptors = 2,
.descriptor[1].max_ch = 2,
.descriptor[0].sample_rates[0] = 48000,
.descriptor[0].sample_rates[1] = 44100,
.descriptor[0].sample_rates[2] = 32000,
.descriptor[0].sample_rates[3] = 16000,
.descriptor[0].sample_rates[4] = 8000,
.descriptor[0].num_sample_rates = 5,
.descriptor[1].bit_rate[0] = 320,
.descriptor[1].bit_rate[1] = 192,
.descriptor[1].num_bitrates = 2,
.descriptor[1].profiles = 0,
.descriptor[1].modes = 0,
.descriptor[1].formats =
(SND_AUDIOSTREAMFORMAT_MP4ADTS |
SND_AUDIOSTREAMFORMAT_RAW),
};
static int sst_cdev_codec_caps(struct snd_compr_codec_caps *codec)
{
if (codec->codec == SND_AUDIOCODEC_MP3)
*codec = caps_mp3;
else if (codec->codec == SND_AUDIOCODEC_AAC)
*codec = caps_aac;
else
return -EINVAL;
return 0;
}
void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id)
{
struct stream_info *stream;
dev_dbg(ctx->dev, "fragment elapsed from firmware for str_id %d\n",
str_id);
stream = &ctx->streams[str_id];
if (stream->compr_cb)
stream->compr_cb(stream->compr_cb_param);
}
/*
* sst_close_pcm_stream - Close PCM interface
*
* @str_id: stream id to be closed
*
* This function is called by MID sound card driver to close
* an existing pcm interface
*/
static int sst_close_pcm_stream(struct device *dev, unsigned int str_id)
{
struct stream_info *stream;
int retval = 0;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
stream = get_stream_info(ctx, str_id);
if (!stream) {
dev_err(ctx->dev, "stream info is NULL for str %d!!!\n", str_id);
return -EINVAL;
}
if (stream->status == STREAM_RESET) {
/* silently fail here as we have cleaned the stream earlier */
dev_dbg(ctx->dev, "stream in reset state...\n");
retval = 0;
goto put;
}
retval = free_stream_context(ctx, str_id);
put:
stream->pcm_substream = NULL;
stream->status = STREAM_UN_INIT;
stream->period_elapsed = NULL;
ctx->stream_cnt--;
if (retval)
dev_err(ctx->dev, "free stream returned err %d\n", retval);
dev_dbg(ctx->dev, "Exit\n");
return 0;
}
static inline int sst_calc_tstamp(struct intel_sst_drv *ctx,
struct pcm_stream_info *info,
struct snd_pcm_substream *substream,
struct snd_sst_tstamp *fw_tstamp)
{
size_t delay_bytes, delay_frames;
size_t buffer_sz;
u32 pointer_bytes, pointer_samples;
dev_dbg(ctx->dev, "mrfld ring_buffer_counter %llu in bytes\n",
fw_tstamp->ring_buffer_counter);
dev_dbg(ctx->dev, "mrfld hardware_counter %llu in bytes\n",
fw_tstamp->hardware_counter);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
delay_bytes = (size_t) (fw_tstamp->ring_buffer_counter -
fw_tstamp->hardware_counter);
else
delay_bytes = (size_t) (fw_tstamp->hardware_counter -
fw_tstamp->ring_buffer_counter);
delay_frames = bytes_to_frames(substream->runtime, delay_bytes);
buffer_sz = snd_pcm_lib_buffer_bytes(substream);
div_u64_rem(fw_tstamp->ring_buffer_counter, buffer_sz, &pointer_bytes);
pointer_samples = bytes_to_samples(substream->runtime, pointer_bytes);
dev_dbg(ctx->dev, "pcm delay %zu in bytes\n", delay_bytes);
info->buffer_ptr = pointer_samples / substream->runtime->channels;
info->pcm_delay = delay_frames / substream->runtime->channels;
dev_dbg(ctx->dev, "buffer ptr %llu pcm_delay rep: %llu\n",
info->buffer_ptr, info->pcm_delay);
return 0;
}
static int sst_read_timestamp(struct device *dev, struct pcm_stream_info *info)
{
struct stream_info *stream;
struct snd_pcm_substream *substream;
struct snd_sst_tstamp fw_tstamp;
unsigned int str_id;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
str_id = info->str_id;
stream = get_stream_info(ctx, str_id);
if (!stream)
return -EINVAL;
if (!stream->pcm_substream)
return -EINVAL;
substream = stream->pcm_substream;
memcpy_fromio(&fw_tstamp,
((void *)(ctx->mailbox + ctx->tstamp)
+ (str_id * sizeof(fw_tstamp))),
sizeof(fw_tstamp));
return sst_calc_tstamp(ctx, info, substream, &fw_tstamp);
}
static int sst_stream_start(struct device *dev, int str_id)
{
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state != SST_FW_RUNNING)
return 0;
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
str_info->prev = str_info->status;
str_info->status = STREAM_RUNNING;
sst_start_stream(ctx, str_id);
return 0;
}
static int sst_stream_drop(struct device *dev, int str_id)
{
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state != SST_FW_RUNNING)
return 0;
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
str_info->prev = STREAM_UN_INIT;
str_info->status = STREAM_INIT;
return sst_drop_stream(ctx, str_id);
}
static int sst_stream_init(struct device *dev, struct pcm_stream_info *str_info)
{
int str_id = 0;
struct stream_info *stream;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
str_id = str_info->str_id;
if (ctx->sst_state != SST_FW_RUNNING)
return 0;
stream = get_stream_info(ctx, str_id);
if (!stream)
return -EINVAL;
dev_dbg(ctx->dev, "setting the period ptrs\n");
stream->pcm_substream = str_info->arg;
stream->period_elapsed = str_info->period_elapsed;
stream->sfreq = str_info->sfreq;
stream->prev = stream->status;
stream->status = STREAM_INIT;
dev_dbg(ctx->dev,
"pcm_substream %p, period_elapsed %p, sfreq %d, status %d\n",
stream->pcm_substream, stream->period_elapsed,
stream->sfreq, stream->status);
return 0;
}
/*
* sst_set_byte_stream - Set generic params
*
* @cmd: control cmd to be set
* @arg: command argument
*
* This function is called by MID sound card driver to configure
* SST runtime params.
*/
static int sst_send_byte_stream(struct device *dev,
struct snd_sst_bytes_v2 *bytes)
{
int ret_val = 0;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (NULL == bytes)
return -EINVAL;
ret_val = pm_runtime_get_sync(ctx->dev);
if (ret_val < 0)
return ret_val;
ret_val = sst_send_byte_stream_mrfld(ctx, bytes);
sst_pm_runtime_put(ctx);
return ret_val;
}
static struct sst_ops pcm_ops = {
.open = sst_open_pcm_stream,
.stream_init = sst_stream_init,
.stream_start = sst_stream_start,
.stream_drop = sst_stream_drop,
.stream_read_tstamp = sst_read_timestamp,
.send_byte_stream = sst_send_byte_stream,
.close = sst_close_pcm_stream,
.power = sst_power_control,
};
static struct compress_sst_ops compr_ops = {
.open = sst_cdev_open,
.close = sst_cdev_close,
.stream_pause = sst_cdev_stream_pause,
.stream_pause_release = sst_cdev_stream_pause_release,
.stream_start = sst_cdev_stream_start,
.stream_drop = sst_cdev_stream_drop,
.stream_drain = sst_cdev_stream_drain,
.stream_partial_drain = sst_cdev_stream_partial_drain,
.tstamp = sst_cdev_tstamp,
.ack = sst_cdev_ack,
.get_caps = sst_cdev_caps,
.get_codec_caps = sst_cdev_codec_caps,
.set_metadata = sst_cdev_set_metadata,
.power = sst_power_control,
};
static struct sst_device sst_dsp_device = {
.name = "Intel(R) SST LPE",
.dev = NULL,
.ops = &pcm_ops,
.compr_ops = &compr_ops,
};
/*
* sst_register - function to register DSP
*
* This functions registers DSP with the platform driver
*/
int sst_register(struct device *dev)
{
int ret_val;
sst_dsp_device.dev = dev;
ret_val = sst_register_dsp(&sst_dsp_device);
if (ret_val)
dev_err(dev, "Unable to register DSP with platform driver\n");
return ret_val;
}
int sst_unregister(struct device *dev)
{
return sst_unregister_dsp(&sst_dsp_device);
}
/*
* sst_ipc.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corporation
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/pci.h>
#include <linux/firmware.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/intel-mid.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
#include "../sst-dsp.h"
struct sst_block *sst_create_block(struct intel_sst_drv *ctx,
u32 msg_id, u32 drv_id)
{
struct sst_block *msg = NULL;
dev_dbg(ctx->dev, "Enter\n");
msg = kzalloc(sizeof(*msg), GFP_KERNEL);
if (!msg)
return NULL;
msg->condition = false;
msg->on = true;
msg->msg_id = msg_id;
msg->drv_id = drv_id;
spin_lock_bh(&ctx->block_lock);
list_add_tail(&msg->node, &ctx->block_list);
spin_unlock_bh(&ctx->block_lock);
return msg;
}
/*
* while handling the interrupts, we need to check for message status and
* then if we are blocking for a message
*
* here we are unblocking the blocked ones, this is based on id we have
* passed and search that for block threads.
* We will not find block in two cases
* a) when its small message and block in not there, so silently ignore
* them
* b) when we are actually not able to find the block (bug perhaps)
*
* Since we have bit of small messages we can spam kernel log with err
* print on above so need to keep as debug prints which should be enabled
* via dynamic debug while debugging IPC issues
*/
int sst_wake_up_block(struct intel_sst_drv *ctx, int result,
u32 drv_id, u32 ipc, void *data, u32 size)
{
struct sst_block *block = NULL;
dev_dbg(ctx->dev, "Enter\n");
spin_lock_bh(&ctx->block_lock);
list_for_each_entry(block, &ctx->block_list, node) {
dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id,
block->drv_id);
if (block->msg_id == ipc && block->drv_id == drv_id) {
dev_dbg(ctx->dev, "free up the block\n");
block->ret_code = result;
block->data = data;
block->size = size;
block->condition = true;
spin_unlock_bh(&ctx->block_lock);
wake_up(&ctx->wait_queue);
return 0;
}
}
spin_unlock_bh(&ctx->block_lock);
dev_dbg(ctx->dev,
"Block not found or a response received for a short msg for ipc %d, drv_id %d\n",
ipc, drv_id);
return -EINVAL;
}
int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed)
{
struct sst_block *block = NULL, *__block;
dev_dbg(ctx->dev, "Enter\n");
spin_lock_bh(&ctx->block_lock);
list_for_each_entry_safe(block, __block, &ctx->block_list, node) {
if (block == freed) {
pr_debug("pvt_id freed --> %d\n", freed->drv_id);
/* toggle the index position of pvt_id */
list_del(&freed->node);
spin_unlock_bh(&ctx->block_lock);
kfree(freed->data);
freed->data = NULL;
kfree(freed);
return 0;
}
}
spin_unlock_bh(&ctx->block_lock);
dev_err(ctx->dev, "block is already freed!!!\n");
return -EINVAL;
}
int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx,
struct ipc_post *ipc_msg, bool sync)
{
struct ipc_post *msg = ipc_msg;
union ipc_header_mrfld header;
unsigned int loop_count = 0;
int retval = 0;
unsigned long irq_flags;
dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync);
spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags);
header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
if (sync) {
while (header.p.header_high.part.busy) {
if (loop_count > 25) {
dev_err(sst_drv_ctx->dev,
"sst: Busy wait failed, cant send this msg\n");
retval = -EBUSY;
goto out;
}
cpu_relax();
loop_count++;
header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
}
} else {
if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) {
/* queue is empty, nothing to send */
spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
dev_dbg(sst_drv_ctx->dev,
"Empty msg queue... NO Action\n");
return 0;
}
if (header.p.header_high.part.busy) {
spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n");
return 0;
}
/* copy msg from list */
msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next,
struct ipc_post, node);
list_del(&msg->node);
}
dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n",
msg->mrfld_header.p.header_high.full);
dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n",
msg->mrfld_header.p.header_low_payload);
if (msg->mrfld_header.p.header_high.part.large)
memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND,
msg->mailbox_data,
msg->mrfld_header.p.header_low_payload);
sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full);
out:
spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
kfree(msg->mailbox_data);
kfree(msg);
return retval;
}
void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx)
{
union interrupt_reg_mrfld isr;
union interrupt_reg_mrfld imr;
union ipc_header_mrfld clear_ipc;
unsigned long irq_flags;
spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags);
imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX);
isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX);
/* write 1 to clear*/
isr.part.busy_interrupt = 1;
sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full);
/* Set IA done bit */
clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD);
clear_ipc.p.header_high.part.busy = 0;
clear_ipc.p.header_high.part.done = 1;
clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS;
sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full);
/* un mask busy interrupt */
imr.part.busy_interrupt = 0;
sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full);
spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
}
/*
* process_fw_init - process the FW init msg
*
* @msg: IPC message mailbox data from FW
*
* This function processes the FW init msg from FW
* marks FW state and prints debug info of loaded FW
*/
static void process_fw_init(struct intel_sst_drv *sst_drv_ctx,
void *msg)
{
struct ipc_header_fw_init *init =
(struct ipc_header_fw_init *)msg;
int retval = 0;
dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n");
if (init->result) {
sst_set_fw_state_locked(sst_drv_ctx, SST_RESET);
dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n",
init->result);
retval = init->result;
goto ret;
}
ret:
sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0);
}
static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx,
struct ipc_post *msg)
{
u32 msg_id;
int str_id;
u32 data_size, i;
void *data_offset;
struct stream_info *stream;
union ipc_header_high msg_high;
u32 msg_low, pipe_id;
msg_high = msg->mrfld_header.p.header_high;
msg_low = msg->mrfld_header.p.header_low_payload;
msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id;
data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr));
data_size = msg_low - (sizeof(struct ipc_dsp_hdr));
switch (msg_id) {
case IPC_SST_PERIOD_ELAPSED_MRFLD:
pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
if (str_id > 0) {
dev_dbg(sst_drv_ctx->dev,
"Period elapsed rcvd for pipe id 0x%x\n",
pipe_id);
stream = &sst_drv_ctx->streams[str_id];
if (stream->period_elapsed)
stream->period_elapsed(stream->pcm_substream);
if (stream->compr_cb)
stream->compr_cb(stream->compr_cb_param);
}
break;
case IPC_IA_DRAIN_STREAM_MRFLD:
pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
if (str_id > 0) {
stream = &sst_drv_ctx->streams[str_id];
if (stream->drain_notify)
stream->drain_notify(stream->drain_cb_param);
}
break;
case IPC_IA_FW_ASYNC_ERR_MRFLD:
dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n");
for (i = 0; i < (data_size/4); i++)
print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE,
16, 4, data_offset, data_size, false);
break;
case IPC_IA_FW_INIT_CMPLT_MRFLD:
process_fw_init(sst_drv_ctx, data_offset);
break;
case IPC_IA_BUF_UNDER_RUN_MRFLD:
pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
if (str_id > 0)
dev_err(sst_drv_ctx->dev,
"Buffer under-run for pipe:%#x str_id:%d\n",
pipe_id, str_id);
break;
default:
dev_err(sst_drv_ctx->dev,
"Unrecognized async msg from FW msg_id %#x\n", msg_id);
}
}
void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx,
struct ipc_post *msg)
{
unsigned int drv_id;
void *data;
union ipc_header_high msg_high;
u32 msg_low;
struct ipc_dsp_hdr *dsp_hdr;
unsigned int cmd_id;
msg_high = msg->mrfld_header.p.header_high;
msg_low = msg->mrfld_header.p.header_low_payload;
dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n",
msg->mrfld_header.p.header_high.full,
msg->mrfld_header.p.header_low_payload);
drv_id = msg_high.part.drv_id;
/* Check for async messages first */
if (drv_id == SST_ASYNC_DRV_ID) {
/*FW sent async large message*/
process_fw_async_msg(sst_drv_ctx, msg);
return;
}
/* FW sent short error response for an IPC */
if (msg_high.part.result && drv_id && !msg_high.part.large) {
/* 32-bit FW error code in msg_low */
dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low);
sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
msg_high.part.drv_id,
msg_high.part.msg_id, NULL, 0);
return;
}
/*
* Process all valid responses
* if it is a large message, the payload contains the size to
* copy from mailbox
**/
if (msg_high.part.large) {
data = kzalloc(msg_low, GFP_KERNEL);
if (!data)
return;
memcpy(data, (void *) msg->mailbox_data, msg_low);
/* Copy command id so that we can use to put sst to reset */
dsp_hdr = (struct ipc_dsp_hdr *)data;
cmd_id = dsp_hdr->cmd_id;
dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id);
if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
msg_high.part.drv_id,
msg_high.part.msg_id, data, msg_low))
kfree(data);
} else {
sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
msg_high.part.drv_id,
msg_high.part.msg_id, NULL, 0);
}
}
/*
* sst_dsp.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This file contains all dsp controlling functions like firmware download,
* setting/resetting dsp cores, etc
*/
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/firmware.h>
#include <linux/dmaengine.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
#include "../sst-dsp.h"
static inline void memcpy32_toio(void __iomem *dst, const void *src, int count)
{
/* __iowrite32_copy uses 32-bit count values so divide by 4 for
* right count in words
*/
__iowrite32_copy(dst, src, count/4);
}
/**
* intel_sst_reset_dsp_mrfld - Resetting SST DSP
*
* This resets DSP in case of MRFLD platfroms
*/
int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx)
{
union config_status_reg_mrfld csr;
dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n");
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
csr.full |= 0x7;
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
csr.full &= ~(0x1);
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
return 0;
}
/**
* sst_start_merrifield - Start the SST DSP processor
*
* This starts the DSP in MERRIFIELD platfroms
*/
int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx)
{
union config_status_reg_mrfld csr;
dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n");
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
csr.full |= 0x7;
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
csr.part.xt_snoop = 1;
csr.full &= ~(0x5);
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n",
csr.full);
return 0;
}
static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size,
struct fw_module_header **module, u32 *num_modules)
{
struct sst_fw_header *header;
const void *sst_fw_in_mem = ctx->fw_in_mem;
dev_dbg(ctx->dev, "Enter\n");
/* Read the header information from the data pointer */
header = (struct sst_fw_header *)sst_fw_in_mem;
dev_dbg(ctx->dev,
"header sign=%s size=%x modules=%x fmt=%x size=%zx\n",
header->signature, header->file_size, header->modules,
header->file_format, sizeof(*header));
/* verify FW */
if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) ||
(size != header->file_size + sizeof(*header))) {
/* Invalid FW signature */
dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n");
return -EINVAL;
}
*num_modules = header->modules;
*module = (void *)sst_fw_in_mem + sizeof(*header);
return 0;
}
/*
* sst_fill_memcpy_list - Fill the memcpy list
*
* @memcpy_list: List to be filled
* @destn: Destination addr to be filled in the list
* @src: Source addr to be filled in the list
* @size: Size to be filled in the list
*
* Adds the node to the list after required fields
* are populated in the node
*/
static int sst_fill_memcpy_list(struct list_head *memcpy_list,
void *destn, const void *src, u32 size, bool is_io)
{
struct sst_memcpy_list *listnode;
listnode = kzalloc(sizeof(*listnode), GFP_KERNEL);
if (listnode == NULL)
return -ENOMEM;
listnode->dstn = destn;
listnode->src = src;
listnode->size = size;
listnode->is_io = is_io;
list_add_tail(&listnode->memcpylist, memcpy_list);
return 0;
}
/**
* sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list
*
* @sst_drv_ctx : driver context
* @module : FW module header
* @memcpy_list : Pointer to the list to be populated
* Create the memcpy list as the number of block to be copied
* returns error or 0 if module sizes are proper
*/
static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx,
struct fw_module_header *module, struct list_head *memcpy_list)
{
struct fw_block_info *block;
u32 count;
int ret_val = 0;
void __iomem *ram_iomem;
dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n",
module->signature, module->mod_size,
module->blocks, module->type);
dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point);
block = (void *)module + sizeof(*module);
for (count = 0; count < module->blocks; count++) {
if (block->size <= 0) {
dev_err(sst_drv_ctx->dev, "block size invalid\n");
return -EINVAL;
}
switch (block->type) {
case SST_IRAM:
ram_iomem = sst_drv_ctx->iram;
break;
case SST_DRAM:
ram_iomem = sst_drv_ctx->dram;
break;
case SST_DDR:
ram_iomem = sst_drv_ctx->ddr;
break;
case SST_CUSTOM_INFO:
block = (void *)block + sizeof(*block) + block->size;
continue;
default:
dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n",
block->type, count);
return -EINVAL;
}
ret_val = sst_fill_memcpy_list(memcpy_list,
ram_iomem + block->ram_offset,
(void *)block + sizeof(*block), block->size, 1);
if (ret_val)
return ret_val;
block = (void *)block + sizeof(*block) + block->size;
}
return 0;
}
/**
* sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy
*
* @ctx : pointer to drv context
* @size : size of the firmware
* @fw_list : pointer to list_head to be populated
* This function parses the FW image and saves the parsed image in the list
* for memcpy
*/
static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size,
struct list_head *fw_list)
{
struct fw_module_header *module;
u32 count, num_modules;
int ret_val;
ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules);
if (ret_val)
return ret_val;
for (count = 0; count < num_modules; count++) {
ret_val = sst_parse_module_memcpy(ctx, module, fw_list);
if (ret_val)
return ret_val;
module = (void *)module + sizeof(*module) + module->mod_size;
}
return 0;
}
/**
* sst_do_memcpy - function initiates the memcpy
*
* @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated
*
* Triggers the memcpy
*/
static void sst_do_memcpy(struct list_head *memcpy_list)
{
struct sst_memcpy_list *listnode;
list_for_each_entry(listnode, memcpy_list, memcpylist) {
if (listnode->is_io == true)
memcpy32_toio((void __iomem *)listnode->dstn,
listnode->src, listnode->size);
else
memcpy(listnode->dstn, listnode->src, listnode->size);
}
}
void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx)
{
struct sst_memcpy_list *listnode, *tmplistnode;
/* Free the list */
if (!list_empty(&sst_drv_ctx->memcpy_list)) {
list_for_each_entry_safe(listnode, tmplistnode,
&sst_drv_ctx->memcpy_list, memcpylist) {
list_del(&listnode->memcpylist);
kfree(listnode);
}
}
}
static int sst_cache_and_parse_fw(struct intel_sst_drv *sst,
const struct firmware *fw)
{
int retval = 0;
sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL);
if (!sst->fw_in_mem) {
retval = -ENOMEM;
goto end_release;
}
dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem);
dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem));
memcpy(sst->fw_in_mem, fw->data, fw->size);
retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list);
if (retval) {
dev_err(sst->dev, "Failed to parse fw\n");
kfree(sst->fw_in_mem);
sst->fw_in_mem = NULL;
}
end_release:
release_firmware(fw);
return retval;
}
void sst_firmware_load_cb(const struct firmware *fw, void *context)
{
struct intel_sst_drv *ctx = context;
dev_dbg(ctx->dev, "Enter\n");
if (fw == NULL) {
dev_err(ctx->dev, "request fw failed\n");
return;
}
mutex_lock(&ctx->sst_lock);
if (ctx->sst_state != SST_RESET ||
ctx->fw_in_mem != NULL) {
if (fw != NULL)
release_firmware(fw);
mutex_unlock(&ctx->sst_lock);
return;
}
dev_dbg(ctx->dev, "Request Fw completed\n");
sst_cache_and_parse_fw(ctx, fw);
mutex_unlock(&ctx->sst_lock);
}
/*
* sst_request_fw - requests audio fw from kernel and saves a copy
*
* This function requests the SST FW from the kernel, parses it and
* saves a copy in the driver context
*/
static int sst_request_fw(struct intel_sst_drv *sst)
{
int retval = 0;
const struct firmware *fw;
retval = request_firmware(&fw, sst->firmware_name, sst->dev);
if (fw == NULL) {
dev_err(sst->dev, "fw is returning as null\n");
return -EINVAL;
}
if (retval) {
dev_err(sst->dev, "request fw failed %d\n", retval);
return retval;
}
mutex_lock(&sst->sst_lock);
retval = sst_cache_and_parse_fw(sst, fw);
mutex_unlock(&sst->sst_lock);
return retval;
}
/*
* Writing the DDR physical base to DCCM offset
* so that FW can use it to setup TLB
*/
static void sst_dccm_config_write(void __iomem *dram_base,
unsigned int ddr_base)
{
void __iomem *addr;
u32 bss_reset = 0;
addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET);
memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32));
bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT);
addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET);
memcpy32_toio(addr, &bss_reset, sizeof(u32));
}
void sst_post_download_mrfld(struct intel_sst_drv *ctx)
{
sst_dccm_config_write(ctx->dram, ctx->ddr_base);
dev_dbg(ctx->dev, "config written to DCCM\n");
}
/**
* sst_load_fw - function to load FW into DSP
* Transfers the FW to DSP using dma/memcpy
*/
int sst_load_fw(struct intel_sst_drv *sst_drv_ctx)
{
int ret_val = 0;
struct sst_block *block;
dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n");
if (sst_drv_ctx->sst_state != SST_RESET ||
sst_drv_ctx->sst_state == SST_SHUTDOWN)
return -EAGAIN;
if (!sst_drv_ctx->fw_in_mem) {
dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n");
ret_val = sst_request_fw(sst_drv_ctx);
if (ret_val)
return ret_val;
}
BUG_ON(!sst_drv_ctx->fw_in_mem);
block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID);
if (block == NULL)
return -ENOMEM;
/* Prevent C-states beyond C6 */
pm_qos_update_request(sst_drv_ctx->qos, 0);
sst_drv_ctx->sst_state = SST_FW_LOADING;
ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx);
if (ret_val)
goto restore;
sst_do_memcpy(&sst_drv_ctx->memcpy_list);
/* Write the DRAM/DCCM config before enabling FW */
if (sst_drv_ctx->ops->post_download)
sst_drv_ctx->ops->post_download(sst_drv_ctx);
/* bring sst out of reset */
ret_val = sst_drv_ctx->ops->start(sst_drv_ctx);
if (ret_val)
goto restore;
ret_val = sst_wait_timeout(sst_drv_ctx, block);
if (ret_val) {
dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val);
/* FW download failed due to timeout */
ret_val = -EBUSY;
}
restore:
/* Re-enable Deeper C-states beyond C6 */
pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE);
sst_free_block(sst_drv_ctx, block);
dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n");
if (sst_drv_ctx->ops->restore_dsp_context)
sst_drv_ctx->ops->restore_dsp_context();
sst_drv_ctx->sst_state = SST_FW_RUNNING;
return ret_val;
}
/*
* sst_pci.c - SST (LPE) driver init file for pci enumeration.
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
static int sst_platform_get_resources(struct intel_sst_drv *ctx)
{
int ddr_base, ret = 0;
struct pci_dev *pci = ctx->pci;
ret = pci_request_regions(pci, SST_DRV_NAME);
if (ret)
return ret;
/* map registers */
/* DDR base */
if (ctx->dev_id == SST_MRFLD_PCI_ID) {
ctx->ddr_base = pci_resource_start(pci, 0);
/* check that the relocated IMR base matches with FW Binary */
ddr_base = relocate_imr_addr_mrfld(ctx->ddr_base);
if (!ctx->pdata->lib_info) {
dev_err(ctx->dev, "lib_info pointer NULL\n");
ret = -EINVAL;
goto do_release_regions;
}
if (ddr_base != ctx->pdata->lib_info->mod_base) {
dev_err(ctx->dev,
"FW LSP DDR BASE does not match with IFWI\n");
ret = -EINVAL;
goto do_release_regions;
}
ctx->ddr_end = pci_resource_end(pci, 0);
ctx->ddr = pcim_iomap(pci, 0,
pci_resource_len(pci, 0));
if (!ctx->ddr) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(ctx->dev, "sst: DDR Ptr %p\n", ctx->ddr);
} else {
ctx->ddr = NULL;
}
/* SHIM */
ctx->shim_phy_add = pci_resource_start(pci, 1);
ctx->shim = pcim_iomap(pci, 1, pci_resource_len(pci, 1));
if (!ctx->shim) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(ctx->dev, "SST Shim Ptr %p\n", ctx->shim);
/* Shared SRAM */
ctx->mailbox_add = pci_resource_start(pci, 2);
ctx->mailbox = pcim_iomap(pci, 2, pci_resource_len(pci, 2));
if (!ctx->mailbox) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(ctx->dev, "SRAM Ptr %p\n", ctx->mailbox);
/* IRAM */
ctx->iram_end = pci_resource_end(pci, 3);
ctx->iram_base = pci_resource_start(pci, 3);
ctx->iram = pcim_iomap(pci, 3, pci_resource_len(pci, 3));
if (!ctx->iram) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(ctx->dev, "IRAM Ptr %p\n", ctx->iram);
/* DRAM */
ctx->dram_end = pci_resource_end(pci, 4);
ctx->dram_base = pci_resource_start(pci, 4);
ctx->dram = pcim_iomap(pci, 4, pci_resource_len(pci, 4));
if (!ctx->dram) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(ctx->dev, "DRAM Ptr %p\n", ctx->dram);
do_release_regions:
pci_release_regions(pci);
return 0;
}
/*
* intel_sst_probe - PCI probe function
*
* @pci: PCI device structure
* @pci_id: PCI device ID structure
*
*/
static int intel_sst_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
int ret = 0;
struct intel_sst_drv *sst_drv_ctx;
struct sst_platform_info *sst_pdata = pci->dev.platform_data;
dev_dbg(&pci->dev, "Probe for DID %x\n", pci->device);
ret = sst_alloc_drv_context(&sst_drv_ctx, &pci->dev, pci->device);
if (ret < 0)
return ret;
sst_drv_ctx->pdata = sst_pdata;
sst_drv_ctx->irq_num = pci->irq;
snprintf(sst_drv_ctx->firmware_name, sizeof(sst_drv_ctx->firmware_name),
"%s%04x%s", "fw_sst_",
sst_drv_ctx->dev_id, ".bin");
ret = sst_context_init(sst_drv_ctx);
if (ret < 0)
return ret;
/* Init the device */
ret = pcim_enable_device(pci);
if (ret) {
dev_err(sst_drv_ctx->dev,
"device can't be enabled. Returned err: %d\n", ret);
goto do_free_drv_ctx;
}
sst_drv_ctx->pci = pci_dev_get(pci);
ret = sst_platform_get_resources(sst_drv_ctx);
if (ret < 0)
goto do_free_drv_ctx;
pci_set_drvdata(pci, sst_drv_ctx);
sst_configure_runtime_pm(sst_drv_ctx);
return ret;
do_free_drv_ctx:
sst_context_cleanup(sst_drv_ctx);
dev_err(sst_drv_ctx->dev, "Probe failed with %d\n", ret);
return ret;
}
/**
* intel_sst_remove - PCI remove function
*
* @pci: PCI device structure
*
* This function is called by OS when a device is unloaded
* This frees the interrupt etc
*/
static void intel_sst_remove(struct pci_dev *pci)
{
struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci);
sst_context_cleanup(sst_drv_ctx);
pci_dev_put(sst_drv_ctx->pci);
pci_release_regions(pci);
pci_set_drvdata(pci, NULL);
}
/* PCI Routines */
static struct pci_device_id intel_sst_ids[] = {
{ PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0},
{ 0, }
};
static struct pci_driver sst_driver = {
.name = SST_DRV_NAME,
.id_table = intel_sst_ids,
.probe = intel_sst_probe,
.remove = intel_sst_remove,
#ifdef CONFIG_PM
.driver = {
.pm = &intel_sst_pm,
},
#endif
};
module_pci_driver(sst_driver);
MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine PCI Driver");
MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
MODULE_AUTHOR("Dharageswari R <dharageswari.r@intel.com>");
MODULE_AUTHOR("KP Jeeja <jeeja.kp@intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("sst");
/*
* sst_pvt.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kobject.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <sound/asound.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
#include "../sst-dsp.h"
int sst_shim_write(void __iomem *addr, int offset, int value)
{
writel(value, addr + offset);
return 0;
}
u32 sst_shim_read(void __iomem *addr, int offset)
{
return readl(addr + offset);
}
u64 sst_reg_read64(void __iomem *addr, int offset)
{
u64 val = 0;
memcpy_fromio(&val, addr + offset, sizeof(val));
return val;
}
int sst_shim_write64(void __iomem *addr, int offset, u64 value)
{
memcpy_toio(addr + offset, &value, sizeof(value));
return 0;
}
u64 sst_shim_read64(void __iomem *addr, int offset)
{
u64 val = 0;
memcpy_fromio(&val, addr + offset, sizeof(val));
return val;
}
void sst_set_fw_state_locked(
struct intel_sst_drv *sst_drv_ctx, int sst_state)
{
mutex_lock(&sst_drv_ctx->sst_lock);
sst_drv_ctx->sst_state = sst_state;
mutex_unlock(&sst_drv_ctx->sst_lock);
}
/*
* sst_wait_interruptible - wait on event
*
* @sst_drv_ctx: Driver context
* @block: Driver block to wait on
*
* This function waits without a timeout (and is interruptable) for a
* given block event
*/
int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx,
struct sst_block *block)
{
int retval = 0;
if (!wait_event_interruptible(sst_drv_ctx->wait_queue,
block->condition)) {
/* event wake */
if (block->ret_code < 0) {
dev_err(sst_drv_ctx->dev,
"stream failed %d\n", block->ret_code);
retval = -EBUSY;
} else {
dev_dbg(sst_drv_ctx->dev, "event up\n");
retval = 0;
}
} else {
dev_err(sst_drv_ctx->dev, "signal interrupted\n");
retval = -EINTR;
}
return retval;
}
unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr)
{
unsigned long long val = 0;
switch (sst->dev_id) {
case SST_MRFLD_PCI_ID:
case SST_BYT_ACPI_ID:
val = sst_shim_read64(sst->shim, addr);
break;
}
return val;
}
void write_shim_data(struct intel_sst_drv *sst, int addr,
unsigned long long data)
{
switch (sst->dev_id) {
case SST_MRFLD_PCI_ID:
case SST_BYT_ACPI_ID:
sst_shim_write64(sst->shim, addr, (u64) data);
break;
}
}
/*
* sst_wait_timeout - wait on event for timeout
*
* @sst_drv_ctx: Driver context
* @block: Driver block to wait on
*
* This function waits with a timeout value (and is not interruptible) on a
* given block event
*/
int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block)
{
int retval = 0;
/*
* NOTE:
* Observed that FW processes the alloc msg and replies even
* before the alloc thread has finished execution
*/
dev_dbg(sst_drv_ctx->dev,
"waiting for condition %x ipc %d drv_id %d\n",
block->condition, block->msg_id, block->drv_id);
if (wait_event_timeout(sst_drv_ctx->wait_queue,
block->condition,
msecs_to_jiffies(SST_BLOCK_TIMEOUT))) {
/* event wake */
dev_dbg(sst_drv_ctx->dev, "Event wake %x\n",
block->condition);
dev_dbg(sst_drv_ctx->dev, "message ret: %d\n",
block->ret_code);
retval = -block->ret_code;
} else {
block->on = false;
dev_err(sst_drv_ctx->dev,
"Wait timed-out condition:%#x, msg_id:%#x fw_state %#x\n",
block->condition, block->msg_id, sst_drv_ctx->sst_state);
sst_drv_ctx->sst_state = SST_RESET;
retval = -EBUSY;
}
return retval;
}
/*
* sst_create_ipc_msg - create a IPC message
*
* @arg: ipc message
* @large: large or short message
*
* this function allocates structures to send a large or short
* message to the firmware
*/
int sst_create_ipc_msg(struct ipc_post **arg, bool large)
{
struct ipc_post *msg;
msg = kzalloc(sizeof(struct ipc_post), GFP_ATOMIC);
if (!msg)
return -ENOMEM;
if (large) {
msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC);
if (!msg->mailbox_data) {
kfree(msg);
return -ENOMEM;
}
} else {
msg->mailbox_data = NULL;
}
msg->is_large = large;
*arg = msg;
return 0;
}
/*
* sst_create_block_and_ipc_msg - Creates IPC message and sst block
* @arg: passed to sst_create_ipc_message API
* @large: large or short message
* @sst_drv_ctx: sst driver context
* @block: return block allocated
* @msg_id: IPC
* @drv_id: stream id or private id
*/
int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large,
struct intel_sst_drv *sst_drv_ctx, struct sst_block **block,
u32 msg_id, u32 drv_id)
{
int retval = 0;
retval = sst_create_ipc_msg(arg, large);
if (retval)
return retval;
*block = sst_create_block(sst_drv_ctx, msg_id, drv_id);
if (*block == NULL) {
kfree(*arg);
return -ENOMEM;
}
return retval;
}
/*
* sst_clean_stream - clean the stream context
*
* @stream: stream structure
*
* this function resets the stream contexts
* should be called in free
*/
void sst_clean_stream(struct stream_info *stream)
{
stream->status = STREAM_UN_INIT;
stream->prev = STREAM_UN_INIT;
mutex_lock(&stream->lock);
stream->cumm_bytes = 0;
mutex_unlock(&stream->lock);
}
int sst_prepare_and_post_msg(struct intel_sst_drv *sst,
int task_id, int ipc_msg, int cmd_id, int pipe_id,
size_t mbox_data_len, const void *mbox_data, void **data,
bool large, bool fill_dsp, bool sync, bool response)
{
struct ipc_post *msg = NULL;
struct ipc_dsp_hdr dsp_hdr;
struct sst_block *block;
int ret = 0, pvt_id;
pvt_id = sst_assign_pvt_id(sst);
if (pvt_id < 0)
return pvt_id;
if (response)
ret = sst_create_block_and_ipc_msg(
&msg, large, sst, &block, ipc_msg, pvt_id);
else
ret = sst_create_ipc_msg(&msg, large);
if (ret < 0) {
test_and_clear_bit(pvt_id, &sst->pvt_id);
return -ENOMEM;
}
dev_dbg(sst->dev, "pvt_id = %d, pipe id = %d, task = %d ipc_msg: %d\n",
pvt_id, pipe_id, task_id, ipc_msg);
sst_fill_header_mrfld(&msg->mrfld_header, ipc_msg,
task_id, large, pvt_id);
msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr) + mbox_data_len;
msg->mrfld_header.p.header_high.part.res_rqd = !sync;
dev_dbg(sst->dev, "header:%x\n",
msg->mrfld_header.p.header_high.full);
dev_dbg(sst->dev, "response rqd: %x",
msg->mrfld_header.p.header_high.part.res_rqd);
dev_dbg(sst->dev, "msg->mrfld_header.p.header_low_payload:%d",
msg->mrfld_header.p.header_low_payload);
if (fill_dsp) {
sst_fill_header_dsp(&dsp_hdr, cmd_id, pipe_id, mbox_data_len);
memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr));
if (mbox_data_len) {
memcpy(msg->mailbox_data + sizeof(dsp_hdr),
mbox_data, mbox_data_len);
}
}
if (sync)
sst->ops->post_message(sst, msg, true);
else
sst_add_to_dispatch_list_and_post(sst, msg);
if (response) {
ret = sst_wait_timeout(sst, block);
if (ret < 0) {
goto out;
} else if(block->data) {
if (!data)
goto out;
*data = kzalloc(block->size, GFP_KERNEL);
if (!(*data)) {
ret = -ENOMEM;
goto out;
} else
memcpy(data, (void *) block->data, block->size);
}
}
out:
if (response)
sst_free_block(sst, block);
test_and_clear_bit(pvt_id, &sst->pvt_id);
return ret;
}
int sst_pm_runtime_put(struct intel_sst_drv *sst_drv)
{
int ret;
pm_runtime_mark_last_busy(sst_drv->dev);
ret = pm_runtime_put_autosuspend(sst_drv->dev);
if (ret < 0)
return ret;
return 0;
}
void sst_fill_header_mrfld(union ipc_header_mrfld *header,
int msg, int task_id, int large, int drv_id)
{
header->full = 0;
header->p.header_high.part.msg_id = msg;
header->p.header_high.part.task_id = task_id;
header->p.header_high.part.large = large;
header->p.header_high.part.drv_id = drv_id;
header->p.header_high.part.done = 0;
header->p.header_high.part.busy = 1;
header->p.header_high.part.res_rqd = 1;
}
void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg,
int pipe_id, int len)
{
dsp->cmd_id = msg;
dsp->mod_index_id = 0xff;
dsp->pipe_id = pipe_id;
dsp->length = len;
dsp->mod_id = 0;
}
#define SST_MAX_BLOCKS 15
/*
* sst_assign_pvt_id - assign a pvt id for stream
*
* @sst_drv_ctx : driver context
*
* this function assigns a private id for calls that dont have stream
* context yet, should be called with lock held
* uses bits for the id, and finds first free bits and assigns that
*/
int sst_assign_pvt_id(struct intel_sst_drv *drv)
{
int local;
spin_lock(&drv->block_lock);
/* find first zero index from lsb */
local = ffz(drv->pvt_id);
dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local);
if (local >= SST_MAX_BLOCKS){
spin_unlock(&drv->block_lock);
dev_err(drv->dev, "PVT _ID error: no free id blocks ");
return -EINVAL;
}
/* toggle the index */
change_bit(local, &drv->pvt_id);
spin_unlock(&drv->block_lock);
return local;
}
void sst_init_stream(struct stream_info *stream,
int codec, int sst_id, int ops, u8 slot)
{
stream->status = STREAM_INIT;
stream->prev = STREAM_UN_INIT;
stream->ops = ops;
}
int sst_validate_strid(
struct intel_sst_drv *sst_drv_ctx, int str_id)
{
if (str_id <= 0 || str_id > sst_drv_ctx->info.max_streams) {
dev_err(sst_drv_ctx->dev,
"SST ERR: invalid stream id : %d, max %d\n",
str_id, sst_drv_ctx->info.max_streams);
return -EINVAL;
}
return 0;
}
struct stream_info *get_stream_info(
struct intel_sst_drv *sst_drv_ctx, int str_id)
{
if (sst_validate_strid(sst_drv_ctx, str_id))
return NULL;
return &sst_drv_ctx->streams[str_id];
}
int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx,
u32 pipe_id)
{
int i;
for (i = 1; i <= sst_drv_ctx->info.max_streams; i++)
if (pipe_id == sst_drv_ctx->streams[i].pipe_id)
return i;
dev_dbg(sst_drv_ctx->dev, "no such pipe_id(%u)", pipe_id);
return -1;
}
u32 relocate_imr_addr_mrfld(u32 base_addr)
{
/* Get the difference from 512MB aligned base addr */
/* relocate the base */
base_addr = MRFLD_FW_VIRTUAL_BASE + (base_addr % (512 * 1024 * 1024));
return base_addr;
}
EXPORT_SYMBOL_GPL(relocate_imr_addr_mrfld);
void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst,
struct ipc_post *msg)
{
unsigned long irq_flags;
spin_lock_irqsave(&sst->ipc_spin_lock, irq_flags);
list_add_tail(&msg->node, &sst->ipc_dispatch_list);
spin_unlock_irqrestore(&sst->ipc_spin_lock, irq_flags);
sst->ops->post_message(sst, NULL, false);
}
/*
* sst_stream.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/pci.h>
#include <linux/firmware.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
#include "../sst-dsp.h"
int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params)
{
struct snd_sst_alloc_mrfld alloc_param;
struct snd_sst_params *str_params;
struct snd_sst_tstamp fw_tstamp;
struct stream_info *str_info;
struct snd_sst_alloc_response *response;
unsigned int str_id, pipe_id, task_id;
int i, num_ch, ret = 0;
void *data = NULL;
dev_dbg(sst_drv_ctx->dev, "Enter\n");
BUG_ON(!params);
str_params = (struct snd_sst_params *)params;
memset(&alloc_param, 0, sizeof(alloc_param));
alloc_param.operation = str_params->ops;
alloc_param.codec_type = str_params->codec;
alloc_param.sg_count = str_params->aparams.sg_count;
alloc_param.ring_buf_info[0].addr =
str_params->aparams.ring_buf_info[0].addr;
alloc_param.ring_buf_info[0].size =
str_params->aparams.ring_buf_info[0].size;
alloc_param.frag_size = str_params->aparams.frag_size;
memcpy(&alloc_param.codec_params, &str_params->sparams,
sizeof(struct snd_sst_stream_params));
/*
* fill channel map params for multichannel support.
* Ideally channel map should be received from upper layers
* for multichannel support.
* Currently hardcoding as per FW reqm.
*/
num_ch = sst_get_num_channel(str_params);
for (i = 0; i < 8; i++) {
if (i < num_ch)
alloc_param.codec_params.uc.pcm_params.channel_map[i] = i;
else
alloc_param.codec_params.uc.pcm_params.channel_map[i] = 0xFF;
}
str_id = str_params->stream_id;
str_info = get_stream_info(sst_drv_ctx, str_id);
if (str_info == NULL) {
dev_err(sst_drv_ctx->dev, "get stream info returned null\n");
return -EINVAL;
}
pipe_id = str_params->device_type;
task_id = str_params->task;
sst_drv_ctx->streams[str_id].pipe_id = pipe_id;
sst_drv_ctx->streams[str_id].task_id = task_id;
sst_drv_ctx->streams[str_id].num_ch = num_ch;
if (sst_drv_ctx->info.lpe_viewpt_rqd)
alloc_param.ts = sst_drv_ctx->info.mailbox_start +
sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp));
else
alloc_param.ts = sst_drv_ctx->mailbox_add +
sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp));
dev_dbg(sst_drv_ctx->dev, "alloc tstamp location = 0x%x\n",
alloc_param.ts);
dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n",
pipe_id, task_id);
/* allocate device type context */
sst_init_stream(&sst_drv_ctx->streams[str_id], alloc_param.codec_type,
str_id, alloc_param.operation, 0);
dev_info(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n",
str_id, pipe_id);
ret = sst_prepare_and_post_msg(sst_drv_ctx, task_id, IPC_CMD,
IPC_IA_ALLOC_STREAM_MRFLD, pipe_id, sizeof(alloc_param),
&alloc_param, data, true, true, false, true);
if (ret < 0) {
dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret);
/* alloc failed, so reset the state to uninit */
str_info->status = STREAM_UN_INIT;
str_id = ret;
} else if (data) {
response = (struct snd_sst_alloc_response *)data;
ret = response->str_type.result;
if (!ret)
goto out;
dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret);
if (ret == SST_ERR_STREAM_IN_USE) {
dev_err(sst_drv_ctx->dev,
"FW not in clean state, send free for:%d\n", str_id);
sst_free_stream(sst_drv_ctx, str_id);
}
str_id = -ret;
}
out:
kfree(data);
return str_id;
}
/**
* sst_start_stream - Send msg for a starting stream
* @str_id: stream ID
*
* This function is called by any function which wants to start
* a stream.
*/
int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
u16 data = 0;
dev_dbg(sst_drv_ctx->dev, "sst_start_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status != STREAM_RUNNING)
return -EBADRQC;
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
IPC_CMD, IPC_IA_START_STREAM_MRFLD, str_info->pipe_id,
sizeof(u16), &data, NULL, true, true, true, false);
return retval;
}
int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx,
struct snd_sst_bytes_v2 *bytes)
{ struct ipc_post *msg = NULL;
u32 length;
int pvt_id, ret = 0;
struct sst_block *block = NULL;
dev_dbg(sst_drv_ctx->dev,
"type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n",
bytes->type, bytes->ipc_msg, bytes->block, bytes->task_id,
bytes->pipe_id, bytes->len);
if (sst_create_ipc_msg(&msg, true))
return -ENOMEM;
pvt_id = sst_assign_pvt_id(sst_drv_ctx);
sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg,
bytes->task_id, 1, pvt_id);
msg->mrfld_header.p.header_high.part.res_rqd = bytes->block;
length = bytes->len;
msg->mrfld_header.p.header_low_payload = length;
dev_dbg(sst_drv_ctx->dev, "length is %d\n", length);
memcpy(msg->mailbox_data, &bytes->bytes, bytes->len);
if (bytes->block) {
block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id);
if (block == NULL) {
kfree(msg);
ret = -ENOMEM;
goto out;
}
}
sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg);
dev_dbg(sst_drv_ctx->dev, "msg->mrfld_header.p.header_low_payload:%d",
msg->mrfld_header.p.header_low_payload);
if (bytes->block) {
ret = sst_wait_timeout(sst_drv_ctx, block);
if (ret) {
dev_err(sst_drv_ctx->dev, "fw returned err %d\n", ret);
sst_free_block(sst_drv_ctx, block);
goto out;
}
}
if (bytes->type == SND_SST_BYTES_GET) {
/*
* copy the reply and send back
* we need to update only sz and payload
*/
if (bytes->block) {
unsigned char *r = block->data;
dev_dbg(sst_drv_ctx->dev, "read back %d bytes",
bytes->len);
memcpy(bytes->bytes, r, bytes->len);
}
}
if (bytes->block)
sst_free_block(sst_drv_ctx, block);
out:
test_and_clear_bit(pvt_id, &sst_drv_ctx->pvt_id);
return 0;
}
/*
* sst_pause_stream - Send msg for a pausing stream
* @str_id: stream ID
*
* This function is called by any function which wants to pause
* an already running stream.
*/
int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_pause_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status == STREAM_PAUSED)
return 0;
if (str_info->status == STREAM_RUNNING ||
str_info->status == STREAM_INIT) {
if (str_info->prev == STREAM_UN_INIT)
return -EBADRQC;
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
IPC_IA_PAUSE_STREAM_MRFLD, str_info->pipe_id,
0, NULL, NULL, true, true, false, true);
if (retval == 0) {
str_info->prev = str_info->status;
str_info->status = STREAM_PAUSED;
} else if (retval == SST_ERR_INVALID_STREAM_ID) {
retval = -EINVAL;
mutex_lock(&sst_drv_ctx->sst_lock);
sst_clean_stream(str_info);
mutex_unlock(&sst_drv_ctx->sst_lock);
}
} else {
retval = -EBADRQC;
dev_dbg(sst_drv_ctx->dev, "SST DBG:BADRQC for stream\n ");
}
return retval;
}
/**
* sst_resume_stream - Send msg for resuming stream
* @str_id: stream ID
*
* This function is called by any function which wants to resume
* an already paused stream.
*/
int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_resume_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status == STREAM_RUNNING)
return 0;
if (str_info->status == STREAM_PAUSED) {
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
IPC_CMD, IPC_IA_RESUME_STREAM_MRFLD,
str_info->pipe_id, 0, NULL, NULL,
true, true, false, true);
if (!retval) {
if (str_info->prev == STREAM_RUNNING)
str_info->status = STREAM_RUNNING;
else
str_info->status = STREAM_INIT;
str_info->prev = STREAM_PAUSED;
} else if (retval == -SST_ERR_INVALID_STREAM_ID) {
retval = -EINVAL;
mutex_lock(&sst_drv_ctx->sst_lock);
sst_clean_stream(str_info);
mutex_unlock(&sst_drv_ctx->sst_lock);
}
} else {
retval = -EBADRQC;
dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream\n");
}
return retval;
}
/**
* sst_drop_stream - Send msg for stopping stream
* @str_id: stream ID
*
* This function is called by any function which wants to stop
* a stream.
*/
int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drop_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status != STREAM_UN_INIT) {
str_info->prev = STREAM_UN_INIT;
str_info->status = STREAM_INIT;
str_info->cumm_bytes = 0;
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
IPC_CMD, IPC_IA_DROP_STREAM_MRFLD,
str_info->pipe_id, 0, NULL, NULL,
true, true, true, false);
} else {
retval = -EBADRQC;
dev_dbg(sst_drv_ctx->dev, "BADQRC for stream, state %x\n",
str_info->status);
}
return retval;
}
/**
* sst_drain_stream - Send msg for draining stream
* @str_id: stream ID
*
* This function is called by any function which wants to drain
* a stream.
*/
int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx,
int str_id, bool partial_drain)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drain_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status != STREAM_RUNNING &&
str_info->status != STREAM_INIT &&
str_info->status != STREAM_PAUSED) {
dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream = %d\n",
str_info->status);
return -EBADRQC;
}
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
IPC_IA_DRAIN_STREAM_MRFLD, str_info->pipe_id,
sizeof(u8), &partial_drain, NULL, true, true, false, false);
/*
* with new non blocked drain implementation in core we dont need to
* wait for respsonse, and need to only invoke callback for drain
* complete
*/
return retval;
}
/**
* sst_free_stream - Frees a stream
* @str_id: stream ID
*
* This function is called by any function which wants to free
* a stream.
*/
int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
struct intel_sst_ops *ops;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_free_stream for %d\n", str_id);
mutex_lock(&sst_drv_ctx->sst_lock);
if (sst_drv_ctx->sst_state == SST_RESET) {
mutex_unlock(&sst_drv_ctx->sst_lock);
return -ENODEV;
}
mutex_unlock(&sst_drv_ctx->sst_lock);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
ops = sst_drv_ctx->ops;
mutex_lock(&str_info->lock);
if (str_info->status != STREAM_UN_INIT) {
str_info->prev = str_info->status;
str_info->status = STREAM_UN_INIT;
mutex_unlock(&str_info->lock);
dev_info(sst_drv_ctx->dev, "Free for str %d pipe %#x\n",
str_id, str_info->pipe_id);
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
IPC_IA_FREE_STREAM_MRFLD, str_info->pipe_id, 0,
NULL, NULL, true, true, false, true);
dev_dbg(sst_drv_ctx->dev, "sst: wait for free returned %d\n",
retval);
mutex_lock(&sst_drv_ctx->sst_lock);
sst_clean_stream(str_info);
mutex_unlock(&sst_drv_ctx->sst_lock);
dev_dbg(sst_drv_ctx->dev, "SST DBG:Stream freed\n");
} else {
mutex_unlock(&str_info->lock);
retval = -EBADRQC;
dev_dbg(sst_drv_ctx->dev, "SST DBG:BADQRC for stream\n");
}
return retval;
}
...@@ -77,25 +77,18 @@ static int qi_lb60_probe(struct platform_device *pdev) ...@@ -77,25 +77,18 @@ static int qi_lb60_probe(struct platform_device *pdev)
{ {
struct qi_lb60 *qi_lb60; struct qi_lb60 *qi_lb60;
struct snd_soc_card *card = &qi_lb60_card; struct snd_soc_card *card = &qi_lb60_card;
int ret;
qi_lb60 = devm_kzalloc(&pdev->dev, sizeof(*qi_lb60), GFP_KERNEL); qi_lb60 = devm_kzalloc(&pdev->dev, sizeof(*qi_lb60), GFP_KERNEL);
if (!qi_lb60) if (!qi_lb60)
return -ENOMEM; return -ENOMEM;
qi_lb60->snd_gpio = devm_gpiod_get(&pdev->dev, "snd"); qi_lb60->snd_gpio = devm_gpiod_get(&pdev->dev, "snd", GPIOD_OUT_LOW);
if (IS_ERR(qi_lb60->snd_gpio)) if (IS_ERR(qi_lb60->snd_gpio))
return PTR_ERR(qi_lb60->snd_gpio); return PTR_ERR(qi_lb60->snd_gpio);
ret = gpiod_direction_output(qi_lb60->snd_gpio, 0);
if (ret)
return ret;
qi_lb60->amp_gpio = devm_gpiod_get(&pdev->dev, "amp"); qi_lb60->amp_gpio = devm_gpiod_get(&pdev->dev, "amp", GPIOD_OUT_LOW);
if (IS_ERR(qi_lb60->amp_gpio)) if (IS_ERR(qi_lb60->amp_gpio))
return PTR_ERR(qi_lb60->amp_gpio); return PTR_ERR(qi_lb60->amp_gpio);
ret = gpiod_direction_output(qi_lb60->amp_gpio, 0);
if (ret)
return ret;
card->dev = &pdev->dev; card->dev = &pdev->dev;
......
...@@ -309,7 +309,7 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, ...@@ -309,7 +309,7 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
/* GPIO descriptor */ /* GPIO descriptor */
gpios[i].desc = gpiod_get_index(gpios[i].gpiod_dev, gpios[i].desc = gpiod_get_index(gpios[i].gpiod_dev,
gpios[i].name, gpios[i].name,
gpios[i].idx); gpios[i].idx, GPIOD_IN);
if (IS_ERR(gpios[i].desc)) { if (IS_ERR(gpios[i].desc)) {
ret = PTR_ERR(gpios[i].desc); ret = PTR_ERR(gpios[i].desc);
dev_err(gpios[i].gpiod_dev, dev_err(gpios[i].gpiod_dev,
...@@ -327,17 +327,14 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, ...@@ -327,17 +327,14 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
goto undo; goto undo;
} }
ret = gpio_request(gpios[i].gpio, gpios[i].name); ret = gpio_request_one(gpios[i].gpio, GPIOF_IN,
gpios[i].name);
if (ret) if (ret)
goto undo; goto undo;
gpios[i].desc = gpio_to_desc(gpios[i].gpio); gpios[i].desc = gpio_to_desc(gpios[i].gpio);
} }
ret = gpiod_direction_input(gpios[i].desc);
if (ret)
goto err;
INIT_DELAYED_WORK(&gpios[i].work, gpio_work); INIT_DELAYED_WORK(&gpios[i].work, gpio_work);
gpios[i].jack = jack; gpios[i].jack = jack;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment