1
0
Fork 0

Merge remote-tracking branches 'asoc/topic/fsl-spdif', 'asoc/topic/img' and 'asoc/topic/intel' into asoc-next

hifive-unleashed-5.1
Mark Brown 2015-12-23 00:23:43 +00:00
commit b9546d09b1
61 changed files with 7028 additions and 776 deletions

View File

@ -27,6 +27,11 @@ Required properties:
Transceiver Clock Diagram" of SoC reference manual.
It can also be referred to TxClk_Source bit of
register SPDIF_STC.
"spba" The spba clock is required when SPDIF is placed as a
bus slave of the Shared Peripheral Bus and when two
or more bus masters (CPU, DMA or DSP) try to access
it. This property is optional depending on the SoC
design.
- big-endian : If this property is absent, the native endian mode
will be in use as default, or the big endian mode

View File

@ -0,0 +1,47 @@
Imagination Technologies I2S Input Controller
Required Properties:
- compatible : Compatible list, must contain "img,i2s-in"
- #sound-dai-cells : Must be equal to 0
- reg : Offset and length of the register set for the device
- clocks : Contains an entry for each entry in clock-names
- clock-names : Must include the following entry:
"sys" The system clock
- dmas: Contains an entry for each entry in dma-names.
- dma-names: Must include the following entry:
"rx" Single DMA channel used by all active I2S channels
- img,i2s-channels : Number of I2S channels instantiated in the I2S in block
Optional Properties:
- interrupts : Contains the I2S in interrupts. Depending on
the configuration, there may be no interrupts, one interrupt,
or an interrupt per I2S channel. For the case where there is
one interrupt per channel, the interrupts should be listed
in ascending channel order
- resets: Contains a phandle to the I2S in reset signal
- reset-names: Contains the reset signal name "rst"
Example:
i2s_in: i2s-in@18100800 {
compatible = "img,i2s-in";
reg = <0x18100800 0x200>;
interrupts = <GIC_SHARED 7 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&mdc 30 0xffffffff 0>;
dma-names = "rx";
clocks = <&cr_periph SYS_CLK_I2S_IN>;
clock-names = "sys";
img,i2s-channels = <6>;
#sound-dai-cells = <0>;
};

View File

@ -0,0 +1,51 @@
Imagination Technologies I2S Output Controller
Required Properties:
- compatible : Compatible list, must contain "img,i2s-out"
- #sound-dai-cells : Must be equal to 0
- reg : Offset and length of the register set for the device
- clocks : Contains an entry for each entry in clock-names
- clock-names : Must include the following entries:
"sys" The system clock
"ref" The reference clock
- dmas: Contains an entry for each entry in dma-names.
- dma-names: Must include the following entry:
"tx" Single DMA channel used by all active I2S channels
- img,i2s-channels : Number of I2S channels instantiated in the I2S out block
- resets: Contains a phandle to the I2S out reset signal
- reset-names: Contains the reset signal name "rst"
Optional Properties:
- interrupts : Contains the I2S out interrupts. Depending on
the configuration, there may be no interrupts, one interrupt,
or an interrupt per I2S channel. For the case where there is
one interrupt per channel, the interrupts should be listed
in ascending channel order
Example:
i2s_out: i2s-out@18100A00 {
compatible = "img,i2s-out";
reg = <0x18100A00 0x200>;
interrupts = <GIC_SHARED 13 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&mdc 23 0xffffffff 0>;
dma-names = "tx";
clocks = <&cr_periph SYS_CLK_I2S_OUT>,
<&clk_core CLK_I2S>;
clock-names = "sys", "ref";
img,i2s-channels = <6>;
resets = <&pistachio_reset PISTACHIO_RESET_I2S_OUT>;
reset-names = "rst";
#sound-dai-cells = <0>;
};

View File

@ -0,0 +1,44 @@
Imagination Technologies Parallel Output Controller
Required Properties:
- compatible : Compatible list, must contain "img,parallel-out".
- #sound-dai-cells : Must be equal to 0
- reg : Offset and length of the register set for the device.
- dmas: Contains an entry for each entry in dma-names.
- dma-names: Must include the following entry:
"tx"
- clocks : Contains an entry for each entry in clock-names.
- clock-names : Includes the following entries:
"sys" The system clock
"ref" The reference clock
- resets: Contains a phandle to the parallel out reset signal
- reset-names: Contains the reset signal name "rst"
Optional Properties:
- interrupts : Contains the parallel out interrupt, if present
Example:
parallel_out: parallel-out@18100C00 {
compatible = "img,parallel-out";
reg = <0x18100C00 0x100>;
interrupts = <GIC_SHARED 19 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&mdc 16 0xffffffff 0>;
dma-names = "tx";
clocks = <&cr_periph SYS_CLK_PAUD_OUT>,
<&clk_core CLK_AUDIO_DAC>;
clock-names = "sys", "ref";
resets = <&pistachio_reset PISTACHIO_RESET_PRL_OUT>;
reset-names = "rst";
#sound-dai-cells = <0>;
};

View File

@ -0,0 +1,18 @@
Pistachio internal DAC DT bindings
Required properties:
- compatible: "img,pistachio-internal-dac"
- img,cr-top : Must contain a phandle to the top level control syscon
node which contains the internal dac control registers
- VDD-supply : Digital power supply regulator (+1.8V or +3.3V)
Examples:
internal_dac: internal-dac {
compatible = "img,pistachio-internal-dac";
img,cr-top = <&cr_top>;
VDD-supply = <&supply3v3>;
};

View File

@ -0,0 +1,41 @@
Imagination Technologies SPDIF Input Controller
Required Properties:
- compatible : Compatible list, must contain "img,spdif-in"
- #sound-dai-cells : Must be equal to 0
- reg : Offset and length of the register set for the device
- dmas: Contains an entry for each entry in dma-names.
- dma-names: Must include the following entry:
"rx"
- clocks : Contains an entry for each entry in clock-names
- clock-names : Includes the following entries:
"sys" The system clock
Optional Properties:
- resets: Should contain a phandle to the spdif in reset signal, if any
- reset-names: Should contain the reset signal name "rst", if a
reset phandle is given
- interrupts : Contains the spdif in interrupt, if present
Example:
spdif_in: spdif-in@18100E00 {
compatible = "img,spdif-in";
reg = <0x18100E00 0x100>;
interrupts = <GIC_SHARED 20 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&mdc 15 0xffffffff 0>;
dma-names = "rx";
clocks = <&cr_periph SYS_CLK_SPDIF_IN>;
clock-names = "sys";
#sound-dai-cells = <0>;
};

View File

@ -0,0 +1,44 @@
Imagination Technologies SPDIF Output Controller
Required Properties:
- compatible : Compatible list, must contain "img,spdif-out"
- #sound-dai-cells : Must be equal to 0
- reg : Offset and length of the register set for the device
- dmas: Contains an entry for each entry in dma-names.
- dma-names: Must include the following entry:
"tx"
- clocks : Contains an entry for each entry in clock-names.
- clock-names : Includes the following entries:
"sys" The system clock
"ref" The reference clock
- resets: Contains a phandle to the spdif out reset signal
- reset-names: Contains the reset signal name "rst"
Optional Properties:
- interrupts : Contains the parallel out interrupt, if present
Example:
spdif_out: spdif-out@18100D00 {
compatible = "img,spdif-out";
reg = <0x18100D00 0x100>;
interrupts = <GIC_SHARED 21 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&mdc 14 0xffffffff 0>;
dma-names = "tx";
clocks = <&cr_periph SYS_CLK_SPDIF_OUT>,
<&clk_core CLK_SPDIF>;
clock-names = "sys", "ref";
resets = <&pistachio_reset PISTACHIO_RESET_SPDIF_OUT>;
reset-names = "rst";
#sound-dai-cells = <0>;
};

View File

@ -0,0 +1,49 @@
The Imagination Technologies SPDIF Input controller contains the following
controls:
name='IEC958 Capture Mask',index=0
This control returns a mask that shows which of the IEC958 status bits
can be read using the 'IEC958 Capture Default' control.
name='IEC958 Capture Default',index=0
This control returns the status bits contained within the SPDIF stream that
is being received. The 'IEC958 Capture Mask' shows which bits can be read
from this control.
name='SPDIF In Multi Frequency Acquire',index=0
name='SPDIF In Multi Frequency Acquire',index=1
name='SPDIF In Multi Frequency Acquire',index=2
name='SPDIF In Multi Frequency Acquire',index=3
This control is used to attempt acquisition of up to four different sample
rates. The active rate can be obtained by reading the 'SPDIF In Lock Frequency'
control.
When the value of this control is set to {0,0,0,0}, the rate given to hw_params
will determine the single rate the block will capture. Else, the rate given to
hw_params will be ignored, and the block will attempt capture for each of the
four sample rates set here.
If less than four rates are required, the same rate can be specified more than
once
name='SPDIF In Lock Frequency',index=0
This control returns the active capture rate, or 0 if a lock has not been
acquired
name='SPDIF In Lock TRK',index=0
This control is used to modify the locking/jitter rejection characteristics
of the block. Larger values increase the locking range, but reduce jitter
rejection.
name='SPDIF In Lock Acquire Threshold',index=0
This control is used to change the threshold at which a lock is acquired.
name='SPDIF In Lock Release Threshold',index=0
This control is used to change the threshold at which a lock is released.

View File

@ -55,6 +55,7 @@ enum sst_audio_device_id_mrfld {
PIPE_MEDIA0_IN = 0x8F,
PIPE_MEDIA1_IN = 0x90,
PIPE_MEDIA2_IN = 0x91,
PIPE_MEDIA3_IN = 0x9C,
PIPE_RSVD = 0xFF,
};

View File

@ -186,9 +186,15 @@ struct hdac_ext_device {
/* codec ops */
struct hdac_ext_codec_ops ops;
struct snd_card *card;
void *scodec;
void *private_data;
};
struct hdac_ext_dma_params {
u32 format;
u8 stream_tag;
};
#define to_ehdac_device(dev) (container_of((dev), \
struct hdac_ext_device, hdac))
/*

View File

@ -50,6 +50,7 @@ source "sound/soc/jz4740/Kconfig"
source "sound/soc/nuc900/Kconfig"
source "sound/soc/omap/Kconfig"
source "sound/soc/kirkwood/Kconfig"
source "sound/soc/img/Kconfig"
source "sound/soc/intel/Kconfig"
source "sound/soc/mediatek/Kconfig"
source "sound/soc/mxs/Kconfig"

View File

@ -27,6 +27,7 @@ obj-$(CONFIG_SND_SOC) += davinci/
obj-$(CONFIG_SND_SOC) += dwc/
obj-$(CONFIG_SND_SOC) += fsl/
obj-$(CONFIG_SND_SOC) += jz4740/
obj-$(CONFIG_SND_SOC) += img/
obj-$(CONFIG_SND_SOC) += intel/
obj-$(CONFIG_SND_SOC) += mediatek/
obj-$(CONFIG_SND_SOC) += mxs/

View File

@ -68,6 +68,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_ES8328_SPI if SPI_MASTER
select SND_SOC_ES8328_I2C if I2C
select SND_SOC_GTM601
select SND_SOC_HDAC_HDMI
select SND_SOC_ICS43432
select SND_SOC_ISABELLE if I2C
select SND_SOC_JZ4740_CODEC
@ -483,6 +484,11 @@ config SND_SOC_ES8328_SPI
config SND_SOC_GTM601
tristate 'GTM601 UMTS modem audio codec'
config SND_SOC_HDAC_HDMI
tristate
select SND_HDA_EXT_CORE
select HDMI
config SND_SOC_ICS43432
tristate

View File

@ -61,6 +61,7 @@ snd-soc-es8328-objs := es8328.o
snd-soc-es8328-i2c-objs := es8328-i2c.o
snd-soc-es8328-spi-objs := es8328-spi.o
snd-soc-gtm601-objs := gtm601.o
snd-soc-hdac-hdmi-objs := hdac_hdmi.o
snd-soc-ics43432-objs := ics43432.o
snd-soc-isabelle-objs := isabelle.o
snd-soc-jz4740-codec-objs := jz4740.o
@ -261,6 +262,7 @@ obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o
obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o
obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o
obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o
obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o
obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o

View File

@ -0,0 +1,659 @@
/*
* hdac_hdmi.c - ASoc HDA-HDMI codec driver for Intel platforms
*
* Copyright (C) 2014-2015 Intel Corp
* Author: Samreen Nilofer <samreen.nilofer@intel.com>
* 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/delay.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/hdmi.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_i915.h>
#include "../../hda/local.h"
#define AMP_OUT_MUTE 0xb080
#define AMP_OUT_UNMUTE 0xb000
#define PIN_OUT (AC_PINCTL_OUT_EN)
#define HDA_MAX_CONNECTIONS 32
struct hdac_hdmi_cvt_params {
unsigned int channels_min;
unsigned int channels_max;
u32 rates;
u64 formats;
unsigned int maxbps;
};
struct hdac_hdmi_cvt {
hda_nid_t nid;
struct hdac_hdmi_cvt_params params;
};
struct hdac_hdmi_pin {
hda_nid_t nid;
int num_mux_nids;
hda_nid_t mux_nids[HDA_MAX_CONNECTIONS];
};
struct hdac_hdmi_dai_pin_map {
int dai_id;
struct hdac_hdmi_pin pin;
struct hdac_hdmi_cvt cvt;
};
struct hdac_hdmi_priv {
hda_nid_t pin_nid[3];
hda_nid_t cvt_nid[3];
struct hdac_hdmi_dai_pin_map dai_map[3];
};
static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev)
{
struct hdac_device *hdac = container_of(dev, struct hdac_device, dev);
return container_of(hdac, struct hdac_ext_device, hdac);
}
static int hdac_hdmi_setup_stream(struct hdac_ext_device *hdac,
hda_nid_t cvt_nid, hda_nid_t pin_nid,
u32 stream_tag, int format)
{
unsigned int val;
dev_dbg(&hdac->hdac.dev, "cvt nid %d pnid %d stream %d format 0x%x\n",
cvt_nid, pin_nid, stream_tag, format);
val = (stream_tag << 4);
snd_hdac_codec_write(&hdac->hdac, cvt_nid, 0,
AC_VERB_SET_CHANNEL_STREAMID, val);
snd_hdac_codec_write(&hdac->hdac, cvt_nid, 0,
AC_VERB_SET_STREAM_FORMAT, format);
return 0;
}
static void
hdac_hdmi_set_dip_index(struct hdac_ext_device *hdac, hda_nid_t pin_nid,
int packet_index, int byte_index)
{
int val;
val = (packet_index << 5) | (byte_index & 0x1f);
snd_hdac_codec_write(&hdac->hdac, pin_nid, 0,
AC_VERB_SET_HDMI_DIP_INDEX, val);
}
static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac,
hda_nid_t cvt_nid, hda_nid_t pin_nid)
{
uint8_t buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AUDIO_INFOFRAME_SIZE];
struct hdmi_audio_infoframe frame;
u8 *dip = (u8 *)&frame;
int ret;
int i;
hdmi_audio_infoframe_init(&frame);
/* Default stereo for now */
frame.channels = 2;
/* setup channel count */
snd_hdac_codec_write(&hdac->hdac, cvt_nid, 0,
AC_VERB_SET_CVT_CHAN_COUNT, frame.channels - 1);
ret = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
if (ret < 0)
return ret;
/* stop infoframe transmission */
hdac_hdmi_set_dip_index(hdac, pin_nid, 0x0, 0x0);
snd_hdac_codec_write(&hdac->hdac, pin_nid, 0,
AC_VERB_SET_HDMI_DIP_XMIT, AC_DIPXMIT_DISABLE);
/* Fill infoframe. Index auto-incremented */
hdac_hdmi_set_dip_index(hdac, pin_nid, 0x0, 0x0);
for (i = 0; i < sizeof(frame); i++)
snd_hdac_codec_write(&hdac->hdac, pin_nid, 0,
AC_VERB_SET_HDMI_DIP_DATA, dip[i]);
/* Start infoframe */
hdac_hdmi_set_dip_index(hdac, pin_nid, 0x0, 0x0);
snd_hdac_codec_write(&hdac->hdac, pin_nid, 0,
AC_VERB_SET_HDMI_DIP_XMIT, AC_DIPXMIT_BEST);
return 0;
}
static void hdac_hdmi_set_power_state(struct hdac_ext_device *edev,
struct hdac_hdmi_dai_pin_map *dai_map, unsigned int pwr_state)
{
/* Power up pin widget */
if (!snd_hdac_check_power_state(&edev->hdac, dai_map->pin.nid, pwr_state))
snd_hdac_codec_write(&edev->hdac, dai_map->pin.nid, 0,
AC_VERB_SET_POWER_STATE, pwr_state);
/* Power up converter */
if (!snd_hdac_check_power_state(&edev->hdac, dai_map->cvt.nid, pwr_state))
snd_hdac_codec_write(&edev->hdac, dai_map->cvt.nid, 0,
AC_VERB_SET_POWER_STATE, pwr_state);
}
static int hdac_hdmi_playback_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai);
struct hdac_hdmi_priv *hdmi = hdac->private_data;
struct hdac_hdmi_dai_pin_map *dai_map;
struct hdac_ext_dma_params *dd;
int ret;
if (dai->id > 0) {
dev_err(&hdac->hdac.dev, "Only one dai supported as of now\n");
return -ENODEV;
}
dai_map = &hdmi->dai_map[dai->id];
dd = (struct hdac_ext_dma_params *)snd_soc_dai_get_dma_data(dai, substream);
dev_dbg(&hdac->hdac.dev, "stream tag from cpu dai %d format in cvt 0x%x\n",
dd->stream_tag, dd->format);
ret = hdac_hdmi_setup_audio_infoframe(hdac, dai_map->cvt.nid,
dai_map->pin.nid);
if (ret < 0)
return ret;
return hdac_hdmi_setup_stream(hdac, dai_map->cvt.nid, dai_map->pin.nid,
dd->stream_tag, dd->format);
}
static int hdac_hdmi_set_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hparams, struct snd_soc_dai *dai)
{
struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai);
struct hdac_ext_dma_params *dd;
if (dai->id > 0) {
dev_err(&hdac->hdac.dev, "Only one dai supported as of now\n");
return -ENODEV;
}
dd = kzalloc(sizeof(*dd), GFP_KERNEL);
if (!dd)
return -ENOMEM;
dd->format = snd_hdac_calc_stream_format(params_rate(hparams),
params_channels(hparams), params_format(hparams),
24, 0);
snd_soc_dai_set_dma_data(dai, substream, (void *)dd);
return 0;
}
static int hdac_hdmi_playback_cleanup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct hdac_ext_device *edev = snd_soc_dai_get_drvdata(dai);
struct hdac_ext_dma_params *dd;
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_dai_pin_map *dai_map;
dai_map = &hdmi->dai_map[dai->id];
snd_hdac_codec_write(&edev->hdac, dai_map->cvt.nid, 0,
AC_VERB_SET_CHANNEL_STREAMID, 0);
snd_hdac_codec_write(&edev->hdac, dai_map->cvt.nid, 0,
AC_VERB_SET_STREAM_FORMAT, 0);
dd = (struct hdac_ext_dma_params *)snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
kfree(dd);
return 0;
}
static int hdac_hdmi_pcm_open(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai);
struct hdac_hdmi_priv *hdmi = hdac->private_data;
struct hdac_hdmi_dai_pin_map *dai_map;
int val;
if (dai->id > 0) {
dev_err(&hdac->hdac.dev, "Only one dai supported as of now\n");
return -ENODEV;
}
dai_map = &hdmi->dai_map[dai->id];
val = snd_hdac_codec_read(&hdac->hdac, dai_map->pin.nid, 0,
AC_VERB_GET_PIN_SENSE, 0);
dev_info(&hdac->hdac.dev, "Val for AC_VERB_GET_PIN_SENSE: %x\n", val);
if ((!(val & AC_PINSENSE_PRESENCE)) || (!(val & AC_PINSENSE_ELDV))) {
dev_err(&hdac->hdac.dev, "Monitor presence invalid with val: %x\n", val);
return -ENODEV;
}
hdac_hdmi_set_power_state(hdac, dai_map, AC_PWRST_D0);
snd_hdac_codec_write(&hdac->hdac, dai_map->pin.nid, 0,
AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS, 2);
return 0;
}
static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai);
struct hdac_hdmi_priv *hdmi = hdac->private_data;
struct hdac_hdmi_dai_pin_map *dai_map;
dai_map = &hdmi->dai_map[dai->id];
hdac_hdmi_set_power_state(hdac, dai_map, AC_PWRST_D3);
snd_hdac_codec_write(&hdac->hdac, dai_map->pin.nid, 0,
AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
}
static int
hdac_hdmi_query_cvt_params(struct hdac_device *hdac, struct hdac_hdmi_cvt *cvt)
{
int err;
/* Only stereo supported as of now */
cvt->params.channels_min = cvt->params.channels_max = 2;
err = snd_hdac_query_supported_pcm(hdac, cvt->nid,
&cvt->params.rates,
&cvt->params.formats,
&cvt->params.maxbps);
if (err < 0)
dev_err(&hdac->dev,
"Failed to query pcm params for nid %d: %d\n",
cvt->nid, err);
return err;
}
static int hdac_hdmi_query_pin_connlist(struct hdac_ext_device *hdac,
struct hdac_hdmi_pin *pin)
{
if (!(get_wcaps(&hdac->hdac, pin->nid) & AC_WCAP_CONN_LIST)) {
dev_warn(&hdac->hdac.dev,
"HDMI: pin %d wcaps %#x does not support connection list\n",
pin->nid, get_wcaps(&hdac->hdac, pin->nid));
return -EINVAL;
}
pin->num_mux_nids = snd_hdac_get_connections(&hdac->hdac, pin->nid,
pin->mux_nids, HDA_MAX_CONNECTIONS);
if (pin->num_mux_nids == 0) {
dev_err(&hdac->hdac.dev, "No connections found\n");
return -ENODEV;
}
return pin->num_mux_nids;
}
static void hdac_hdmi_fill_widget_info(struct snd_soc_dapm_widget *w,
enum snd_soc_dapm_type id,
const char *wname, const char *stream)
{
w->id = id;
w->name = wname;
w->sname = stream;
w->reg = SND_SOC_NOPM;
w->shift = 0;
w->kcontrol_news = NULL;
w->num_kcontrols = 0;
w->priv = NULL;
}
static void hdac_hdmi_fill_route(struct snd_soc_dapm_route *route,
const char *sink, const char *control, const char *src)
{
route->sink = sink;
route->source = src;
route->control = control;
route->connected = NULL;
}
static void create_fill_widget_route_map(struct snd_soc_dapm_context *dapm,
struct hdac_hdmi_dai_pin_map *dai_map)
{
struct snd_soc_dapm_route route[1];
struct snd_soc_dapm_widget widgets[2] = { {0} };
memset(&route, 0, sizeof(route));
hdac_hdmi_fill_widget_info(&widgets[0], snd_soc_dapm_output,
"hif1 Output", NULL);
hdac_hdmi_fill_widget_info(&widgets[1], snd_soc_dapm_aif_in,
"Coverter 1", "hif1");
hdac_hdmi_fill_route(&route[0], "hif1 Output", NULL, "Coverter 1");
snd_soc_dapm_new_controls(dapm, widgets, ARRAY_SIZE(widgets));
snd_soc_dapm_add_routes(dapm, route, ARRAY_SIZE(route));
}
static int hdac_hdmi_init_dai_map(struct hdac_ext_device *edev,
struct hdac_hdmi_dai_pin_map *dai_map,
hda_nid_t pin_nid, hda_nid_t cvt_nid, int dai_id)
{
int ret;
dai_map->dai_id = dai_id;
dai_map->pin.nid = pin_nid;
ret = hdac_hdmi_query_pin_connlist(edev, &dai_map->pin);
if (ret < 0) {
dev_err(&edev->hdac.dev,
"Error querying connection list: %d\n", ret);
return ret;
}
dai_map->cvt.nid = cvt_nid;
/* Enable out path for this pin widget */
snd_hdac_codec_write(&edev->hdac, pin_nid, 0,
AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
/* Enable transmission */
snd_hdac_codec_write(&edev->hdac, cvt_nid, 0,
AC_VERB_SET_DIGI_CONVERT_1, 1);
/* Category Code (CC) to zero */
snd_hdac_codec_write(&edev->hdac, cvt_nid, 0,
AC_VERB_SET_DIGI_CONVERT_2, 0);
snd_hdac_codec_write(&edev->hdac, pin_nid, 0,
AC_VERB_SET_CONNECT_SEL, 0);
return hdac_hdmi_query_cvt_params(&edev->hdac, &dai_map->cvt);
}
/*
* Parse all nodes and store the cvt/pin nids in array
* Add one time initialization for pin and cvt widgets
*/
static int hdac_hdmi_parse_and_map_nid(struct hdac_ext_device *edev)
{
hda_nid_t nid;
int i, num_nodes;
struct hdac_device *hdac = &edev->hdac;
struct hdac_hdmi_priv *hdmi = edev->private_data;
int cvt_nid = 0, pin_nid = 0;
num_nodes = snd_hdac_get_sub_nodes(hdac, hdac->afg, &nid);
if (!nid || num_nodes < 0) {
dev_warn(&hdac->dev, "HDMI: failed to get afg sub nodes\n");
return -EINVAL;
}
hdac->num_nodes = num_nodes;
hdac->start_nid = nid;
for (i = 0; i < hdac->num_nodes; i++, nid++) {
unsigned int caps;
unsigned int type;
caps = get_wcaps(hdac, nid);
type = get_wcaps_type(caps);
if (!(caps & AC_WCAP_DIGITAL))
continue;
switch (type) {
case AC_WID_AUD_OUT:
hdmi->cvt_nid[cvt_nid] = nid;
cvt_nid++;
break;
case AC_WID_PIN:
hdmi->pin_nid[pin_nid] = nid;
pin_nid++;
break;
}
}
hdac->end_nid = nid;
if (!pin_nid || !cvt_nid)
return -EIO;
/*
* Currently on board only 1 pin and 1 converter is enabled for
* simplification, more will be added eventually
* So using fixed map for dai_id:pin:cvt
*/
return hdac_hdmi_init_dai_map(edev, &hdmi->dai_map[0], hdmi->pin_nid[0],
hdmi->cvt_nid[0], 0);
}
static int hdmi_codec_probe(struct snd_soc_codec *codec)
{
struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec);
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct snd_soc_dapm_context *dapm =
snd_soc_component_get_dapm(&codec->component);
edev->scodec = codec;
create_fill_widget_route_map(dapm, &hdmi->dai_map[0]);
/* Imp: Store the card pointer in hda_codec */
edev->card = dapm->card->snd_card;
/*
* hdac_device core already sets the state to active and calls
* get_noresume. So enable runtime and set the device to suspend.
*/
pm_runtime_enable(&edev->hdac.dev);
pm_runtime_put(&edev->hdac.dev);
pm_runtime_suspend(&edev->hdac.dev);
return 0;
}
static int hdmi_codec_remove(struct snd_soc_codec *codec)
{
struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec);
pm_runtime_disable(&edev->hdac.dev);
return 0;
}
static struct snd_soc_codec_driver hdmi_hda_codec = {
.probe = hdmi_codec_probe,
.remove = hdmi_codec_remove,
.idle_bias_off = true,
};
static struct snd_soc_dai_ops hdmi_dai_ops = {
.startup = hdac_hdmi_pcm_open,
.shutdown = hdac_hdmi_pcm_close,
.hw_params = hdac_hdmi_set_hw_params,
.prepare = hdac_hdmi_playback_prepare,
.hw_free = hdac_hdmi_playback_cleanup,
};
static struct snd_soc_dai_driver hdmi_dais[] = {
{ .name = "intel-hdmi-hif1",
.playback = {
.stream_name = "hif1",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &hdmi_dai_ops,
},
};
static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
{
struct hdac_device *codec = &edev->hdac;
struct hdac_hdmi_priv *hdmi_priv;
int ret = 0;
hdmi_priv = devm_kzalloc(&codec->dev, sizeof(*hdmi_priv), GFP_KERNEL);
if (hdmi_priv == NULL)
return -ENOMEM;
edev->private_data = hdmi_priv;
dev_set_drvdata(&codec->dev, edev);
ret = hdac_hdmi_parse_and_map_nid(edev);
if (ret < 0)
return ret;
/* ASoC specific initialization */
return snd_soc_register_codec(&codec->dev, &hdmi_hda_codec,
hdmi_dais, ARRAY_SIZE(hdmi_dais));
}
static int hdac_hdmi_dev_remove(struct hdac_ext_device *edev)
{
snd_soc_unregister_codec(&edev->hdac.dev);
return 0;
}
#ifdef CONFIG_PM
static int hdac_hdmi_runtime_suspend(struct device *dev)
{
struct hdac_ext_device *edev = to_hda_ext_device(dev);
struct hdac_device *hdac = &edev->hdac;
struct hdac_bus *bus = hdac->bus;
int err;
dev_dbg(dev, "Enter: %s\n", __func__);
/* controller may not have been initialized for the first time */
if (!bus)
return 0;
/* Power down afg */
if (!snd_hdac_check_power_state(hdac, hdac->afg, AC_PWRST_D3))
snd_hdac_codec_write(hdac, hdac->afg, 0,
AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
err = snd_hdac_display_power(bus, false);
if (err < 0) {
dev_err(bus->dev, "Cannot turn on display power on i915\n");
return err;
}
return 0;
}
static int hdac_hdmi_runtime_resume(struct device *dev)
{
struct hdac_ext_device *edev = to_hda_ext_device(dev);
struct hdac_device *hdac = &edev->hdac;
struct hdac_bus *bus = hdac->bus;
int err;
dev_dbg(dev, "Enter: %s\n", __func__);
/* controller may not have been initialized for the first time */
if (!bus)
return 0;
err = snd_hdac_display_power(bus, true);
if (err < 0) {
dev_err(bus->dev, "Cannot turn on display power on i915\n");
return err;
}
/* Power up afg */
if (!snd_hdac_check_power_state(hdac, hdac->afg, AC_PWRST_D0))
snd_hdac_codec_write(hdac, hdac->afg, 0,
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
return 0;
}
#else
#define hdac_hdmi_runtime_suspend NULL
#define hdac_hdmi_runtime_resume NULL
#endif
static const struct dev_pm_ops hdac_hdmi_pm = {
SET_RUNTIME_PM_OPS(hdac_hdmi_runtime_suspend, hdac_hdmi_runtime_resume, NULL)
};
static const struct hda_device_id hdmi_list[] = {
HDA_CODEC_EXT_ENTRY(0x80862809, 0x100000, "Skylake HDMI", 0),
{}
};
MODULE_DEVICE_TABLE(hdaudio, hdmi_list);
static struct hdac_ext_driver hdmi_driver = {
. hdac = {
.driver = {
.name = "HDMI HDA Codec",
.pm = &hdac_hdmi_pm,
},
.id_table = hdmi_list,
},
.probe = hdac_hdmi_dev_probe,
.remove = hdac_hdmi_dev_remove,
};
static int __init hdmi_init(void)
{
return snd_hda_ext_driver_register(&hdmi_driver);
}
static void __exit hdmi_exit(void)
{
snd_hda_ext_driver_unregister(&hdmi_driver);
}
module_init(hdmi_init);
module_exit(hdmi_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("HDMI HD codec");
MODULE_AUTHOR("Samreen Nilofer<samreen.nilofer@intel.com>");
MODULE_AUTHOR("Subhransu S. Prusty<subhransu.s.prusty@intel.com>");

View File

@ -88,6 +88,7 @@ struct spdif_mixer_control {
* @rxclk: rx clock sources for capture
* @coreclk: core clock for register access via DMA
* @sysclk: system clock for rx clock rate measurement
* @spbaclk: SPBA clock (optional, depending on SoC design)
* @dma_params_tx: DMA parameters for transmit channel
* @dma_params_rx: DMA parameters for receive channel
*/
@ -106,6 +107,7 @@ struct fsl_spdif_priv {
struct clk *rxclk;
struct clk *coreclk;
struct clk *sysclk;
struct clk *spbaclk;
struct snd_dmaengine_dai_dma_data dma_params_tx;
struct snd_dmaengine_dai_dma_data dma_params_rx;
/* regcache for SRPC */
@ -474,6 +476,14 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream,
return ret;
}
if (!IS_ERR(spdif_priv->spbaclk)) {
ret = clk_prepare_enable(spdif_priv->spbaclk);
if (ret) {
dev_err(&pdev->dev, "failed to enable spba clock\n");
goto err_spbaclk;
}
}
ret = spdif_softreset(spdif_priv);
if (ret) {
dev_err(&pdev->dev, "failed to soft reset\n");
@ -515,6 +525,9 @@ disable_txclk:
for (i--; i >= 0; i--)
clk_disable_unprepare(spdif_priv->txclk[i]);
err:
if (!IS_ERR(spdif_priv->spbaclk))
clk_disable_unprepare(spdif_priv->spbaclk);
err_spbaclk:
clk_disable_unprepare(spdif_priv->coreclk);
return ret;
@ -548,6 +561,8 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream,
spdif_intr_status_clear(spdif_priv);
regmap_update_bits(regmap, REG_SPDIF_SCR,
SCR_LOW_POWER, SCR_LOW_POWER);
if (!IS_ERR(spdif_priv->spbaclk))
clk_disable_unprepare(spdif_priv->spbaclk);
clk_disable_unprepare(spdif_priv->coreclk);
}
}
@ -1261,6 +1276,10 @@ static int fsl_spdif_probe(struct platform_device *pdev)
return PTR_ERR(spdif_priv->coreclk);
}
spdif_priv->spbaclk = devm_clk_get(&pdev->dev, "spba");
if (IS_ERR(spdif_priv->spbaclk))
dev_warn(&pdev->dev, "no spba clock in devicetree\n");
/* Select clock source for rx/tx clock */
spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1");
if (IS_ERR(spdif_priv->rxclk)) {

View File

@ -0,0 +1,52 @@
config SND_SOC_IMG
bool "Audio support for Imagination Technologies designs"
help
Audio support for Imagination Technologies audio hardware
config SND_SOC_IMG_I2S_IN
tristate "Imagination I2S Input Device Driver"
depends on SND_SOC_IMG
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for I2S in driver for
Imagination Technologies I2S in device.
config SND_SOC_IMG_I2S_OUT
tristate "Imagination I2S Output Device Driver"
depends on SND_SOC_IMG
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for I2S out driver for
Imagination Technologies I2S out device.
config SND_SOC_IMG_PARALLEL_OUT
tristate "Imagination Parallel Output Device Driver"
depends on SND_SOC_IMG
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for parallel out driver for
Imagination Technologies parallel out device.
config SND_SOC_IMG_SPDIF_IN
tristate "Imagination SPDIF Input Device Driver"
depends on SND_SOC_IMG
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for SPDIF input driver for
Imagination Technologies SPDIF input device.
config SND_SOC_IMG_SPDIF_OUT
tristate "Imagination SPDIF Output Device Driver"
depends on SND_SOC_IMG
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y or M if you want to add support for SPDIF out driver for
Imagination Technologies SPDIF out device.
config SND_SOC_IMG_PISTACHIO_INTERNAL_DAC
tristate "Support for Pistachio SoC Internal DAC Driver"
depends on SND_SOC_IMG
help
Say Y or M if you want to add support for Pistachio internal DAC
driver for Imagination Technologies Pistachio internal DAC device.

View File

@ -0,0 +1,7 @@
obj-$(CONFIG_SND_SOC_IMG_I2S_IN) += img-i2s-in.o
obj-$(CONFIG_SND_SOC_IMG_I2S_OUT) += img-i2s-out.o
obj-$(CONFIG_SND_SOC_IMG_PARALLEL_OUT) += img-parallel-out.o
obj-$(CONFIG_SND_SOC_IMG_SPDIF_IN) += img-spdif-in.o
obj-$(CONFIG_SND_SOC_IMG_SPDIF_OUT) += img-spdif-out.o
obj-$(CONFIG_SND_SOC_IMG_PISTACHIO_INTERNAL_DAC) += pistachio-internal-dac.o

View File

@ -0,0 +1,516 @@
/*
* IMG I2S input controller driver
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*
* Author: Damien Horsley <Damien.Horsley@imgtec.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.
*/
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define IMG_I2S_IN_RX_FIFO 0x0
#define IMG_I2S_IN_CTL 0x4
#define IMG_I2S_IN_CTL_ACTIVE_CHAN_MASK 0xfffffffc
#define IMG_I2S_IN_CTL_ACTIVE_CH_SHIFT 2
#define IMG_I2S_IN_CTL_16PACK_MASK BIT(1)
#define IMG_I2S_IN_CTL_ME_MASK BIT(0)
#define IMG_I2S_IN_CH_CTL 0x4
#define IMG_I2S_IN_CH_CTL_CCDEL_MASK 0x38000
#define IMG_I2S_IN_CH_CTL_CCDEL_SHIFT 15
#define IMG_I2S_IN_CH_CTL_FEN_MASK BIT(14)
#define IMG_I2S_IN_CH_CTL_FMODE_MASK BIT(13)
#define IMG_I2S_IN_CH_CTL_16PACK_MASK BIT(12)
#define IMG_I2S_IN_CH_CTL_JUST_MASK BIT(10)
#define IMG_I2S_IN_CH_CTL_PACKH_MASK BIT(9)
#define IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK BIT(8)
#define IMG_I2S_IN_CH_CTL_BLKP_MASK BIT(7)
#define IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK BIT(6)
#define IMG_I2S_IN_CH_CTL_LRD_MASK BIT(3)
#define IMG_I2S_IN_CH_CTL_FW_MASK BIT(2)
#define IMG_I2S_IN_CH_CTL_SW_MASK BIT(1)
#define IMG_I2S_IN_CH_CTL_ME_MASK BIT(0)
#define IMG_I2S_IN_CH_STRIDE 0x20
struct img_i2s_in {
void __iomem *base;
struct clk *clk_sys;
struct snd_dmaengine_dai_dma_data dma_data;
struct device *dev;
unsigned int max_i2s_chan;
void __iomem *channel_base;
unsigned int active_channels;
struct snd_soc_dai_driver dai_driver;
};
static inline void img_i2s_in_writel(struct img_i2s_in *i2s, u32 val, u32 reg)
{
writel(val, i2s->base + reg);
}
static inline u32 img_i2s_in_readl(struct img_i2s_in *i2s, u32 reg)
{
return readl(i2s->base + reg);
}
static inline void img_i2s_in_ch_writel(struct img_i2s_in *i2s, u32 chan,
u32 val, u32 reg)
{
writel(val, i2s->channel_base + (chan * IMG_I2S_IN_CH_STRIDE) + reg);
}
static inline u32 img_i2s_in_ch_readl(struct img_i2s_in *i2s, u32 chan,
u32 reg)
{
return readl(i2s->channel_base + (chan * IMG_I2S_IN_CH_STRIDE) + reg);
}
static inline void img_i2s_in_ch_disable(struct img_i2s_in *i2s, u32 chan)
{
u32 reg;
reg = img_i2s_in_ch_readl(i2s, chan, IMG_I2S_IN_CH_CTL);
reg &= ~IMG_I2S_IN_CH_CTL_ME_MASK;
img_i2s_in_ch_writel(i2s, chan, reg, IMG_I2S_IN_CH_CTL);
}
static inline void img_i2s_in_ch_enable(struct img_i2s_in *i2s, u32 chan)
{
u32 reg;
reg = img_i2s_in_ch_readl(i2s, chan, IMG_I2S_IN_CH_CTL);
reg |= IMG_I2S_IN_CH_CTL_ME_MASK;
img_i2s_in_ch_writel(i2s, chan, reg, IMG_I2S_IN_CH_CTL);
}
static inline void img_i2s_in_disable(struct img_i2s_in *i2s)
{
u32 reg;
reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL);
reg &= ~IMG_I2S_IN_CTL_ME_MASK;
img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL);
}
static inline void img_i2s_in_enable(struct img_i2s_in *i2s)
{
u32 reg;
reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL);
reg |= IMG_I2S_IN_CTL_ME_MASK;
img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL);
}
static inline void img_i2s_in_flush(struct img_i2s_in *i2s)
{
int i;
u32 reg;
for (i = 0; i < i2s->active_channels; i++) {
reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL);
reg |= IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK;
img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL);
reg &= ~IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK;
img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL);
}
}
static int img_i2s_in_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
img_i2s_in_enable(i2s);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
img_i2s_in_disable(i2s);
break;
default:
return -EINVAL;
}
return 0;
}
static int img_i2s_in_check_rate(struct img_i2s_in *i2s,
unsigned int sample_rate, unsigned int frame_size,
unsigned int *bclk_filter_enable,
unsigned int *bclk_filter_value)
{
unsigned int bclk_freq, cur_freq;
bclk_freq = sample_rate * frame_size;
cur_freq = clk_get_rate(i2s->clk_sys);
if (cur_freq >= bclk_freq * 8) {
*bclk_filter_enable = 1;
*bclk_filter_value = 0;
} else if (cur_freq >= bclk_freq * 7) {
*bclk_filter_enable = 1;
*bclk_filter_value = 1;
} else if (cur_freq >= bclk_freq * 6) {
*bclk_filter_enable = 0;
*bclk_filter_value = 0;
} else {
dev_err(i2s->dev,
"Sys clock rate %u insufficient for sample rate %u\n",
cur_freq, sample_rate);
return -EINVAL;
}
return 0;
}
static int img_i2s_in_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int rate, channels, i2s_channels, frame_size;
unsigned int bclk_filter_enable, bclk_filter_value;
int i, ret = 0;
u32 reg, control_mask, chan_control_mask;
u32 control_set = 0, chan_control_set = 0;
snd_pcm_format_t format;
rate = params_rate(params);
format = params_format(params);
channels = params_channels(params);
i2s_channels = channels / 2;
switch (format) {
case SNDRV_PCM_FORMAT_S32_LE:
frame_size = 64;
chan_control_set |= IMG_I2S_IN_CH_CTL_SW_MASK;
chan_control_set |= IMG_I2S_IN_CH_CTL_FW_MASK;
chan_control_set |= IMG_I2S_IN_CH_CTL_PACKH_MASK;
break;
case SNDRV_PCM_FORMAT_S24_LE:
frame_size = 64;
chan_control_set |= IMG_I2S_IN_CH_CTL_SW_MASK;
chan_control_set |= IMG_I2S_IN_CH_CTL_FW_MASK;
break;
case SNDRV_PCM_FORMAT_S16_LE:
frame_size = 32;
control_set |= IMG_I2S_IN_CTL_16PACK_MASK;
chan_control_set |= IMG_I2S_IN_CH_CTL_16PACK_MASK;
break;
default:
return -EINVAL;
}
if ((channels < 2) ||
(channels > (i2s->max_i2s_chan * 2)) ||
(channels % 2))
return -EINVAL;
control_set |= ((i2s_channels - 1) << IMG_I2S_IN_CTL_ACTIVE_CH_SHIFT);
ret = img_i2s_in_check_rate(i2s, rate, frame_size,
&bclk_filter_enable, &bclk_filter_value);
if (ret < 0)
return ret;
if (bclk_filter_enable)
chan_control_set |= IMG_I2S_IN_CH_CTL_FEN_MASK;
if (bclk_filter_value)
chan_control_set |= IMG_I2S_IN_CH_CTL_FMODE_MASK;
control_mask = IMG_I2S_IN_CTL_16PACK_MASK |
IMG_I2S_IN_CTL_ACTIVE_CHAN_MASK;
chan_control_mask = IMG_I2S_IN_CH_CTL_16PACK_MASK |
IMG_I2S_IN_CH_CTL_FEN_MASK |
IMG_I2S_IN_CH_CTL_FMODE_MASK |
IMG_I2S_IN_CH_CTL_SW_MASK |
IMG_I2S_IN_CH_CTL_FW_MASK |
IMG_I2S_IN_CH_CTL_PACKH_MASK;
reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL);
reg = (reg & ~control_mask) | control_set;
img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL);
for (i = 0; i < i2s->active_channels; i++)
img_i2s_in_ch_disable(i2s, i);
for (i = 0; i < i2s->max_i2s_chan; i++) {
reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL);
reg = (reg & ~chan_control_mask) | chan_control_set;
img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL);
}
i2s->active_channels = i2s_channels;
img_i2s_in_flush(i2s);
for (i = 0; i < i2s->active_channels; i++)
img_i2s_in_ch_enable(i2s, i);
return 0;
}
static int img_i2s_in_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai);
int i;
u32 chan_control_mask, lrd_set = 0, blkp_set = 0, chan_control_set = 0;
u32 reg;
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
lrd_set |= IMG_I2S_IN_CH_CTL_LRD_MASK;
break;
case SND_SOC_DAIFMT_NB_IF:
break;
case SND_SOC_DAIFMT_IB_NF:
lrd_set |= IMG_I2S_IN_CH_CTL_LRD_MASK;
blkp_set |= IMG_I2S_IN_CH_CTL_BLKP_MASK;
break;
case SND_SOC_DAIFMT_IB_IF:
blkp_set |= IMG_I2S_IN_CH_CTL_BLKP_MASK;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
chan_control_set |= IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK;
break;
case SND_SOC_DAIFMT_LEFT_J:
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
break;
default:
return -EINVAL;
}
chan_control_mask = IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK;
for (i = 0; i < i2s->active_channels; i++)
img_i2s_in_ch_disable(i2s, i);
/*
* BLKP and LRD must be set during separate register writes
*/
for (i = 0; i < i2s->max_i2s_chan; i++) {
reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL);
reg = (reg & ~chan_control_mask) | chan_control_set;
img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL);
reg = (reg & ~IMG_I2S_IN_CH_CTL_BLKP_MASK) | blkp_set;
img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL);
reg = (reg & ~IMG_I2S_IN_CH_CTL_LRD_MASK) | lrd_set;
img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL);
}
for (i = 0; i < i2s->active_channels; i++)
img_i2s_in_ch_enable(i2s, i);
return 0;
}
static const struct snd_soc_dai_ops img_i2s_in_dai_ops = {
.trigger = img_i2s_in_trigger,
.hw_params = img_i2s_in_hw_params,
.set_fmt = img_i2s_in_set_fmt
};
static int img_i2s_in_dai_probe(struct snd_soc_dai *dai)
{
struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, NULL, &i2s->dma_data);
return 0;
}
static const struct snd_soc_component_driver img_i2s_in_component = {
.name = "img-i2s-in"
};
static int img_i2s_in_dma_prepare_slave_config(struct snd_pcm_substream *st,
struct snd_pcm_hw_params *params, struct dma_slave_config *sc)
{
unsigned int i2s_channels = params_channels(params) / 2;
struct snd_soc_pcm_runtime *rtd = st->private_data;
struct snd_dmaengine_dai_dma_data *dma_data;
int ret;
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, st);
ret = snd_hwparams_to_dma_slave_config(st, params, sc);
if (ret)
return ret;
sc->src_addr = dma_data->addr;
sc->src_addr_width = dma_data->addr_width;
sc->src_maxburst = 4 * i2s_channels;
return 0;
}
static const struct snd_dmaengine_pcm_config img_i2s_in_dma_config = {
.prepare_slave_config = img_i2s_in_dma_prepare_slave_config
};
static int img_i2s_in_probe(struct platform_device *pdev)
{
struct img_i2s_in *i2s;
struct resource *res;
void __iomem *base;
int ret, i;
struct reset_control *rst;
unsigned int max_i2s_chan_pow_2;
struct device *dev = &pdev->dev;
i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL);
if (!i2s)
return -ENOMEM;
platform_set_drvdata(pdev, i2s);
i2s->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
i2s->base = base;
if (of_property_read_u32(pdev->dev.of_node, "img,i2s-channels",
&i2s->max_i2s_chan)) {
dev_err(dev, "No img,i2s-channels property\n");
return -EINVAL;
}
max_i2s_chan_pow_2 = 1 << get_count_order(i2s->max_i2s_chan);
i2s->channel_base = base + (max_i2s_chan_pow_2 * 0x20);
i2s->clk_sys = devm_clk_get(dev, "sys");
if (IS_ERR(i2s->clk_sys)) {
if (PTR_ERR(i2s->clk_sys) != -EPROBE_DEFER)
dev_err(dev, "Failed to acquire clock 'sys'\n");
return PTR_ERR(i2s->clk_sys);
}
ret = clk_prepare_enable(i2s->clk_sys);
if (ret)
return ret;
i2s->active_channels = 1;
i2s->dma_data.addr = res->start + IMG_I2S_IN_RX_FIFO;
i2s->dma_data.addr_width = 4;
i2s->dai_driver.probe = img_i2s_in_dai_probe;
i2s->dai_driver.capture.channels_min = 2;
i2s->dai_driver.capture.channels_max = i2s->max_i2s_chan * 2;
i2s->dai_driver.capture.rates = SNDRV_PCM_RATE_8000_192000;
i2s->dai_driver.capture.formats = SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE;
i2s->dai_driver.ops = &img_i2s_in_dai_ops;
rst = devm_reset_control_get(dev, "rst");
if (IS_ERR(rst)) {
if (PTR_ERR(rst) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err_clk_disable;
}
dev_dbg(dev, "No top level reset found\n");
img_i2s_in_disable(i2s);
for (i = 0; i < i2s->max_i2s_chan; i++)
img_i2s_in_ch_disable(i2s, i);
} else {
reset_control_assert(rst);
reset_control_deassert(rst);
}
img_i2s_in_writel(i2s, 0, IMG_I2S_IN_CTL);
for (i = 0; i < i2s->max_i2s_chan; i++)
img_i2s_in_ch_writel(i2s, i,
(4 << IMG_I2S_IN_CH_CTL_CCDEL_SHIFT) |
IMG_I2S_IN_CH_CTL_JUST_MASK |
IMG_I2S_IN_CH_CTL_FW_MASK, IMG_I2S_IN_CH_CTL);
ret = devm_snd_soc_register_component(dev, &img_i2s_in_component,
&i2s->dai_driver, 1);
if (ret)
goto err_clk_disable;
ret = devm_snd_dmaengine_pcm_register(dev, &img_i2s_in_dma_config, 0);
if (ret)
goto err_clk_disable;
return 0;
err_clk_disable:
clk_disable_unprepare(i2s->clk_sys);
return ret;
}
static int img_i2s_in_dev_remove(struct platform_device *pdev)
{
struct img_i2s_in *i2s = platform_get_drvdata(pdev);
clk_disable_unprepare(i2s->clk_sys);
return 0;
}
static const struct of_device_id img_i2s_in_of_match[] = {
{ .compatible = "img,i2s-in" },
{}
};
MODULE_DEVICE_TABLE(of, img_i2s_in_of_match);
static struct platform_driver img_i2s_in_driver = {
.driver = {
.name = "img-i2s-in",
.of_match_table = img_i2s_in_of_match
},
.probe = img_i2s_in_probe,
.remove = img_i2s_in_dev_remove
};
module_platform_driver(img_i2s_in_driver);
MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
MODULE_DESCRIPTION("IMG I2S Input Driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,565 @@
/*
* IMG I2S output controller driver
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*
* Author: Damien Horsley <Damien.Horsley@imgtec.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.
*/
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define IMG_I2S_OUT_TX_FIFO 0x0
#define IMG_I2S_OUT_CTL 0x4
#define IMG_I2S_OUT_CTL_DATA_EN_MASK BIT(24)
#define IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK 0xffe000
#define IMG_I2S_OUT_CTL_ACTIVE_CHAN_SHIFT 13
#define IMG_I2S_OUT_CTL_FRM_SIZE_MASK BIT(8)
#define IMG_I2S_OUT_CTL_MASTER_MASK BIT(6)
#define IMG_I2S_OUT_CTL_CLK_MASK BIT(5)
#define IMG_I2S_OUT_CTL_CLK_EN_MASK BIT(4)
#define IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK BIT(3)
#define IMG_I2S_OUT_CTL_BCLK_POL_MASK BIT(2)
#define IMG_I2S_OUT_CTL_ME_MASK BIT(0)
#define IMG_I2S_OUT_CH_CTL 0x4
#define IMG_I2S_OUT_CHAN_CTL_CH_MASK BIT(11)
#define IMG_I2S_OUT_CHAN_CTL_LT_MASK BIT(10)
#define IMG_I2S_OUT_CHAN_CTL_FMT_MASK 0xf0
#define IMG_I2S_OUT_CHAN_CTL_FMT_SHIFT 4
#define IMG_I2S_OUT_CHAN_CTL_JUST_MASK BIT(3)
#define IMG_I2S_OUT_CHAN_CTL_CLKT_MASK BIT(1)
#define IMG_I2S_OUT_CHAN_CTL_ME_MASK BIT(0)
#define IMG_I2S_OUT_CH_STRIDE 0x20
struct img_i2s_out {
void __iomem *base;
struct clk *clk_sys;
struct clk *clk_ref;
struct snd_dmaengine_dai_dma_data dma_data;
struct device *dev;
unsigned int max_i2s_chan;
void __iomem *channel_base;
bool force_clk_active;
unsigned int active_channels;
struct reset_control *rst;
struct snd_soc_dai_driver dai_driver;
};
static int img_i2s_out_suspend(struct device *dev)
{
struct img_i2s_out *i2s = dev_get_drvdata(dev);
if (!i2s->force_clk_active)
clk_disable_unprepare(i2s->clk_ref);
return 0;
}
static int img_i2s_out_resume(struct device *dev)
{
struct img_i2s_out *i2s = dev_get_drvdata(dev);
int ret;
if (!i2s->force_clk_active) {
ret = clk_prepare_enable(i2s->clk_ref);
if (ret) {
dev_err(dev, "clk_enable failed: %d\n", ret);
return ret;
}
}
return 0;
}
static inline void img_i2s_out_writel(struct img_i2s_out *i2s, u32 val,
u32 reg)
{
writel(val, i2s->base + reg);
}
static inline u32 img_i2s_out_readl(struct img_i2s_out *i2s, u32 reg)
{
return readl(i2s->base + reg);
}
static inline void img_i2s_out_ch_writel(struct img_i2s_out *i2s,
u32 chan, u32 val, u32 reg)
{
writel(val, i2s->channel_base + (chan * IMG_I2S_OUT_CH_STRIDE) + reg);
}
static inline u32 img_i2s_out_ch_readl(struct img_i2s_out *i2s, u32 chan,
u32 reg)
{
return readl(i2s->channel_base + (chan * IMG_I2S_OUT_CH_STRIDE) + reg);
}
static inline void img_i2s_out_ch_disable(struct img_i2s_out *i2s, u32 chan)
{
u32 reg;
reg = img_i2s_out_ch_readl(i2s, chan, IMG_I2S_OUT_CH_CTL);
reg &= ~IMG_I2S_OUT_CHAN_CTL_ME_MASK;
img_i2s_out_ch_writel(i2s, chan, reg, IMG_I2S_OUT_CH_CTL);
}
static inline void img_i2s_out_ch_enable(struct img_i2s_out *i2s, u32 chan)
{
u32 reg;
reg = img_i2s_out_ch_readl(i2s, chan, IMG_I2S_OUT_CH_CTL);
reg |= IMG_I2S_OUT_CHAN_CTL_ME_MASK;
img_i2s_out_ch_writel(i2s, chan, reg, IMG_I2S_OUT_CH_CTL);
}
static inline void img_i2s_out_disable(struct img_i2s_out *i2s)
{
u32 reg;
reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL);
reg &= ~IMG_I2S_OUT_CTL_ME_MASK;
img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL);
}
static inline void img_i2s_out_enable(struct img_i2s_out *i2s)
{
u32 reg;
reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL);
reg |= IMG_I2S_OUT_CTL_ME_MASK;
img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL);
}
static void img_i2s_out_reset(struct img_i2s_out *i2s)
{
int i;
u32 core_ctl, chan_ctl;
core_ctl = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL) &
~IMG_I2S_OUT_CTL_ME_MASK &
~IMG_I2S_OUT_CTL_DATA_EN_MASK;
if (!i2s->force_clk_active)
core_ctl &= ~IMG_I2S_OUT_CTL_CLK_EN_MASK;
chan_ctl = img_i2s_out_ch_readl(i2s, 0, IMG_I2S_OUT_CH_CTL) &
~IMG_I2S_OUT_CHAN_CTL_ME_MASK;
reset_control_assert(i2s->rst);
reset_control_deassert(i2s->rst);
for (i = 0; i < i2s->max_i2s_chan; i++)
img_i2s_out_ch_writel(i2s, i, chan_ctl, IMG_I2S_OUT_CH_CTL);
for (i = 0; i < i2s->active_channels; i++)
img_i2s_out_ch_enable(i2s, i);
img_i2s_out_writel(i2s, core_ctl, IMG_I2S_OUT_CTL);
img_i2s_out_enable(i2s);
}
static int img_i2s_out_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai);
u32 reg;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL);
if (!i2s->force_clk_active)
reg |= IMG_I2S_OUT_CTL_CLK_EN_MASK;
reg |= IMG_I2S_OUT_CTL_DATA_EN_MASK;
img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
img_i2s_out_reset(i2s);
break;
default:
return -EINVAL;
}
return 0;
}
static int img_i2s_out_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int channels, i2s_channels;
long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate;
int i;
u32 reg, control_mask, control_set = 0;
snd_pcm_format_t format;
rate = params_rate(params);
format = params_format(params);
channels = params_channels(params);
i2s_channels = channels / 2;
if (format != SNDRV_PCM_FORMAT_S32_LE)
return -EINVAL;
if ((channels < 2) ||
(channels > (i2s->max_i2s_chan * 2)) ||
(channels % 2))
return -EINVAL;
pre_div_a = clk_round_rate(i2s->clk_ref, rate * 256);
if (pre_div_a < 0)
return pre_div_a;
pre_div_b = clk_round_rate(i2s->clk_ref, rate * 384);
if (pre_div_b < 0)
return pre_div_b;
diff_a = abs((pre_div_a / 256) - rate);
diff_b = abs((pre_div_b / 384) - rate);
/* If diffs are equal, use lower clock rate */
if (diff_a > diff_b)
clk_set_rate(i2s->clk_ref, pre_div_b);
else
clk_set_rate(i2s->clk_ref, pre_div_a);
/*
* Another driver (eg alsa machine driver) may have rejected the above
* change. Get the current rate and set the register bit according to
* the new minimum diff
*/
clk_rate = clk_get_rate(i2s->clk_ref);
diff_a = abs((clk_rate / 256) - rate);
diff_b = abs((clk_rate / 384) - rate);
if (diff_a > diff_b)
control_set |= IMG_I2S_OUT_CTL_CLK_MASK;
control_set |= ((i2s_channels - 1) <<
IMG_I2S_OUT_CTL_ACTIVE_CHAN_SHIFT) &
IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK;
control_mask = IMG_I2S_OUT_CTL_CLK_MASK |
IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK;
img_i2s_out_disable(i2s);
reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL);
reg = (reg & ~control_mask) | control_set;
img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL);
for (i = 0; i < i2s_channels; i++)
img_i2s_out_ch_enable(i2s, i);
for (; i < i2s->max_i2s_chan; i++)
img_i2s_out_ch_disable(i2s, i);
img_i2s_out_enable(i2s);
i2s->active_channels = i2s_channels;
return 0;
}
static int img_i2s_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai);
int i;
bool force_clk_active;
u32 chan_control_mask, control_mask, chan_control_set = 0;
u32 reg, control_set = 0;
force_clk_active = ((fmt & SND_SOC_DAIFMT_CLOCK_MASK) ==
SND_SOC_DAIFMT_CONT);
if (force_clk_active)
control_set |= IMG_I2S_OUT_CTL_CLK_EN_MASK;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
break;
case SND_SOC_DAIFMT_CBS_CFS:
control_set |= IMG_I2S_OUT_CTL_MASTER_MASK;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
control_set |= IMG_I2S_OUT_CTL_BCLK_POL_MASK;
break;
case SND_SOC_DAIFMT_NB_IF:
control_set |= IMG_I2S_OUT_CTL_BCLK_POL_MASK;
control_set |= IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK;
break;
case SND_SOC_DAIFMT_IB_NF:
break;
case SND_SOC_DAIFMT_IB_IF:
control_set |= IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
chan_control_set |= IMG_I2S_OUT_CHAN_CTL_CLKT_MASK;
break;
case SND_SOC_DAIFMT_LEFT_J:
break;
default:
return -EINVAL;
}
control_mask = IMG_I2S_OUT_CTL_CLK_EN_MASK |
IMG_I2S_OUT_CTL_MASTER_MASK |
IMG_I2S_OUT_CTL_BCLK_POL_MASK |
IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK;
chan_control_mask = IMG_I2S_OUT_CHAN_CTL_CLKT_MASK;
img_i2s_out_disable(i2s);
reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL);
reg = (reg & ~control_mask) | control_set;
img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL);
for (i = 0; i < i2s->active_channels; i++)
img_i2s_out_ch_disable(i2s, i);
for (i = 0; i < i2s->max_i2s_chan; i++) {
reg = img_i2s_out_ch_readl(i2s, i, IMG_I2S_OUT_CH_CTL);
reg = (reg & ~chan_control_mask) | chan_control_set;
img_i2s_out_ch_writel(i2s, i, reg, IMG_I2S_OUT_CH_CTL);
}
for (i = 0; i < i2s->active_channels; i++)
img_i2s_out_ch_enable(i2s, i);
img_i2s_out_enable(i2s);
i2s->force_clk_active = force_clk_active;
return 0;
}
static const struct snd_soc_dai_ops img_i2s_out_dai_ops = {
.trigger = img_i2s_out_trigger,
.hw_params = img_i2s_out_hw_params,
.set_fmt = img_i2s_out_set_fmt
};
static int img_i2s_out_dai_probe(struct snd_soc_dai *dai)
{
struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &i2s->dma_data, NULL);
return 0;
}
static const struct snd_soc_component_driver img_i2s_out_component = {
.name = "img-i2s-out"
};
static int img_i2s_out_dma_prepare_slave_config(struct snd_pcm_substream *st,
struct snd_pcm_hw_params *params, struct dma_slave_config *sc)
{
unsigned int i2s_channels = params_channels(params) / 2;
struct snd_soc_pcm_runtime *rtd = st->private_data;
struct snd_dmaengine_dai_dma_data *dma_data;
int ret;
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, st);
ret = snd_hwparams_to_dma_slave_config(st, params, sc);
if (ret)
return ret;
sc->dst_addr = dma_data->addr;
sc->dst_addr_width = dma_data->addr_width;
sc->dst_maxburst = 4 * i2s_channels;
return 0;
}
static const struct snd_dmaengine_pcm_config img_i2s_out_dma_config = {
.prepare_slave_config = img_i2s_out_dma_prepare_slave_config
};
static int img_i2s_out_probe(struct platform_device *pdev)
{
struct img_i2s_out *i2s;
struct resource *res;
void __iomem *base;
int i, ret;
unsigned int max_i2s_chan_pow_2;
u32 reg;
struct device *dev = &pdev->dev;
i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
if (!i2s)
return -ENOMEM;
platform_set_drvdata(pdev, i2s);
i2s->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
i2s->base = base;
if (of_property_read_u32(pdev->dev.of_node, "img,i2s-channels",
&i2s->max_i2s_chan)) {
dev_err(&pdev->dev, "No img,i2s-channels property\n");
return -EINVAL;
}
max_i2s_chan_pow_2 = 1 << get_count_order(i2s->max_i2s_chan);
i2s->channel_base = base + (max_i2s_chan_pow_2 * 0x20);
i2s->rst = devm_reset_control_get(&pdev->dev, "rst");
if (IS_ERR(i2s->rst)) {
if (PTR_ERR(i2s->rst) != -EPROBE_DEFER)
dev_err(&pdev->dev, "No top level reset found\n");
return PTR_ERR(i2s->rst);
}
i2s->clk_sys = devm_clk_get(&pdev->dev, "sys");
if (IS_ERR(i2s->clk_sys)) {
if (PTR_ERR(i2s->clk_sys) != -EPROBE_DEFER)
dev_err(dev, "Failed to acquire clock 'sys'\n");
return PTR_ERR(i2s->clk_sys);
}
i2s->clk_ref = devm_clk_get(&pdev->dev, "ref");
if (IS_ERR(i2s->clk_ref)) {
if (PTR_ERR(i2s->clk_ref) != -EPROBE_DEFER)
dev_err(dev, "Failed to acquire clock 'ref'\n");
return PTR_ERR(i2s->clk_ref);
}
ret = clk_prepare_enable(i2s->clk_sys);
if (ret)
return ret;
reg = IMG_I2S_OUT_CTL_FRM_SIZE_MASK;
img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL);
reg = IMG_I2S_OUT_CHAN_CTL_JUST_MASK |
IMG_I2S_OUT_CHAN_CTL_LT_MASK |
IMG_I2S_OUT_CHAN_CTL_CH_MASK |
(8 << IMG_I2S_OUT_CHAN_CTL_FMT_SHIFT);
for (i = 0; i < i2s->max_i2s_chan; i++)
img_i2s_out_ch_writel(i2s, i, reg, IMG_I2S_OUT_CH_CTL);
img_i2s_out_reset(i2s);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev)) {
ret = img_i2s_out_resume(&pdev->dev);
if (ret)
goto err_pm_disable;
}
i2s->active_channels = 1;
i2s->dma_data.addr = res->start + IMG_I2S_OUT_TX_FIFO;
i2s->dma_data.addr_width = 4;
i2s->dma_data.maxburst = 4;
i2s->dai_driver.probe = img_i2s_out_dai_probe;
i2s->dai_driver.playback.channels_min = 2;
i2s->dai_driver.playback.channels_max = i2s->max_i2s_chan * 2;
i2s->dai_driver.playback.rates = SNDRV_PCM_RATE_8000_192000;
i2s->dai_driver.playback.formats = SNDRV_PCM_FMTBIT_S32_LE;
i2s->dai_driver.ops = &img_i2s_out_dai_ops;
ret = devm_snd_soc_register_component(&pdev->dev,
&img_i2s_out_component, &i2s->dai_driver, 1);
if (ret)
goto err_suspend;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
&img_i2s_out_dma_config, 0);
if (ret)
goto err_suspend;
return 0;
err_suspend:
if (!pm_runtime_status_suspended(&pdev->dev))
img_i2s_out_suspend(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(i2s->clk_sys);
return ret;
}
static int img_i2s_out_dev_remove(struct platform_device *pdev)
{
struct img_i2s_out *i2s = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
img_i2s_out_suspend(&pdev->dev);
clk_disable_unprepare(i2s->clk_sys);
return 0;
}
static const struct of_device_id img_i2s_out_of_match[] = {
{ .compatible = "img,i2s-out" },
{}
};
MODULE_DEVICE_TABLE(of, img_i2s_out_of_match);
static const struct dev_pm_ops img_i2s_out_pm_ops = {
SET_RUNTIME_PM_OPS(img_i2s_out_suspend,
img_i2s_out_resume, NULL)
};
static struct platform_driver img_i2s_out_driver = {
.driver = {
.name = "img-i2s-out",
.of_match_table = img_i2s_out_of_match,
.pm = &img_i2s_out_pm_ops
},
.probe = img_i2s_out_probe,
.remove = img_i2s_out_dev_remove
};
module_platform_driver(img_i2s_out_driver);
MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
MODULE_DESCRIPTION("IMG I2S Output Driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,327 @@
/*
* IMG parallel output controller driver
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*
* Author: Damien Horsley <Damien.Horsley@imgtec.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.
*/
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define IMG_PRL_OUT_TX_FIFO 0
#define IMG_PRL_OUT_CTL 0x4
#define IMG_PRL_OUT_CTL_CH_MASK BIT(4)
#define IMG_PRL_OUT_CTL_PACKH_MASK BIT(3)
#define IMG_PRL_OUT_CTL_EDGE_MASK BIT(2)
#define IMG_PRL_OUT_CTL_ME_MASK BIT(1)
#define IMG_PRL_OUT_CTL_SRST_MASK BIT(0)
struct img_prl_out {
void __iomem *base;
struct clk *clk_sys;
struct clk *clk_ref;
struct snd_dmaengine_dai_dma_data dma_data;
struct device *dev;
struct reset_control *rst;
};
static int img_prl_out_suspend(struct device *dev)
{
struct img_prl_out *prl = dev_get_drvdata(dev);
clk_disable_unprepare(prl->clk_ref);
return 0;
}
static int img_prl_out_resume(struct device *dev)
{
struct img_prl_out *prl = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(prl->clk_ref);
if (ret) {
dev_err(dev, "clk_enable failed: %d\n", ret);
return ret;
}
return 0;
}
static inline void img_prl_out_writel(struct img_prl_out *prl,
u32 val, u32 reg)
{
writel(val, prl->base + reg);
}
static inline u32 img_prl_out_readl(struct img_prl_out *prl, u32 reg)
{
return readl(prl->base + reg);
}
static void img_prl_out_reset(struct img_prl_out *prl)
{
u32 ctl;
ctl = img_prl_out_readl(prl, IMG_PRL_OUT_CTL) &
~IMG_PRL_OUT_CTL_ME_MASK;
reset_control_assert(prl->rst);
reset_control_deassert(prl->rst);
img_prl_out_writel(prl, ctl, IMG_PRL_OUT_CTL);
}
static int img_prl_out_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);
u32 reg;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);
reg |= IMG_PRL_OUT_CTL_ME_MASK;
img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
img_prl_out_reset(prl);
break;
default:
return -EINVAL;
}
return 0;
}
static int img_prl_out_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);
unsigned int rate, channels;
u32 reg, control_set = 0;
snd_pcm_format_t format;
rate = params_rate(params);
format = params_format(params);
channels = params_channels(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S32_LE:
control_set |= IMG_PRL_OUT_CTL_PACKH_MASK;
break;
case SNDRV_PCM_FORMAT_S24_LE:
break;
default:
return -EINVAL;
}
if (channels != 2)
return -EINVAL;
clk_set_rate(prl->clk_ref, rate * 256);
reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);
reg = (reg & ~IMG_PRL_OUT_CTL_PACKH_MASK) | control_set;
img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);
return 0;
}
static int img_prl_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);
u32 reg, control_set = 0;
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_NB_IF:
control_set |= IMG_PRL_OUT_CTL_EDGE_MASK;
break;
default:
return -EINVAL;
}
reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);
reg = (reg & ~IMG_PRL_OUT_CTL_EDGE_MASK) | control_set;
img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);
return 0;
}
static const struct snd_soc_dai_ops img_prl_out_dai_ops = {
.trigger = img_prl_out_trigger,
.hw_params = img_prl_out_hw_params,
.set_fmt = img_prl_out_set_fmt
};
static int img_prl_out_dai_probe(struct snd_soc_dai *dai)
{
struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &prl->dma_data, NULL);
return 0;
}
static struct snd_soc_dai_driver img_prl_out_dai = {
.probe = img_prl_out_dai_probe,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE
},
.ops = &img_prl_out_dai_ops
};
static const struct snd_soc_component_driver img_prl_out_component = {
.name = "img-prl-out"
};
static int img_prl_out_probe(struct platform_device *pdev)
{
struct img_prl_out *prl;
struct resource *res;
void __iomem *base;
int ret;
struct device *dev = &pdev->dev;
prl = devm_kzalloc(&pdev->dev, sizeof(*prl), GFP_KERNEL);
if (!prl)
return -ENOMEM;
platform_set_drvdata(pdev, prl);
prl->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
prl->base = base;
prl->rst = devm_reset_control_get(&pdev->dev, "rst");
if (IS_ERR(prl->rst)) {
if (PTR_ERR(prl->rst) != -EPROBE_DEFER)
dev_err(&pdev->dev, "No top level reset found\n");
return PTR_ERR(prl->rst);
}
prl->clk_sys = devm_clk_get(&pdev->dev, "sys");
if (IS_ERR(prl->clk_sys)) {
if (PTR_ERR(prl->clk_sys) != -EPROBE_DEFER)
dev_err(dev, "Failed to acquire clock 'sys'\n");
return PTR_ERR(prl->clk_sys);
}
prl->clk_ref = devm_clk_get(&pdev->dev, "ref");
if (IS_ERR(prl->clk_ref)) {
if (PTR_ERR(prl->clk_ref) != -EPROBE_DEFER)
dev_err(dev, "Failed to acquire clock 'ref'\n");
return PTR_ERR(prl->clk_ref);
}
ret = clk_prepare_enable(prl->clk_sys);
if (ret)
return ret;
img_prl_out_writel(prl, IMG_PRL_OUT_CTL_EDGE_MASK, IMG_PRL_OUT_CTL);
img_prl_out_reset(prl);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev)) {
ret = img_prl_out_resume(&pdev->dev);
if (ret)
goto err_pm_disable;
}
prl->dma_data.addr = res->start + IMG_PRL_OUT_TX_FIFO;
prl->dma_data.addr_width = 4;
prl->dma_data.maxburst = 4;
ret = devm_snd_soc_register_component(&pdev->dev,
&img_prl_out_component,
&img_prl_out_dai, 1);
if (ret)
goto err_suspend;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret)
goto err_suspend;
return 0;
err_suspend:
if (!pm_runtime_status_suspended(&pdev->dev))
img_prl_out_suspend(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(prl->clk_sys);
return ret;
}
static int img_prl_out_dev_remove(struct platform_device *pdev)
{
struct img_prl_out *prl = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
img_prl_out_suspend(&pdev->dev);
clk_disable_unprepare(prl->clk_sys);
return 0;
}
static const struct of_device_id img_prl_out_of_match[] = {
{ .compatible = "img,parallel-out" },
{}
};
MODULE_DEVICE_TABLE(of, img_prl_out_of_match);
static const struct dev_pm_ops img_prl_out_pm_ops = {
SET_RUNTIME_PM_OPS(img_prl_out_suspend,
img_prl_out_resume, NULL)
};
static struct platform_driver img_prl_out_driver = {
.driver = {
.name = "img-parallel-out",
.of_match_table = img_prl_out_of_match,
.pm = &img_prl_out_pm_ops
},
.probe = img_prl_out_probe,
.remove = img_prl_out_dev_remove
};
module_platform_driver(img_prl_out_driver);
MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
MODULE_DESCRIPTION("IMG Parallel Output Driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,806 @@
/*
* IMG SPDIF input controller driver
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*
* Author: Damien Horsley <Damien.Horsley@imgtec.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.
*/
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define IMG_SPDIF_IN_RX_FIFO_OFFSET 0
#define IMG_SPDIF_IN_CTL 0x4
#define IMG_SPDIF_IN_CTL_LOCKLO_MASK 0xff
#define IMG_SPDIF_IN_CTL_LOCKLO_SHIFT 0
#define IMG_SPDIF_IN_CTL_LOCKHI_MASK 0xff00
#define IMG_SPDIF_IN_CTL_LOCKHI_SHIFT 8
#define IMG_SPDIF_IN_CTL_TRK_MASK 0xff0000
#define IMG_SPDIF_IN_CTL_TRK_SHIFT 16
#define IMG_SPDIF_IN_CTL_SRD_MASK 0x70000000
#define IMG_SPDIF_IN_CTL_SRD_SHIFT 28
#define IMG_SPDIF_IN_CTL_SRT_MASK BIT(31)
#define IMG_SPDIF_IN_STATUS 0x8
#define IMG_SPDIF_IN_STATUS_SAM_MASK 0x7000
#define IMG_SPDIF_IN_STATUS_SAM_SHIFT 12
#define IMG_SPDIF_IN_STATUS_LOCK_MASK BIT(15)
#define IMG_SPDIF_IN_STATUS_LOCK_SHIFT 15
#define IMG_SPDIF_IN_CLKGEN 0x1c
#define IMG_SPDIF_IN_CLKGEN_NOM_MASK 0x3ff
#define IMG_SPDIF_IN_CLKGEN_NOM_SHIFT 0
#define IMG_SPDIF_IN_CLKGEN_HLD_MASK 0x3ff0000
#define IMG_SPDIF_IN_CLKGEN_HLD_SHIFT 16
#define IMG_SPDIF_IN_CSL 0x20
#define IMG_SPDIF_IN_CSH 0x24
#define IMG_SPDIF_IN_CSH_MASK 0xff
#define IMG_SPDIF_IN_CSH_SHIFT 0
#define IMG_SPDIF_IN_SOFT_RESET 0x28
#define IMG_SPDIF_IN_SOFT_RESET_MASK BIT(0)
#define IMG_SPDIF_IN_ACLKGEN_START 0x2c
#define IMG_SPDIF_IN_ACLKGEN_NOM_MASK 0x3ff
#define IMG_SPDIF_IN_ACLKGEN_NOM_SHIFT 0
#define IMG_SPDIF_IN_ACLKGEN_HLD_MASK 0xffc00
#define IMG_SPDIF_IN_ACLKGEN_HLD_SHIFT 10
#define IMG_SPDIF_IN_ACLKGEN_TRK_MASK 0xff00000
#define IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT 20
#define IMG_SPDIF_IN_NUM_ACLKGEN 4
struct img_spdif_in {
spinlock_t lock;
void __iomem *base;
struct clk *clk_sys;
struct snd_dmaengine_dai_dma_data dma_data;
struct device *dev;
unsigned int trk;
bool multi_freq;
int lock_acquire;
int lock_release;
unsigned int single_freq;
unsigned int multi_freqs[IMG_SPDIF_IN_NUM_ACLKGEN];
bool active;
/* Write-only registers */
unsigned int aclkgen_regs[IMG_SPDIF_IN_NUM_ACLKGEN];
};
static inline void img_spdif_in_writel(struct img_spdif_in *spdif,
u32 val, u32 reg)
{
writel(val, spdif->base + reg);
}
static inline u32 img_spdif_in_readl(struct img_spdif_in *spdif, u32 reg)
{
return readl(spdif->base + reg);
}
static inline void img_spdif_in_aclkgen_writel(struct img_spdif_in *spdif,
u32 index)
{
img_spdif_in_writel(spdif, spdif->aclkgen_regs[index],
IMG_SPDIF_IN_ACLKGEN_START + (index * 0x4));
}
static int img_spdif_in_check_max_rate(struct img_spdif_in *spdif,
unsigned int sample_rate, unsigned long *actual_freq)
{
unsigned long min_freq, freq_t;
/* Clock rate must be at least 24x the bit rate */
min_freq = sample_rate * 2 * 32 * 24;
freq_t = clk_get_rate(spdif->clk_sys);
if (freq_t < min_freq)
return -EINVAL;
*actual_freq = freq_t;
return 0;
}
static int img_spdif_in_do_clkgen_calc(unsigned int rate, unsigned int *pnom,
unsigned int *phld, unsigned long clk_rate)
{
unsigned int ori, nom, hld;
/*
* Calculate oversampling ratio, nominal phase increment and hold
* increment for the given rate / frequency
*/
if (!rate)
return -EINVAL;
ori = clk_rate / (rate * 64);
if (!ori)
return -EINVAL;
nom = (4096 / ori) + 1;
do
hld = 4096 - (--nom * (ori - 1));
while (hld < 120);
*pnom = nom;
*phld = hld;
return 0;
}
static int img_spdif_in_do_clkgen_single(struct img_spdif_in *spdif,
unsigned int rate)
{
unsigned int nom, hld;
unsigned long flags, clk_rate;
int ret = 0;
u32 reg;
ret = img_spdif_in_check_max_rate(spdif, rate, &clk_rate);
if (ret)
return ret;
ret = img_spdif_in_do_clkgen_calc(rate, &nom, &hld, clk_rate);
if (ret)
return ret;
reg = (nom << IMG_SPDIF_IN_CLKGEN_NOM_SHIFT) &
IMG_SPDIF_IN_CLKGEN_NOM_MASK;
reg |= (hld << IMG_SPDIF_IN_CLKGEN_HLD_SHIFT) &
IMG_SPDIF_IN_CLKGEN_HLD_MASK;
spin_lock_irqsave(&spdif->lock, flags);
if (spdif->active) {
spin_unlock_irqrestore(&spdif->lock, flags);
return -EBUSY;
}
img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CLKGEN);
spdif->single_freq = rate;
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static int img_spdif_in_do_clkgen_multi(struct img_spdif_in *spdif,
unsigned int multi_freqs[])
{
unsigned int nom, hld, rate, max_rate = 0;
unsigned long flags, clk_rate;
int i, ret = 0;
u32 reg, trk_reg, temp_regs[IMG_SPDIF_IN_NUM_ACLKGEN];
for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++)
if (multi_freqs[i] > max_rate)
max_rate = multi_freqs[i];
ret = img_spdif_in_check_max_rate(spdif, max_rate, &clk_rate);
if (ret)
return ret;
for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) {
rate = multi_freqs[i];
ret = img_spdif_in_do_clkgen_calc(rate, &nom, &hld, clk_rate);
if (ret)
return ret;
reg = (nom << IMG_SPDIF_IN_ACLKGEN_NOM_SHIFT) &
IMG_SPDIF_IN_ACLKGEN_NOM_MASK;
reg |= (hld << IMG_SPDIF_IN_ACLKGEN_HLD_SHIFT) &
IMG_SPDIF_IN_ACLKGEN_HLD_MASK;
temp_regs[i] = reg;
}
spin_lock_irqsave(&spdif->lock, flags);
if (spdif->active) {
spin_unlock_irqrestore(&spdif->lock, flags);
return -EBUSY;
}
trk_reg = spdif->trk << IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT;
for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) {
spdif->aclkgen_regs[i] = temp_regs[i] | trk_reg;
img_spdif_in_aclkgen_writel(spdif, i);
}
spdif->multi_freq = true;
spdif->multi_freqs[0] = multi_freqs[0];
spdif->multi_freqs[1] = multi_freqs[1];
spdif->multi_freqs[2] = multi_freqs[2];
spdif->multi_freqs[3] = multi_freqs[3];
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static int img_spdif_in_iec958_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int img_spdif_in_get_status_mask(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.iec958.status[0] = 0xff;
ucontrol->value.iec958.status[1] = 0xff;
ucontrol->value.iec958.status[2] = 0xff;
ucontrol->value.iec958.status[3] = 0xff;
ucontrol->value.iec958.status[4] = 0xff;
return 0;
}
static int img_spdif_in_get_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
u32 reg;
reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CSL);
ucontrol->value.iec958.status[0] = reg & 0xff;
ucontrol->value.iec958.status[1] = (reg >> 8) & 0xff;
ucontrol->value.iec958.status[2] = (reg >> 16) & 0xff;
ucontrol->value.iec958.status[3] = (reg >> 24) & 0xff;
reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CSH);
ucontrol->value.iec958.status[4] = (reg & IMG_SPDIF_IN_CSH_MASK)
>> IMG_SPDIF_IN_CSH_SHIFT;
return 0;
}
static int img_spdif_in_info_multi_freq(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = IMG_SPDIF_IN_NUM_ACLKGEN;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = LONG_MAX;
return 0;
}
static int img_spdif_in_get_multi_freq(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
unsigned long flags;
spin_lock_irqsave(&spdif->lock, flags);
if (spdif->multi_freq) {
ucontrol->value.integer.value[0] = spdif->multi_freqs[0];
ucontrol->value.integer.value[1] = spdif->multi_freqs[1];
ucontrol->value.integer.value[2] = spdif->multi_freqs[2];
ucontrol->value.integer.value[3] = spdif->multi_freqs[3];
} else {
ucontrol->value.integer.value[0] = 0;
ucontrol->value.integer.value[1] = 0;
ucontrol->value.integer.value[2] = 0;
ucontrol->value.integer.value[3] = 0;
}
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static int img_spdif_in_set_multi_freq(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
unsigned int multi_freqs[IMG_SPDIF_IN_NUM_ACLKGEN];
bool multi_freq;
unsigned long flags;
if ((ucontrol->value.integer.value[0] == 0) &&
(ucontrol->value.integer.value[1] == 0) &&
(ucontrol->value.integer.value[2] == 0) &&
(ucontrol->value.integer.value[3] == 0)) {
multi_freq = false;
} else {
multi_freqs[0] = ucontrol->value.integer.value[0];
multi_freqs[1] = ucontrol->value.integer.value[1];
multi_freqs[2] = ucontrol->value.integer.value[2];
multi_freqs[3] = ucontrol->value.integer.value[3];
multi_freq = true;
}
if (multi_freq)
return img_spdif_in_do_clkgen_multi(spdif, multi_freqs);
spin_lock_irqsave(&spdif->lock, flags);
if (spdif->active) {
spin_unlock_irqrestore(&spdif->lock, flags);
return -EBUSY;
}
spdif->multi_freq = false;
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static int img_spdif_in_info_lock_freq(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = LONG_MAX;
return 0;
}
static int img_spdif_in_get_lock_freq(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *uc)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
u32 reg;
int i;
unsigned long flags;
spin_lock_irqsave(&spdif->lock, flags);
reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_STATUS);
if (reg & IMG_SPDIF_IN_STATUS_LOCK_MASK) {
if (spdif->multi_freq) {
i = ((reg & IMG_SPDIF_IN_STATUS_SAM_MASK) >>
IMG_SPDIF_IN_STATUS_SAM_SHIFT) - 1;
uc->value.integer.value[0] = spdif->multi_freqs[i];
} else {
uc->value.integer.value[0] = spdif->single_freq;
}
} else {
uc->value.integer.value[0] = 0;
}
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static int img_spdif_in_info_trk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 255;
return 0;
}
static int img_spdif_in_get_trk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
ucontrol->value.integer.value[0] = spdif->trk;
return 0;
}
static int img_spdif_in_set_trk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
unsigned long flags;
int i;
u32 reg;
spin_lock_irqsave(&spdif->lock, flags);
if (spdif->active) {
spin_unlock_irqrestore(&spdif->lock, flags);
return -EBUSY;
}
spdif->trk = ucontrol->value.integer.value[0];
reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL);
reg &= ~IMG_SPDIF_IN_CTL_TRK_MASK;
reg |= spdif->trk << IMG_SPDIF_IN_CTL_TRK_SHIFT;
img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL);
for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) {
spdif->aclkgen_regs[i] = (spdif->aclkgen_regs[i] &
~IMG_SPDIF_IN_ACLKGEN_TRK_MASK) |
(spdif->trk << IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT);
img_spdif_in_aclkgen_writel(spdif, i);
}
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static int img_spdif_in_info_lock(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = -128;
uinfo->value.integer.max = 127;
return 0;
}
static int img_spdif_in_get_lock_acquire(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
ucontrol->value.integer.value[0] = spdif->lock_acquire;
return 0;
}
static int img_spdif_in_set_lock_acquire(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
unsigned long flags;
u32 reg;
spin_lock_irqsave(&spdif->lock, flags);
if (spdif->active) {
spin_unlock_irqrestore(&spdif->lock, flags);
return -EBUSY;
}
spdif->lock_acquire = ucontrol->value.integer.value[0];
reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL);
reg &= ~IMG_SPDIF_IN_CTL_LOCKHI_MASK;
reg |= (spdif->lock_acquire << IMG_SPDIF_IN_CTL_LOCKHI_SHIFT) &
IMG_SPDIF_IN_CTL_LOCKHI_MASK;
img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL);
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static int img_spdif_in_get_lock_release(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
ucontrol->value.integer.value[0] = spdif->lock_release;
return 0;
}
static int img_spdif_in_set_lock_release(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai);
unsigned long flags;
u32 reg;
spin_lock_irqsave(&spdif->lock, flags);
if (spdif->active) {
spin_unlock_irqrestore(&spdif->lock, flags);
return -EBUSY;
}
spdif->lock_release = ucontrol->value.integer.value[0];
reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL);
reg &= ~IMG_SPDIF_IN_CTL_LOCKLO_MASK;
reg |= (spdif->lock_release << IMG_SPDIF_IN_CTL_LOCKLO_SHIFT) &
IMG_SPDIF_IN_CTL_LOCKLO_MASK;
img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL);
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static struct snd_kcontrol_new img_spdif_in_controls[] = {
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK),
.info = img_spdif_in_iec958_info,
.get = img_spdif_in_get_status_mask
},
{
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
.info = img_spdif_in_iec958_info,
.get = img_spdif_in_get_status
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "SPDIF In Multi Frequency Acquire",
.info = img_spdif_in_info_multi_freq,
.get = img_spdif_in_get_multi_freq,
.put = img_spdif_in_set_multi_freq
},
{
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "SPDIF In Lock Frequency",
.info = img_spdif_in_info_lock_freq,
.get = img_spdif_in_get_lock_freq
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "SPDIF In Lock TRK",
.info = img_spdif_in_info_trk,
.get = img_spdif_in_get_trk,
.put = img_spdif_in_set_trk
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "SPDIF In Lock Acquire Threshold",
.info = img_spdif_in_info_lock,
.get = img_spdif_in_get_lock_acquire,
.put = img_spdif_in_set_lock_acquire
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "SPDIF In Lock Release Threshold",
.info = img_spdif_in_info_lock,
.get = img_spdif_in_get_lock_release,
.put = img_spdif_in_set_lock_release
}
};
static int img_spdif_in_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
unsigned long flags;
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai);
int ret = 0;
u32 reg;
spin_lock_irqsave(&spdif->lock, flags);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL);
if (spdif->multi_freq)
reg &= ~IMG_SPDIF_IN_CTL_SRD_MASK;
else
reg |= (1UL << IMG_SPDIF_IN_CTL_SRD_SHIFT);
reg |= IMG_SPDIF_IN_CTL_SRT_MASK;
img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL);
spdif->active = true;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL);
reg &= ~IMG_SPDIF_IN_CTL_SRT_MASK;
img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL);
spdif->active = false;
break;
default:
ret = -EINVAL;
}
spin_unlock_irqrestore(&spdif->lock, flags);
return ret;
}
static int img_spdif_in_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai);
unsigned int rate, channels;
snd_pcm_format_t format;
rate = params_rate(params);
channels = params_channels(params);
format = params_format(params);
if (format != SNDRV_PCM_FORMAT_S32_LE)
return -EINVAL;
if (channels != 2)
return -EINVAL;
return img_spdif_in_do_clkgen_single(spdif, rate);
}
static const struct snd_soc_dai_ops img_spdif_in_dai_ops = {
.trigger = img_spdif_in_trigger,
.hw_params = img_spdif_in_hw_params
};
static int img_spdif_in_dai_probe(struct snd_soc_dai *dai)
{
struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, NULL, &spdif->dma_data);
snd_soc_add_dai_controls(dai, img_spdif_in_controls,
ARRAY_SIZE(img_spdif_in_controls));
return 0;
}
static struct snd_soc_dai_driver img_spdif_in_dai = {
.probe = img_spdif_in_dai_probe,
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S32_LE
},
.ops = &img_spdif_in_dai_ops
};
static const struct snd_soc_component_driver img_spdif_in_component = {
.name = "img-spdif-in"
};
static int img_spdif_in_probe(struct platform_device *pdev)
{
struct img_spdif_in *spdif;
struct resource *res;
void __iomem *base;
int ret;
struct reset_control *rst;
u32 reg;
struct device *dev = &pdev->dev;
spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL);
if (!spdif)
return -ENOMEM;
platform_set_drvdata(pdev, spdif);
spdif->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
spdif->base = base;
spdif->clk_sys = devm_clk_get(dev, "sys");
if (IS_ERR(spdif->clk_sys)) {
if (PTR_ERR(spdif->clk_sys) != -EPROBE_DEFER)
dev_err(dev, "Failed to acquire clock 'sys'\n");
return PTR_ERR(spdif->clk_sys);
}
ret = clk_prepare_enable(spdif->clk_sys);
if (ret)
return ret;
rst = devm_reset_control_get(&pdev->dev, "rst");
if (IS_ERR(rst)) {
if (PTR_ERR(rst) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err_clk_disable;
}
dev_dbg(dev, "No top level reset found\n");
img_spdif_in_writel(spdif, IMG_SPDIF_IN_SOFT_RESET_MASK,
IMG_SPDIF_IN_SOFT_RESET);
img_spdif_in_writel(spdif, 0, IMG_SPDIF_IN_SOFT_RESET);
} else {
reset_control_assert(rst);
reset_control_deassert(rst);
}
spin_lock_init(&spdif->lock);
spdif->dma_data.addr = res->start + IMG_SPDIF_IN_RX_FIFO_OFFSET;
spdif->dma_data.addr_width = 4;
spdif->dma_data.maxburst = 4;
spdif->trk = 0x80;
spdif->lock_acquire = 4;
spdif->lock_release = -128;
reg = (spdif->lock_acquire << IMG_SPDIF_IN_CTL_LOCKHI_SHIFT) &
IMG_SPDIF_IN_CTL_LOCKHI_MASK;
reg |= (spdif->lock_release << IMG_SPDIF_IN_CTL_LOCKLO_SHIFT) &
IMG_SPDIF_IN_CTL_LOCKLO_MASK;
reg |= (spdif->trk << IMG_SPDIF_IN_CTL_TRK_SHIFT) &
IMG_SPDIF_IN_CTL_TRK_MASK;
img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL);
ret = devm_snd_soc_register_component(&pdev->dev,
&img_spdif_in_component, &img_spdif_in_dai, 1);
if (ret)
goto err_clk_disable;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret)
goto err_clk_disable;
return 0;
err_clk_disable:
clk_disable_unprepare(spdif->clk_sys);
return ret;
}
static int img_spdif_in_dev_remove(struct platform_device *pdev)
{
struct img_spdif_in *spdif = platform_get_drvdata(pdev);
clk_disable_unprepare(spdif->clk_sys);
return 0;
}
static const struct of_device_id img_spdif_in_of_match[] = {
{ .compatible = "img,spdif-in" },
{}
};
MODULE_DEVICE_TABLE(of, img_spdif_in_of_match);
static struct platform_driver img_spdif_in_driver = {
.driver = {
.name = "img-spdif-in",
.of_match_table = img_spdif_in_of_match
},
.probe = img_spdif_in_probe,
.remove = img_spdif_in_dev_remove
};
module_platform_driver(img_spdif_in_driver);
MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
MODULE_DESCRIPTION("IMG SPDIF Input driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,441 @@
/*
* IMG SPDIF output controller driver
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*
* Author: Damien Horsley <Damien.Horsley@imgtec.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.
*/
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define IMG_SPDIF_OUT_TX_FIFO 0x0
#define IMG_SPDIF_OUT_CTL 0x4
#define IMG_SPDIF_OUT_CTL_FS_MASK BIT(4)
#define IMG_SPDIF_OUT_CTL_CLK_MASK BIT(2)
#define IMG_SPDIF_OUT_CTL_SRT_MASK BIT(0)
#define IMG_SPDIF_OUT_CSL 0x14
#define IMG_SPDIF_OUT_CSH_UV 0x18
#define IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT 0
#define IMG_SPDIF_OUT_CSH_UV_CSH_MASK 0xff
struct img_spdif_out {
spinlock_t lock;
void __iomem *base;
struct clk *clk_sys;
struct clk *clk_ref;
struct snd_dmaengine_dai_dma_data dma_data;
struct device *dev;
struct reset_control *rst;
};
static int img_spdif_out_suspend(struct device *dev)
{
struct img_spdif_out *spdif = dev_get_drvdata(dev);
clk_disable_unprepare(spdif->clk_ref);
return 0;
}
static int img_spdif_out_resume(struct device *dev)
{
struct img_spdif_out *spdif = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(spdif->clk_ref);
if (ret) {
dev_err(dev, "clk_enable failed: %d\n", ret);
return ret;
}
return 0;
}
static inline void img_spdif_out_writel(struct img_spdif_out *spdif, u32 val,
u32 reg)
{
writel(val, spdif->base + reg);
}
static inline u32 img_spdif_out_readl(struct img_spdif_out *spdif, u32 reg)
{
return readl(spdif->base + reg);
}
static void img_spdif_out_reset(struct img_spdif_out *spdif)
{
u32 ctl, status_low, status_high;
ctl = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL) &
~IMG_SPDIF_OUT_CTL_SRT_MASK;
status_low = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL);
status_high = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV);
reset_control_assert(spdif->rst);
reset_control_deassert(spdif->rst);
img_spdif_out_writel(spdif, ctl, IMG_SPDIF_OUT_CTL);
img_spdif_out_writel(spdif, status_low, IMG_SPDIF_OUT_CSL);
img_spdif_out_writel(spdif, status_high, IMG_SPDIF_OUT_CSH_UV);
}
static int img_spdif_out_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int img_spdif_out_get_status_mask(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.iec958.status[0] = 0xff;
ucontrol->value.iec958.status[1] = 0xff;
ucontrol->value.iec958.status[2] = 0xff;
ucontrol->value.iec958.status[3] = 0xff;
ucontrol->value.iec958.status[4] = 0xff;
return 0;
}
static int img_spdif_out_get_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai);
u32 reg;
unsigned long flags;
spin_lock_irqsave(&spdif->lock, flags);
reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL);
ucontrol->value.iec958.status[0] = reg & 0xff;
ucontrol->value.iec958.status[1] = (reg >> 8) & 0xff;
ucontrol->value.iec958.status[2] = (reg >> 16) & 0xff;
ucontrol->value.iec958.status[3] = (reg >> 24) & 0xff;
reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV);
ucontrol->value.iec958.status[4] =
(reg & IMG_SPDIF_OUT_CSH_UV_CSH_MASK) >>
IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT;
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static int img_spdif_out_set_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai);
u32 reg;
unsigned long flags;
reg = ((u32)ucontrol->value.iec958.status[3] << 24);
reg |= ((u32)ucontrol->value.iec958.status[2] << 16);
reg |= ((u32)ucontrol->value.iec958.status[1] << 8);
reg |= (u32)ucontrol->value.iec958.status[0];
spin_lock_irqsave(&spdif->lock, flags);
img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSL);
reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV);
reg &= ~IMG_SPDIF_OUT_CSH_UV_CSH_MASK;
reg |= (u32)ucontrol->value.iec958.status[4] <<
IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT;
img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSH_UV);
spin_unlock_irqrestore(&spdif->lock, flags);
return 0;
}
static struct snd_kcontrol_new img_spdif_out_controls[] = {
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
.info = img_spdif_out_info,
.get = img_spdif_out_get_status_mask
},
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
.info = img_spdif_out_info,
.get = img_spdif_out_get_status,
.put = img_spdif_out_set_status
}
};
static int img_spdif_out_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai);
u32 reg;
unsigned long flags;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL);
reg |= IMG_SPDIF_OUT_CTL_SRT_MASK;
img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
spin_lock_irqsave(&spdif->lock, flags);
img_spdif_out_reset(spdif);
spin_unlock_irqrestore(&spdif->lock, flags);
break;
default:
return -EINVAL;
}
return 0;
}
static int img_spdif_out_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai);
unsigned int channels;
long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate;
u32 reg;
snd_pcm_format_t format;
rate = params_rate(params);
format = params_format(params);
channels = params_channels(params);
dev_dbg(spdif->dev, "hw_params rate %ld channels %u format %u\n",
rate, channels, format);
if (format != SNDRV_PCM_FORMAT_S32_LE)
return -EINVAL;
if (channels != 2)
return -EINVAL;
pre_div_a = clk_round_rate(spdif->clk_ref, rate * 256);
if (pre_div_a < 0)
return pre_div_a;
pre_div_b = clk_round_rate(spdif->clk_ref, rate * 384);
if (pre_div_b < 0)
return pre_div_b;
diff_a = abs((pre_div_a / 256) - rate);
diff_b = abs((pre_div_b / 384) - rate);
/* If diffs are equal, use lower clock rate */
if (diff_a > diff_b)
clk_set_rate(spdif->clk_ref, pre_div_b);
else
clk_set_rate(spdif->clk_ref, pre_div_a);
/*
* Another driver (eg machine driver) may have rejected the above
* change. Get the current rate and set the register bit according to
* the new min diff
*/
clk_rate = clk_get_rate(spdif->clk_ref);
diff_a = abs((clk_rate / 256) - rate);
diff_b = abs((clk_rate / 384) - rate);
reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL);
if (diff_a <= diff_b)
reg &= ~IMG_SPDIF_OUT_CTL_CLK_MASK;
else
reg |= IMG_SPDIF_OUT_CTL_CLK_MASK;
img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL);
return 0;
}
static const struct snd_soc_dai_ops img_spdif_out_dai_ops = {
.trigger = img_spdif_out_trigger,
.hw_params = img_spdif_out_hw_params
};
static int img_spdif_out_dai_probe(struct snd_soc_dai *dai)
{
struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL);
snd_soc_add_dai_controls(dai, img_spdif_out_controls,
ARRAY_SIZE(img_spdif_out_controls));
return 0;
}
static struct snd_soc_dai_driver img_spdif_out_dai = {
.probe = img_spdif_out_dai_probe,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S32_LE
},
.ops = &img_spdif_out_dai_ops
};
static const struct snd_soc_component_driver img_spdif_out_component = {
.name = "img-spdif-out"
};
static int img_spdif_out_probe(struct platform_device *pdev)
{
struct img_spdif_out *spdif;
struct resource *res;
void __iomem *base;
int ret;
struct device *dev = &pdev->dev;
spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL);
if (!spdif)
return -ENOMEM;
platform_set_drvdata(pdev, spdif);
spdif->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
spdif->base = base;
spdif->rst = devm_reset_control_get(&pdev->dev, "rst");
if (IS_ERR(spdif->rst)) {
if (PTR_ERR(spdif->rst) != -EPROBE_DEFER)
dev_err(&pdev->dev, "No top level reset found\n");
return PTR_ERR(spdif->rst);
}
spdif->clk_sys = devm_clk_get(&pdev->dev, "sys");
if (IS_ERR(spdif->clk_sys)) {
if (PTR_ERR(spdif->clk_sys) != -EPROBE_DEFER)
dev_err(dev, "Failed to acquire clock 'sys'\n");
return PTR_ERR(spdif->clk_sys);
}
spdif->clk_ref = devm_clk_get(&pdev->dev, "ref");
if (IS_ERR(spdif->clk_ref)) {
if (PTR_ERR(spdif->clk_ref) != -EPROBE_DEFER)
dev_err(dev, "Failed to acquire clock 'ref'\n");
return PTR_ERR(spdif->clk_ref);
}
ret = clk_prepare_enable(spdif->clk_sys);
if (ret)
return ret;
img_spdif_out_writel(spdif, IMG_SPDIF_OUT_CTL_FS_MASK,
IMG_SPDIF_OUT_CTL);
img_spdif_out_reset(spdif);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev)) {
ret = img_spdif_out_resume(&pdev->dev);
if (ret)
goto err_pm_disable;
}
spin_lock_init(&spdif->lock);
spdif->dma_data.addr = res->start + IMG_SPDIF_OUT_TX_FIFO;
spdif->dma_data.addr_width = 4;
spdif->dma_data.maxburst = 4;
ret = devm_snd_soc_register_component(&pdev->dev,
&img_spdif_out_component,
&img_spdif_out_dai, 1);
if (ret)
goto err_suspend;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret)
goto err_suspend;
dev_dbg(&pdev->dev, "Probe successful\n");
return 0;
err_suspend:
if (!pm_runtime_status_suspended(&pdev->dev))
img_spdif_out_suspend(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(spdif->clk_sys);
return ret;
}
static int img_spdif_out_dev_remove(struct platform_device *pdev)
{
struct img_spdif_out *spdif = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
img_spdif_out_suspend(&pdev->dev);
clk_disable_unprepare(spdif->clk_sys);
return 0;
}
static const struct of_device_id img_spdif_out_of_match[] = {
{ .compatible = "img,spdif-out" },
{}
};
MODULE_DEVICE_TABLE(of, img_spdif_out_of_match);
static const struct dev_pm_ops img_spdif_out_pm_ops = {
SET_RUNTIME_PM_OPS(img_spdif_out_suspend,
img_spdif_out_resume, NULL)
};
static struct platform_driver img_spdif_out_driver = {
.driver = {
.name = "img-spdif-out",
.of_match_table = img_spdif_out_of_match,
.pm = &img_spdif_out_pm_ops
},
.probe = img_spdif_out_probe,
.remove = img_spdif_out_dev_remove
};
module_platform_driver(img_spdif_out_driver);
MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
MODULE_DESCRIPTION("IMG SPDIF Output driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,287 @@
/*
* Pistachio internal dac driver
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*
* Author: Damien Horsley <Damien.Horsley@imgtec.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.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define PISTACHIO_INTERNAL_DAC_CTRL 0x40
#define PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK 0x2
#define PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK 0x1
#define PISTACHIO_INTERNAL_DAC_SRST 0x44
#define PISTACHIO_INTERNAL_DAC_SRST_MASK 0x1
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL 0x48
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_SHIFT 0
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_MASK 0xFFF
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK 0x1000
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_SHIFT 13
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_MASK 0x1FE000
#define PISTACHIO_INTERNAL_DAC_PWR 0x1
#define PISTACHIO_INTERNAL_DAC_PWR_MASK 0x1
#define PISTACHIO_INTERNAL_DAC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
/* codec private data */
struct pistachio_internal_dac {
struct regmap *regmap;
struct regulator *supply;
bool mute;
};
static const struct snd_kcontrol_new pistachio_internal_dac_snd_controls[] = {
SOC_SINGLE("Playback Switch", PISTACHIO_INTERNAL_DAC_CTRL, 2, 1, 1)
};
static const struct snd_soc_dapm_widget pistachio_internal_dac_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_OUTPUT("AOUTL"),
SND_SOC_DAPM_OUTPUT("AOUTR"),
};
static const struct snd_soc_dapm_route pistachio_internal_dac_routes[] = {
{ "AOUTL", NULL, "DAC" },
{ "AOUTR", NULL, "DAC" },
};
static void pistachio_internal_dac_reg_writel(struct regmap *top_regs,
u32 val, u32 reg)
{
regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL,
PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_MASK,
reg << PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_SHIFT);
regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL,
PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_MASK,
val << PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_SHIFT);
regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL,
PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK,
PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK);
regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL,
PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK, 0);
}
static void pistachio_internal_dac_pwr_off(struct pistachio_internal_dac *dac)
{
regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL,
PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK,
PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK);
pistachio_internal_dac_reg_writel(dac->regmap, 0,
PISTACHIO_INTERNAL_DAC_PWR);
}
static void pistachio_internal_dac_pwr_on(struct pistachio_internal_dac *dac)
{
regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_SRST,
PISTACHIO_INTERNAL_DAC_SRST_MASK,
PISTACHIO_INTERNAL_DAC_SRST_MASK);
regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_SRST,
PISTACHIO_INTERNAL_DAC_SRST_MASK, 0);
pistachio_internal_dac_reg_writel(dac->regmap,
PISTACHIO_INTERNAL_DAC_PWR_MASK,
PISTACHIO_INTERNAL_DAC_PWR);
regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL,
PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK, 0);
}
static struct snd_soc_dai_driver pistachio_internal_dac_dais[] = {
{
.name = "pistachio_internal_dac",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = PISTACHIO_INTERNAL_DAC_FORMATS,
}
},
};
static int pistachio_internal_dac_codec_probe(struct snd_soc_codec *codec)
{
struct pistachio_internal_dac *dac = snd_soc_codec_get_drvdata(codec);
snd_soc_codec_init_regmap(codec, dac->regmap);
return 0;
}
static const struct snd_soc_codec_driver pistachio_internal_dac_driver = {
.probe = pistachio_internal_dac_codec_probe,
.idle_bias_off = true,
.controls = pistachio_internal_dac_snd_controls,
.num_controls = ARRAY_SIZE(pistachio_internal_dac_snd_controls),
.dapm_widgets = pistachio_internal_dac_widgets,
.num_dapm_widgets = ARRAY_SIZE(pistachio_internal_dac_widgets),
.dapm_routes = pistachio_internal_dac_routes,
.num_dapm_routes = ARRAY_SIZE(pistachio_internal_dac_routes),
};
static int pistachio_internal_dac_probe(struct platform_device *pdev)
{
struct pistachio_internal_dac *dac;
int ret, voltage;
struct device *dev = &pdev->dev;
u32 reg;
dac = devm_kzalloc(dev, sizeof(*dac), GFP_KERNEL);
if (!dac)
return -ENOMEM;
platform_set_drvdata(pdev, dac);
dac->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
"img,cr-top");
if (IS_ERR(dac->regmap))
return PTR_ERR(dac->regmap);
dac->supply = devm_regulator_get(dev, "VDD");
if (IS_ERR(dac->supply)) {
ret = PTR_ERR(dac->supply);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to acquire supply 'VDD-supply': %d\n", ret);
return ret;
}
ret = regulator_enable(dac->supply);
if (ret) {
dev_err(dev, "failed to enable supply: %d\n", ret);
return ret;
}
voltage = regulator_get_voltage(dac->supply);
switch (voltage) {
case 1800000:
reg = 0;
break;
case 3300000:
reg = PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK;
break;
default:
dev_err(dev, "invalid voltage: %d\n", voltage);
ret = -EINVAL;
goto err_regulator;
}
regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL,
PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK, reg);
pistachio_internal_dac_pwr_off(dac);
pistachio_internal_dac_pwr_on(dac);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_idle(dev);
ret = snd_soc_register_codec(dev, &pistachio_internal_dac_driver,
pistachio_internal_dac_dais,
ARRAY_SIZE(pistachio_internal_dac_dais));
if (ret) {
dev_err(dev, "failed to register codec: %d\n", ret);
goto err_pwr;
}
return 0;
err_pwr:
pm_runtime_disable(&pdev->dev);
pistachio_internal_dac_pwr_off(dac);
err_regulator:
regulator_disable(dac->supply);
return ret;
}
static int pistachio_internal_dac_remove(struct platform_device *pdev)
{
struct pistachio_internal_dac *dac = dev_get_drvdata(&pdev->dev);
snd_soc_unregister_codec(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pistachio_internal_dac_pwr_off(dac);
regulator_disable(dac->supply);
return 0;
}
#ifdef CONFIG_PM
static int pistachio_internal_dac_rt_resume(struct device *dev)
{
struct pistachio_internal_dac *dac = dev_get_drvdata(dev);
int ret;
ret = regulator_enable(dac->supply);
if (ret) {
dev_err(dev, "failed to enable supply: %d\n", ret);
return ret;
}
pistachio_internal_dac_pwr_on(dac);
return 0;
}
static int pistachio_internal_dac_rt_suspend(struct device *dev)
{
struct pistachio_internal_dac *dac = dev_get_drvdata(dev);
pistachio_internal_dac_pwr_off(dac);
regulator_disable(dac->supply);
return 0;
}
#endif
static const struct dev_pm_ops pistachio_internal_dac_pm_ops = {
SET_RUNTIME_PM_OPS(pistachio_internal_dac_rt_suspend,
pistachio_internal_dac_rt_resume, NULL)
};
static const struct of_device_id pistachio_internal_dac_of_match[] = {
{ .compatible = "img,pistachio-internal-dac" },
{}
};
MODULE_DEVICE_TABLE(of, pistachio_internal_dac_of_match);
static struct platform_driver pistachio_internal_dac_plat_driver = {
.driver = {
.name = "img-pistachio-internal-dac",
.of_match_table = pistachio_internal_dac_of_match,
.pm = &pistachio_internal_dac_pm_ops
},
.probe = pistachio_internal_dac_probe,
.remove = pistachio_internal_dac_remove
};
module_platform_driver(pistachio_internal_dac_plat_driver);
MODULE_DESCRIPTION("Pistachio Internal DAC driver");
MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
MODULE_LICENSE("GPL v2");

View File

@ -24,6 +24,7 @@ config SND_SST_IPC_PCI
config SND_SST_IPC_ACPI
tristate
select SND_SST_IPC
select SND_SOC_INTEL_SST
depends on ACPI
config SND_SOC_INTEL_SST
@ -43,7 +44,7 @@ config SND_SOC_INTEL_BAYTRAIL
config SND_SOC_INTEL_HASWELL_MACH
tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint"
depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM
depends on DW_DMAC_CORE
depends on DW_DMAC_CORE=y
select SND_SOC_INTEL_SST
select SND_SOC_INTEL_HASWELL
select SND_SOC_RT5640
@ -56,18 +57,19 @@ config SND_SOC_INTEL_HASWELL_MACH
config SND_SOC_INTEL_BYT_RT5640_MACH
tristate "ASoC Audio driver for Intel Baytrail with RT5640 codec"
depends on X86_INTEL_LPSS && I2C
depends on DW_DMAC_CORE
depends on DW_DMAC_CORE=y && (SND_SOC_INTEL_BYTCR_RT5640_MACH = n)
select SND_SOC_INTEL_SST
select SND_SOC_INTEL_BAYTRAIL
select SND_SOC_RT5640
help
This adds audio driver for Intel Baytrail platform based boards
with the RT5640 audio codec.
with the RT5640 audio codec. This driver is deprecated, use
SND_SOC_INTEL_BYTCR_RT5640_MACH instead for better functionality
config SND_SOC_INTEL_BYT_MAX98090_MACH
tristate "ASoC Audio driver for Intel Baytrail with MAX98090 codec"
depends on X86_INTEL_LPSS && I2C
depends on DW_DMAC_CORE
depends on DW_DMAC_CORE=y
select SND_SOC_INTEL_SST
select SND_SOC_INTEL_BAYTRAIL
select SND_SOC_MAX98090
@ -79,7 +81,7 @@ config SND_SOC_INTEL_BROADWELL_MACH
tristate "ASoC Audio DSP support for Intel Broadwell Wildcatpoint"
depends on X86_INTEL_LPSS && I2C && DW_DMAC && \
I2C_DESIGNWARE_PLATFORM
depends on DW_DMAC_CORE
depends on DW_DMAC_CORE=y
select SND_SOC_INTEL_SST
select SND_SOC_INTEL_HASWELL
select SND_SOC_RT286
@ -90,14 +92,14 @@ config SND_SOC_INTEL_BROADWELL_MACH
If unsure select "N".
config SND_SOC_INTEL_BYTCR_RT5640_MACH
tristate "ASoC Audio DSP Support for MID BYT Platform"
tristate "ASoC Audio driver for Intel Baytrail and Baytrail-CR with RT5640 codec"
depends on X86 && I2C
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
This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR
platforms with RT5640 audio codec.
Say Y if you have such a device
If unsure select "N".
@ -154,3 +156,31 @@ config SND_SOC_INTEL_SKL_RT286_MACH
with RT286 I2S audio codec.
Say Y if you have such a device
If unsure select "N".
config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH
tristate "ASoC Audio driver for SKL with NAU88L25 and SSM4567 in I2S Mode"
depends on X86_INTEL_LPSS && I2C
select SND_SOC_INTEL_SST
select SND_SOC_INTEL_SKYLAKE
select SND_SOC_NAU8825
select SND_SOC_SSM4567
select SND_SOC_DMIC
help
This adds support for ASoC Onboard Codec I2S machine driver. This will
create an alsa sound card for NAU88L25 + SSM4567.
Say Y if you have such a device
If unsure select "N".
config SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH
tristate "ASoC Audio driver for SKL with NAU88L25 and MAX98357A in I2S Mode"
depends on X86_INTEL_LPSS && I2C
select SND_SOC_INTEL_SST
select SND_SOC_INTEL_SKYLAKE
select SND_SOC_NAU8825
select SND_SOC_MAX98357A
select SND_SOC_DMIC
help
This adds support for ASoC Onboard Codec I2S machine driver. This will
create an alsa sound card for NAU88L25 + MAX98357A.
Say Y if you have such a device
If unsure select "N".

View File

@ -443,7 +443,7 @@ static int sst_gain_get(struct snd_kcontrol *kcontrol,
break;
case SST_GAIN_MUTE:
ucontrol->value.integer.value[0] = gv->mute ? 1 : 0;
ucontrol->value.integer.value[0] = gv->mute ? 0 : 1;
break;
case SST_GAIN_RAMP_DURATION:
@ -479,7 +479,7 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol,
break;
case SST_GAIN_MUTE:
gv->mute = !!ucontrol->value.integer.value[0];
gv->mute = !ucontrol->value.integer.value[0];
dev_dbg(cmpnt->dev, "%s: Mute %d\n", mc->pname, gv->mute);
break;
@ -1109,6 +1109,7 @@ static const struct snd_soc_dapm_route intercon[] = {
{"media0_in", NULL, "Compress Playback"},
{"media1_in", NULL, "Headset Playback"},
{"media2_in", NULL, "pcm0_out"},
{"media3_in", NULL, "Deepbuffer Playback"},
{"media0_out mix 0", "media0_in Switch", "media0_in"},
{"media0_out mix 0", "media1_in Switch", "media1_in"},

View File

@ -28,6 +28,7 @@
enum {
MERR_DPCM_AUDIO = 0,
MERR_DPCM_DEEP_BUFFER,
MERR_DPCM_COMPR,
};

View File

@ -98,6 +98,7 @@ static struct sst_dev_stream_map dpcm_strm_map[] = {
{MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA1_IN, SST_TASK_ID_MEDIA, 0},
{MERR_DPCM_COMPR, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA0_IN, SST_TASK_ID_MEDIA, 0},
{MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0},
{MERR_DPCM_DEEP_BUFFER, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA3_IN, SST_TASK_ID_MEDIA, 0},
};
static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream)
@ -500,14 +501,25 @@ static struct snd_soc_dai_driver sst_platform_dai[] = {
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_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,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
},
},
{
.name = "deepbuffer-cpu-dai",
.ops = &sst_media_dai_ops,
.playback = {
.stream_name = "Deepbuffer Playback",
.channels_min = SST_STEREO,
.channels_max = SST_STEREO,
.rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
},
},
{
@ -516,10 +528,6 @@ static struct snd_soc_dai_driver sst_platform_dai[] = {
.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 */

View File

@ -40,18 +40,9 @@
#include <acpi/acpi_bus.h>
#include "../sst-mfld-platform.h"
#include "../../common/sst-dsp.h"
#include "../../common/sst-acpi.h"
#include "sst.h"
struct sst_machines {
char *codec_id;
char board[32];
char machine[32];
void (*machine_quirk)(void);
char firmware[FW_NAME_SIZE];
struct sst_platform_info *pdata;
};
/* LPE viewpoint addresses */
#define SST_BYT_IRAM_PHY_START 0xff2c0000
#define SST_BYT_IRAM_PHY_END 0xff2d4000
@ -223,37 +214,16 @@ static int sst_platform_get_resources(struct intel_sst_drv *ctx)
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;
}
static 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 sst_acpi_mach *mach;
struct platform_device *mdev;
struct platform_device *plat_dev;
struct sst_platform_info *pdata;
unsigned int dev_id;
id = acpi_match_device(dev->driver->acpi_match_table, dev);
@ -261,12 +231,13 @@ static int sst_acpi_probe(struct platform_device *pdev)
return -ENODEV;
dev_dbg(dev, "for %s", id->id);
mach = (struct sst_machines *)id->driver_data;
mach = (struct sst_acpi_mach *)id->driver_data;
mach = sst_acpi_find_machine(mach);
if (mach == NULL) {
dev_err(dev, "No matching machine driver found\n");
return -ENODEV;
}
pdata = mach->pdata;
ret = kstrtouint(id->id, 16, &dev_id);
if (ret < 0) {
@ -276,16 +247,16 @@ static int sst_acpi_probe(struct platform_device *pdev)
dev_dbg(dev, "ACPI device id: %x\n", dev_id);
plat_dev = platform_device_register_data(dev, mach->pdata->platform, -1, NULL, 0);
plat_dev = platform_device_register_data(dev, pdata->platform, -1, NULL, 0);
if (IS_ERR(plat_dev)) {
dev_err(dev, "Failed to create machine device: %s\n", mach->pdata->platform);
dev_err(dev, "Failed to create machine device: %s\n", pdata->platform);
return PTR_ERR(plat_dev);
}
/* Create platform device for sst machine driver */
mdev = platform_device_register_data(dev, mach->machine, -1, NULL, 0);
mdev = platform_device_register_data(dev, mach->drv_name, -1, NULL, 0);
if (IS_ERR(mdev)) {
dev_err(dev, "Failed to create machine device: %s\n", mach->machine);
dev_err(dev, "Failed to create machine device: %s\n", mach->drv_name);
return PTR_ERR(mdev);
}
@ -294,8 +265,8 @@ static int sst_acpi_probe(struct platform_device *pdev)
return ret;
/* Fill sst platform data */
ctx->pdata = mach->pdata;
strcpy(ctx->firmware_name, mach->firmware);
ctx->pdata = pdata;
strcpy(ctx->firmware_name, mach->fw_filename);
ret = sst_platform_get_resources(ctx);
if (ret)
@ -342,22 +313,22 @@ static int sst_acpi_remove(struct platform_device *pdev)
return 0;
}
static struct sst_machines sst_acpi_bytcr[] = {
{"10EC5640", "T100", "bytt100_rt5640", NULL, "intel/fw_sst_0f28.bin",
static struct sst_acpi_mach sst_acpi_bytcr[] = {
{"10EC5640", "bytcr_rt5640", "intel/fw_sst_0f28.bin", "bytcr_rt5640", NULL,
&byt_rvp_platform_data },
{},
};
/* Cherryview-based platforms: CherryTrail and Braswell */
static struct sst_machines sst_acpi_chv[] = {
{"10EC5670", "cht-bsw", "cht-bsw-rt5672", NULL, "intel/fw_sst_22a8.bin",
static struct sst_acpi_mach sst_acpi_chv[] = {
{"10EC5670", "cht-bsw-rt5672", "intel/fw_sst_22a8.bin", "cht-bsw", NULL,
&chv_platform_data },
{"10EC5645", "cht-bsw", "cht-bsw-rt5645", NULL, "intel/fw_sst_22a8.bin",
{"10EC5645", "cht-bsw-rt5645", "intel/fw_sst_22a8.bin", "cht-bsw", NULL,
&chv_platform_data },
{"10EC5650", "cht-bsw", "cht-bsw-rt5645", NULL, "intel/fw_sst_22a8.bin",
{"10EC5650", "cht-bsw-rt5645", "intel/fw_sst_22a8.bin", "cht-bsw", NULL,
&chv_platform_data },
{"193C9890", "cht-bsw-max98090", "intel/fw_sst_22a8.bin", "cht-bsw", NULL,
&chv_platform_data },
{"193C9890", "cht-bsw", "cht-bsw-max98090", NULL,
"intel/fw_sst_22a8.bin", &chv_platform_data },
{},
};

View File

@ -108,7 +108,7 @@ int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params)
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);
&alloc_param, &data, true, true, false, true);
if (ret < 0) {
dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret);

View File

@ -7,6 +7,8 @@ snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o
snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o
snd-soc-sst-cht-bsw-max98090_ti-objs := cht_bsw_max98090_ti.o
snd-soc-skl_rt286-objs := skl_rt286.o
snd-skl_nau88l25_max98357a-objs := skl_nau88l25_max98357a.o
snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.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
@ -17,3 +19,5 @@ obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o
obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o
obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o
obj-$(CONFIG_SND_SOC_INTEL_SKL_RT286_MACH) += snd-soc-skl_rt286.o
obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH) += snd-skl_nau88l25_max98357a.o
obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH) += snd-soc-skl_nau88l25_ssm4567.o

View File

@ -20,51 +20,75 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "../../codecs/rt5640.h"
#include "../atom/sst-atom-controls.h"
static const struct snd_soc_dapm_widget byt_dapm_widgets[] = {
static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Int Mic", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
SND_SOC_DAPM_MIC("Internal Mic", NULL),
SND_SOC_DAPM_SPK("Speaker", 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"},
static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = {
{"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"},
{"Headset Mic", NULL, "MICBIAS1"},
{"IN2P", NULL, "Headset Mic"},
{"Headphone", NULL, "HPOL"},
{"Headphone", NULL, "HPOR"},
{"Speaker", NULL, "SPOLP"},
{"Speaker", NULL, "SPOLN"},
{"Speaker", NULL, "SPORP"},
{"Speaker", NULL, "SPORN"},
};
static const struct snd_kcontrol_new byt_mc_controls[] = {
static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = {
{"DMIC1", NULL, "Internal Mic"},
};
static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = {
{"DMIC2", NULL, "Internal Mic"},
};
static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = {
{"Internal Mic", NULL, "MICBIAS1"},
{"IN1P", NULL, "Internal Mic"},
};
enum {
BYT_RT5640_DMIC1_MAP,
BYT_RT5640_DMIC2_MAP,
BYT_RT5640_IN1_MAP,
};
#define BYT_RT5640_MAP(quirk) ((quirk) & 0xff)
#define BYT_RT5640_DMIC_EN BIT(16)
static unsigned long byt_rt5640_quirk = BYT_RT5640_DMIC1_MAP |
BYT_RT5640_DMIC_EN;
static const struct snd_kcontrol_new byt_rt5640_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
SOC_DAPM_PIN_SWITCH("Ext Spk"),
SOC_DAPM_PIN_SWITCH("Internal Mic"),
SOC_DAPM_PIN_SWITCH("Speaker"),
};
static int byt_aif1_hw_params(struct snd_pcm_substream *substream,
static int byt_rt5640_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
@ -92,7 +116,82 @@ static int byt_aif1_hw_params(struct snd_pcm_substream *substream,
return 0;
}
static const struct snd_soc_pcm_stream byt_dai_params = {
static int byt_rt5640_quirk_cb(const struct dmi_system_id *id)
{
byt_rt5640_quirk = (unsigned long)id->driver_data;
return 1;
}
static const struct dmi_system_id byt_rt5640_quirk_table[] = {
{
.callback = byt_rt5640_quirk_cb,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"),
},
.driver_data = (unsigned long *)BYT_RT5640_IN1_MAP,
},
{
.callback = byt_rt5640_quirk_cb,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "DellInc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5830"),
},
.driver_data = (unsigned long *)(BYT_RT5640_DMIC2_MAP |
BYT_RT5640_DMIC_EN),
},
{}
};
static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime)
{
int ret;
struct snd_soc_codec *codec = runtime->codec;
struct snd_soc_card *card = runtime->card;
const struct snd_soc_dapm_route *custom_map;
int num_routes;
card->dapm.idle_bias_off = true;
ret = snd_soc_add_card_controls(card, byt_rt5640_controls,
ARRAY_SIZE(byt_rt5640_controls));
if (ret) {
dev_err(card->dev, "unable to add card controls\n");
return ret;
}
dmi_check_system(byt_rt5640_quirk_table);
switch (BYT_RT5640_MAP(byt_rt5640_quirk)) {
case BYT_RT5640_IN1_MAP:
custom_map = byt_rt5640_intmic_in1_map;
num_routes = ARRAY_SIZE(byt_rt5640_intmic_in1_map);
break;
case BYT_RT5640_DMIC2_MAP:
custom_map = byt_rt5640_intmic_dmic2_map;
num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic2_map);
break;
default:
custom_map = byt_rt5640_intmic_dmic1_map;
num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map);
}
ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes);
if (ret)
return ret;
if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) {
ret = rt5640_dmic_enable(codec, 0, 0);
if (ret)
return ret;
}
snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone");
snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker");
return ret;
}
static const struct snd_soc_pcm_stream byt_rt5640_dai_params = {
.formats = SNDRV_PCM_FMTBIT_S24_LE,
.rate_min = 48000,
.rate_max = 48000,
@ -100,13 +199,14 @@ static const struct snd_soc_pcm_stream byt_dai_params = {
.channels_max = 2,
};
static int byt_codec_fixup(struct snd_soc_pcm_runtime *rtd,
static int byt_rt5640_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);
int ret;
/* The DSP will covert the FE rate to 48k, stereo, 24bits */
rate->min = rate->max = 48000;
@ -114,24 +214,46 @@ static int byt_codec_fixup(struct snd_soc_pcm_runtime *rtd,
/* set SSP2 to 24-bit */
params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
/*
* Default mode for SSP configuration is TDM 4 slot, override config
* with explicit setting to I2S 2ch 24-bit. The word length is set with
* dai_set_tdm_slot() since there is no other API exposed
*/
ret = snd_soc_dai_set_fmt(rtd->cpu_dai,
SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_IF |
SND_SOC_DAIFMT_CBS_CFS
);
if (ret < 0) {
dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24);
if (ret < 0) {
dev_err(rtd->dev, "can't set I2S config, err %d\n", ret);
return ret;
}
return 0;
}
static int byt_aif1_startup(struct snd_pcm_substream *substream)
static int byt_rt5640_aif1_startup(struct snd_pcm_substream *substream)
{
return snd_pcm_hw_constraint_single(substream->runtime,
SNDRV_PCM_HW_PARAM_RATE, 48000);
}
static struct snd_soc_ops byt_aif1_ops = {
.startup = byt_aif1_startup,
static struct snd_soc_ops byt_rt5640_aif1_ops = {
.startup = byt_rt5640_aif1_startup,
};
static struct snd_soc_ops byt_be_ssp2_ops = {
.hw_params = byt_aif1_hw_params,
static struct snd_soc_ops byt_rt5640_be_ssp2_ops = {
.hw_params = byt_rt5640_aif1_hw_params,
};
static struct snd_soc_dai_link byt_dailink[] = {
static struct snd_soc_dai_link byt_rt5640_dais[] = {
[MERR_DPCM_AUDIO] = {
.name = "Baytrail Audio Port",
.stream_name = "Baytrail Audio",
@ -143,7 +265,20 @@ static struct snd_soc_dai_link byt_dailink[] = {
.dynamic = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &byt_aif1_ops,
.ops = &byt_rt5640_aif1_ops,
},
[MERR_DPCM_DEEP_BUFFER] = {
.name = "Deep-Buffer Audio Port",
.stream_name = "Deep-Buffer Audio",
.cpu_dai_name = "deepbuffer-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
.ignore_suspend = 1,
.nonatomic = true,
.dynamic = 1,
.dpcm_playback = 1,
.ops = &byt_rt5640_aif1_ops,
},
[MERR_DPCM_COMPR] = {
.name = "Baytrail Compressed Port",
@ -164,55 +299,57 @@ static struct snd_soc_dai_link byt_dailink[] = {
.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,
.be_hw_params_fixup = byt_rt5640_codec_fixup,
.ignore_suspend = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &byt_be_ssp2_ops,
.init = byt_rt5640_init,
.ops = &byt_rt5640_be_ssp2_ops,
},
};
/* SoC card */
static struct snd_soc_card snd_soc_card_byt = {
.name = "baytrailcraudio",
static struct snd_soc_card byt_rt5640_card = {
.name = "bytcr-rt5640",
.owner = THIS_MODULE,
.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),
.dai_link = byt_rt5640_dais,
.num_links = ARRAY_SIZE(byt_rt5640_dais),
.dapm_widgets = byt_rt5640_widgets,
.num_dapm_widgets = ARRAY_SIZE(byt_rt5640_widgets),
.dapm_routes = byt_rt5640_audio_map,
.num_dapm_routes = ARRAY_SIZE(byt_rt5640_audio_map),
.fully_routed = true,
};
static int snd_byt_mc_probe(struct platform_device *pdev)
static int snd_byt_rt5640_mc_probe(struct platform_device *pdev)
{
int ret_val = 0;
/* register the soc card */
snd_soc_card_byt.dev = &pdev->dev;
byt_rt5640_card.dev = &pdev->dev;
ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5640_card);
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);
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);
platform_set_drvdata(pdev, &byt_rt5640_card);
return ret_val;
}
static struct platform_driver snd_byt_mc_driver = {
static struct platform_driver snd_byt_rt5640_mc_driver = {
.driver = {
.name = "bytt100_rt5640",
.name = "bytcr_rt5640",
.pm = &snd_soc_pm_ops,
},
.probe = snd_byt_mc_probe,
.probe = snd_byt_rt5640_mc_probe,
};
module_platform_driver(snd_byt_mc_driver);
module_platform_driver(snd_byt_rt5640_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:bytt100_rt5640");
MODULE_ALIAS("platform:bytcr_rt5640");

View File

@ -232,6 +232,18 @@ static struct snd_soc_dai_link cht_dailink[] = {
.dpcm_capture = 1,
.ops = &cht_aif1_ops,
},
[MERR_DPCM_DEEP_BUFFER] = {
.name = "Deep-Buffer Audio Port",
.stream_name = "Deep-Buffer Audio",
.cpu_dai_name = "deepbuffer-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
.nonatomic = true,
.dynamic = 1,
.dpcm_playback = 1,
.ops = &cht_aif1_ops,
},
[MERR_DPCM_COMPR] = {
.name = "Compressed Port",
.stream_name = "Compress",

View File

@ -260,6 +260,18 @@ static struct snd_soc_dai_link cht_dailink[] = {
.dpcm_capture = 1,
.ops = &cht_aif1_ops,
},
[MERR_DPCM_DEEP_BUFFER] = {
.name = "Deep-Buffer Audio Port",
.stream_name = "Deep-Buffer Audio",
.cpu_dai_name = "deepbuffer-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
.nonatomic = true,
.dynamic = 1,
.dpcm_playback = 1,
.ops = &cht_aif1_ops,
},
[MERR_DPCM_COMPR] = {
.name = "Compressed Port",
.stream_name = "Compress",

View File

@ -248,6 +248,18 @@ static struct snd_soc_dai_link cht_dailink[] = {
.dpcm_capture = 1,
.ops = &cht_aif1_ops,
},
[MERR_DPCM_DEEP_BUFFER] = {
.name = "Deep-Buffer Audio Port",
.stream_name = "Deep-Buffer Audio",
.cpu_dai_name = "deepbuffer-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
.nonatomic = true,
.dynamic = 1,
.dpcm_playback = 1,
.ops = &cht_aif1_ops,
},
[MERR_DPCM_COMPR] = {
.name = "Compressed Port",
.stream_name = "Compress",

View File

@ -0,0 +1,485 @@
/*
* Intel Skylake I2S Machine Driver with MAXIM98357A
* and NAU88L25
*
* Copyright (C) 2015, Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "../../codecs/nau8825.h"
#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi"
#define SKL_MAXIM_CODEC_DAI "HiFi"
static struct snd_soc_jack skylake_headset;
static struct snd_soc_card skylake_audio_card;
static inline struct snd_soc_dai *skl_get_codec_dai(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
list_for_each_entry(rtd, &card->rtd_list, list) {
if (!strncmp(rtd->codec_dai->name, SKL_NUVOTON_CODEC_DAI,
strlen(SKL_NUVOTON_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;
int ret;
codec_dai = skl_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_ON(event)) {
ret = snd_soc_dai_set_sysclk(codec_dai,
NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "set sysclk err = %d\n", ret);
return -EIO;
}
} else {
ret = snd_soc_dai_set_sysclk(codec_dai,
NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "set sysclk err = %d\n", ret);
return -EIO;
}
}
return ret;
}
static const struct snd_kcontrol_new skylake_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Spk"),
};
static const struct snd_soc_dapm_widget skylake_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Spk", NULL),
SND_SOC_DAPM_MIC("SoC DMIC", NULL),
SND_SOC_DAPM_SINK("WoV Sink"),
SND_SOC_DAPM_SPK("DP", NULL),
SND_SOC_DAPM_SPK("HDMI", NULL),
SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
platform_clock_control, SND_SOC_DAPM_PRE_PMU |
SND_SOC_DAPM_POST_PMD),
};
static const struct snd_soc_dapm_route skylake_map[] = {
/* HP jack connectors - unknown if we have jack detection */
{ "Headphone Jack", NULL, "HPOL" },
{ "Headphone Jack", NULL, "HPOR" },
/* speaker */
{ "Spk", NULL, "Speaker" },
/* other jacks */
{ "MIC", NULL, "Headset Mic" },
{ "DMic", NULL, "SoC DMIC" },
{"WoV Sink", NULL, "hwd_in sink"},
{"HDMI", NULL, "hif5 Output"},
{"DP", NULL, "hif6 Output"},
/* CODEC BE connections */
{ "HiFi Playback", NULL, "ssp0 Tx" },
{ "ssp0 Tx", NULL, "codec0_out" },
{ "Playback", NULL, "ssp1 Tx" },
{ "ssp1 Tx", NULL, "codec1_out" },
{ "codec0_in", NULL, "ssp1 Rx" },
{ "ssp1 Rx", NULL, "Capture" },
/* DMIC */
{ "dmic01_hifi", NULL, "DMIC01 Rx" },
{ "DMIC01 Rx", NULL, "DMIC AIF" },
{ "hifi1", NULL, "iDisp Tx"},
{ "iDisp Tx", NULL, "iDisp_out"},
{ "Headphone Jack", NULL, "Platform Clock" },
{ "Headset Mic", NULL, "Platform Clock" },
};
static int skylake_ssp_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);
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
/* The ADSP will covert the FE rate to 48k, stereo */
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
/* set SSP0 to 24 bit */
snd_mask_none(fmt);
snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24_LE);
return 0;
}
static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
{
int ret;
struct snd_soc_codec *codec = rtd->codec;
/*
* Headset buttons map to the google Reference headset.
* These can be configured by userspace.
*/
ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset,
NULL, 0);
if (ret) {
dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret);
return ret;
}
nau8825_enable_jack_detect(codec, &skylake_headset);
snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "WoV Sink");
return ret;
}
static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dapm_context *dapm;
struct snd_soc_component *component = rtd->cpu_dai->component;
dapm = snd_soc_component_get_dapm(component);
snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
return 0;
}
static unsigned int rates[] = {
48000,
};
static struct snd_pcm_hw_constraint_list constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static unsigned int channels[] = {
2,
};
static struct snd_pcm_hw_constraint_list constraints_channels = {
.count = ARRAY_SIZE(channels),
.list = channels,
.mask = 0,
};
static int skl_fe_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
/*
* On this platform for PCM device we support,
* 48Khz
* stereo
* 16 bit audio
*/
runtime->hw.channels_max = 2;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
return 0;
}
static const struct snd_soc_ops skylake_nau8825_fe_ops = {
.startup = skl_fe_startup,
};
static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
ret = snd_soc_dai_set_sysclk(codec_dai,
NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
return ret;
}
static struct snd_soc_ops skylake_nau8825_ops = {
.hw_params = skylake_nau8825_hw_params,
};
static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
if (params_channels(params) == 2)
channels->min = channels->max = 2;
else
channels->min = channels->max = 4;
return 0;
}
static unsigned int channels_dmic[] = {
2, 4,
};
static struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
.count = ARRAY_SIZE(channels_dmic),
.list = channels_dmic,
.mask = 0,
};
static int skylake_dmic_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw.channels_max = 4;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_dmic_channels);
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
}
static struct snd_soc_ops skylake_dmic_ops = {
.startup = skylake_dmic_startup,
};
static unsigned int rates_16000[] = {
16000,
};
static struct snd_pcm_hw_constraint_list constraints_16000 = {
.count = ARRAY_SIZE(rates_16000),
.list = rates_16000,
};
static int skylake_refcap_startup(struct snd_pcm_substream *substream)
{
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_16000);
}
static struct snd_soc_ops skylaye_refcap_ops = {
.startup = skylake_refcap_startup,
};
/* skylake digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link skylake_dais[] = {
/* Front End DAI links */
{
.name = "Skl Audio Port",
.stream_name = "Audio",
.cpu_dai_name = "System Pin",
.platform_name = "0000:00:1f.3",
.dynamic = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.nonatomic = 1,
.init = skylake_nau8825_fe_init,
.trigger = {
SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dpcm_playback = 1,
.ops = &skylake_nau8825_fe_ops,
},
{
.name = "Skl Audio Capture Port",
.stream_name = "Audio Record",
.cpu_dai_name = "System Pin",
.platform_name = "0000:00:1f.3",
.dynamic = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.nonatomic = 1,
.trigger = {
SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dpcm_capture = 1,
.ops = &skylake_nau8825_fe_ops,
},
{
.name = "Skl Audio Reference cap",
.stream_name = "Wake on Voice",
.cpu_dai_name = "Reference Pin",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.platform_name = "0000:00:1f.3",
.init = NULL,
.dpcm_capture = 1,
.ignore_suspend = 1,
.nonatomic = 1,
.dynamic = 1,
.ops = &skylaye_refcap_ops,
},
{
.name = "Skl Audio DMIC cap",
.stream_name = "dmiccap",
.cpu_dai_name = "DMIC Pin",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.platform_name = "0000:00:1f.3",
.init = NULL,
.dpcm_capture = 1,
.nonatomic = 1,
.dynamic = 1,
.ops = &skylake_dmic_ops,
},
{
.name = "Skl HDMI Port",
.stream_name = "Hdmi",
.cpu_dai_name = "HDMI Pin",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.platform_name = "0000:00:1f.3",
.dpcm_playback = 1,
.init = NULL,
.nonatomic = 1,
.dynamic = 1,
},
/* Back End DAI links */
{
/* SSP0 - Codec */
.name = "SSP0-Codec",
.be_id = 0,
.cpu_dai_name = "SSP0 Pin",
.platform_name = "0000:00:1f.3",
.no_pcm = 1,
.codec_name = "MX98357A:00",
.codec_dai_name = SKL_MAXIM_CODEC_DAI,
.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = skylake_ssp_fixup,
.dpcm_playback = 1,
},
{
/* SSP1 - Codec */
.name = "SSP1-Codec",
.be_id = 0,
.cpu_dai_name = "SSP1 Pin",
.platform_name = "0000:00:1f.3",
.no_pcm = 1,
.codec_name = "i2c-10508825:00",
.codec_dai_name = SKL_NUVOTON_CODEC_DAI,
.init = skylake_nau8825_codec_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = skylake_ssp_fixup,
.ops = &skylake_nau8825_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "dmic01",
.be_id = 1,
.cpu_dai_name = "DMIC01 Pin",
.codec_name = "dmic-codec",
.codec_dai_name = "dmic-hifi",
.platform_name = "0000:00:1f.3",
.be_hw_params_fixup = skylake_dmic_fixup,
.ignore_suspend = 1,
.dpcm_capture = 1,
.no_pcm = 1,
},
{
.name = "iDisp",
.be_id = 3,
.cpu_dai_name = "iDisp Pin",
.codec_name = "ehdaudio0D2",
.codec_dai_name = "intel-hdmi-hifi1",
.platform_name = "0000:00:1f.3",
.dpcm_playback = 1,
.no_pcm = 1,
},
};
/* skylake audio machine driver for SPT + NAU88L25 */
static struct snd_soc_card skylake_audio_card = {
.name = "sklnau8825max",
.owner = THIS_MODULE,
.dai_link = skylake_dais,
.num_links = ARRAY_SIZE(skylake_dais),
.controls = skylake_controls,
.num_controls = ARRAY_SIZE(skylake_controls),
.dapm_widgets = skylake_widgets,
.num_dapm_widgets = ARRAY_SIZE(skylake_widgets),
.dapm_routes = skylake_map,
.num_dapm_routes = ARRAY_SIZE(skylake_map),
.fully_routed = true,
};
static int skylake_audio_probe(struct platform_device *pdev)
{
skylake_audio_card.dev = &pdev->dev;
return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card);
}
static struct platform_driver skylake_audio = {
.probe = skylake_audio_probe,
.driver = {
.name = "skl_nau88l25_max98357a_i2s",
.pm = &snd_soc_pm_ops,
},
};
module_platform_driver(skylake_audio)
/* Module information */
MODULE_DESCRIPTION("Audio Machine driver-NAU88L25 & MAX98357A in I2S mode");
MODULE_AUTHOR("Rohit Ainapure <rohit.m.ainapure@intel.com");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:skl_nau88l25_max98357a_i2s");

View File

@ -0,0 +1,536 @@
/*
* Intel Skylake I2S Machine Driver for NAU88L25+SSM4567
*
* Copyright (C) 2015, Intel Corporation. All rights reserved.
*
* Modified from:
* Intel Skylake I2S Machine Driver for NAU88L25 and SSM4567
*
* Copyright (C) 2015, Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <sound/pcm_params.h>
#include "../../codecs/nau8825.h"
#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi"
#define SKL_SSM_CODEC_DAI "ssm4567-hifi"
static struct snd_soc_jack skylake_headset;
static struct snd_soc_card skylake_audio_card;
static inline struct snd_soc_dai *skl_get_codec_dai(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
list_for_each_entry(rtd, &card->rtd_list, list) {
if (!strncmp(rtd->codec_dai->name, SKL_NUVOTON_CODEC_DAI,
strlen(SKL_NUVOTON_CODEC_DAI)))
return rtd->codec_dai;
}
return NULL;
}
static const struct snd_kcontrol_new skylake_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Left Speaker"),
SOC_DAPM_PIN_SWITCH("Right Speaker"),
};
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;
int ret;
codec_dai = skl_get_codec_dai(card);
if (!codec_dai) {
dev_err(card->dev, "Codec dai not found\n");
return -EIO;
}
if (SND_SOC_DAPM_EVENT_ON(event)) {
ret = snd_soc_dai_set_sysclk(codec_dai,
NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "set sysclk err = %d\n", ret);
return -EIO;
}
} else {
ret = snd_soc_dai_set_sysclk(codec_dai,
NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev, "set sysclk err = %d\n", ret);
return -EIO;
}
}
return ret;
}
static const struct snd_soc_dapm_widget skylake_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Left Speaker", NULL),
SND_SOC_DAPM_SPK("Right Speaker", NULL),
SND_SOC_DAPM_MIC("SoC DMIC", NULL),
SND_SOC_DAPM_SINK("WoV Sink"),
SND_SOC_DAPM_SPK("DP", NULL),
SND_SOC_DAPM_SPK("HDMI", NULL),
SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
platform_clock_control, SND_SOC_DAPM_PRE_PMU |
SND_SOC_DAPM_POST_PMD),
};
static const struct snd_soc_dapm_route skylake_map[] = {
/* HP jack connectors - unknown if we have jack detection */
{"Headphone Jack", NULL, "HPOL"},
{"Headphone Jack", NULL, "HPOR"},
/* speaker */
{"Left Speaker", NULL, "Left OUT"},
{"Right Speaker", NULL, "Right OUT"},
/* other jacks */
{"MIC", NULL, "Headset Mic"},
{"DMic", NULL, "SoC DMIC"},
{"WoV Sink", NULL, "hwd_in sink"},
{"HDMI", NULL, "hif5 Output"},
{"DP", NULL, "hif6 Output"},
/* CODEC BE connections */
{ "Left Playback", NULL, "ssp0 Tx"},
{ "Right Playback", NULL, "ssp0 Tx"},
{ "ssp0 Tx", NULL, "codec0_out"},
{ "Playback", NULL, "ssp1 Tx"},
{ "ssp1 Tx", NULL, "codec1_out"},
{ "codec0_in", NULL, "ssp1 Rx" },
{ "ssp1 Rx", NULL, "Capture" },
/* DMIC */
{ "dmic01_hifi", NULL, "DMIC01 Rx" },
{ "DMIC01 Rx", NULL, "DMIC AIF" },
{ "hifi1", NULL, "iDisp Tx"},
{ "iDisp Tx", NULL, "iDisp_out"},
{ "Headphone Jack", NULL, "Platform Clock" },
{ "Headset Mic", NULL, "Platform Clock" },
};
static struct snd_soc_codec_conf ssm4567_codec_conf[] = {
{
.dev_name = "i2c-INT343B:00",
.name_prefix = "Left",
},
{
.dev_name = "i2c-INT343B:01",
.name_prefix = "Right",
},
};
static struct snd_soc_dai_link_component ssm4567_codec_components[] = {
{ /* Left */
.name = "i2c-INT343B:00",
.dai_name = SKL_SSM_CODEC_DAI,
},
{ /* Right */
.name = "i2c-INT343B:01",
.dai_name = SKL_SSM_CODEC_DAI,
},
};
static int skylake_ssm4567_codec_init(struct snd_soc_pcm_runtime *rtd)
{
int ret;
/* Slot 1 for left */
ret = snd_soc_dai_set_tdm_slot(rtd->codec_dais[0], 0x01, 0x01, 2, 48);
if (ret < 0)
return ret;
/* Slot 2 for right */
ret = snd_soc_dai_set_tdm_slot(rtd->codec_dais[1], 0x02, 0x02, 2, 48);
if (ret < 0)
return ret;
return ret;
}
static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
{
int ret;
struct snd_soc_codec *codec = rtd->codec;
/*
* 4 buttons here map to the google Reference headset
* The use of these buttons can be decided by the user space.
*/
ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset,
NULL, 0);
if (ret) {
dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret);
return ret;
}
nau8825_enable_jack_detect(codec, &skylake_headset);
snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "WoV Sink");
return ret;
}
static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dapm_context *dapm;
struct snd_soc_component *component = rtd->cpu_dai->component;
dapm = snd_soc_component_get_dapm(component);
snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
return 0;
}
static unsigned int rates[] = {
48000,
};
static struct snd_pcm_hw_constraint_list constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static unsigned int channels[] = {
2,
};
static struct snd_pcm_hw_constraint_list constraints_channels = {
.count = ARRAY_SIZE(channels),
.list = channels,
.mask = 0,
};
static int skl_fe_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
/*
* on this platform for PCM device we support,
* 48Khz
* stereo
* 16 bit audio
*/
runtime->hw.channels_max = 2;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
return 0;
}
static const struct snd_soc_ops skylake_nau8825_fe_ops = {
.startup = skl_fe_startup,
};
static int skylake_ssp_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);
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
/* The ADSP will covert the FE rate to 48k, stereo */
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
/* set SSP0 to 24 bit */
snd_mask_none(fmt);
snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24_LE);
return 0;
}
static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
if (params_channels(params) == 2)
channels->min = channels->max = 2;
else
channels->min = channels->max = 4;
return 0;
}
static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
ret = snd_soc_dai_set_sysclk(codec_dai,
NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
return ret;
}
static struct snd_soc_ops skylake_nau8825_ops = {
.hw_params = skylake_nau8825_hw_params,
};
static unsigned int channels_dmic[] = {
2, 4,
};
static struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
.count = ARRAY_SIZE(channels_dmic),
.list = channels_dmic,
.mask = 0,
};
static int skylake_dmic_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw.channels_max = 4;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_dmic_channels);
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
}
static struct snd_soc_ops skylake_dmic_ops = {
.startup = skylake_dmic_startup,
};
static unsigned int rates_16000[] = {
16000,
};
static struct snd_pcm_hw_constraint_list constraints_16000 = {
.count = ARRAY_SIZE(rates_16000),
.list = rates_16000,
};
static int skylake_refcap_startup(struct snd_pcm_substream *substream)
{
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_16000);
}
static struct snd_soc_ops skylaye_refcap_ops = {
.startup = skylake_refcap_startup,
};
/* skylake digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link skylake_dais[] = {
/* Front End DAI links */
{
.name = "Skl Audio Port",
.stream_name = "Audio",
.cpu_dai_name = "System Pin",
.platform_name = "0000:00:1f.3",
.dynamic = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.nonatomic = 1,
.init = skylake_nau8825_fe_init,
.trigger = {
SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dpcm_playback = 1,
.ops = &skylake_nau8825_fe_ops,
},
{
.name = "Skl Audio Capture Port",
.stream_name = "Audio Record",
.cpu_dai_name = "System Pin",
.platform_name = "0000:00:1f.3",
.dynamic = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.nonatomic = 1,
.trigger = {
SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dpcm_capture = 1,
.ops = &skylake_nau8825_fe_ops,
},
{
.name = "Skl Audio Reference cap",
.stream_name = "Wake on Voice",
.cpu_dai_name = "Reference Pin",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.platform_name = "0000:00:1f.3",
.init = NULL,
.dpcm_capture = 1,
.ignore_suspend = 1,
.nonatomic = 1,
.dynamic = 1,
.ops = &skylaye_refcap_ops,
},
{
.name = "Skl Audio DMIC cap",
.stream_name = "dmiccap",
.cpu_dai_name = "DMIC Pin",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.platform_name = "0000:00:1f.3",
.init = NULL,
.dpcm_capture = 1,
.nonatomic = 1,
.dynamic = 1,
.ops = &skylake_dmic_ops,
},
{
.name = "Skl HDMI Port",
.stream_name = "Hdmi",
.cpu_dai_name = "HDMI Pin",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.platform_name = "0000:00:1f.3",
.dpcm_playback = 1,
.init = NULL,
.nonatomic = 1,
.dynamic = 1,
},
/* Back End DAI links */
{
/* SSP0 - Codec */
.name = "SSP0-Codec",
.be_id = 0,
.cpu_dai_name = "SSP0 Pin",
.platform_name = "0000:00:1f.3",
.no_pcm = 1,
.codecs = ssm4567_codec_components,
.num_codecs = ARRAY_SIZE(ssm4567_codec_components),
.dai_fmt = SND_SOC_DAIFMT_DSP_A |
SND_SOC_DAIFMT_IB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.init = skylake_ssm4567_codec_init,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = skylake_ssp_fixup,
.dpcm_playback = 1,
},
{
/* SSP1 - Codec */
.name = "SSP1-Codec",
.be_id = 0,
.cpu_dai_name = "SSP1 Pin",
.platform_name = "0000:00:1f.3",
.no_pcm = 1,
.codec_name = "i2c-10508825:00",
.codec_dai_name = SKL_NUVOTON_CODEC_DAI,
.init = skylake_nau8825_codec_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = skylake_ssp_fixup,
.ops = &skylake_nau8825_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "dmic01",
.be_id = 1,
.cpu_dai_name = "DMIC01 Pin",
.codec_name = "dmic-codec",
.codec_dai_name = "dmic-hifi",
.platform_name = "0000:00:1f.3",
.ignore_suspend = 1,
.be_hw_params_fixup = skylake_dmic_fixup,
.dpcm_capture = 1,
.no_pcm = 1,
},
{
.name = "iDisp",
.be_id = 3,
.cpu_dai_name = "iDisp Pin",
.codec_name = "ehdaudio0D2",
.codec_dai_name = "intel-hdmi-hifi1",
.platform_name = "0000:00:1f.3",
.dpcm_playback = 1,
.no_pcm = 1,
},
};
/* skylake audio machine driver for SPT + NAU88L25 */
static struct snd_soc_card skylake_audio_card = {
.name = "sklnau8825adi",
.owner = THIS_MODULE,
.dai_link = skylake_dais,
.num_links = ARRAY_SIZE(skylake_dais),
.controls = skylake_controls,
.num_controls = ARRAY_SIZE(skylake_controls),
.dapm_widgets = skylake_widgets,
.num_dapm_widgets = ARRAY_SIZE(skylake_widgets),
.dapm_routes = skylake_map,
.num_dapm_routes = ARRAY_SIZE(skylake_map),
.codec_conf = ssm4567_codec_conf,
.num_configs = ARRAY_SIZE(ssm4567_codec_conf),
.fully_routed = true,
};
static int skylake_audio_probe(struct platform_device *pdev)
{
skylake_audio_card.dev = &pdev->dev;
return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card);
}
static struct platform_driver skylake_audio = {
.probe = skylake_audio_probe,
.driver = {
.name = "skl_nau88l25_ssm4567_i2s",
.pm = &snd_soc_pm_ops,
},
};
module_platform_driver(skylake_audio)
/* Module information */
MODULE_AUTHOR("Conrad Cooke <conrad.cooke@intel.com>");
MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>");
MODULE_AUTHOR("Naveen M <naveen.m@intel.com>");
MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>");
MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>");
MODULE_DESCRIPTION("Intel Audio Machine driver for SKL with NAU88L25 and SSM4567 in I2S Mode");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:skl_nau88l25_ssm4567_i2s");

View File

@ -52,6 +52,7 @@ static const struct snd_soc_dapm_widget skylake_widgets[] = {
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_MIC("DMIC2", NULL),
SND_SOC_DAPM_MIC("SoC DMIC", NULL),
SND_SOC_DAPM_SINK("WoV Sink"),
};
static const struct snd_soc_dapm_route skylake_rt286_map[] = {
@ -67,7 +68,9 @@ static const struct snd_soc_dapm_route skylake_rt286_map[] = {
/* digital mics */
{"DMIC1 Pin", NULL, "DMIC2"},
{"DMIC AIF", NULL, "SoC DMIC"},
{"DMic", NULL, "SoC DMIC"},
{"WoV Sink", NULL, "hwd_in sink"},
/* CODEC BE connections */
{ "AIF1 Playback", NULL, "ssp0 Tx"},
@ -79,13 +82,24 @@ static const struct snd_soc_dapm_route skylake_rt286_map[] = {
{ "ssp0 Rx", NULL, "AIF1 Capture" },
{ "dmic01_hifi", NULL, "DMIC01 Rx" },
{ "DMIC01 Rx", NULL, "Capture" },
{ "DMIC01 Rx", NULL, "DMIC AIF" },
{ "hif1", NULL, "iDisp Tx"},
{ "iDisp Tx", NULL, "iDisp_out"},
};
static int skylake_rt286_fe_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dapm_context *dapm;
struct snd_soc_component *component = rtd->cpu_dai->component;
dapm = snd_soc_component_get_dapm(component);
snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
return 0;
}
static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
@ -101,9 +115,59 @@ static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd)
rt286_mic_detect(codec, &skylake_headset);
snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "WoV Sink");
return 0;
}
static unsigned int rates[] = {
48000,
};
static struct snd_pcm_hw_constraint_list constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static unsigned int channels[] = {
2,
};
static struct snd_pcm_hw_constraint_list constraints_channels = {
.count = ARRAY_SIZE(channels),
.list = channels,
.mask = 0,
};
static int skl_fe_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
/*
* on this platform for PCM device we support,
* 48Khz
* stereo
* 16 bit audio
*/
runtime->hw.channels_max = 2;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_channels);
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
return 0;
}
static const struct snd_soc_ops skylake_rt286_fe_ops = {
.startup = skl_fe_startup,
};
static int skylake_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
@ -112,12 +176,15 @@ static int skylake_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
/* The output is 48KHz, stereo, 16bits */
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
/* set SSP0 to 24 bit */
snd_mask_none(fmt);
snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24_LE);
return 0;
}
@ -140,6 +207,42 @@ static struct snd_soc_ops skylake_rt286_ops = {
.hw_params = skylake_rt286_hw_params,
};
static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
channels->min = channels->max = 4;
return 0;
}
static unsigned int channels_dmic[] = {
2, 4,
};
static struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
.count = ARRAY_SIZE(channels_dmic),
.list = channels_dmic,
.mask = 0,
};
static int skylake_dmic_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw.channels_max = 4;
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints_dmic_channels);
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
}
static struct snd_soc_ops skylake_dmic_ops = {
.startup = skylake_dmic_startup,
};
/* skylake digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link skylake_rt286_dais[] = {
/* Front End DAI links */
@ -152,11 +255,13 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = {
.dynamic = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.init = skylake_rt286_fe_init,
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dpcm_playback = 1,
.ops = &skylake_rt286_fe_ops,
},
{
.name = "Skl Audio Capture Port",
@ -172,6 +277,7 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = {
SND_SOC_DPCM_TRIGGER_POST
},
.dpcm_capture = 1,
.ops = &skylake_rt286_fe_ops,
},
{
.name = "Skl Audio Reference cap",
@ -186,6 +292,19 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = {
.nonatomic = 1,
.dynamic = 1,
},
{
.name = "Skl Audio DMIC cap",
.stream_name = "dmiccap",
.cpu_dai_name = "DMIC Pin",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.platform_name = "0000:00:1f.3",
.init = NULL,
.dpcm_capture = 1,
.nonatomic = 1,
.dynamic = 1,
.ops = &skylake_dmic_ops,
},
/* Back End DAI links */
{
@ -201,7 +320,6 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = {
.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = skylake_ssp0_fixup,
.ops = &skylake_rt286_ops,
@ -215,6 +333,7 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = {
.codec_name = "dmic-codec",
.codec_dai_name = "dmic-hifi",
.platform_name = "0000:00:1f.3",
.be_hw_params_fixup = skylake_dmic_fixup,
.ignore_suspend = 1,
.dpcm_capture = 1,
.no_pcm = 1,
@ -247,6 +366,7 @@ static struct platform_driver skylake_audio = {
.probe = skylake_audio_probe,
.driver = {
.name = "skl_alc286s_i2s",
.pm = &snd_soc_pm_ops,
},
};

View File

@ -1,11 +1,8 @@
snd-soc-sst-dsp-objs := sst-dsp.o
snd-soc-sst-acpi-objs := sst-acpi.o
snd-soc-sst-acpi-objs := sst-acpi.o sst-match-acpi.o
snd-soc-sst-ipc-objs := sst-ipc.o
ifneq ($(CONFIG_DW_DMAC_CORE),)
snd-soc-sst-dsp-objs += sst-firmware.o
endif
snd-soc-sst-dsp-$(CONFIG_DW_DMAC_CORE) += sst-firmware.o
obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o snd-soc-sst-ipc.o
obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o

View File

@ -21,21 +21,12 @@
#include <linux/platform_device.h>
#include "sst-dsp.h"
#include "sst-acpi.h"
#define SST_LPT_DSP_DMA_ADDR_OFFSET 0x0F0000
#define SST_WPT_DSP_DMA_ADDR_OFFSET 0x0FE000
#define SST_LPT_DSP_DMA_SIZE (1024 - 1)
/* Descriptor for SST ASoC machine driver */
struct sst_acpi_mach {
/* ACPI ID for the matching machine driver. Audio codec for instance */
const u8 id[ACPI_ID_LEN];
/* machine driver name */
const char *drv_name;
/* firmware file name */
const char *fw_filename;
};
/* Descriptor for setting up SST platform data */
struct sst_acpi_desc {
const char *drv_name;
@ -88,28 +79,6 @@ static void sst_acpi_fw_cb(const struct firmware *fw, void *context)
return;
}
static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level,
void *context, void **ret)
{
*(bool *)context = true;
return AE_OK;
}
static struct sst_acpi_mach *sst_acpi_find_machine(
struct sst_acpi_mach *machines)
{
struct sst_acpi_mach *mach;
bool found = false;
for (mach = machines; mach->id[0]; mach++)
if (ACPI_SUCCESS(acpi_get_devices(mach->id,
sst_acpi_mach_match,
&found, NULL)) && found)
return mach;
return NULL;
}
static int sst_acpi_probe(struct platform_device *pdev)
{
const struct acpi_device_id *id;
@ -211,7 +180,7 @@ static int sst_acpi_remove(struct platform_device *pdev)
}
static struct sst_acpi_mach haswell_machines[] = {
{ "INT33CA", "haswell-audio", "intel/IntcSST1.bin" },
{ "INT33CA", "haswell-audio", "intel/IntcSST1.bin", NULL, NULL, NULL },
{}
};
@ -229,7 +198,7 @@ static struct sst_acpi_desc sst_acpi_haswell_desc = {
};
static struct sst_acpi_mach broadwell_machines[] = {
{ "INT343A", "broadwell-audio", "intel/IntcSST2.bin" },
{ "INT343A", "broadwell-audio", "intel/IntcSST2.bin", NULL, NULL, NULL },
{}
};
@ -247,8 +216,8 @@ static struct sst_acpi_desc sst_acpi_broadwell_desc = {
};
static struct sst_acpi_mach baytrail_machines[] = {
{ "10EC5640", "byt-rt5640", "intel/fw_sst_0f28.bin-48kHz_i2s_master" },
{ "193C9890", "byt-max98090", "intel/fw_sst_0f28.bin-48kHz_i2s_master" },
{ "10EC5640", "byt-rt5640", "intel/fw_sst_0f28.bin-48kHz_i2s_master", NULL, NULL, NULL },
{ "193C9890", "byt-max98090", "intel/fw_sst_0f28.bin-48kHz_i2s_master", NULL, NULL, NULL },
{}
};

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2013-15, Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/acpi.h>
/* acpi match */
struct sst_acpi_mach *sst_acpi_find_machine(struct sst_acpi_mach *machines);
/* Descriptor for SST ASoC machine driver */
struct sst_acpi_mach {
/* ACPI ID for the matching machine driver. Audio codec for instance */
const u8 id[ACPI_ID_LEN];
/* machine driver name */
const char *drv_name;
/* firmware file name */
const char *fw_filename;
/* board name */
const char *board;
void (*machine_quirk)(void);
void *pdata;
};

View File

@ -243,7 +243,7 @@ struct sst_mem_block {
u32 size; /* block size */
u32 index; /* block index 0..N */
enum sst_mem_type type; /* block memory type IRAM/DRAM */
struct sst_block_ops *ops; /* block operations, if any */
const struct sst_block_ops *ops;/* block operations, if any */
/* block status */
u32 bytes_used; /* bytes in use by modules */
@ -308,6 +308,8 @@ struct sst_dsp {
/* SKL data */
const char *fw_name;
/* To allocate CL dma buffers */
struct skl_dsp_loader_ops dsp_ops;
struct skl_dsp_fw_ops fw_ops;
@ -376,8 +378,8 @@ 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,
u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index,
void *private);
u32 size, enum sst_mem_type type, const struct sst_block_ops *ops,
u32 index, void *private);
void sst_mem_block_unregister_all(struct sst_dsp *dsp);
/* Create/Free DMA resources */

View File

@ -420,7 +420,7 @@ void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes)
}
EXPORT_SYMBOL_GPL(sst_dsp_inbox_read);
#if IS_ENABLED(CONFIG_DW_DMAC_CORE)
#ifdef CONFIG_DW_DMAC_CORE
struct sst_dsp *sst_dsp_new(struct device *dev,
struct sst_dsp_device *sst_dev, struct sst_pdata *pdata)
{

View File

@ -216,7 +216,7 @@ struct sst_pdata {
void *dsp;
};
#if IS_ENABLED(CONFIG_DW_DMAC_CORE)
#ifdef CONFIG_DW_DMAC_CORE
/* Initialization */
struct sst_dsp *sst_dsp_new(struct device *dev,
struct sst_dsp_device *sst_dev, struct sst_pdata *pdata);

View File

@ -51,8 +51,22 @@ struct sst_dma {
static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes)
{
u32 tmp = 0;
int i, m, n;
const u8 *src_byte = src;
m = bytes / 4;
n = bytes % 4;
/* __iowrite32_copy use 32bit size values so divide by 4 */
__iowrite32_copy((void *)dest, src, bytes/4);
__iowrite32_copy((void *)dest, src, m);
if (n) {
for (i = 0; i < n; i++)
tmp |= (u32)*(src_byte + m * 4 + i) << (i * 8);
__iowrite32_copy((void *)(dest + m * 4), &tmp, 1);
}
}
static void sst_dma_transfer_complete(void *arg)
@ -1014,8 +1028,8 @@ 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,
u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index,
void *private)
u32 size, enum sst_mem_type type, const struct sst_block_ops *ops,
u32 index, void *private)
{
struct sst_mem_block *block;

View File

@ -0,0 +1,43 @@
/*
* sst_match_apci.c - SST (LPE) match for ACPI enumeration.
*
* Copyright (c) 2013-15, Intel Corporation.
*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "sst-acpi.h"
static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level,
void *context, void **ret)
{
*(bool *)context = true;
return AE_OK;
}
struct sst_acpi_mach *sst_acpi_find_machine(struct sst_acpi_mach *machines)
{
struct sst_acpi_mach *mach;
bool found = false;
for (mach = machines; mach->id[0]; mach++)
if (ACPI_SUCCESS(acpi_get_devices(mach->id,
sst_acpi_mach_match,
&found, NULL)) && found)
return mach;
return NULL;
}
EXPORT_SYMBOL_GPL(sst_acpi_find_machine);

View File

@ -607,7 +607,7 @@ static int hsw_block_disable(struct sst_mem_block *block)
return 0;
}
static struct sst_block_ops sst_hsw_ops = {
static const struct sst_block_ops sst_hsw_ops = {
.enable = hsw_block_enable,
.disable = hsw_block_disable,
};

View File

@ -778,7 +778,6 @@ static irqreturn_t hsw_irq_thread(int irq, void *context)
struct sst_hsw *hsw = sst_dsp_get_thread_context(sst);
struct sst_generic_ipc *ipc = &hsw->ipc;
u32 ipcx, ipcd;
int handled;
unsigned long flags;
spin_lock_irqsave(&sst->spinlock, flags);
@ -790,34 +789,30 @@ static irqreturn_t hsw_irq_thread(int irq, void *context)
if (ipcx & SST_IPCX_DONE) {
/* Handle Immediate reply from DSP Core */
handled = hsw_process_reply(hsw, ipcx);
hsw_process_reply(hsw, ipcx);
if (handled > 0) {
/* clear DONE bit - tell DSP we have completed */
sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX,
SST_IPCX_DONE, 0);
/* clear DONE bit - tell DSP we have completed */
sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX,
SST_IPCX_DONE, 0);
/* unmask Done interrupt */
sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
SST_IMRX_DONE, 0);
}
/* unmask Done interrupt */
sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
SST_IMRX_DONE, 0);
}
/* new message from DSP */
if (ipcd & SST_IPCD_BUSY) {
/* Handle Notification and Delayed reply from DSP Core */
handled = hsw_process_notification(hsw);
hsw_process_notification(hsw);
/* clear BUSY bit and set DONE bit - accept new messages */
if (handled > 0) {
sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD,
SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE);
sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD,
SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE);
/* unmask busy interrupt */
sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
SST_IMRX_BUSY, 0);
}
/* unmask busy interrupt */
sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
SST_IMRX_BUSY, 0);
}
spin_unlock_irqrestore(&sst->spinlock, flags);

View File

@ -96,7 +96,7 @@ int skl_init_dsp(struct skl *skl)
}
ret = skl_sst_dsp_init(bus->dev, mmio_base, irq,
loader_ops, &skl->skl_sst);
skl->fw_name, loader_ops, &skl->skl_sst);
if (ret < 0)
return ret;
@ -182,94 +182,6 @@ enum skl_bitdepth skl_get_bit_depth(int params)
}
}
static u32 skl_create_channel_map(enum skl_ch_cfg ch_cfg)
{
u32 config;
switch (ch_cfg) {
case SKL_CH_CFG_MONO:
config = (0xFFFFFFF0 | SKL_CHANNEL_LEFT);
break;
case SKL_CH_CFG_STEREO:
config = (0xFFFFFF00 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_RIGHT << 4));
break;
case SKL_CH_CFG_2_1:
config = (0xFFFFF000 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_RIGHT << 4)
| (SKL_CHANNEL_LFE << 8));
break;
case SKL_CH_CFG_3_0:
config = (0xFFFFF000 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_CENTER << 4)
| (SKL_CHANNEL_RIGHT << 8));
break;
case SKL_CH_CFG_3_1:
config = (0xFFFF0000 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_CENTER << 4)
| (SKL_CHANNEL_RIGHT << 8)
| (SKL_CHANNEL_LFE << 12));
break;
case SKL_CH_CFG_QUATRO:
config = (0xFFFF0000 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_RIGHT << 4)
| (SKL_CHANNEL_LEFT_SURROUND << 8)
| (SKL_CHANNEL_RIGHT_SURROUND << 12));
break;
case SKL_CH_CFG_4_0:
config = (0xFFFF0000 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_CENTER << 4)
| (SKL_CHANNEL_RIGHT << 8)
| (SKL_CHANNEL_CENTER_SURROUND << 12));
break;
case SKL_CH_CFG_5_0:
config = (0xFFF00000 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_CENTER << 4)
| (SKL_CHANNEL_RIGHT << 8)
| (SKL_CHANNEL_LEFT_SURROUND << 12)
| (SKL_CHANNEL_RIGHT_SURROUND << 16));
break;
case SKL_CH_CFG_5_1:
config = (0xFF000000 | SKL_CHANNEL_CENTER
| (SKL_CHANNEL_LEFT << 4)
| (SKL_CHANNEL_RIGHT << 8)
| (SKL_CHANNEL_LEFT_SURROUND << 12)
| (SKL_CHANNEL_RIGHT_SURROUND << 16)
| (SKL_CHANNEL_LFE << 20));
break;
case SKL_CH_CFG_DUAL_MONO:
config = (0xFFFFFF00 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_LEFT << 4));
break;
case SKL_CH_CFG_I2S_DUAL_STEREO_0:
config = (0xFFFFFF00 | SKL_CHANNEL_LEFT
| (SKL_CHANNEL_RIGHT << 4));
break;
case SKL_CH_CFG_I2S_DUAL_STEREO_1:
config = (0xFFFF00FF | (SKL_CHANNEL_LEFT << 8)
| (SKL_CHANNEL_RIGHT << 12));
break;
default:
config = 0xFFFFFFFF;
break;
}
return config;
}
/*
* Each module in DSP expects a base module configuration, which consists of
* PCM format information, which we calculate in driver and resource values
@ -280,7 +192,7 @@ static void skl_set_base_module_format(struct skl_sst *ctx,
struct skl_module_cfg *mconfig,
struct skl_base_cfg *base_cfg)
{
struct skl_module_fmt *format = &mconfig->in_fmt;
struct skl_module_fmt *format = &mconfig->in_fmt[0];
base_cfg->audio_fmt.number_of_channels = (u8)format->channels;
@ -293,14 +205,14 @@ static void skl_set_base_module_format(struct skl_sst *ctx,
format->bit_depth, format->valid_bit_depth,
format->ch_cfg);
base_cfg->audio_fmt.channel_map = skl_create_channel_map(
base_cfg->audio_fmt.ch_cfg);
base_cfg->audio_fmt.channel_map = format->ch_map;
base_cfg->audio_fmt.interleaving = SKL_INTERLEAVING_PER_CHANNEL;
base_cfg->audio_fmt.interleaving = format->interleaving_style;
base_cfg->cps = mconfig->mcps;
base_cfg->ibs = mconfig->ibs;
base_cfg->obs = mconfig->obs;
base_cfg->is_pages = mconfig->mem_pages;
}
/*
@ -399,7 +311,7 @@ static void skl_setup_out_format(struct skl_sst *ctx,
struct skl_module_cfg *mconfig,
struct skl_audio_data_format *out_fmt)
{
struct skl_module_fmt *format = &mconfig->out_fmt;
struct skl_module_fmt *format = &mconfig->out_fmt[0];
out_fmt->number_of_channels = (u8)format->channels;
out_fmt->s_freq = format->s_freq;
@ -407,8 +319,9 @@ static void skl_setup_out_format(struct skl_sst *ctx,
out_fmt->valid_bit_depth = format->valid_bit_depth;
out_fmt->ch_cfg = format->ch_cfg;
out_fmt->channel_map = skl_create_channel_map(out_fmt->ch_cfg);
out_fmt->interleaving = SKL_INTERLEAVING_PER_CHANNEL;
out_fmt->channel_map = format->ch_map;
out_fmt->interleaving = format->interleaving_style;
out_fmt->sample_type = format->sample_type;
dev_dbg(ctx->dev, "copier out format chan=%d fre=%d bitdepth=%d\n",
out_fmt->number_of_channels, format->s_freq, format->bit_depth);
@ -423,7 +336,7 @@ static void skl_set_src_format(struct skl_sst *ctx,
struct skl_module_cfg *mconfig,
struct skl_src_module_cfg *src_mconfig)
{
struct skl_module_fmt *fmt = &mconfig->out_fmt;
struct skl_module_fmt *fmt = &mconfig->out_fmt[0];
skl_set_base_module_format(ctx, mconfig,
(struct skl_base_cfg *)src_mconfig);
@ -440,7 +353,7 @@ static void skl_set_updown_mixer_format(struct skl_sst *ctx,
struct skl_module_cfg *mconfig,
struct skl_up_down_mixer_cfg *mixer_mconfig)
{
struct skl_module_fmt *fmt = &mconfig->out_fmt;
struct skl_module_fmt *fmt = &mconfig->out_fmt[0];
int i = 0;
skl_set_base_module_format(ctx, mconfig,
@ -475,6 +388,47 @@ static void skl_set_copier_format(struct skl_sst *ctx,
skl_setup_cpr_gateway_cfg(ctx, mconfig, cpr_mconfig);
}
/*
* Algo module are DSP pre processing modules. Algo module take base module
* configuration and params
*/
static void skl_set_algo_format(struct skl_sst *ctx,
struct skl_module_cfg *mconfig,
struct skl_algo_cfg *algo_mcfg)
{
struct skl_base_cfg *base_cfg = (struct skl_base_cfg *)algo_mcfg;
skl_set_base_module_format(ctx, mconfig, base_cfg);
if (mconfig->formats_config.caps_size == 0)
return;
memcpy(algo_mcfg->params,
mconfig->formats_config.caps,
mconfig->formats_config.caps_size);
}
/*
* Mic select module allows selecting one or many input channels, thus
* acting as a demux.
*
* Mic select module take base module configuration and out-format
* configuration
*/
static void skl_set_base_outfmt_format(struct skl_sst *ctx,
struct skl_module_cfg *mconfig,
struct skl_base_outfmt_cfg *base_outfmt_mcfg)
{
struct skl_audio_data_format *out_fmt = &base_outfmt_mcfg->out_fmt;
struct skl_base_cfg *base_cfg =
(struct skl_base_cfg *)base_outfmt_mcfg;
skl_set_base_module_format(ctx, mconfig, base_cfg);
skl_setup_out_format(ctx, mconfig, out_fmt);
}
static u16 skl_get_module_param_size(struct skl_sst *ctx,
struct skl_module_cfg *mconfig)
{
@ -492,6 +446,14 @@ static u16 skl_get_module_param_size(struct skl_sst *ctx,
case SKL_MODULE_TYPE_UPDWMIX:
return sizeof(struct skl_up_down_mixer_cfg);
case SKL_MODULE_TYPE_ALGO:
param_size = sizeof(struct skl_base_cfg);
param_size += mconfig->formats_config.caps_size;
return param_size;
case SKL_MODULE_TYPE_BASE_OUTFMT:
return sizeof(struct skl_base_outfmt_cfg);
default:
/*
* return only base cfg when no specific module type is
@ -538,6 +500,14 @@ static int skl_set_module_format(struct skl_sst *ctx,
skl_set_updown_mixer_format(ctx, module_config, *param_data);
break;
case SKL_MODULE_TYPE_ALGO:
skl_set_algo_format(ctx, module_config, *param_data);
break;
case SKL_MODULE_TYPE_BASE_OUTFMT:
skl_set_base_outfmt_format(ctx, module_config, *param_data);
break;
default:
skl_set_base_module_format(ctx, module_config, *param_data);
break;
@ -571,10 +541,10 @@ static int skl_get_queue_index(struct skl_module_pin *mpin,
* In static, the pin_index is fixed based on module_id and instance id
*/
static int skl_alloc_queue(struct skl_module_pin *mpin,
struct skl_module_inst_id id, int max)
struct skl_module_cfg *tgt_cfg, int max)
{
int i;
struct skl_module_inst_id id = tgt_cfg->id;
/*
* if pin in dynamic, find first free pin
* otherwise find match module and instance id pin as topology will
@ -583,16 +553,23 @@ static int skl_alloc_queue(struct skl_module_pin *mpin,
*/
for (i = 0; i < max; i++) {
if (mpin[i].is_dynamic) {
if (!mpin[i].in_use) {
if (!mpin[i].in_use &&
mpin[i].pin_state == SKL_PIN_UNBIND) {
mpin[i].in_use = true;
mpin[i].id.module_id = id.module_id;
mpin[i].id.instance_id = id.instance_id;
mpin[i].tgt_mcfg = tgt_cfg;
return i;
}
} else {
if (mpin[i].id.module_id == id.module_id &&
mpin[i].id.instance_id == id.instance_id)
mpin[i].id.instance_id == id.instance_id &&
mpin[i].pin_state == SKL_PIN_UNBIND) {
mpin[i].tgt_mcfg = tgt_cfg;
return i;
}
}
}
@ -606,6 +583,28 @@ static void skl_free_queue(struct skl_module_pin *mpin, int q_index)
mpin[q_index].id.module_id = 0;
mpin[q_index].id.instance_id = 0;
}
mpin[q_index].pin_state = SKL_PIN_UNBIND;
mpin[q_index].tgt_mcfg = NULL;
}
/* Module state will be set to unint, if all the out pin state is UNBIND */
static void skl_clear_module_state(struct skl_module_pin *mpin, int max,
struct skl_module_cfg *mcfg)
{
int i;
bool found = false;
for (i = 0; i < max; i++) {
if (mpin[i].pin_state == SKL_PIN_UNBIND)
continue;
found = true;
break;
}
if (!found)
mcfg->m_state = SKL_MODULE_UNINIT;
return;
}
/*
@ -615,7 +614,7 @@ static void skl_free_queue(struct skl_module_pin *mpin, int q_index)
* invoke the DSP by sending IPC INIT_INSTANCE using ipc helper
*/
int skl_init_module(struct skl_sst *ctx,
struct skl_module_cfg *mconfig, char *param)
struct skl_module_cfg *mconfig)
{
u16 module_config_size = 0;
void *param_data = NULL;
@ -682,37 +681,30 @@ int skl_unbind_modules(struct skl_sst *ctx,
struct skl_module_inst_id dst_id = dst_mcfg->id;
int in_max = dst_mcfg->max_in_queue;
int out_max = src_mcfg->max_out_queue;
int src_index, dst_index;
int src_index, dst_index, src_pin_state, dst_pin_state;
skl_dump_bind_info(ctx, src_mcfg, dst_mcfg);
if (src_mcfg->m_state != SKL_MODULE_BIND_DONE)
return 0;
/*
* if intra module unbind, check if both modules are BIND,
* then send unbind
*/
if ((src_mcfg->pipe->ppl_id != dst_mcfg->pipe->ppl_id) &&
dst_mcfg->m_state != SKL_MODULE_BIND_DONE)
return 0;
else if (src_mcfg->m_state < SKL_MODULE_INIT_DONE &&
dst_mcfg->m_state < SKL_MODULE_INIT_DONE)
return 0;
/* get src queue index */
src_index = skl_get_queue_index(src_mcfg->m_out_pin, dst_id, out_max);
if (src_index < 0)
return -EINVAL;
msg.src_queue = src_mcfg->m_out_pin[src_index].pin_index;
msg.src_queue = src_index;
/* get dst queue index */
dst_index = skl_get_queue_index(dst_mcfg->m_in_pin, src_id, in_max);
if (dst_index < 0)
return -EINVAL;
msg.dst_queue = dst_mcfg->m_in_pin[dst_index].pin_index;
msg.dst_queue = dst_index;
src_pin_state = src_mcfg->m_out_pin[src_index].pin_state;
dst_pin_state = dst_mcfg->m_in_pin[dst_index].pin_state;
if (src_pin_state != SKL_PIN_BIND_DONE ||
dst_pin_state != SKL_PIN_BIND_DONE)
return 0;
msg.module_id = src_mcfg->id.module_id;
msg.instance_id = src_mcfg->id.instance_id;
@ -722,10 +714,15 @@ int skl_unbind_modules(struct skl_sst *ctx,
ret = skl_ipc_bind_unbind(&ctx->ipc, &msg);
if (!ret) {
src_mcfg->m_state = SKL_MODULE_UNINIT;
/* free queue only if unbind is success */
skl_free_queue(src_mcfg->m_out_pin, src_index);
skl_free_queue(dst_mcfg->m_in_pin, dst_index);
/*
* check only if src module bind state, bind is
* always from src -> sink
*/
skl_clear_module_state(src_mcfg->m_out_pin, out_max, src_mcfg);
}
return ret;
@ -744,8 +741,6 @@ int skl_bind_modules(struct skl_sst *ctx,
{
int ret;
struct skl_ipc_bind_unbind_msg msg;
struct skl_module_inst_id src_id = src_mcfg->id;
struct skl_module_inst_id dst_id = dst_mcfg->id;
int in_max = dst_mcfg->max_in_queue;
int out_max = src_mcfg->max_out_queue;
int src_index, dst_index;
@ -756,18 +751,18 @@ int skl_bind_modules(struct skl_sst *ctx,
dst_mcfg->m_state < SKL_MODULE_INIT_DONE)
return 0;
src_index = skl_alloc_queue(src_mcfg->m_out_pin, dst_id, out_max);
src_index = skl_alloc_queue(src_mcfg->m_out_pin, dst_mcfg, out_max);
if (src_index < 0)
return -EINVAL;
msg.src_queue = src_mcfg->m_out_pin[src_index].pin_index;
dst_index = skl_alloc_queue(dst_mcfg->m_in_pin, src_id, in_max);
msg.src_queue = src_index;
dst_index = skl_alloc_queue(dst_mcfg->m_in_pin, src_mcfg, in_max);
if (dst_index < 0) {
skl_free_queue(src_mcfg->m_out_pin, src_index);
return -EINVAL;
}
msg.dst_queue = dst_mcfg->m_in_pin[dst_index].pin_index;
msg.dst_queue = dst_index;
dev_dbg(ctx->dev, "src queue = %d dst queue =%d\n",
msg.src_queue, msg.dst_queue);
@ -782,6 +777,8 @@ int skl_bind_modules(struct skl_sst *ctx,
if (!ret) {
src_mcfg->m_state = SKL_MODULE_BIND_DONE;
src_mcfg->m_out_pin[src_index].pin_state = SKL_PIN_BIND_DONE;
dst_mcfg->m_in_pin[dst_index].pin_state = SKL_PIN_BIND_DONE;
} else {
/* error case , if IPC fails, clear the queue index */
skl_free_queue(src_mcfg->m_out_pin, src_index);
@ -852,6 +849,8 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe)
ret = skl_ipc_delete_pipeline(&ctx->ipc, pipe->ppl_id);
if (ret < 0)
dev_err(ctx->dev, "Failed to delete pipeline\n");
pipe->state = SKL_PIPE_INVALID;
}
return ret;
@ -916,3 +915,30 @@ int skl_stop_pipe(struct skl_sst *ctx, struct skl_pipe *pipe)
return 0;
}
/* Algo parameter set helper function */
int skl_set_module_params(struct skl_sst *ctx, u32 *params, int size,
u32 param_id, struct skl_module_cfg *mcfg)
{
struct skl_ipc_large_config_msg msg;
msg.module_id = mcfg->id.module_id;
msg.instance_id = mcfg->id.instance_id;
msg.param_data_size = size;
msg.large_param_id = param_id;
return skl_ipc_set_large_config(&ctx->ipc, &msg, params);
}
int skl_get_module_params(struct skl_sst *ctx, u32 *params, int size,
u32 param_id, struct skl_module_cfg *mcfg)
{
struct skl_ipc_large_config_msg msg;
msg.module_id = mcfg->id.module_id;
msg.instance_id = mcfg->id.instance_id;
msg.param_data_size = size;
msg.large_param_id = param_id;
return skl_ipc_get_large_config(&ctx->ipc, &msg, params);
}

View File

@ -55,7 +55,7 @@ void skl_nhlt_free(void *addr)
static struct nhlt_specific_cfg *skl_get_specific_cfg(
struct device *dev, struct nhlt_fmt *fmt,
u8 no_ch, u32 rate, u16 bps)
u8 no_ch, u32 rate, u16 bps, u8 linktype)
{
struct nhlt_specific_cfg *sp_config;
struct wav_fmt *wfmt;
@ -68,11 +68,17 @@ static struct nhlt_specific_cfg *skl_get_specific_cfg(
wfmt = &fmt_config->fmt_ext.fmt;
dev_dbg(dev, "ch=%d fmt=%d s_rate=%d\n", wfmt->channels,
wfmt->bits_per_sample, wfmt->samples_per_sec);
if (wfmt->channels == no_ch && wfmt->samples_per_sec == rate &&
wfmt->bits_per_sample == bps) {
if (wfmt->channels == no_ch && wfmt->bits_per_sample == bps) {
/*
* if link type is dmic ignore rate check as the blob is
* generic for all rates
*/
sp_config = &fmt_config->config;
if (linktype == NHLT_LINK_DMIC)
return sp_config;
return sp_config;
if (wfmt->samples_per_sec == rate)
return sp_config;
}
fmt_config = (struct nhlt_fmt_cfg *)(fmt_config->config.caps +
@ -115,7 +121,7 @@ struct nhlt_specific_cfg
struct device *dev = bus->dev;
struct nhlt_specific_cfg *sp_config;
struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt;
u16 bps = num_ch * s_fmt;
u16 bps = (s_fmt == 16) ? 16 : 32;
u8 j;
dump_config(dev, instance, link_type, s_fmt, num_ch, s_rate, dirn, bps);
@ -128,7 +134,8 @@ struct nhlt_specific_cfg
if (skl_check_ep_match(dev, epnt, instance, link_type, dirn)) {
fmt = (struct nhlt_fmt *)(epnt->config.caps +
epnt->config.size);
sp_config = skl_get_specific_cfg(dev, fmt, num_ch, s_rate, bps);
sp_config = skl_get_specific_cfg(dev, fmt, num_ch,
s_rate, bps, link_type);
if (sp_config)
return sp_config;
}

View File

@ -28,6 +28,7 @@
#define HDA_MONO 1
#define HDA_STEREO 2
#define HDA_QUAD 4
static struct snd_pcm_hardware azx_pcm_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
@ -39,12 +40,15 @@ static struct snd_pcm_hardware azx_pcm_hw = {
SNDRV_PCM_INFO_HAS_WALL_CLOCK | /* legacy */
SNDRV_PCM_INFO_HAS_LINK_ATIME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_S24_LE,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_8000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.channels_min = 1,
.channels_max = HDA_QUAD,
.buffer_bytes_max = AZX_MAX_BUF_SIZE,
.period_bytes_min = 128,
.period_bytes_max = AZX_MAX_BUF_SIZE / 2,
@ -105,6 +109,31 @@ static enum hdac_ext_stream_type skl_get_host_stream_type(struct hdac_ext_bus *e
return HDAC_EXT_STREAM_TYPE_COUPLED;
}
/*
* check if the stream opened is marked as ignore_suspend by machine, if so
* then enable suspend_active refcount
*
* The count supend_active does not need lock as it is used in open/close
* and suspend context
*/
static void skl_set_suspend_active(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai, bool enable)
{
struct hdac_ext_bus *ebus = dev_get_drvdata(dai->dev);
struct snd_soc_dapm_widget *w;
struct skl *skl = ebus_to_skl(ebus);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
w = dai->playback_widget;
else
w = dai->capture_widget;
if (w->ignore_suspend && enable)
skl->supend_active++;
else if (w->ignore_suspend && !enable)
skl->supend_active--;
}
static int skl_pcm_open(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
@ -112,12 +141,8 @@ static int skl_pcm_open(struct snd_pcm_substream *substream,
struct hdac_ext_stream *stream;
struct snd_pcm_runtime *runtime = substream->runtime;
struct skl_dma_params *dma_params;
int ret;
dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
ret = pm_runtime_get_sync(dai->dev);
if (ret < 0)
return ret;
stream = snd_hdac_ext_stream_assign(ebus, substream,
skl_get_host_stream_type(ebus));
@ -146,6 +171,7 @@ static int skl_pcm_open(struct snd_pcm_substream *substream,
dev_dbg(dai->dev, "stream tag set in dma params=%d\n",
dma_params->stream_tag);
skl_set_suspend_active(substream, dai, true);
snd_pcm_set_sync(substream);
return 0;
@ -185,10 +211,6 @@ static int skl_pcm_prepare(struct snd_pcm_substream *substream,
int err;
dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
if (hdac_stream(stream)->prepared) {
dev_dbg(dai->dev, "already stream is prepared - returning\n");
return 0;
}
format_val = skl_get_format(substream, dai);
dev_dbg(dai->dev, "stream_tag=%d formatvalue=%d\n",
@ -261,9 +283,8 @@ static void skl_pcm_close(struct snd_pcm_substream *substream,
* dma_params
*/
snd_soc_dai_set_dma_data(dai, substream, NULL);
skl_set_suspend_active(substream, dai, false);
pm_runtime_mark_last_busy(dai->dev);
pm_runtime_put_autosuspend(dai->dev);
kfree(dma_params);
}
@ -291,7 +312,53 @@ static int skl_be_hw_params(struct snd_pcm_substream *substream,
p_params.ch = params_channels(params);
p_params.s_freq = params_rate(params);
p_params.stream = substream->stream;
skl_tplg_be_update_params(dai, &p_params);
return skl_tplg_be_update_params(dai, &p_params);
}
static int skl_decoupled_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct hdac_ext_bus *ebus = get_bus_ctx(substream);
struct hdac_bus *bus = ebus_to_hbus(ebus);
struct hdac_ext_stream *stream;
int start;
unsigned long cookie;
struct hdac_stream *hstr;
stream = get_hdac_ext_stream(substream);
hstr = hdac_stream(stream);
if (!hstr->prepared)
return -EPIPE;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
start = 1;
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
start = 0;
break;
default:
return -EINVAL;
}
spin_lock_irqsave(&bus->reg_lock, cookie);
if (start) {
snd_hdac_stream_start(hdac_stream(stream), true);
snd_hdac_stream_timecounter_init(hstr, 0);
} else {
snd_hdac_stream_stop(hdac_stream(stream));
}
spin_unlock_irqrestore(&bus->reg_lock, cookie);
return 0;
}
@ -302,23 +369,54 @@ static int skl_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
struct skl *skl = get_skl_ctx(dai->dev);
struct skl_sst *ctx = skl->skl_sst;
struct skl_module_cfg *mconfig;
struct hdac_ext_bus *ebus = get_bus_ctx(substream);
struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
int ret;
mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream);
if (!mconfig)
return -EIO;
switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
skl_pcm_prepare(substream, dai);
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/*
* Start HOST DMA and Start FE Pipe.This is to make sure that
* there are no underrun/overrun in the case when the FE
* pipeline is started but there is a delay in starting the
* DMA channel on the host.
*/
snd_hdac_ext_stream_decouple(ebus, stream, true);
ret = skl_decoupled_trigger(substream, cmd);
if (ret < 0)
return ret;
return skl_run_pipe(ctx, mconfig->pipe);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
return skl_stop_pipe(ctx, mconfig->pipe);
case SNDRV_PCM_TRIGGER_STOP:
/*
* Stop FE Pipe first and stop DMA. This is to make sure that
* there are no underrun/overrun in the case if there is a delay
* between the two operations.
*/
ret = skl_stop_pipe(ctx, mconfig->pipe);
if (ret < 0)
return ret;
ret = skl_decoupled_trigger(substream, cmd);
if (cmd == SNDRV_PCM_TRIGGER_SUSPEND)
snd_hdac_ext_stream_decouple(ebus, stream, false);
break;
default:
return 0;
return -EINVAL;
}
return 0;
}
static int skl_link_hw_params(struct snd_pcm_substream *substream,
@ -352,9 +450,7 @@ static int skl_link_hw_params(struct snd_pcm_substream *substream,
p_params.stream = substream->stream;
p_params.link_dma_id = hdac_stream(link_dev)->stream_tag - 1;
skl_tplg_be_update_params(dai, &p_params);
return 0;
return skl_tplg_be_update_params(dai, &p_params);
}
static int skl_link_pcm_prepare(struct snd_pcm_substream *substream,
@ -443,19 +539,6 @@ static int skl_link_hw_free(struct snd_pcm_substream *substream,
return 0;
}
static int skl_be_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
return pm_runtime_get_sync(dai->dev);
}
static void skl_be_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
pm_runtime_mark_last_busy(dai->dev);
pm_runtime_put_autosuspend(dai->dev);
}
static struct snd_soc_dai_ops skl_pcm_dai_ops = {
.startup = skl_pcm_open,
.shutdown = skl_pcm_close,
@ -466,24 +549,18 @@ static struct snd_soc_dai_ops skl_pcm_dai_ops = {
};
static struct snd_soc_dai_ops skl_dmic_dai_ops = {
.startup = skl_be_startup,
.hw_params = skl_be_hw_params,
.shutdown = skl_be_shutdown,
};
static struct snd_soc_dai_ops skl_be_ssp_dai_ops = {
.startup = skl_be_startup,
.hw_params = skl_be_hw_params,
.shutdown = skl_be_shutdown,
};
static struct snd_soc_dai_ops skl_link_dai_ops = {
.startup = skl_be_startup,
.prepare = skl_link_pcm_prepare,
.hw_params = skl_link_hw_params,
.hw_free = skl_link_hw_free,
.trigger = skl_link_pcm_trigger,
.shutdown = skl_be_shutdown,
};
static struct snd_soc_dai_driver skl_platform_dai[] = {
@ -511,7 +588,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.capture = {
.stream_name = "Reference Capture",
.channels_min = HDA_MONO,
.channels_max = HDA_STEREO,
.channels_max = HDA_QUAD,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
},
@ -538,6 +615,18 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
},
},
{
.name = "DMIC Pin",
.ops = &skl_pcm_dai_ops,
.capture = {
.stream_name = "DMIC Capture",
.channels_min = HDA_MONO,
.channels_max = HDA_QUAD,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
},
},
/* BE CPU Dais */
{
.name = "SSP0 Pin",
@ -557,6 +646,24 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
{
.name = "SSP1 Pin",
.ops = &skl_be_ssp_dai_ops,
.playback = {
.stream_name = "ssp1 Tx",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "ssp1 Rx",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
{
.name = "iDisp Pin",
.ops = &skl_link_dai_ops,
@ -573,8 +680,8 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
.ops = &skl_dmic_dai_ops,
.capture = {
.stream_name = "DMIC01 Rx",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.channels_min = HDA_MONO,
.channels_max = HDA_QUAD,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
},
@ -688,66 +795,15 @@ static int skl_coupled_trigger(struct snd_pcm_substream *substream,
return 0;
}
static int skl_decoupled_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct hdac_ext_bus *ebus = get_bus_ctx(substream);
struct hdac_bus *bus = ebus_to_hbus(ebus);
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct hdac_ext_stream *stream;
int start;
unsigned long cookie;
struct hdac_stream *hstr;
dev_dbg(bus->dev, "In %s cmd=%d streamname=%s\n", __func__, cmd, cpu_dai->name);
stream = get_hdac_ext_stream(substream);
hstr = hdac_stream(stream);
if (!hstr->prepared)
return -EPIPE;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
start = 1;
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
start = 0;
break;
default:
return -EINVAL;
}
spin_lock_irqsave(&bus->reg_lock, cookie);
if (start)
snd_hdac_stream_start(hdac_stream(stream), true);
else
snd_hdac_stream_stop(hdac_stream(stream));
if (start)
snd_hdac_stream_timecounter_init(hstr, 0);
spin_unlock_irqrestore(&bus->reg_lock, cookie);
return 0;
}
static int skl_platform_pcm_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct hdac_ext_bus *ebus = get_bus_ctx(substream);
if (ebus->ppcap)
return skl_decoupled_trigger(substream, cmd);
else
if (!ebus->ppcap)
return skl_coupled_trigger(substream, cmd);
return 0;
}
/* calculate runtime delay from LPIB */
@ -789,7 +845,7 @@ static unsigned int skl_get_position(struct hdac_ext_stream *hstream,
{
struct hdac_stream *hstr = hdac_stream(hstream);
struct snd_pcm_substream *substream = hstr->substream;
struct hdac_ext_bus *ebus = get_bus_ctx(substream);
struct hdac_ext_bus *ebus;
unsigned int pos;
int delay;
@ -800,6 +856,7 @@ static unsigned int skl_get_position(struct hdac_ext_stream *hstream,
pos = 0;
if (substream->runtime) {
ebus = get_bus_ctx(substream);
delay = skl_get_delay_from_lpib(ebus, hstream, pos)
+ codec_delay;
substream->runtime->delay += delay;
@ -941,7 +998,6 @@ int skl_platform_register(struct device *dev)
struct skl *skl = ebus_to_skl(ebus);
INIT_LIST_HEAD(&skl->ppl_list);
INIT_LIST_HEAD(&skl->dapm_path_list);
ret = snd_soc_register_platform(dev, &skl_platform_drv);
if (ret) {

View File

@ -18,6 +18,7 @@
#include <linux/device.h>
#include <linux/mm.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include "../common/sst-dsp.h"
#include "../common/sst-dsp-priv.h"
@ -33,6 +34,53 @@ void skl_cldma_int_disable(struct sst_dsp *ctx)
SKL_ADSP_REG_ADSPIC, SKL_ADSPIC_CL_DMA, 0);
}
static void skl_cldma_stream_run(struct sst_dsp *ctx, bool enable)
{
unsigned char val;
int timeout;
sst_dsp_shim_update_bits_unlocked(ctx,
SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_RUN_MASK, CL_SD_CTL_RUN(enable));
udelay(3);
timeout = 300;
do {
/* waiting for hardware to report that the stream Run bit set */
val = sst_dsp_shim_read(ctx, SKL_ADSP_REG_CL_SD_CTL) &
CL_SD_CTL_RUN_MASK;
if (enable && val)
break;
else if (!enable && !val)
break;
udelay(3);
} while (--timeout);
if (timeout == 0)
dev_err(ctx->dev, "Failed to set Run bit=%d enable=%d\n", val, enable);
}
static void skl_cldma_stream_clear(struct sst_dsp *ctx)
{
/* make sure Run bit is cleared before setting stream register */
skl_cldma_stream_run(ctx, 0);
sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_IOCE_MASK, CL_SD_CTL_IOCE(0));
sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_FEIE_MASK, CL_SD_CTL_FEIE(0));
sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_DEIE_MASK, CL_SD_CTL_DEIE(0));
sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_STRM_MASK, CL_SD_CTL_STRM(0));
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPL, CL_SD_BDLPLBA(0));
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPU, 0);
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_CBL, 0);
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_LVI, 0);
}
/* Code loader helper APIs */
static void skl_cldma_setup_bdle(struct sst_dsp *ctx,
struct snd_dma_buffer *dmab_data,
@ -68,6 +116,7 @@ static void skl_cldma_setup_controller(struct sst_dsp *ctx,
struct snd_dma_buffer *dmab_bdl, unsigned int max_size,
u32 count)
{
skl_cldma_stream_clear(ctx);
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPL,
CL_SD_BDLPLBA(dmab_bdl->addr));
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPU,
@ -107,36 +156,13 @@ static void skl_cldma_cleanup_spb(struct sst_dsp *ctx)
sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_CL_SPBFIFO_SPIB, 0);
}
static void skl_cldma_trigger(struct sst_dsp *ctx, bool enable)
{
if (enable)
sst_dsp_shim_update_bits_unlocked(ctx,
SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_RUN_MASK, CL_SD_CTL_RUN(1));
else
sst_dsp_shim_update_bits_unlocked(ctx,
SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_RUN_MASK, CL_SD_CTL_RUN(0));
}
static void skl_cldma_cleanup(struct sst_dsp *ctx)
{
skl_cldma_cleanup_spb(ctx);
skl_cldma_stream_clear(ctx);
sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_IOCE_MASK, CL_SD_CTL_IOCE(0));
sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_FEIE_MASK, CL_SD_CTL_FEIE(0));
sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_DEIE_MASK, CL_SD_CTL_DEIE(0));
sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
CL_SD_CTL_STRM_MASK, CL_SD_CTL_STRM(0));
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPL, CL_SD_BDLPLBA(0));
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPU, 0);
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_CBL, 0);
sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_LVI, 0);
ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_data);
ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_bdl);
}
static int skl_cldma_wait_interruptible(struct sst_dsp *ctx)
@ -164,7 +190,7 @@ cleanup:
static void skl_cldma_stop(struct sst_dsp *ctx)
{
ctx->cl_dev.ops.cl_trigger(ctx, false);
skl_cldma_stream_run(ctx, false);
}
static void skl_cldma_fill_buffer(struct sst_dsp *ctx, unsigned int size,
@ -175,6 +201,21 @@ static void skl_cldma_fill_buffer(struct sst_dsp *ctx, unsigned int size,
ctx->cl_dev.dma_buffer_offset, trigger);
dev_dbg(ctx->dev, "spib position: %d\n", ctx->cl_dev.curr_spib_pos);
/*
* Check if the size exceeds buffer boundary. If it exceeds
* max_buffer size, then copy till buffer size and then copy
* remaining buffer from the start of ring buffer.
*/
if (ctx->cl_dev.dma_buffer_offset + size > ctx->cl_dev.bufsize) {
unsigned int size_b = ctx->cl_dev.bufsize -
ctx->cl_dev.dma_buffer_offset;
memcpy(ctx->cl_dev.dmab_data.area + ctx->cl_dev.dma_buffer_offset,
curr_pos, size_b);
size -= size_b;
curr_pos += size_b;
ctx->cl_dev.dma_buffer_offset = 0;
}
memcpy(ctx->cl_dev.dmab_data.area + ctx->cl_dev.dma_buffer_offset,
curr_pos, size);
@ -291,7 +332,7 @@ int skl_cldma_prepare(struct sst_dsp *ctx)
ctx->cl_dev.ops.cl_setup_controller = skl_cldma_setup_controller;
ctx->cl_dev.ops.cl_setup_spb = skl_cldma_setup_spb;
ctx->cl_dev.ops.cl_cleanup_spb = skl_cldma_cleanup_spb;
ctx->cl_dev.ops.cl_trigger = skl_cldma_trigger;
ctx->cl_dev.ops.cl_trigger = skl_cldma_stream_run;
ctx->cl_dev.ops.cl_cleanup_controller = skl_cldma_cleanup;
ctx->cl_dev.ops.cl_copy_to_dmabuf = skl_cldma_copy_to_buf;
ctx->cl_dev.ops.cl_stop_dma = skl_cldma_stop;

View File

@ -58,9 +58,9 @@ struct sst_dsp_device;
#define SKL_ADSP_MMIO_LEN 0x10000
#define SKL_ADSP_W0_STAT_SZ 0x800
#define SKL_ADSP_W0_STAT_SZ 0x1000
#define SKL_ADSP_W0_UP_SZ 0x800
#define SKL_ADSP_W0_UP_SZ 0x1000
#define SKL_ADSP_W1_SZ 0x1000
@ -114,6 +114,9 @@ struct skl_dsp_fw_ops {
int (*set_state_D0)(struct sst_dsp *ctx);
int (*set_state_D3)(struct sst_dsp *ctx);
unsigned int (*get_fw_errcode)(struct sst_dsp *ctx);
int (*load_mod)(struct sst_dsp *ctx, u16 mod_id, char *mod_name);
int (*unload_mod)(struct sst_dsp *ctx, u16 mod_id);
};
struct skl_dsp_loader_ops {
@ -123,6 +126,17 @@ struct skl_dsp_loader_ops {
struct snd_dma_buffer *dmab);
};
struct skl_load_module_info {
u16 mod_id;
const struct firmware *fw;
};
struct skl_module_table {
struct skl_load_module_info *mod_info;
unsigned int usage_cnt;
struct list_head list;
};
void skl_cldma_process_intr(struct sst_dsp *ctx);
void skl_cldma_int_disable(struct sst_dsp *ctx);
int skl_cldma_prepare(struct sst_dsp *ctx);
@ -139,7 +153,8 @@ void skl_dsp_free(struct sst_dsp *dsp);
int skl_dsp_boot(struct sst_dsp *ctx);
int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp);
const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
struct skl_sst **dsp);
void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx);
#endif /*__SKL_SST_DSP_H__*/

View File

@ -130,6 +130,11 @@
#define IPC_SRC_QUEUE_MASK 0x7
#define IPC_SRC_QUEUE(x) (((x) & IPC_SRC_QUEUE_MASK) \
<< IPC_SRC_QUEUE_SHIFT)
/* Load Module count */
#define IPC_LOAD_MODULE_SHIFT 0
#define IPC_LOAD_MODULE_MASK 0xFF
#define IPC_LOAD_MODULE_CNT(x) (((x) & IPC_LOAD_MODULE_MASK) \
<< IPC_LOAD_MODULE_SHIFT)
/* Save pipeline messgae extension register */
#define IPC_DMA_ID_SHIFT 0
@ -344,6 +349,8 @@ static void skl_ipc_process_reply(struct sst_generic_ipc *ipc,
switch (reply) {
case IPC_GLB_REPLY_SUCCESS:
dev_info(ipc->dev, "ipc FW reply %x: success\n", header.primary);
/* copy the rx data from the mailbox */
sst_dsp_inbox_read(ipc->dsp, msg->rx_data, msg->rx_size);
break;
case IPC_GLB_REPLY_OUT_OF_MEMORY:
@ -650,7 +657,7 @@ int skl_ipc_set_dx(struct sst_generic_ipc *ipc, u8 instance_id,
dev_dbg(ipc->dev, "In %s primary =%x ext=%x\n", __func__,
header.primary, header.extension);
ret = sst_ipc_tx_message_wait(ipc, *ipc_header,
dx, sizeof(dx), NULL, 0);
dx, sizeof(*dx), NULL, 0);
if (ret < 0) {
dev_err(ipc->dev, "ipc: set dx failed, err %d\n", ret);
return ret;
@ -728,6 +735,54 @@ int skl_ipc_bind_unbind(struct sst_generic_ipc *ipc,
}
EXPORT_SYMBOL_GPL(skl_ipc_bind_unbind);
/*
* In order to load a module we need to send IPC to initiate that. DMA will
* performed to load the module memory. The FW supports multiple module load
* at single shot, so we can send IPC with N modules represented by
* module_cnt
*/
int skl_ipc_load_modules(struct sst_generic_ipc *ipc,
u8 module_cnt, void *data)
{
struct skl_ipc_header header = {0};
u64 *ipc_header = (u64 *)(&header);
int ret;
header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
header.primary |= IPC_GLB_TYPE(IPC_GLB_LOAD_MULTIPLE_MODS);
header.primary |= IPC_LOAD_MODULE_CNT(module_cnt);
ret = sst_ipc_tx_message_wait(ipc, *ipc_header, data,
(sizeof(u16) * module_cnt), NULL, 0);
if (ret < 0)
dev_err(ipc->dev, "ipc: load modules failed :%d\n", ret);
return ret;
}
EXPORT_SYMBOL_GPL(skl_ipc_load_modules);
int skl_ipc_unload_modules(struct sst_generic_ipc *ipc, u8 module_cnt,
void *data)
{
struct skl_ipc_header header = {0};
u64 *ipc_header = (u64 *)(&header);
int ret;
header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
header.primary |= IPC_GLB_TYPE(IPC_GLB_UNLOAD_MULTIPLE_MODS);
header.primary |= IPC_LOAD_MODULE_CNT(module_cnt);
ret = sst_ipc_tx_message_wait(ipc, *ipc_header, data,
(sizeof(u16) * module_cnt), NULL, 0);
if (ret < 0)
dev_err(ipc->dev, "ipc: unload modules failed :%d\n", ret);
return ret;
}
EXPORT_SYMBOL_GPL(skl_ipc_unload_modules);
int skl_ipc_set_large_config(struct sst_generic_ipc *ipc,
struct skl_ipc_large_config_msg *msg, u32 *param)
{
@ -781,3 +836,54 @@ int skl_ipc_set_large_config(struct sst_generic_ipc *ipc,
return ret;
}
EXPORT_SYMBOL_GPL(skl_ipc_set_large_config);
int skl_ipc_get_large_config(struct sst_generic_ipc *ipc,
struct skl_ipc_large_config_msg *msg, u32 *param)
{
struct skl_ipc_header header = {0};
u64 *ipc_header = (u64 *)(&header);
int ret = 0;
size_t sz_remaining, rx_size, data_offset;
header.primary = IPC_MSG_TARGET(IPC_MOD_MSG);
header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
header.primary |= IPC_GLB_TYPE(IPC_MOD_LARGE_CONFIG_GET);
header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id);
header.primary |= IPC_MOD_ID(msg->module_id);
header.extension = IPC_DATA_OFFSET_SZ(msg->param_data_size);
header.extension |= IPC_LARGE_PARAM_ID(msg->large_param_id);
header.extension |= IPC_FINAL_BLOCK(1);
header.extension |= IPC_INITIAL_BLOCK(1);
sz_remaining = msg->param_data_size;
data_offset = 0;
while (sz_remaining != 0) {
rx_size = sz_remaining > SKL_ADSP_W1_SZ
? SKL_ADSP_W1_SZ : sz_remaining;
if (rx_size == sz_remaining)
header.extension |= IPC_FINAL_BLOCK(1);
ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0,
((char *)param) + data_offset,
msg->param_data_size);
if (ret < 0) {
dev_err(ipc->dev,
"ipc: get large config fail, err: %d\n", ret);
return ret;
}
sz_remaining -= rx_size;
data_offset = msg->param_data_size - sz_remaining;
/* clear the fields */
header.extension &= IPC_INITIAL_BLOCK_CLEAR;
header.extension &= IPC_DATA_OFFSET_SZ_CLEAR;
/* fill the fields */
header.extension |= IPC_INITIAL_BLOCK(1);
header.extension |= IPC_DATA_OFFSET_SZ(data_offset);
}
return ret;
}
EXPORT_SYMBOL_GPL(skl_ipc_get_large_config);

View File

@ -108,12 +108,21 @@ int skl_ipc_init_instance(struct sst_generic_ipc *sst_ipc,
int skl_ipc_bind_unbind(struct sst_generic_ipc *sst_ipc,
struct skl_ipc_bind_unbind_msg *msg);
int skl_ipc_load_modules(struct sst_generic_ipc *ipc,
u8 module_cnt, void *data);
int skl_ipc_unload_modules(struct sst_generic_ipc *ipc,
u8 module_cnt, void *data);
int skl_ipc_set_dx(struct sst_generic_ipc *ipc,
u8 instance_id, u16 module_id, struct skl_ipc_dxstate_info *dx);
int skl_ipc_set_large_config(struct sst_generic_ipc *ipc,
struct skl_ipc_large_config_msg *msg, u32 *param);
int skl_ipc_get_large_config(struct sst_generic_ipc *ipc,
struct skl_ipc_large_config_msg *msg, u32 *param);
void skl_ipc_int_enable(struct sst_dsp *dsp);
void skl_ipc_op_int_enable(struct sst_dsp *ctx);
void skl_ipc_op_int_disable(struct sst_dsp *ctx);

View File

@ -19,6 +19,7 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include "../common/sst-dsp.h"
#include "../common/sst-dsp-priv.h"
#include "../common/sst-ipc.h"
@ -37,6 +38,8 @@
#define SKL_INSTANCE_ID 0
#define SKL_BASE_FW_MODULE_ID 0
#define SKL_NUM_MODULES 1
static bool skl_check_fw_status(struct sst_dsp *ctx, u32 status)
{
u32 cur_sts;
@ -77,7 +80,7 @@ static int skl_load_base_firmware(struct sst_dsp *ctx)
init_waitqueue_head(&skl->boot_wait);
if (ctx->fw == NULL) {
ret = request_firmware(&ctx->fw, "dsp_fw_release.bin", ctx->dev);
ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev);
if (ret < 0) {
dev_err(ctx->dev, "Request firmware failed %d\n", ret);
skl_dsp_disable_core(ctx);
@ -115,27 +118,28 @@ static int skl_load_base_firmware(struct sst_dsp *ctx)
dev_err(ctx->dev,
"Timeout waiting for ROM init done, reg:0x%x\n", reg);
ret = -EIO;
goto skl_load_base_firmware_failed;
goto transfer_firmware_failed;
}
ret = skl_transfer_firmware(ctx, ctx->fw->data, ctx->fw->size);
if (ret < 0) {
dev_err(ctx->dev, "Transfer firmware failed%d\n", ret);
goto skl_load_base_firmware_failed;
goto transfer_firmware_failed;
} else {
ret = wait_event_timeout(skl->boot_wait, skl->boot_complete,
msecs_to_jiffies(SKL_IPC_BOOT_MSECS));
if (ret == 0) {
dev_err(ctx->dev, "DSP boot failed, FW Ready timed-out\n");
ret = -EIO;
goto skl_load_base_firmware_failed;
goto transfer_firmware_failed;
}
dev_dbg(ctx->dev, "Download firmware successful%d\n", ret);
skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING);
}
return 0;
transfer_firmware_failed:
ctx->cl_dev.ops.cl_cleanup_controller(ctx);
skl_load_base_firmware_failed:
skl_dsp_disable_core(ctx);
release_firmware(ctx->fw);
@ -175,10 +179,15 @@ static int skl_set_dsp_D3(struct sst_dsp *ctx)
dx.core_mask = SKL_DSP_CORE0_MASK;
dx.dx_mask = SKL_IPC_D3_MASK;
ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, SKL_BASE_FW_MODULE_ID, &dx);
if (ret < 0) {
dev_err(ctx->dev, "Failed to set DSP to D3 state\n");
return ret;
}
if (ret < 0)
dev_err(ctx->dev,
"D3 request to FW failed, continuing reset: %d", ret);
/* disable Interrupt */
ctx->cl_dev.ops.cl_cleanup_controller(ctx);
skl_cldma_int_disable(ctx);
skl_ipc_op_int_disable(ctx);
skl_ipc_int_disable(ctx);
ret = skl_dsp_disable_core(ctx);
if (ret < 0) {
@ -187,12 +196,6 @@ static int skl_set_dsp_D3(struct sst_dsp *ctx)
}
skl_dsp_set_state_locked(ctx, SKL_DSP_RESET);
/* disable Interrupt */
ctx->cl_dev.ops.cl_cleanup_controller(ctx);
skl_cldma_int_disable(ctx);
skl_ipc_op_int_disable(ctx);
skl_ipc_int_disable(ctx);
return ret;
}
@ -201,11 +204,182 @@ static unsigned int skl_get_errorcode(struct sst_dsp *ctx)
return sst_dsp_shim_read(ctx, SKL_ADSP_ERROR_CODE);
}
/*
* since get/set_module are called from DAPM context,
* we don't need lock for usage count
*/
static int skl_get_module(struct sst_dsp *ctx, u16 mod_id)
{
struct skl_module_table *module;
list_for_each_entry(module, &ctx->module_list, list) {
if (module->mod_info->mod_id == mod_id)
return ++module->usage_cnt;
}
return -EINVAL;
}
static int skl_put_module(struct sst_dsp *ctx, u16 mod_id)
{
struct skl_module_table *module;
list_for_each_entry(module, &ctx->module_list, list) {
if (module->mod_info->mod_id == mod_id)
return --module->usage_cnt;
}
return -EINVAL;
}
static struct skl_module_table *skl_fill_module_table(struct sst_dsp *ctx,
char *mod_name, int mod_id)
{
const struct firmware *fw;
struct skl_module_table *skl_module;
unsigned int size;
int ret;
ret = request_firmware(&fw, mod_name, ctx->dev);
if (ret < 0) {
dev_err(ctx->dev, "Request Module %s failed :%d\n",
mod_name, ret);
return NULL;
}
skl_module = devm_kzalloc(ctx->dev, sizeof(*skl_module), GFP_KERNEL);
if (skl_module == NULL) {
release_firmware(fw);
return NULL;
}
size = sizeof(*skl_module->mod_info);
skl_module->mod_info = devm_kzalloc(ctx->dev, size, GFP_KERNEL);
if (skl_module->mod_info == NULL) {
release_firmware(fw);
return NULL;
}
skl_module->mod_info->mod_id = mod_id;
skl_module->mod_info->fw = fw;
list_add(&skl_module->list, &ctx->module_list);
return skl_module;
}
/* get a module from it's unique ID */
static struct skl_module_table *skl_module_get_from_id(
struct sst_dsp *ctx, u16 mod_id)
{
struct skl_module_table *module;
if (list_empty(&ctx->module_list)) {
dev_err(ctx->dev, "Module list is empty\n");
return NULL;
}
list_for_each_entry(module, &ctx->module_list, list) {
if (module->mod_info->mod_id == mod_id)
return module;
}
return NULL;
}
static int skl_transfer_module(struct sst_dsp *ctx,
struct skl_load_module_info *module)
{
int ret;
struct skl_sst *skl = ctx->thread_context;
ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, module->fw->data,
module->fw->size);
if (ret < 0)
return ret;
ret = skl_ipc_load_modules(&skl->ipc, SKL_NUM_MODULES,
(void *)&module->mod_id);
if (ret < 0)
dev_err(ctx->dev, "Failed to Load module: %d\n", ret);
ctx->cl_dev.ops.cl_stop_dma(ctx);
return ret;
}
static int skl_load_module(struct sst_dsp *ctx, u16 mod_id, char *guid)
{
struct skl_module_table *module_entry = NULL;
int ret = 0;
char mod_name[64]; /* guid str = 32 chars + 4 hyphens */
snprintf(mod_name, sizeof(mod_name), "%s%s%s",
"intel/dsp_fw_", guid, ".bin");
module_entry = skl_module_get_from_id(ctx, mod_id);
if (module_entry == NULL) {
module_entry = skl_fill_module_table(ctx, mod_name, mod_id);
if (module_entry == NULL) {
dev_err(ctx->dev, "Failed to Load module\n");
return -EINVAL;
}
}
if (!module_entry->usage_cnt) {
ret = skl_transfer_module(ctx, module_entry->mod_info);
if (ret < 0) {
dev_err(ctx->dev, "Failed to Load module\n");
return ret;
}
}
ret = skl_get_module(ctx, mod_id);
return ret;
}
static int skl_unload_module(struct sst_dsp *ctx, u16 mod_id)
{
int usage_cnt;
struct skl_sst *skl = ctx->thread_context;
int ret = 0;
usage_cnt = skl_put_module(ctx, mod_id);
if (usage_cnt < 0) {
dev_err(ctx->dev, "Module bad usage cnt!:%d\n", usage_cnt);
return -EIO;
}
ret = skl_ipc_unload_modules(&skl->ipc,
SKL_NUM_MODULES, &mod_id);
if (ret < 0) {
dev_err(ctx->dev, "Failed to UnLoad module\n");
skl_get_module(ctx, mod_id);
return ret;
}
return ret;
}
static void skl_clear_module_table(struct sst_dsp *ctx)
{
struct skl_module_table *module, *tmp;
if (list_empty(&ctx->module_list))
return;
list_for_each_entry_safe(module, tmp, &ctx->module_list, list) {
list_del(&module->list);
release_firmware(module->mod_info->fw);
}
}
static struct skl_dsp_fw_ops skl_fw_ops = {
.set_state_D0 = skl_set_dsp_D0,
.set_state_D3 = skl_set_dsp_D3,
.load_fw = skl_load_base_firmware,
.get_fw_errcode = skl_get_errorcode,
.load_mod = skl_load_module,
.unload_mod = skl_unload_module,
};
static struct sst_ops skl_ops = {
@ -223,7 +397,7 @@ static struct sst_dsp_device skl_dev = {
};
int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp)
const char *fw_name, struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp)
{
struct skl_sst *skl;
struct sst_dsp *sst;
@ -244,11 +418,13 @@ int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
sst = skl->dsp;
sst->fw_name = fw_name;
sst->addr.lpe = mmio_base;
sst->addr.shim = mmio_base;
sst_dsp_mailbox_init(sst, (SKL_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ),
SKL_ADSP_W0_UP_SZ, SKL_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ);
INIT_LIST_HEAD(&sst->module_list);
sst->dsp_ops = dsp_ops;
sst->fw_ops = skl_fw_ops;
@ -259,23 +435,24 @@ int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
ret = sst->fw_ops.load_fw(sst);
if (ret < 0) {
dev_err(dev, "Load base fw failed : %d", ret);
return ret;
goto cleanup;
}
if (dsp)
*dsp = skl;
return 0;
return ret;
skl_ipc_free(&skl->ipc);
cleanup:
skl_sst_dsp_cleanup(dev, skl);
return ret;
}
EXPORT_SYMBOL_GPL(skl_sst_dsp_init);
void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx)
{
skl_clear_module_table(ctx->dsp);
skl_ipc_free(&ctx->ipc);
ctx->dsp->cl_dev.ops.cl_cleanup_controller(ctx->dsp);
ctx->dsp->ops->free(ctx->dsp);
}
EXPORT_SYMBOL_GPL(skl_sst_dsp_cleanup);

View File

@ -26,6 +26,8 @@
#include "skl-topology.h"
#include "skl.h"
#include "skl-tplg-interface.h"
#include "../common/sst-dsp.h"
#include "../common/sst-dsp-priv.h"
#define SKL_CH_FIXUP_MASK (1 << 0)
#define SKL_RATE_FIXUP_MASK (1 << 1)
@ -129,17 +131,15 @@ static void skl_dump_mconfig(struct skl_sst *ctx,
{
dev_dbg(ctx->dev, "Dumping config\n");
dev_dbg(ctx->dev, "Input Format:\n");
dev_dbg(ctx->dev, "channels = %d\n", mcfg->in_fmt.channels);
dev_dbg(ctx->dev, "s_freq = %d\n", mcfg->in_fmt.s_freq);
dev_dbg(ctx->dev, "ch_cfg = %d\n", mcfg->in_fmt.ch_cfg);
dev_dbg(ctx->dev, "valid bit depth = %d\n",
mcfg->in_fmt.valid_bit_depth);
dev_dbg(ctx->dev, "channels = %d\n", mcfg->in_fmt[0].channels);
dev_dbg(ctx->dev, "s_freq = %d\n", mcfg->in_fmt[0].s_freq);
dev_dbg(ctx->dev, "ch_cfg = %d\n", mcfg->in_fmt[0].ch_cfg);
dev_dbg(ctx->dev, "valid bit depth = %d\n", mcfg->in_fmt[0].valid_bit_depth);
dev_dbg(ctx->dev, "Output Format:\n");
dev_dbg(ctx->dev, "channels = %d\n", mcfg->out_fmt.channels);
dev_dbg(ctx->dev, "s_freq = %d\n", mcfg->out_fmt.s_freq);
dev_dbg(ctx->dev, "valid bit depth = %d\n",
mcfg->out_fmt.valid_bit_depth);
dev_dbg(ctx->dev, "ch_cfg = %d\n", mcfg->out_fmt.ch_cfg);
dev_dbg(ctx->dev, "channels = %d\n", mcfg->out_fmt[0].channels);
dev_dbg(ctx->dev, "s_freq = %d\n", mcfg->out_fmt[0].s_freq);
dev_dbg(ctx->dev, "valid bit depth = %d\n", mcfg->out_fmt[0].valid_bit_depth);
dev_dbg(ctx->dev, "ch_cfg = %d\n", mcfg->out_fmt[0].ch_cfg);
}
static void skl_tplg_update_params(struct skl_module_fmt *fmt,
@ -149,8 +149,24 @@ static void skl_tplg_update_params(struct skl_module_fmt *fmt,
fmt->s_freq = params->s_freq;
if (fixup & SKL_CH_FIXUP_MASK)
fmt->channels = params->ch;
if (fixup & SKL_FMT_FIXUP_MASK)
fmt->valid_bit_depth = params->s_fmt;
if (fixup & SKL_FMT_FIXUP_MASK) {
fmt->valid_bit_depth = skl_get_bit_depth(params->s_fmt);
/*
* 16 bit is 16 bit container whereas 24 bit is in 32 bit
* container so update bit depth accordingly
*/
switch (fmt->valid_bit_depth) {
case SKL_DEPTH_16BIT:
fmt->bit_depth = fmt->valid_bit_depth;
break;
default:
fmt->bit_depth = SKL_DEPTH_32BIT;
break;
}
}
}
/*
@ -171,8 +187,9 @@ static void skl_tplg_update_params_fixup(struct skl_module_cfg *m_cfg,
int in_fixup, out_fixup;
struct skl_module_fmt *in_fmt, *out_fmt;
in_fmt = &m_cfg->in_fmt;
out_fmt = &m_cfg->out_fmt;
/* Fixups will be applied to pin 0 only */
in_fmt = &m_cfg->in_fmt[0];
out_fmt = &m_cfg->out_fmt[0];
if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (is_fe) {
@ -209,18 +226,25 @@ static void skl_tplg_update_buffer_size(struct skl_sst *ctx,
struct skl_module_cfg *mcfg)
{
int multiplier = 1;
struct skl_module_fmt *in_fmt, *out_fmt;
/* Since fixups is applied to pin 0 only, ibs, obs needs
* change for pin 0 only
*/
in_fmt = &mcfg->in_fmt[0];
out_fmt = &mcfg->out_fmt[0];
if (mcfg->m_type == SKL_MODULE_TYPE_SRCINT)
multiplier = 5;
mcfg->ibs = (mcfg->in_fmt.s_freq / 1000) *
(mcfg->in_fmt.channels) *
(mcfg->in_fmt.bit_depth >> 3) *
mcfg->ibs = (in_fmt->s_freq / 1000) *
(mcfg->in_fmt->channels) *
(mcfg->in_fmt->bit_depth >> 3) *
multiplier;
mcfg->obs = (mcfg->out_fmt.s_freq / 1000) *
(mcfg->out_fmt.channels) *
(mcfg->out_fmt.bit_depth >> 3) *
mcfg->obs = (mcfg->out_fmt->s_freq / 1000) *
(mcfg->out_fmt->channels) *
(mcfg->out_fmt->bit_depth >> 3) *
multiplier;
}
@ -291,6 +315,83 @@ static int skl_tplg_alloc_pipe_widget(struct device *dev,
return 0;
}
/*
* some modules can have multiple params set from user control and
* need to be set after module is initialized. If set_param flag is
* set module params will be done after module is initialised.
*/
static int skl_tplg_set_module_params(struct snd_soc_dapm_widget *w,
struct skl_sst *ctx)
{
int i, ret;
struct skl_module_cfg *mconfig = w->priv;
const struct snd_kcontrol_new *k;
struct soc_bytes_ext *sb;
struct skl_algo_data *bc;
struct skl_specific_cfg *sp_cfg;
if (mconfig->formats_config.caps_size > 0 &&
mconfig->formats_config.set_params == SKL_PARAM_SET) {
sp_cfg = &mconfig->formats_config;
ret = skl_set_module_params(ctx, sp_cfg->caps,
sp_cfg->caps_size,
sp_cfg->param_id, mconfig);
if (ret < 0)
return ret;
}
for (i = 0; i < w->num_kcontrols; i++) {
k = &w->kcontrol_news[i];
if (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
sb = (void *) k->private_value;
bc = (struct skl_algo_data *)sb->dobj.private;
if (bc->set_params == SKL_PARAM_SET) {
ret = skl_set_module_params(ctx,
(u32 *)bc->params, bc->max,
bc->param_id, mconfig);
if (ret < 0)
return ret;
}
}
}
return 0;
}
/*
* some module param can set from user control and this is required as
* when module is initailzed. if module param is required in init it is
* identifed by set_param flag. if set_param flag is not set, then this
* parameter needs to set as part of module init.
*/
static int skl_tplg_set_module_init_data(struct snd_soc_dapm_widget *w)
{
const struct snd_kcontrol_new *k;
struct soc_bytes_ext *sb;
struct skl_algo_data *bc;
struct skl_module_cfg *mconfig = w->priv;
int i;
for (i = 0; i < w->num_kcontrols; i++) {
k = &w->kcontrol_news[i];
if (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
sb = (struct soc_bytes_ext *)k->private_value;
bc = (struct skl_algo_data *)sb->dobj.private;
if (bc->set_params != SKL_PARAM_INIT)
continue;
mconfig->formats_config.caps = (u32 *)&bc->params;
mconfig->formats_config.caps_size = bc->max;
break;
}
}
return 0;
}
/*
* Inside a pipe instance, we can have various modules. These modules need
* to instantiated in DSP by invoking INIT_MODULE IPC, which is achieved by
@ -313,12 +414,25 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe)
if (!skl_tplg_alloc_pipe_mcps(skl, mconfig))
return -ENOMEM;
if (mconfig->is_loadable && ctx->dsp->fw_ops.load_mod) {
ret = ctx->dsp->fw_ops.load_mod(ctx->dsp,
mconfig->id.module_id, mconfig->guid);
if (ret < 0)
return ret;
}
/*
* apply fix/conversion to module params based on
* FE/BE params
*/
skl_tplg_update_module_params(w, ctx);
ret = skl_init_module(ctx, mconfig, NULL);
skl_tplg_set_module_init_data(w);
ret = skl_init_module(ctx, mconfig);
if (ret < 0)
return ret;
ret = skl_tplg_set_module_params(w, ctx);
if (ret < 0)
return ret;
}
@ -326,6 +440,24 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe)
return 0;
}
static int skl_tplg_unload_pipe_modules(struct skl_sst *ctx,
struct skl_pipe *pipe)
{
struct skl_pipe_module *w_module = NULL;
struct skl_module_cfg *mconfig = NULL;
list_for_each_entry(w_module, &pipe->w_list, node) {
mconfig = w_module->w->priv;
if (mconfig->is_loadable && ctx->dsp->fw_ops.unload_mod)
return ctx->dsp->fw_ops.unload_mod(ctx->dsp,
mconfig->id.module_id);
}
/* no modules to unload in this path, so return */
return 0;
}
/*
* Mixer module represents a pipeline. So in the Pre-PMU event of mixer we
* need create the pipeline. So we do following:
@ -397,6 +529,58 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w,
return 0;
}
static int skl_tplg_bind_sinks(struct snd_soc_dapm_widget *w,
struct skl *skl,
struct skl_module_cfg *src_mconfig)
{
struct snd_soc_dapm_path *p;
struct snd_soc_dapm_widget *sink = NULL, *next_sink = NULL;
struct skl_module_cfg *sink_mconfig;
struct skl_sst *ctx = skl->skl_sst;
int ret;
snd_soc_dapm_widget_for_each_sink_path(w, p) {
if (!p->connect)
continue;
dev_dbg(ctx->dev, "%s: src widget=%s\n", __func__, w->name);
dev_dbg(ctx->dev, "%s: sink widget=%s\n", __func__, p->sink->name);
next_sink = p->sink;
/*
* here we will check widgets in sink pipelines, so that
* can be any widgets type and we are only interested if
* they are ones used for SKL so check that first
*/
if ((p->sink->priv != NULL) &&
is_skl_dsp_widget_type(p->sink)) {
sink = p->sink;
sink_mconfig = sink->priv;
/* Bind source to sink, mixin is always source */
ret = skl_bind_modules(ctx, src_mconfig, sink_mconfig);
if (ret)
return ret;
/* Start sinks pipe first */
if (sink_mconfig->pipe->state != SKL_PIPE_STARTED) {
if (sink_mconfig->pipe->conn_type !=
SKL_PIPE_CONN_TYPE_FE)
ret = skl_run_pipe(ctx,
sink_mconfig->pipe);
if (ret)
return ret;
}
}
}
if (!sink)
return skl_tplg_bind_sinks(next_sink, skl, src_mconfig);
return 0;
}
/*
* A PGA represents a module in a pipeline. So in the Pre-PMU event of PGA
* we need to do following:
@ -408,75 +592,62 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w,
* - Then run current pipe
*/
static int skl_tplg_pga_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w,
struct skl *skl)
struct skl *skl)
{
struct snd_soc_dapm_path *p;
struct skl_dapm_path_list *path_list;
struct snd_soc_dapm_widget *source, *sink;
struct skl_module_cfg *src_mconfig, *sink_mconfig;
struct skl_module_cfg *src_mconfig;
struct skl_sst *ctx = skl->skl_sst;
int ret = 0;
source = w;
src_mconfig = source->priv;
src_mconfig = w->priv;
/*
* find which sink it is connected to, bind with the sink,
* if sink is not started, start sink pipe first, then start
* this pipe
*/
snd_soc_dapm_widget_for_each_source_path(w, p) {
if (!p->connect)
continue;
dev_dbg(ctx->dev, "%s: src widget=%s\n", __func__, w->name);
dev_dbg(ctx->dev, "%s: sink widget=%s\n", __func__, p->sink->name);
/*
* here we will check widgets in sink pipelines, so that
* can be any widgets type and we are only interested if
* they are ones used for SKL so check that first
*/
if ((p->sink->priv != NULL) &&
is_skl_dsp_widget_type(p->sink)) {
sink = p->sink;
src_mconfig = source->priv;
sink_mconfig = sink->priv;
/* Bind source to sink, mixin is always source */
ret = skl_bind_modules(ctx, src_mconfig, sink_mconfig);
if (ret)
return ret;
/* Start sinks pipe first */
if (sink_mconfig->pipe->state != SKL_PIPE_STARTED) {
ret = skl_run_pipe(ctx, sink_mconfig->pipe);
if (ret)
return ret;
}
path_list = kzalloc(
sizeof(struct skl_dapm_path_list),
GFP_KERNEL);
if (path_list == NULL)
return -ENOMEM;
/* Add connected path to one global list */
path_list->dapm_path = p;
list_add_tail(&path_list->node, &skl->dapm_path_list);
break;
}
}
/* Start source pipe last after starting all sinks */
ret = skl_run_pipe(ctx, src_mconfig->pipe);
ret = skl_tplg_bind_sinks(w, skl, src_mconfig);
if (ret)
return ret;
/* Start source pipe last after starting all sinks */
if (src_mconfig->pipe->conn_type != SKL_PIPE_CONN_TYPE_FE)
return skl_run_pipe(ctx, src_mconfig->pipe);
return 0;
}
static struct snd_soc_dapm_widget *skl_get_src_dsp_widget(
struct snd_soc_dapm_widget *w, struct skl *skl)
{
struct snd_soc_dapm_path *p;
struct snd_soc_dapm_widget *src_w = NULL;
struct skl_sst *ctx = skl->skl_sst;
snd_soc_dapm_widget_for_each_source_path(w, p) {
src_w = p->source;
if (!p->connect)
continue;
dev_dbg(ctx->dev, "sink widget=%s\n", w->name);
dev_dbg(ctx->dev, "src widget=%s\n", p->source->name);
/*
* here we will check widgets in sink pipelines, so that can
* be any widgets type and we are only interested if they are
* ones used for SKL so check that first
*/
if ((p->source->priv != NULL) &&
is_skl_dsp_widget_type(p->source)) {
return p->source;
}
}
if (src_w != NULL)
return skl_get_src_dsp_widget(src_w, skl);
return NULL;
}
/*
* in the Post-PMU event of mixer we need to do following:
* - Check if this pipe is running
@ -490,7 +661,6 @@ static int skl_tplg_mixer_dapm_post_pmu_event(struct snd_soc_dapm_widget *w,
struct skl *skl)
{
int ret = 0;
struct snd_soc_dapm_path *p;
struct snd_soc_dapm_widget *source, *sink;
struct skl_module_cfg *src_mconfig, *sink_mconfig;
struct skl_sst *ctx = skl->skl_sst;
@ -504,32 +674,18 @@ static int skl_tplg_mixer_dapm_post_pmu_event(struct snd_soc_dapm_widget *w,
* one more sink before this sink got connected, Since source is
* started, bind this sink to source and start this pipe.
*/
snd_soc_dapm_widget_for_each_sink_path(w, p) {
if (!p->connect)
continue;
dev_dbg(ctx->dev, "sink widget=%s\n", w->name);
dev_dbg(ctx->dev, "src widget=%s\n", p->source->name);
source = skl_get_src_dsp_widget(w, skl);
if (source != NULL) {
src_mconfig = source->priv;
sink_mconfig = sink->priv;
src_pipe_started = 1;
/*
* here we will check widgets in sink pipelines, so that
* can be any widgets type and we are only interested if
* they are ones used for SKL so check that first
* check pipe state, then no need to bind or start the
* pipe
*/
if ((p->source->priv != NULL) &&
is_skl_dsp_widget_type(p->source)) {
source = p->source;
src_mconfig = source->priv;
sink_mconfig = sink->priv;
src_pipe_started = 1;
/*
* check pipe state, then no need to bind or start
* the pipe
*/
if (src_mconfig->pipe->state != SKL_PIPE_STARTED)
src_pipe_started = 0;
}
if (src_mconfig->pipe->state != SKL_PIPE_STARTED)
src_pipe_started = 0;
}
if (src_pipe_started) {
@ -537,7 +693,8 @@ static int skl_tplg_mixer_dapm_post_pmu_event(struct snd_soc_dapm_widget *w,
if (ret)
return ret;
ret = skl_run_pipe(ctx, sink_mconfig->pipe);
if (sink_mconfig->pipe->conn_type != SKL_PIPE_CONN_TYPE_FE)
ret = skl_run_pipe(ctx, sink_mconfig->pipe);
}
return ret;
@ -552,54 +709,37 @@ static int skl_tplg_mixer_dapm_post_pmu_event(struct snd_soc_dapm_widget *w,
static int skl_tplg_mixer_dapm_pre_pmd_event(struct snd_soc_dapm_widget *w,
struct skl *skl)
{
struct snd_soc_dapm_widget *source, *sink;
struct skl_module_cfg *src_mconfig, *sink_mconfig;
int ret = 0, path_found = 0;
struct skl_dapm_path_list *path_list, *tmp_list;
int ret = 0, i;
struct skl_sst *ctx = skl->skl_sst;
sink = w;
sink_mconfig = sink->priv;
sink_mconfig = w->priv;
/* Stop the pipe */
ret = skl_stop_pipe(ctx, sink_mconfig->pipe);
if (ret)
return ret;
/*
* This list, dapm_path_list handling here does not need any locks
* as we are under dapm lock while handling widget events.
* List can be manipulated safely only under dapm widgets handler
* routines
*/
list_for_each_entry_safe(path_list, tmp_list,
&skl->dapm_path_list, node) {
if (path_list->dapm_path->sink == sink) {
dev_dbg(ctx->dev, "Path found = %s\n",
path_list->dapm_path->name);
source = path_list->dapm_path->source;
src_mconfig = source->priv;
path_found = 1;
for (i = 0; i < sink_mconfig->max_in_queue; i++) {
if (sink_mconfig->m_in_pin[i].pin_state == SKL_PIN_BIND_DONE) {
src_mconfig = sink_mconfig->m_in_pin[i].tgt_mcfg;
if (!src_mconfig)
continue;
/*
* If path_found == 1, that means pmd for source
* pipe has not occurred, source is connected to
* some other sink. so its responsibility of sink
* to unbind itself from source.
*/
ret = skl_stop_pipe(ctx, src_mconfig->pipe);
if (ret < 0)
return ret;
list_del(&path_list->node);
kfree(path_list);
break;
ret = skl_unbind_modules(ctx,
src_mconfig, sink_mconfig);
}
}
/*
* If path_found == 1, that means pmd for source pipe has
* not occurred, source is connected to some other sink.
* so its responsibility of sink to unbind itself from source.
*/
if (path_found) {
ret = skl_stop_pipe(ctx, src_mconfig->pipe);
if (ret < 0)
return ret;
ret = skl_unbind_modules(ctx, src_mconfig, sink_mconfig);
}
return ret;
}
@ -622,10 +762,12 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w,
int ret = 0;
skl_tplg_free_pipe_mcps(skl, mconfig);
skl_tplg_free_pipe_mem(skl, mconfig);
list_for_each_entry(w_module, &s_pipe->w_list, node) {
dst_module = w_module->w->priv;
skl_tplg_free_pipe_mcps(skl, dst_module);
if (src_module == NULL) {
src_module = dst_module;
continue;
@ -639,9 +781,8 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w,
}
ret = skl_delete_pipe(ctx, mconfig->pipe);
skl_tplg_free_pipe_mem(skl, mconfig);
return ret;
return skl_tplg_unload_pipe_modules(ctx, s_pipe);
}
/*
@ -653,47 +794,34 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w,
static int skl_tplg_pga_dapm_post_pmd_event(struct snd_soc_dapm_widget *w,
struct skl *skl)
{
struct snd_soc_dapm_widget *source, *sink;
struct skl_module_cfg *src_mconfig, *sink_mconfig;
int ret = 0, path_found = 0;
struct skl_dapm_path_list *path_list, *tmp_path_list;
int ret = 0, i;
struct skl_sst *ctx = skl->skl_sst;
source = w;
src_mconfig = source->priv;
src_mconfig = w->priv;
skl_tplg_free_pipe_mcps(skl, src_mconfig);
/* Stop the pipe since this is a mixin module */
ret = skl_stop_pipe(ctx, src_mconfig->pipe);
if (ret)
return ret;
list_for_each_entry_safe(path_list, tmp_path_list, &skl->dapm_path_list, node) {
if (path_list->dapm_path->source == source) {
dev_dbg(ctx->dev, "Path found = %s\n",
path_list->dapm_path->name);
sink = path_list->dapm_path->sink;
sink_mconfig = sink->priv;
path_found = 1;
list_del(&path_list->node);
kfree(path_list);
break;
for (i = 0; i < src_mconfig->max_out_queue; i++) {
if (src_mconfig->m_out_pin[i].pin_state == SKL_PIN_BIND_DONE) {
sink_mconfig = src_mconfig->m_out_pin[i].tgt_mcfg;
if (!sink_mconfig)
continue;
/*
* This is a connecter and if path is found that means
* unbind between source and sink has not happened yet
*/
ret = skl_stop_pipe(ctx, sink_mconfig->pipe);
if (ret < 0)
return ret;
ret = skl_unbind_modules(ctx, src_mconfig,
sink_mconfig);
}
}
/*
* This is a connector and if path is found that means
* unbind between source and sink has not happened yet
*/
if (path_found) {
ret = skl_stop_pipe(ctx, src_mconfig->pipe);
if (ret < 0)
return ret;
ret = skl_unbind_modules(ctx, src_mconfig, sink_mconfig);
}
return ret;
}
@ -774,6 +902,67 @@ static int skl_tplg_pga_event(struct snd_soc_dapm_widget *w,
return 0;
}
static int skl_tplg_tlv_control_get(struct snd_kcontrol *kcontrol,
unsigned int __user *data, unsigned int size)
{
struct soc_bytes_ext *sb =
(struct soc_bytes_ext *)kcontrol->private_value;
struct skl_algo_data *bc = (struct skl_algo_data *)sb->dobj.private;
struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
struct skl_module_cfg *mconfig = w->priv;
struct skl *skl = get_skl_ctx(w->dapm->dev);
if (w->power)
skl_get_module_params(skl->skl_sst, (u32 *)bc->params,
bc->max, bc->param_id, mconfig);
if (bc->params) {
if (copy_to_user(data, &bc->param_id, sizeof(u32)))
return -EFAULT;
if (copy_to_user(data + 1, &size, sizeof(u32)))
return -EFAULT;
if (copy_to_user(data + 2, bc->params, size))
return -EFAULT;
}
return 0;
}
#define SKL_PARAM_VENDOR_ID 0xff
static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol,
const unsigned int __user *data, unsigned int size)
{
struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
struct skl_module_cfg *mconfig = w->priv;
struct soc_bytes_ext *sb =
(struct soc_bytes_ext *)kcontrol->private_value;
struct skl_algo_data *ac = (struct skl_algo_data *)sb->dobj.private;
struct skl *skl = get_skl_ctx(w->dapm->dev);
if (ac->params) {
/*
* if the param_is is of type Vendor, firmware expects actual
* parameter id and size from the control.
*/
if (ac->param_id == SKL_PARAM_VENDOR_ID) {
if (copy_from_user(ac->params, data, size))
return -EFAULT;
} else {
if (copy_from_user(ac->params,
data + 2 * sizeof(u32), size))
return -EFAULT;
}
if (w->power)
return skl_set_module_params(skl->skl_sst,
(u32 *)ac->params, ac->max,
ac->param_id, mconfig);
}
return 0;
}
/*
* The FE params are passed by hw_params of the DAI.
* On hw_params, the params are stored in Gateway module of the FE and we
@ -790,9 +979,9 @@ int skl_tplg_update_pipe_params(struct device *dev,
memcpy(pipe->p_params, params, sizeof(*params));
if (params->stream == SNDRV_PCM_STREAM_PLAYBACK)
format = &mconfig->in_fmt;
format = &mconfig->in_fmt[0];
else
format = &mconfig->out_fmt;
format = &mconfig->out_fmt[0];
/* set the hw_params */
format->s_freq = params->s_freq;
@ -809,6 +998,7 @@ int skl_tplg_update_pipe_params(struct device *dev,
break;
case SKL_DEPTH_24BIT:
case SKL_DEPTH_32BIT:
format->bit_depth = SKL_DEPTH_32BIT;
break;
@ -846,7 +1036,7 @@ skl_tplg_fe_get_cpr_module(struct snd_soc_dai *dai, int stream)
w = dai->playback_widget;
snd_soc_dapm_widget_for_each_sink_path(w, p) {
if (p->connect && p->sink->power &&
is_skl_dsp_widget_type(p->sink))
!is_skl_dsp_widget_type(p->sink))
continue;
if (p->sink->priv) {
@ -859,7 +1049,7 @@ skl_tplg_fe_get_cpr_module(struct snd_soc_dai *dai, int stream)
w = dai->capture_widget;
snd_soc_dapm_widget_for_each_source_path(w, p) {
if (p->connect && p->source->power &&
is_skl_dsp_widget_type(p->source))
!is_skl_dsp_widget_type(p->source))
continue;
if (p->source->priv) {
@ -920,6 +1110,9 @@ static int skl_tplg_be_fill_pipe_params(struct snd_soc_dai *dai,
memcpy(pipe->p_params, params, sizeof(*params));
if (link_type == NHLT_LINK_HDA)
return 0;
/* update the blob based on virtual bus_id*/
cfg = skl_get_ep_blob(skl, mconfig->vbus_id, link_type,
params->s_fmt, params->ch,
@ -950,18 +1143,13 @@ static int skl_tplg_be_set_src_pipe_params(struct snd_soc_dai *dai,
if (p->connect && is_skl_dsp_widget_type(p->source) &&
p->source->priv) {
if (!p->source->power) {
ret = skl_tplg_be_fill_pipe_params(
dai, p->source->priv,
params);
if (ret < 0)
return ret;
} else {
return -EBUSY;
}
ret = skl_tplg_be_fill_pipe_params(dai,
p->source->priv, params);
if (ret < 0)
return ret;
} else {
ret = skl_tplg_be_set_src_pipe_params(
dai, p->source, params);
ret = skl_tplg_be_set_src_pipe_params(dai,
p->source, params);
if (ret < 0)
return ret;
}
@ -980,15 +1168,10 @@ static int skl_tplg_be_set_sink_pipe_params(struct snd_soc_dai *dai,
if (p->connect && is_skl_dsp_widget_type(p->sink) &&
p->sink->priv) {
if (!p->sink->power) {
ret = skl_tplg_be_fill_pipe_params(
dai, p->sink->priv, params);
if (ret < 0)
return ret;
} else {
return -EBUSY;
}
ret = skl_tplg_be_fill_pipe_params(dai,
p->sink->priv, params);
if (ret < 0)
return ret;
} else {
ret = skl_tplg_be_set_sink_pipe_params(
dai, p->sink, params);
@ -1030,6 +1213,11 @@ static const struct snd_soc_tplg_widget_events skl_tplg_widget_ops[] = {
{SKL_PGA_EVENT, skl_tplg_pga_event},
};
static const struct snd_soc_tplg_bytes_ext_ops skl_tlv_ops[] = {
{SKL_CONTROL_TYPE_BYTE_TLV, skl_tplg_tlv_control_get,
skl_tplg_tlv_control_set},
};
/*
* The topology binary passes the pin info for a module so initialize the pin
* info passed into module instance
@ -1045,6 +1233,7 @@ static void skl_fill_module_pin_info(struct skl_dfw_module_pin *dfw_pin,
m_pin[i].id.instance_id = dfw_pin[i].instance_id;
m_pin[i].in_use = false;
m_pin[i].is_dynamic = is_dynamic;
m_pin[i].pin_state = SKL_PIN_UNBIND;
}
}
@ -1092,6 +1281,24 @@ static struct skl_pipe *skl_tplg_add_pipe(struct device *dev,
return ppl->pipe;
}
static void skl_tplg_fill_fmt(struct skl_module_fmt *dst_fmt,
struct skl_dfw_module_fmt *src_fmt,
int pins)
{
int i;
for (i = 0; i < pins; i++) {
dst_fmt[i].channels = src_fmt[i].channels;
dst_fmt[i].s_freq = src_fmt[i].freq;
dst_fmt[i].bit_depth = src_fmt[i].bit_depth;
dst_fmt[i].valid_bit_depth = src_fmt[i].valid_bit_depth;
dst_fmt[i].ch_cfg = src_fmt[i].ch_cfg;
dst_fmt[i].ch_map = src_fmt[i].ch_map;
dst_fmt[i].interleaving_style = src_fmt[i].interleaving_style;
dst_fmt[i].sample_type = src_fmt[i].sample_type;
}
}
/*
* Topology core widget load callback
*
@ -1130,22 +1337,16 @@ static int skl_tplg_widget_load(struct snd_soc_component *cmpnt,
mconfig->max_in_queue = dfw_config->max_in_queue;
mconfig->max_out_queue = dfw_config->max_out_queue;
mconfig->is_loadable = dfw_config->is_loadable;
mconfig->in_fmt.channels = dfw_config->in_fmt.channels;
mconfig->in_fmt.s_freq = dfw_config->in_fmt.freq;
mconfig->in_fmt.bit_depth = dfw_config->in_fmt.bit_depth;
mconfig->in_fmt.valid_bit_depth =
dfw_config->in_fmt.valid_bit_depth;
mconfig->in_fmt.ch_cfg = dfw_config->in_fmt.ch_cfg;
mconfig->out_fmt.channels = dfw_config->out_fmt.channels;
mconfig->out_fmt.s_freq = dfw_config->out_fmt.freq;
mconfig->out_fmt.bit_depth = dfw_config->out_fmt.bit_depth;
mconfig->out_fmt.valid_bit_depth =
dfw_config->out_fmt.valid_bit_depth;
mconfig->out_fmt.ch_cfg = dfw_config->out_fmt.ch_cfg;
skl_tplg_fill_fmt(mconfig->in_fmt, dfw_config->in_fmt,
MODULE_MAX_IN_PINS);
skl_tplg_fill_fmt(mconfig->out_fmt, dfw_config->out_fmt,
MODULE_MAX_OUT_PINS);
mconfig->params_fixup = dfw_config->params_fixup;
mconfig->converter = dfw_config->converter;
mconfig->m_type = dfw_config->module_type;
mconfig->vbus_id = dfw_config->vbus_id;
mconfig->mem_pages = dfw_config->mem_pages;
pipe = skl_tplg_add_pipe(bus->dev, skl, &dfw_config->pipe);
if (pipe)
@ -1156,10 +1357,13 @@ static int skl_tplg_widget_load(struct snd_soc_component *cmpnt,
mconfig->time_slot = dfw_config->time_slot;
mconfig->formats_config.caps_size = dfw_config->caps.caps_size;
mconfig->m_in_pin = devm_kzalloc(bus->dev,
(mconfig->max_in_queue) *
sizeof(*mconfig->m_in_pin),
GFP_KERNEL);
if (dfw_config->is_loadable)
memcpy(mconfig->guid, dfw_config->uuid,
ARRAY_SIZE(dfw_config->uuid));
mconfig->m_in_pin = devm_kzalloc(bus->dev, (mconfig->max_in_queue) *
sizeof(*mconfig->m_in_pin),
GFP_KERNEL);
if (!mconfig->m_in_pin)
return -ENOMEM;
@ -1188,7 +1392,9 @@ static int skl_tplg_widget_load(struct snd_soc_component *cmpnt,
return -ENOMEM;
memcpy(mconfig->formats_config.caps, dfw_config->caps.caps,
dfw_config->caps.caps_size);
dfw_config->caps.caps_size);
mconfig->formats_config.param_id = dfw_config->caps.param_id;
mconfig->formats_config.set_params = dfw_config->caps.set_params;
bind_event:
if (tplg_w->event_type == 0) {
@ -1209,8 +1415,70 @@ bind_event:
return 0;
}
static int skl_init_algo_data(struct device *dev, struct soc_bytes_ext *be,
struct snd_soc_tplg_bytes_control *bc)
{
struct skl_algo_data *ac;
struct skl_dfw_algo_data *dfw_ac =
(struct skl_dfw_algo_data *)bc->priv.data;
ac = devm_kzalloc(dev, sizeof(*ac), GFP_KERNEL);
if (!ac)
return -ENOMEM;
/* Fill private data */
ac->max = dfw_ac->max;
ac->param_id = dfw_ac->param_id;
ac->set_params = dfw_ac->set_params;
if (ac->max) {
ac->params = (char *) devm_kzalloc(dev, ac->max, GFP_KERNEL);
if (!ac->params)
return -ENOMEM;
if (dfw_ac->params)
memcpy(ac->params, dfw_ac->params, ac->max);
}
be->dobj.private = ac;
return 0;
}
static int skl_tplg_control_load(struct snd_soc_component *cmpnt,
struct snd_kcontrol_new *kctl,
struct snd_soc_tplg_ctl_hdr *hdr)
{
struct soc_bytes_ext *sb;
struct snd_soc_tplg_bytes_control *tplg_bc;
struct hdac_ext_bus *ebus = snd_soc_component_get_drvdata(cmpnt);
struct hdac_bus *bus = ebus_to_hbus(ebus);
switch (hdr->ops.info) {
case SND_SOC_TPLG_CTL_BYTES:
tplg_bc = container_of(hdr,
struct snd_soc_tplg_bytes_control, hdr);
if (kctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
sb = (struct soc_bytes_ext *)kctl->private_value;
if (tplg_bc->priv.size)
return skl_init_algo_data(
bus->dev, sb, tplg_bc);
}
break;
default:
dev_warn(bus->dev, "Control load not supported %d:%d:%d\n",
hdr->ops.get, hdr->ops.put, hdr->ops.info);
break;
}
return 0;
}
static struct snd_soc_tplg_ops skl_tplg_ops = {
.widget_load = skl_tplg_widget_load,
.control_load = skl_tplg_control_load,
.bytes_ext_ops = skl_tlv_ops,
.bytes_ext_ops_count = ARRAY_SIZE(skl_tlv_ops),
};
/* This will be read from topology manifest, currently defined here */

View File

@ -36,6 +36,9 @@
/* Maximum number of coefficients up down mixer module */
#define UP_DOWN_MIXER_MAX_COEFF 6
#define MODULE_MAX_IN_PINS 8
#define MODULE_MAX_OUT_PINS 8
enum skl_channel_index {
SKL_CHANNEL_LEFT = 0,
SKL_CHANNEL_RIGHT = 1,
@ -55,12 +58,6 @@ enum skl_bitdepth {
SKL_DEPTH_INVALID
};
enum skl_interleaving {
/* [s1_ch1...s1_chN,...,sM_ch1...sM_chN] */
SKL_INTERLEAVING_PER_CHANNEL = 0,
/* [s1_ch1...sM_ch1,...,s1_chN...sM_chN] */
SKL_INTERLEAVING_PER_SAMPLE = 1,
};
enum skl_s_freq {
SKL_FS_8000 = 8000,
@ -143,6 +140,16 @@ struct skl_up_down_mixer_cfg {
s32 coeff[UP_DOWN_MIXER_MAX_COEFF];
} __packed;
struct skl_algo_cfg {
struct skl_base_cfg base_cfg;
char params[0];
} __packed;
struct skl_base_outfmt_cfg {
struct skl_base_cfg base_cfg;
struct skl_audio_data_format out_fmt;
} __packed;
enum skl_dma_type {
SKL_DMA_HDA_HOST_OUTPUT_CLASS = 0,
SKL_DMA_HDA_HOST_INPUT_CLASS = 1,
@ -178,21 +185,34 @@ struct skl_module_fmt {
u32 bit_depth;
u32 valid_bit_depth;
u32 ch_cfg;
u32 interleaving_style;
u32 sample_type;
u32 ch_map;
};
struct skl_module_cfg;
struct skl_module_inst_id {
u32 module_id;
u32 instance_id;
};
enum skl_module_pin_state {
SKL_PIN_UNBIND = 0,
SKL_PIN_BIND_DONE = 1,
};
struct skl_module_pin {
struct skl_module_inst_id id;
u8 pin_index;
bool is_dynamic;
bool in_use;
enum skl_module_pin_state pin_state;
struct skl_module_cfg *tgt_mcfg;
};
struct skl_specific_cfg {
u32 set_params;
u32 param_id;
u32 caps_size;
u32 *caps;
};
@ -238,9 +258,13 @@ enum skl_module_state {
};
struct skl_module_cfg {
char guid[SKL_UUID_STR_SZ];
struct skl_module_inst_id id;
struct skl_module_fmt in_fmt;
struct skl_module_fmt out_fmt;
u8 domain;
bool homogenous_inputs;
bool homogenous_outputs;
struct skl_module_fmt in_fmt[MODULE_MAX_IN_PINS];
struct skl_module_fmt out_fmt[MODULE_MAX_OUT_PINS];
u8 max_in_queue;
u8 max_out_queue;
u8 in_queue_mask;
@ -258,6 +282,7 @@ struct skl_module_cfg {
u32 params_fixup;
u32 converter;
u32 vbus_id;
u32 mem_pages;
struct skl_module_pin *m_in_pin;
struct skl_module_pin *m_out_pin;
enum skl_module_type m_type;
@ -267,13 +292,15 @@ struct skl_module_cfg {
struct skl_specific_cfg formats_config;
};
struct skl_pipeline {
struct skl_pipe *pipe;
struct list_head node;
struct skl_algo_data {
u32 param_id;
u32 set_params;
u32 max;
char *params;
};
struct skl_dapm_path_list {
struct snd_soc_dapm_path *dapm_path;
struct skl_pipeline {
struct skl_pipe *pipe;
struct list_head node;
};
@ -305,8 +332,7 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe);
int skl_stop_pipe(struct skl_sst *ctx, struct skl_pipe *pipe);
int skl_init_module(struct skl_sst *ctx, struct skl_module_cfg *module_config,
char *param);
int skl_init_module(struct skl_sst *ctx, struct skl_module_cfg *module_config);
int skl_bind_modules(struct skl_sst *ctx, struct skl_module_cfg
*src_module, struct skl_module_cfg *dst_module);
@ -314,5 +340,10 @@ int skl_bind_modules(struct skl_sst *ctx, struct skl_module_cfg
int skl_unbind_modules(struct skl_sst *ctx, struct skl_module_cfg
*src_module, struct skl_module_cfg *dst_module);
int skl_set_module_params(struct skl_sst *ctx, u32 *params, int size,
u32 param_id, struct skl_module_cfg *mcfg);
int skl_get_module_params(struct skl_sst *ctx, u32 *params, int size,
u32 param_id, struct skl_module_cfg *mcfg);
enum skl_bitdepth skl_get_bit_depth(int params);
#endif

View File

@ -23,15 +23,13 @@
* Default types range from 0~12. type can range from 0 to 0xff
* SST types start at higher to avoid any overlapping in future
*/
#define SOC_CONTROL_TYPE_HDA_SST_ALGO_PARAMS 0x100
#define SOC_CONTROL_TYPE_HDA_SST_MUX 0x101
#define SOC_CONTROL_TYPE_HDA_SST_MIX 0x101
#define SOC_CONTROL_TYPE_HDA_SST_BYTE 0x103
#define SKL_CONTROL_TYPE_BYTE_TLV 0x100
#define HDA_SST_CFG_MAX 900 /* size of copier cfg*/
#define MAX_IN_QUEUE 8
#define MAX_OUT_QUEUE 8
#define SKL_UUID_STR_SZ 40
/* Event types goes here */
/* Reserve event type 0 for no event handlers */
enum skl_event_types {
@ -72,6 +70,7 @@ enum skl_ch_cfg {
SKL_CH_CFG_DUAL_MONO = 9,
SKL_CH_CFG_I2S_DUAL_STEREO_0 = 10,
SKL_CH_CFG_I2S_DUAL_STEREO_1 = 11,
SKL_CH_CFG_4_CHANNEL = 12,
SKL_CH_CFG_INVALID
};
@ -79,7 +78,9 @@ enum skl_module_type {
SKL_MODULE_TYPE_MIXER = 0,
SKL_MODULE_TYPE_COPIER,
SKL_MODULE_TYPE_UPDWMIX,
SKL_MODULE_TYPE_SRCINT
SKL_MODULE_TYPE_SRCINT,
SKL_MODULE_TYPE_ALGO,
SKL_MODULE_TYPE_BASE_OUTFMT
};
enum skl_core_affinity {
@ -110,6 +111,42 @@ enum skl_dev_type {
SKL_DEVICE_NONE
};
/**
* enum skl_interleaving - interleaving style
*
* @SKL_INTERLEAVING_PER_CHANNEL: [s1_ch1...s1_chN,...,sM_ch1...sM_chN]
* @SKL_INTERLEAVING_PER_SAMPLE: [s1_ch1...sM_ch1,...,s1_chN...sM_chN]
*/
enum skl_interleaving {
SKL_INTERLEAVING_PER_CHANNEL = 0,
SKL_INTERLEAVING_PER_SAMPLE = 1,
};
enum skl_sample_type {
SKL_SAMPLE_TYPE_INT_MSB = 0,
SKL_SAMPLE_TYPE_INT_LSB = 1,
SKL_SAMPLE_TYPE_INT_SIGNED = 2,
SKL_SAMPLE_TYPE_INT_UNSIGNED = 3,
SKL_SAMPLE_TYPE_FLOAT = 4
};
enum module_pin_type {
/* All pins of the module takes same PCM inputs or outputs
* e.g. mixout
*/
SKL_PIN_TYPE_HOMOGENEOUS,
/* All pins of the module takes different PCM inputs or outputs
* e.g mux
*/
SKL_PIN_TYPE_HETEROGENEOUS,
};
enum skl_module_param_type {
SKL_PARAM_DEFAULT = 0,
SKL_PARAM_INIT,
SKL_PARAM_SET
};
struct skl_dfw_module_pin {
u16 module_id;
u16 instance_id;
@ -121,9 +158,15 @@ struct skl_dfw_module_fmt {
u32 bit_depth;
u32 valid_bit_depth;
u32 ch_cfg;
u32 interleaving_style;
u32 sample_type;
u32 ch_map;
} __packed;
struct skl_dfw_module_caps {
u32 set_params:2;
u32 rsvd:30;
u32 param_id;
u32 caps_size;
u32 caps[HDA_SST_CFG_MAX];
};
@ -131,41 +174,57 @@ struct skl_dfw_module_caps {
struct skl_dfw_pipe {
u8 pipe_id;
u8 pipe_priority;
u16 conn_type;
u32 memory_pages;
u16 conn_type:4;
u16 rsvd:4;
u16 memory_pages:8;
} __packed;
struct skl_dfw_module {
char uuid[SKL_UUID_STR_SZ];
u16 module_id;
u16 instance_id;
u32 max_mcps;
u8 core_id;
u8 max_in_queue;
u8 max_out_queue;
u8 is_loadable;
u8 conn_type;
u8 dev_type;
u8 hw_conn_type;
u8 time_slot;
u32 mem_pages;
u32 obs;
u32 ibs;
u32 params_fixup;
u32 converter;
u32 module_type;
u32 vbus_id;
u8 is_dynamic_in_pin;
u8 is_dynamic_out_pin;
u32 max_in_queue:8;
u32 max_out_queue:8;
u32 time_slot:8;
u32 core_id:4;
u32 rsvd1:4;
u32 module_type:8;
u32 conn_type:4;
u32 dev_type:4;
u32 hw_conn_type:4;
u32 rsvd2:12;
u32 params_fixup:8;
u32 converter:8;
u32 input_pin_type:1;
u32 output_pin_type:1;
u32 is_dynamic_in_pin:1;
u32 is_dynamic_out_pin:1;
u32 is_loadable:1;
u32 rsvd3:11;
struct skl_dfw_pipe pipe;
struct skl_dfw_module_fmt in_fmt;
struct skl_dfw_module_fmt out_fmt;
struct skl_dfw_module_fmt in_fmt[MAX_IN_QUEUE];
struct skl_dfw_module_fmt out_fmt[MAX_OUT_QUEUE];
struct skl_dfw_module_pin in_pin[MAX_IN_QUEUE];
struct skl_dfw_module_pin out_pin[MAX_OUT_QUEUE];
struct skl_dfw_module_caps caps;
} __packed;
struct skl_dfw_algo_data {
u32 set_params:2;
u32 rsvd:30;
u32 param_id;
u32 max;
char *params;
char params[0];
} __packed;
#endif

View File

@ -26,6 +26,7 @@
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <sound/pcm.h>
#include "../common/sst-acpi.h"
#include "skl.h"
/*
@ -129,51 +130,13 @@ static int skl_acquire_irq(struct hdac_ext_bus *ebus, int do_disconnect)
return 0;
}
#ifdef CONFIG_PM_SLEEP
/*
* power management
*/
static int skl_suspend(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct hdac_ext_bus *ebus = pci_get_drvdata(pci);
struct hdac_bus *bus = ebus_to_hbus(ebus);
snd_hdac_bus_stop_chip(bus);
snd_hdac_bus_enter_link_reset(bus);
return 0;
}
static int skl_resume(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct hdac_ext_bus *ebus = pci_get_drvdata(pci);
struct hdac_bus *bus = ebus_to_hbus(ebus);
struct skl *hda = ebus_to_skl(ebus);
skl_init_pci(hda);
snd_hdac_bus_init_chip(bus, 1);
return 0;
}
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM
static int skl_runtime_suspend(struct device *dev)
static int _skl_suspend(struct hdac_ext_bus *ebus)
{
struct pci_dev *pci = to_pci_dev(dev);
struct hdac_ext_bus *ebus = pci_get_drvdata(pci);
struct hdac_bus *bus = ebus_to_hbus(ebus);
struct skl *skl = ebus_to_skl(ebus);
struct hdac_bus *bus = ebus_to_hbus(ebus);
int ret;
dev_dbg(bus->dev, "in %s\n", __func__);
/* enable controller wake up event */
snd_hdac_chip_updatew(bus, WAKEEN, 0, STATESTS_INT_MASK);
snd_hdac_ext_bus_link_power_down_all(ebus);
ret = skl_suspend_dsp(skl);
@ -186,25 +149,84 @@ static int skl_runtime_suspend(struct device *dev)
return 0;
}
static int _skl_resume(struct hdac_ext_bus *ebus)
{
struct skl *skl = ebus_to_skl(ebus);
struct hdac_bus *bus = ebus_to_hbus(ebus);
skl_init_pci(skl);
snd_hdac_bus_init_chip(bus, true);
return skl_resume_dsp(skl);
}
#endif
#ifdef CONFIG_PM_SLEEP
/*
* power management
*/
static int skl_suspend(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct hdac_ext_bus *ebus = pci_get_drvdata(pci);
struct skl *skl = ebus_to_skl(ebus);
/*
* Do not suspend if streams which are marked ignore suspend are
* running, we need to save the state for these and continue
*/
if (skl->supend_active) {
pci_save_state(pci);
pci_disable_device(pci);
return 0;
} else {
return _skl_suspend(ebus);
}
}
static int skl_resume(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct hdac_ext_bus *ebus = pci_get_drvdata(pci);
struct skl *skl = ebus_to_skl(ebus);
int ret;
/*
* resume only when we are not in suspend active, otherwise need to
* restore the device
*/
if (skl->supend_active) {
pci_restore_state(pci);
ret = pci_enable_device(pci);
} else {
ret = _skl_resume(ebus);
}
return ret;
}
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM
static int skl_runtime_suspend(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct hdac_ext_bus *ebus = pci_get_drvdata(pci);
struct hdac_bus *bus = ebus_to_hbus(ebus);
dev_dbg(bus->dev, "in %s\n", __func__);
return _skl_suspend(ebus);
}
static int skl_runtime_resume(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct hdac_ext_bus *ebus = pci_get_drvdata(pci);
struct hdac_bus *bus = ebus_to_hbus(ebus);
struct skl *skl = ebus_to_skl(ebus);
int status;
dev_dbg(bus->dev, "in %s\n", __func__);
/* Read STATESTS before controller reset */
status = snd_hdac_chip_readw(bus, STATESTS);
skl_init_pci(skl);
snd_hdac_bus_init_chip(bus, true);
/* disable controller Wake Up event */
snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, 0);
return skl_resume_dsp(skl);
return _skl_resume(ebus);
}
#endif /* CONFIG_PM */
@ -241,6 +263,43 @@ static int skl_free(struct hdac_ext_bus *ebus)
return 0;
}
static int skl_machine_device_register(struct skl *skl, void *driver_data)
{
struct hdac_bus *bus = ebus_to_hbus(&skl->ebus);
struct platform_device *pdev;
struct sst_acpi_mach *mach = driver_data;
int ret;
mach = sst_acpi_find_machine(mach);
if (mach == NULL) {
dev_err(bus->dev, "No matching machine driver found\n");
return -ENODEV;
}
skl->fw_name = mach->fw_filename;
pdev = platform_device_alloc(mach->drv_name, -1);
if (pdev == NULL) {
dev_err(bus->dev, "platform device alloc failed\n");
return -EIO;
}
ret = platform_device_add(pdev);
if (ret) {
dev_err(bus->dev, "failed to add machine device\n");
platform_device_put(pdev);
return -EIO;
}
skl->i2s_dev = pdev;
return 0;
}
static void skl_machine_device_unregister(struct skl *skl)
{
if (skl->i2s_dev)
platform_device_unregister(skl->i2s_dev);
}
static int skl_dmic_device_register(struct skl *skl)
{
struct hdac_bus *bus = ebus_to_hbus(&skl->ebus);
@ -434,8 +493,7 @@ static int skl_first_init(struct hdac_ext_bus *ebus)
/* codec detection */
if (!bus->codec_mask) {
dev_err(bus->dev, "no codecs found!\n");
return -ENODEV;
dev_info(bus->dev, "no hda codecs found!\n");
}
return 0;
@ -470,10 +528,15 @@ static int skl_probe(struct pci_dev *pci,
/* check if dsp is there */
if (ebus->ppcap) {
err = skl_machine_device_register(skl,
(void *)pci_id->driver_data);
if (err < 0)
goto out_free;
err = skl_init_dsp(skl);
if (err < 0) {
dev_dbg(bus->dev, "error failed to register dsp\n");
goto out_free;
goto out_mach_free;
}
}
if (ebus->mlcap)
@ -508,6 +571,8 @@ out_dmic_free:
skl_dmic_device_unregister(skl);
out_dsp_free:
skl_free_dsp(skl);
out_mach_free:
skl_machine_device_unregister(skl);
out_free:
skl->init_failed = 1;
skl_free(ebus);
@ -525,15 +590,26 @@ static void skl_remove(struct pci_dev *pci)
pci_dev_put(pci);
skl_platform_unregister(&pci->dev);
skl_free_dsp(skl);
skl_machine_device_unregister(skl);
skl_dmic_device_unregister(skl);
skl_free(ebus);
dev_set_drvdata(&pci->dev, NULL);
}
static struct sst_acpi_mach sst_skl_devdata[] = {
{ "INT343A", "skl_alc286s_i2s", "intel/dsp_fw_release.bin", NULL, NULL, NULL },
{ "INT343B", "skl_nau88l25_ssm4567_i2s", "intel/dsp_fw_release.bin",
NULL, NULL, NULL },
{ "MX98357A", "skl_nau88l25_max98357a_i2s", "intel/dsp_fw_release.bin",
NULL, NULL, NULL },
{}
};
/* PCI IDs */
static const struct pci_device_id skl_ids[] = {
/* Sunrise Point-LP */
{ PCI_DEVICE(0x8086, 0x9d70), 0},
{ PCI_DEVICE(0x8086, 0x9d70),
.driver_data = (unsigned long)&sst_skl_devdata},
{ 0, }
};
MODULE_DEVICE_TABLE(pci, skl_ids);

View File

@ -61,13 +61,17 @@ struct skl {
unsigned int init_failed:1; /* delayed init failed */
struct platform_device *dmic_dev;
struct platform_device *i2s_dev;
void *nhlt; /* nhlt ptr */
struct skl_sst *skl_sst; /* sst skl ctx */
struct skl_dsp_resource resource;
struct list_head ppl_list;
struct list_head dapm_path_list;
const char *fw_name;
int supend_active;
};
#define skl_to_ebus(s) (&(s)->ebus)