Commit addaeea9 authored by Mark Brown's avatar Mark Brown

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

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