linux/sound/soc/sof/intel/hda-dsp.c
Ranjani Sridharan ed3baacd76
ASoC: SOF: intel: hda: add hw_params_upon_resume flag for hda stream
The prepare() ioctl for BE dai link gets called both
when the stream is started and when it is resumed from
suspend. SOF uses this ioctl to set the hw params
again only if the stream has been suspended.

When the stream is started, the hw_params ioctl gets called
before prepare() and hw_params is set for the BE dai link.
So the prepare call does not need to do anything further.

When the stream resumes after system suspend, SOF requires
that the hw_params be set again for the BE dai. In order
to determine which streams should set the hw params
during prepare(), an internal flag called "hw_params_upon_resume"
is introduced in struct sof_intel_hda_stream. The flag is set
for hda streams when the sof device suspends and is
cleared after hw_params is set.

Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-05-03 15:01:45 +09:00

472 lines
12 KiB
C

// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018 Intel Corporation. All rights reserved.
//
// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Rander Wang <rander.wang@intel.com>
// Keyon Jie <yang.jie@linux.intel.com>
//
/*
* Hardware interface for generic Intel audio DSP HDA IP
*/
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include "../ops.h"
#include "hda.h"
/*
* DSP Core control.
*/
int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask)
{
u32 adspcs;
u32 reset;
int ret;
/* set reset bits for cores */
reset = HDA_DSP_ADSPCS_CRST_MASK(core_mask);
snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS,
reset, reset),
/* poll with timeout to check if operation successful */
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS, adspcs,
((adspcs & reset) == reset),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
/* has core entered reset ? */
adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS);
if ((adspcs & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) !=
HDA_DSP_ADSPCS_CRST_MASK(core_mask)) {
dev_err(sdev->dev,
"error: reset enter failed: core_mask %x adspcs 0x%x\n",
core_mask, adspcs);
ret = -EIO;
}
return ret;
}
int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask)
{
unsigned int crst;
u32 adspcs;
int ret;
/* clear reset bits for cores */
snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS,
HDA_DSP_ADSPCS_CRST_MASK(core_mask),
0);
/* poll with timeout to check if operation successful */
crst = HDA_DSP_ADSPCS_CRST_MASK(core_mask);
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS, adspcs,
!(adspcs & crst),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
/* has core left reset ? */
adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS);
if ((adspcs & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) != 0) {
dev_err(sdev->dev,
"error: reset leave failed: core_mask %x adspcs 0x%x\n",
core_mask, adspcs);
ret = -EIO;
}
return ret;
}
int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask)
{
/* stall core */
snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS,
HDA_DSP_ADSPCS_CSTALL_MASK(core_mask),
HDA_DSP_ADSPCS_CSTALL_MASK(core_mask));
/* set reset state */
return hda_dsp_core_reset_enter(sdev, core_mask);
}
int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask)
{
int ret;
/* leave reset state */
ret = hda_dsp_core_reset_leave(sdev, core_mask);
if (ret < 0)
return ret;
/* run core */
dev_dbg(sdev->dev, "unstall/run core: core_mask = %x\n", core_mask);
snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS,
HDA_DSP_ADSPCS_CSTALL_MASK(core_mask),
0);
/* is core now running ? */
if (!hda_dsp_core_is_enabled(sdev, core_mask)) {
hda_dsp_core_stall_reset(sdev, core_mask);
dev_err(sdev->dev, "error: DSP start core failed: core_mask %x\n",
core_mask);
ret = -EIO;
}
return ret;
}
/*
* Power Management.
*/
int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask)
{
unsigned int cpa;
u32 adspcs;
int ret;
/* update bits */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS,
HDA_DSP_ADSPCS_SPA_MASK(core_mask),
HDA_DSP_ADSPCS_SPA_MASK(core_mask));
/* poll with timeout to check if operation successful */
cpa = HDA_DSP_ADSPCS_CPA_MASK(core_mask);
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS, adspcs,
(adspcs & cpa) == cpa,
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
if (ret < 0)
dev_err(sdev->dev, "error: timeout on core powerup\n");
/* did core power up ? */
adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS);
if ((adspcs & HDA_DSP_ADSPCS_CPA_MASK(core_mask)) !=
HDA_DSP_ADSPCS_CPA_MASK(core_mask)) {
dev_err(sdev->dev,
"error: power up core failed core_mask %xadspcs 0x%x\n",
core_mask, adspcs);
ret = -EIO;
}
return ret;
}
int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask)
{
u32 adspcs;
/* update bits */
snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS,
HDA_DSP_ADSPCS_SPA_MASK(core_mask), 0);
return snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS, adspcs,
!(adspcs & HDA_DSP_ADSPCS_SPA_MASK(core_mask)),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC);
}
bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev,
unsigned int core_mask)
{
int val;
bool is_enable;
val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS);
is_enable = ((val & HDA_DSP_ADSPCS_CPA_MASK(core_mask)) &&
(val & HDA_DSP_ADSPCS_SPA_MASK(core_mask)) &&
!(val & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) &&
!(val & HDA_DSP_ADSPCS_CSTALL_MASK(core_mask)));
dev_dbg(sdev->dev, "DSP core(s) enabled? %d : core_mask %x\n",
is_enable, core_mask);
return is_enable;
}
int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask)
{
int ret;
/* return if core is already enabled */
if (hda_dsp_core_is_enabled(sdev, core_mask))
return 0;
/* power up */
ret = hda_dsp_core_power_up(sdev, core_mask);
if (ret < 0) {
dev_err(sdev->dev, "error: dsp core power up failed: core_mask %x\n",
core_mask);
return ret;
}
return hda_dsp_core_run(sdev, core_mask);
}
int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev,
unsigned int core_mask)
{
int ret;
/* place core in reset prior to power down */
ret = hda_dsp_core_stall_reset(sdev, core_mask);
if (ret < 0) {
dev_err(sdev->dev, "error: dsp core reset failed: core_mask %x\n",
core_mask);
return ret;
}
/* power down core */
ret = hda_dsp_core_power_down(sdev, core_mask);
if (ret < 0) {
dev_err(sdev->dev, "error: dsp core power down fail mask %x: %d\n",
core_mask, ret);
return ret;
}
/* make sure we are in OFF state */
if (hda_dsp_core_is_enabled(sdev, core_mask)) {
dev_err(sdev->dev, "error: dsp core disable fail mask %x: %d\n",
core_mask, ret);
ret = -EIO;
}
return ret;
}
void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
/* enable IPC DONE and BUSY interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY,
HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY);
/* enable IPC interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC);
}
void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
/* disable IPC interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
HDA_DSP_ADSPIC_IPC, 0);
/* disable IPC BUSY and DONE interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
HDA_DSP_REG_HIPCCTL_BUSY | HDA_DSP_REG_HIPCCTL_DONE, 0);
}
static int hda_suspend(struct snd_sof_dev *sdev, int state)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
struct hdac_bus *bus = sof_to_bus(sdev);
#endif
int ret;
/* disable IPC interrupts */
hda_dsp_ipc_int_disable(sdev);
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
/* power down all hda link */
snd_hdac_ext_bus_link_power_down_all(bus);
#endif
/* power down DSP */
ret = hda_dsp_core_reset_power_down(sdev, chip->cores_mask);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to power down core during suspend\n");
return ret;
}
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
/* disable ppcap interrupt */
snd_hdac_ext_bus_ppcap_int_enable(bus, false);
snd_hdac_ext_bus_ppcap_enable(bus, false);
/* disable hda bus irq and i/o */
snd_hdac_bus_stop_chip(bus);
#else
/* disable ppcap interrupt */
hda_dsp_ctrl_ppcap_enable(sdev, false);
hda_dsp_ctrl_ppcap_int_enable(sdev, false);
/* disable hda bus irq */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN,
0);
#endif
/* disable LP retention mode */
snd_sof_pci_update_bits(sdev, PCI_PGCTL,
PCI_PGCTL_LSRMD_MASK, PCI_PGCTL_LSRMD_MASK);
/* reset controller */
ret = hda_dsp_ctrl_link_reset(sdev, true);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to reset controller during suspend\n");
return ret;
}
return 0;
}
static int hda_resume(struct snd_sof_dev *sdev)
{
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
struct hdac_bus *bus = sof_to_bus(sdev);
struct hdac_ext_link *hlink = NULL;
#endif
int ret;
/*
* clear TCSEL to clear playback on some HD Audio
* codecs. PCI TCSEL is defined in the Intel manuals.
*/
snd_sof_pci_update_bits(sdev, PCI_TCSEL, 0x07, 0);
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
/* reset and start hda controller */
ret = hda_dsp_ctrl_init_chip(sdev, true);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to start controller after resume\n");
return ret;
}
hda_dsp_ctrl_misc_clock_gating(sdev, false);
/* Reset stream-to-link mapping */
list_for_each_entry(hlink, &bus->hlink_list, list)
bus->io_ops->reg_writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
hda_dsp_ctrl_misc_clock_gating(sdev, true);
/* enable ppcap interrupt */
snd_hdac_ext_bus_ppcap_enable(bus, true);
snd_hdac_ext_bus_ppcap_int_enable(bus, true);
#else
hda_dsp_ctrl_misc_clock_gating(sdev, false);
/* reset controller */
ret = hda_dsp_ctrl_link_reset(sdev, true);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to reset controller during resume\n");
return ret;
}
/* take controller out of reset */
ret = hda_dsp_ctrl_link_reset(sdev, false);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to ready controller during resume\n");
return ret;
}
/* enable hda bus irq */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN);
hda_dsp_ctrl_misc_clock_gating(sdev, true);
/* enable ppcap interrupt */
hda_dsp_ctrl_ppcap_enable(sdev, true);
hda_dsp_ctrl_ppcap_int_enable(sdev, true);
#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
/* turn off the links that were off before suspend */
list_for_each_entry(hlink, &bus->hlink_list, list) {
if (!hlink->ref_count)
snd_hdac_ext_bus_link_power_down(hlink);
}
/* check dma status and clean up CORB/RIRB buffers */
if (!bus->cmd_dma_state)
snd_hdac_bus_stop_cmd_io(bus);
#endif
return 0;
}
int hda_dsp_resume(struct snd_sof_dev *sdev)
{
/* init hda controller. DSP cores will be powered up during fw boot */
return hda_resume(sdev);
}
int hda_dsp_runtime_resume(struct snd_sof_dev *sdev)
{
/* init hda controller. DSP cores will be powered up during fw boot */
return hda_resume(sdev);
}
int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev, int state)
{
/* stop hda controller and power dsp off */
return hda_suspend(sdev, state);
}
int hda_dsp_suspend(struct snd_sof_dev *sdev, int state)
{
struct hdac_bus *bus = sof_to_bus(sdev);
int ret;
/* stop hda controller and power dsp off */
ret = hda_suspend(sdev, state);
if (ret < 0) {
dev_err(bus->dev, "error: suspending dsp\n");
return ret;
}
return 0;
}
void hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
struct sof_intel_hda_stream *hda_stream;
struct hdac_ext_stream *stream;
struct hdac_stream *s;
/* set internal flag for BE */
list_for_each_entry(s, &bus->stream_list, list) {
stream = stream_to_hdac_ext_stream(s);
hda_stream = container_of(stream, struct sof_intel_hda_stream,
hda_stream);
hda_stream->hw_params_upon_resume = 1;
}
}