Merge remote-tracking branches 'asoc/topic/hdmi', 'asoc/topic/intel', 'asoc/topic/jack', 'asoc/topic/jz4740' and 'asoc/topic/lm49453' into asoc-next

This commit is contained in:
Mark Brown 2014-12-08 13:12:00 +00:00
35 changed files with 8284 additions and 544 deletions

View file

@ -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 {
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

View file

@ -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 = {
.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)

View file

@ -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),

View file

@ -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
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
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
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
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".

View file

@ -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/

View file

@ -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 @@
#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[] = {
{"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[] = {
{"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)
} }
/* 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[] = {
/* 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[] = {
.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[] = {
.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[] = {
.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 = {
.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,

View file

@ -0,0 +1,230 @@
/*
* 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");

View file

@ -0,0 +1,285 @@
/*
* 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");

View file

@ -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[] = {
.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[] = {
.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 */
{ {

File diff suppressed because it is too large Load diff

View file

@ -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 {
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 {
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

View file

@ -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,
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,
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;
} }

View file

@ -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 {
/* 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 {
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 {
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 {
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 {
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 {
/* 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 {
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 {
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 {
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);
/* 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);
int sst_module_free_blocks(struct sst_module *module);
/* allocate/free pesistent/scratch memory regions managed by drv */ /* Create/Free firmware module runtime instances */
struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp); struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module,
void sst_mem_block_free_scratch(struct sst_dsp *dsp, int id, void *private);
struct sst_module *scratch); void sst_module_runtime_free(struct sst_module_runtime *runtime);
int sst_block_module_remove(struct sst_module *module); 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,
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

View file

@ -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,
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,
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)
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);

View file

@ -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 @@
#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,
/* 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);

File diff suppressed because it is too large Load diff

View file

@ -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,
{ {
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,
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,
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,
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)
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)
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)
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);
/* disable all clock gating */ /* Enable Interrupt from both sides */
writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL2); sst_dsp_shim_update_bits(sst, SST_IMRX, (SST_IMRX_BUSY | SST_IMRX_DONE),
0x0);
sst_dsp_shim_update_bits(sst, SST_IMRD, (SST_IMRD_DONE | SST_IMRD_BUSY |
SST_IMRD_SSP0 | SST_IMRD_DMAC), 0x0);
/* clear IPC registers */
sst_dsp_shim_write(sst, SST_IPCX, 0x0);
sst_dsp_shim_write(sst, SST_IPCD, 0x0);
sst_dsp_shim_write(sst, 0x80, 0x6);
sst_dsp_shim_write(sst, 0xe0, 0x300a);
return 0;
}
static void hsw_boot(struct sst_dsp *sst)
{
/* set oportunistic mode on engine 0,1 for all channels */
sst_dsp_shim_update_bits(sst, SST_HMDC,
SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, 0);
/* set DSP to RUN */ /* 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)
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)
/* 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)
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 = {
.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)
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)
} }
/* 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)
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)
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,

View file

@ -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 {
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 {
/* 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,
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,
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,
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,
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,
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,
} }
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,
/* 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, stream->request.scratch_mem.offset =
struct sst_hsw_stream *stream, u32 offset, u32 size) sst_dsp_get_offset(dsp, dsp->scratch_offset, SST_MEM_DRAM);
{ stream->request.scratch_mem.size = dsp->scratch_size;
if (stream->commited) {
dev_err(hsw->dev, "error: stream committed for set pmem\n");
return -EINVAL;
}
stream->request.persistent_mem.offset = offset; dev_dbg(hsw->dev, "module %d runtime %d using:\n", module->id,
stream->request.persistent_mem.size = size; runtime->id);
dev_dbg(hsw->dev, " persistent offset 0x%x bytes 0x%x\n",
return 0; stream->request.persistent_mem.offset,
} stream->request.persistent_mem.size);
dev_dbg(hsw->dev, " scratch offset 0x%x bytes 0x%x\n",
int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, stream->request.scratch_mem.offset,
struct sst_hsw_stream *stream, u32 offset, u32 size) stream->request.scratch_mem.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,
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,
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;
}
trace_ipc_request("PM get Dx state", entry_no); 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;
}
if (item >= entry_no) err = sst_module_runtime_alloc_blocks(runtime, offset);
if (err < 0) {
dev_err(dsp->dev, "error: failed to alloc blocks for module %d runtime\n",
mod_id);
sst_module_runtime_free(runtime);
return NULL;
}
dev_dbg(dsp->dev, "runtime id %d created for module %d\n", runtime->id,
mod_id);
return runtime;
}
void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime)
{
sst_module_runtime_free_blocks(runtime);
sst_module_runtime_free(runtime);
}
#ifdef CONFIG_PM
static int sst_hsw_dx_state_dump(struct sst_hsw *hsw)
{
struct sst_dsp *sst = hsw->dsp;
u32 item, offset, size;
int ret = 0;
trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS);
if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) {
dev_err(hsw->dev,
"error: number of FW context regions greater than %d\n",
SST_HSW_MAX_DX_REGIONS);
memset(&hsw->dx, 0, sizeof(hsw->dx));
return -EINVAL; return -EINVAL;
}
dx_mem = &dx_reply->mem_info[item]; ret = sst_dsp_dma_get_channel(sst, 0);
*offset = dx_mem->offset; if (ret < 0) {
*size = dx_mem->size; dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
*source = dx_mem->source; 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; 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;
/* drop all TX and Rx messages before we stall + reset DSP */
spin_lock_irqsave(&hsw->dsp->spinlock, flags);
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;
}
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)
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)
{ {
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)
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)
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)
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)
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);

View file

@ -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;
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 {
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 {
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,
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,
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

View file

@ -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[] = {
#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 = {
.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)
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,
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,
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,
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,
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,
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,
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);
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,
/* 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:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
stream_type = SST_HSW_STREAM_TYPE_SYSTEM; stream_type = SST_HSW_STREAM_TYPE_SYSTEM;
module_id = SST_HSW_MODULE_PCM_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,
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,
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,
return -EINVAL; return -EINVAL;
} }
/* we use hardcoded memory offsets atm, will be updated for new FW */
if (stream_type == SST_HSW_STREAM_TYPE_CAPTURE) {
sst_hsw_stream_set_module_info(hsw, pcm_data->stream, sst_hsw_stream_set_module_info(hsw, pcm_data->stream,
SST_HSW_MODULE_PCM_CAPTURE, module_data->entry); pcm_data->runtime);
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;
} }
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; 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)
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)
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)
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)
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)
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 = {
.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)
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)
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)
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[] = {
.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[] = {
{ {
/* 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[] = {
}, },
{ {
.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[] = {
.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)
{ {
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)
} }
} }
/* 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)
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)
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,

View file

@ -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)
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)

View file

@ -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,
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)
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);

View file

@ -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 {
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);

View file

@ -0,0 +1,7 @@
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

437
sound/soc/intel/sst/sst.c Normal file
View file

@ -0,0 +1,437 @@
/*
* 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);

546
sound/soc/intel/sst/sst.h Normal file
View file

@ -0,0 +1,546 @@
/*
* 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

View file

@ -0,0 +1,383 @@
/*
* 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");

View file

@ -0,0 +1,686 @@
/*
* 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);
}

View file

@ -0,0 +1,373 @@
/*
* 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);
}
}

View file

@ -0,0 +1,456 @@
/*
* 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;
}

View file

@ -0,0 +1,209 @@
/*
* 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");

View file

@ -0,0 +1,449 @@
/*
* 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);
}

View file

@ -0,0 +1,437 @@
/*
* 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;
}

View file

@ -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;

View file

@ -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,
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;