提交 7b2f32cc 编写于 作者: M Mark Brown

Merge remote-tracking branch 'asoc/topic/intel' into asoc-next

......@@ -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,
};
......
......@@ -233,6 +233,15 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
#define AZX_MLCTL_SPA (1<<16)
#define AZX_MLCTL_CPA 23
/* registers for DMA Resume Capability Structure */
#define AZX_DRSM_CAP_ID 0x5
#define AZX_REG_DRSM_CTL 0x4
/* Base used to calculate the iterating register offset */
#define AZX_DRSM_BASE 0x08
/* Interval used to calculate the iterating register offset */
#define AZX_DRSM_INTERVAL 0x08
/*
* helpers to read the stream position
*/
......
......@@ -12,6 +12,7 @@
* @spbcap: SPIB capabilities pointer
* @mlcap: MultiLink capabilities pointer
* @gtscap: gts capabilities pointer
* @drsmcap: dma resume capabilities pointer
* @hlink_list: link list of HDA links
*/
struct hdac_ext_bus {
......@@ -23,6 +24,7 @@ struct hdac_ext_bus {
void __iomem *spbcap;
void __iomem *mlcap;
void __iomem *gtscap;
void __iomem *drsmcap;
struct list_head hlink_list;
};
......@@ -72,6 +74,9 @@ enum hdac_ext_stream_type {
* @pplc_addr: processing pipe link stream pointer
* @spib_addr: software position in buffers stream pointer
* @fifo_addr: software position Max fifos stream pointer
* @dpibr_addr: DMA position in buffer resume pointer
* @dpib: DMA position in buffer
* @lpib: Linear position in buffer
* @decoupled: stream host and link is decoupled
* @link_locked: link is locked
* @link_prepared: link is prepared
......@@ -86,6 +91,10 @@ struct hdac_ext_stream {
void __iomem *spib_addr;
void __iomem *fifo_addr;
void __iomem *dpibr_addr;
u32 dpib;
u32 lpib;
bool decoupled:1;
bool link_locked:1;
bool link_prepared;
......@@ -116,6 +125,11 @@ int snd_hdac_ext_stream_set_spib(struct hdac_ext_bus *ebus,
struct hdac_ext_stream *stream, u32 value);
int snd_hdac_ext_stream_get_spbmaxfifo(struct hdac_ext_bus *ebus,
struct hdac_ext_stream *stream);
void snd_hdac_ext_stream_drsm_enable(struct hdac_ext_bus *ebus,
bool enable, int index);
int snd_hdac_ext_stream_set_dpibr(struct hdac_ext_bus *ebus,
struct hdac_ext_stream *stream, u32 value);
int snd_hdac_ext_stream_set_lpib(struct hdac_ext_stream *stream, u32 value);
void snd_hdac_ext_link_stream_start(struct hdac_ext_stream *hstream);
void snd_hdac_ext_link_stream_clear(struct hdac_ext_stream *hstream);
......@@ -133,6 +147,7 @@ struct hdac_ext_link {
int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *link);
int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *link);
int snd_hdac_ext_bus_link_power_up_all(struct hdac_ext_bus *ebus);
int snd_hdac_ext_bus_link_power_down_all(struct hdac_ext_bus *ebus);
void snd_hdac_ext_link_set_stream_id(struct hdac_ext_link *link,
int stream);
......@@ -186,9 +201,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))
/*
......
......@@ -49,6 +49,9 @@ struct device;
#define SND_SOC_DAPM_SIGGEN(wname) \
{ .id = snd_soc_dapm_siggen, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_SINK(wname) \
{ .id = snd_soc_dapm_sink, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_INPUT(wname) \
{ .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \
.num_kcontrols = 0, .reg = SND_SOC_NOPM }
......@@ -485,6 +488,7 @@ enum snd_soc_dapm_type {
snd_soc_dapm_aif_in, /* audio interface input */
snd_soc_dapm_aif_out, /* audio interface output */
snd_soc_dapm_siggen, /* signal generator */
snd_soc_dapm_sink,
snd_soc_dapm_dai_in, /* link to DAI structure */
snd_soc_dapm_dai_out,
snd_soc_dapm_dai_link, /* link between two DAI structures */
......
......@@ -1106,7 +1106,7 @@ struct snd_soc_card {
/* CPU <--> Codec DAI links */
struct snd_soc_dai_link *dai_link;
int num_links;
struct snd_soc_pcm_runtime *rtd;
struct list_head rtd_list;
int num_rtd;
/* optional codec specific configuration */
......@@ -1201,6 +1201,9 @@ struct snd_soc_pcm_runtime {
struct dentry *debugfs_dpcm_root;
struct dentry *debugfs_dpcm_state;
#endif
unsigned int num; /* 0-based and monotonic increasing */
struct list_head list; /* rtd list of the soc card */
};
/* mixer control */
......
......@@ -77,6 +77,12 @@ int snd_hdac_ext_bus_parse_capabilities(struct hdac_ext_bus *ebus)
ebus->spbcap = bus->remap_addr + offset;
break;
case AZX_DRSM_CAP_ID:
/* DMA resume capability found, handler function */
dev_dbg(bus->dev, "Found DRSM capability\n");
ebus->drsmcap = bus->remap_addr + offset;
break;
default:
dev_dbg(bus->dev, "Unknown capability %d\n", cur_cap);
break;
......@@ -240,7 +246,7 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable)
int mask = (1 << AZX_MLCTL_CPA);
udelay(3);
timeout = 50;
timeout = 150;
do {
val = readl(link->ml_addr + AZX_REG_ML_LCTL);
......@@ -281,6 +287,27 @@ int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *link)
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down);
/**
* snd_hdac_ext_bus_link_power_up_all -power up all hda link
* @ebus: HD-audio extended bus
*/
int snd_hdac_ext_bus_link_power_up_all(struct hdac_ext_bus *ebus)
{
struct hdac_ext_link *hlink = NULL;
int ret;
list_for_each_entry(hlink, &ebus->hlink_list, list) {
snd_hdac_updatel(hlink->ml_addr,
AZX_REG_ML_LCTL, 0, AZX_MLCTL_SPA);
ret = check_hdac_link_power_active(hlink, true);
if (ret < 0)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up_all);
/**
* snd_hdac_ext_bus_link_power_down_all -power down all hda link
* @ebus: HD-audio extended bus
......
......@@ -59,6 +59,10 @@ void snd_hdac_ext_stream_init(struct hdac_ext_bus *ebus,
AZX_SPB_MAXFIFO;
}
if (ebus->drsmcap)
stream->dpibr_addr = ebus->drsmcap + AZX_DRSM_BASE +
AZX_DRSM_INTERVAL * idx;
stream->decoupled = false;
snd_hdac_stream_init(bus, &stream->hstream, idx, direction, tag);
}
......@@ -107,6 +111,7 @@ void snd_hdac_stream_free_all(struct hdac_ext_bus *ebus)
while (!list_empty(&bus->stream_list)) {
s = list_first_entry(&bus->stream_list, struct hdac_stream, list);
stream = stream_to_hdac_ext_stream(s);
snd_hdac_ext_stream_decouple(ebus, stream, false);
list_del(&s->list);
kfree(stream);
}
......@@ -497,3 +502,70 @@ void snd_hdac_ext_stop_streams(struct hdac_ext_bus *ebus)
}
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stop_streams);
/**
* snd_hdac_ext_stream_drsm_enable - enable DMA resume for a stream
* @ebus: HD-audio ext core bus
* @enable: flag to enable/disable DRSM
* @index: stream index for which DRSM need to be enabled
*/
void snd_hdac_ext_stream_drsm_enable(struct hdac_ext_bus *ebus,
bool enable, int index)
{
u32 mask = 0;
u32 register_mask = 0;
struct hdac_bus *bus = &ebus->bus;
if (!ebus->drsmcap) {
dev_err(bus->dev, "Address of DRSM capability is NULL");
return;
}
mask |= (1 << index);
register_mask = readl(ebus->drsmcap + AZX_REG_SPB_SPBFCCTL);
mask |= register_mask;
if (enable)
snd_hdac_updatel(ebus->drsmcap, AZX_REG_DRSM_CTL, 0, mask);
else
snd_hdac_updatel(ebus->drsmcap, AZX_REG_DRSM_CTL, mask, 0);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_drsm_enable);
/**
* snd_hdac_ext_stream_set_dpibr - sets the dpibr value of a stream
* @ebus: HD-audio ext core bus
* @stream: hdac_ext_stream
* @value: dpib value to set
*/
int snd_hdac_ext_stream_set_dpibr(struct hdac_ext_bus *ebus,
struct hdac_ext_stream *stream, u32 value)
{
struct hdac_bus *bus = &ebus->bus;
if (!ebus->drsmcap) {
dev_err(bus->dev, "Address of DRSM capability is NULL");
return -EINVAL;
}
writel(value, stream->dpibr_addr);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_dpibr);
/**
* snd_hdac_ext_stream_set_lpib - sets the lpib value of a stream
* @ebus: HD-audio ext core bus
* @stream: hdac_ext_stream
* @value: lpib value to set
*/
int snd_hdac_ext_stream_set_lpib(struct hdac_ext_stream *stream, u32 value)
{
snd_hdac_stream_writel(&stream->hstream, SD_LPIB, value);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_lpib);
......@@ -66,6 +66,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
......@@ -468,6 +469,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
......
......@@ -59,6 +59,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
......@@ -254,6 +255,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
......
/*
* 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 {
struct list_head head;
hda_nid_t nid;
struct hdac_hdmi_cvt_params params;
};
struct hdac_hdmi_pin {
struct list_head head;
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 {
struct hdac_hdmi_dai_pin_map dai_map[3];
struct list_head pin_list;
struct list_head cvt_list;
int num_pin;
int num_cvt;
};
static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev)
{
struct hdac_device *hdac = dev_to_hdac_dev(dev);
return to_ehdac_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 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_priv *hdmi = edev->private_data;
struct hdac_hdmi_dai_pin_map *dai_map = &hdmi->dai_map[0];
struct hdac_hdmi_cvt *cvt;
struct hdac_hdmi_pin *pin;
if (list_empty(&hdmi->cvt_list) || list_empty(&hdmi->pin_list))
return -EINVAL;
/*
* 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
*/
cvt = list_first_entry(&hdmi->cvt_list, struct hdac_hdmi_cvt, head);
pin = list_first_entry(&hdmi->pin_list, struct hdac_hdmi_pin, head);
dai_map->dai_id = 0;
dai_map->pin = pin;
dai_map->cvt = cvt;
/* 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 0;
}
static int hdac_hdmi_add_cvt(struct hdac_ext_device *edev, hda_nid_t nid)
{
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_cvt *cvt;
cvt = kzalloc(sizeof(*cvt), GFP_KERNEL);
if (!cvt)
return -ENOMEM;
cvt->nid = nid;
list_add_tail(&cvt->head, &hdmi->cvt_list);
hdmi->num_cvt++;
return hdac_hdmi_query_cvt_params(&edev->hdac, cvt);
}
static int hdac_hdmi_add_pin(struct hdac_ext_device *edev, hda_nid_t nid)
{
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_pin *pin;
pin = kzalloc(sizeof(*pin), GFP_KERNEL);
if (!pin)
return -ENOMEM;
pin->nid = nid;
list_add_tail(&pin->head, &hdmi->pin_list);
hdmi->num_pin++;
return 0;
}
/*
* 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 ret;
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:
ret = hdac_hdmi_add_cvt(edev, nid);
if (ret < 0)
return ret;
break;
case AC_WID_PIN:
ret = hdac_hdmi_add_pin(edev, nid);
if (ret < 0)
return ret;
break;
}
}
hdac->end_nid = nid;
if (!hdmi->num_pin || !hdmi->num_cvt)
return -EIO;
return hdac_hdmi_init_dai_map(edev);
}
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);
INIT_LIST_HEAD(&hdmi_priv->pin_list);
INIT_LIST_HEAD(&hdmi_priv->cvt_list);
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)
{
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_pin *pin, *pin_next;
struct hdac_hdmi_cvt *cvt, *cvt_next;
snd_soc_unregister_codec(&edev->hdac.dev);
list_for_each_entry_safe(cvt, cvt_next, &hdmi->cvt_list, head) {
list_del(&cvt->head);
kfree(cvt);
}
list_for_each_entry_safe(pin, pin_next, &hdmi->pin_list, head) {
list_del(&pin->head);
kfree(pin);
}
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>");
......@@ -488,6 +488,18 @@ static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source,
return 0;
}
static int is_using_asrc(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink)
{
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(source->dapm);
struct rt5640_priv *rt5640 = snd_soc_codec_get_drvdata(codec);
if (!rt5640->asrc_en)
return 0;
return 1;
}
/* Digital Mixer */
static const struct snd_kcontrol_new rt5640_sto_adc_l_mix[] = {
SOC_DAPM_SINGLE("ADC1 Switch", RT5640_STO_ADC_MIXER,
......@@ -1059,6 +1071,20 @@ static int rt5640_hp_post_event(struct snd_soc_dapm_widget *w,
static const struct snd_soc_dapm_widget rt5640_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("PLL1", RT5640_PWR_ANLG2,
RT5640_PWR_PLL_BIT, 0, NULL, 0),
/* ASRC */
SND_SOC_DAPM_SUPPLY_S("Stereo Filter ASRC", 1, RT5640_ASRC_1,
15, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("I2S2 Filter ASRC", 1, RT5640_ASRC_1,
12, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5640_ASRC_1,
11, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("DMIC1 ASRC", 1, RT5640_ASRC_1,
9, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("DMIC2 ASRC", 1, RT5640_ASRC_1,
8, 0, NULL, 0),
/* Input Side */
/* micbias */
SND_SOC_DAPM_SUPPLY("LDO2", RT5640_PWR_ANLG1,
......@@ -1319,6 +1345,12 @@ static const struct snd_soc_dapm_widget rt5639_specific_dapm_widgets[] = {
};
static const struct snd_soc_dapm_route rt5640_dapm_routes[] = {
{ "I2S1", NULL, "Stereo Filter ASRC", is_using_asrc },
{ "I2S2", NULL, "I2S2 ASRC", is_using_asrc },
{ "I2S2", NULL, "I2S2 Filter ASRC", is_using_asrc },
{ "DMIC1", NULL, "DMIC1 ASRC", is_using_asrc },
{ "DMIC2", NULL, "DMIC2 ASRC", is_using_asrc },
{"IN1P", NULL, "LDO2"},
{"IN2P", NULL, "LDO2"},
{"IN3P", NULL, "LDO2"},
......@@ -1981,6 +2013,76 @@ int rt5640_dmic_enable(struct snd_soc_codec *codec,
}
EXPORT_SYMBOL_GPL(rt5640_dmic_enable);
int rt5640_sel_asrc_clk_src(struct snd_soc_codec *codec,
unsigned int filter_mask, unsigned int clk_src)
{
struct rt5640_priv *rt5640 = snd_soc_codec_get_drvdata(codec);
unsigned int asrc2_mask = 0;
unsigned int asrc2_value = 0;
switch (clk_src) {
case RT5640_CLK_SEL_SYS:
case RT5640_CLK_SEL_ASRC:
break;
default:
return -EINVAL;
}
if (!filter_mask)
return -EINVAL;
if (filter_mask & RT5640_DA_STEREO_FILTER) {
asrc2_mask |= RT5640_STO_DAC_M_MASK;
asrc2_value = (asrc2_value & ~RT5640_STO_DAC_M_MASK)
| (clk_src << RT5640_STO_DAC_M_SFT);
}
if (filter_mask & RT5640_DA_MONO_L_FILTER) {
asrc2_mask |= RT5640_MDA_L_M_MASK;
asrc2_value = (asrc2_value & ~RT5640_MDA_L_M_MASK)
| (clk_src << RT5640_MDA_L_M_SFT);
}
if (filter_mask & RT5640_DA_MONO_R_FILTER) {
asrc2_mask |= RT5640_MDA_R_M_MASK;
asrc2_value = (asrc2_value & ~RT5640_MDA_R_M_MASK)
| (clk_src << RT5640_MDA_R_M_SFT);
}
if (filter_mask & RT5640_AD_STEREO_FILTER) {
asrc2_mask |= RT5640_ADC_M_MASK;
asrc2_value = (asrc2_value & ~RT5640_ADC_M_MASK)
| (clk_src << RT5640_ADC_M_SFT);
}
if (filter_mask & RT5640_AD_MONO_L_FILTER) {
asrc2_mask |= RT5640_MAD_L_M_MASK;
asrc2_value = (asrc2_value & ~RT5640_MAD_L_M_MASK)
| (clk_src << RT5640_MAD_L_M_SFT);
}
if (filter_mask & RT5640_AD_MONO_R_FILTER) {
asrc2_mask |= RT5640_MAD_R_M_MASK;
asrc2_value = (asrc2_value & ~RT5640_MAD_R_M_MASK)
| (clk_src << RT5640_MAD_R_M_SFT);
}
snd_soc_update_bits(codec, RT5640_ASRC_2,
asrc2_mask, asrc2_value);
if (snd_soc_read(codec, RT5640_ASRC_2)) {
rt5640->asrc_en = true;
snd_soc_update_bits(codec, RT5640_JD_CTRL, 0x3, 0x3);
} else {
rt5640->asrc_en = false;
snd_soc_update_bits(codec, RT5640_JD_CTRL, 0x3, 0x0);
}
return 0;
}
EXPORT_SYMBOL_GPL(rt5640_sel_asrc_clk_src);
static int rt5640_probe(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
......@@ -2175,6 +2277,7 @@ static const struct acpi_device_id rt5640_acpi_match[] = {
{ "INT33CA", 0 },
{ "10EC5640", 0 },
{ "10EC5642", 0 },
{ "INTCCFFD", 0 },
{ },
};
MODULE_DEVICE_TABLE(acpi, rt5640_acpi_match);
......
......@@ -1033,6 +1033,10 @@
#define RT5640_DMIC_2_M_NOR (0x0 << 8)
#define RT5640_DMIC_2_M_ASYN (0x1 << 8)
/* ASRC clock source selection (0x84) */
#define RT5640_CLK_SEL_SYS (0x0)
#define RT5640_CLK_SEL_ASRC (0x1)
/* ASRC Control 2 (0x84) */
#define RT5640_MDA_L_M_MASK (0x1 << 15)
#define RT5640_MDA_L_M_SFT 15
......@@ -2079,6 +2083,16 @@ enum {
RT5640_DMIC2,
};
/* filter mask */
enum {
RT5640_DA_STEREO_FILTER = 0x1,
RT5640_DA_MONO_L_FILTER = (0x1 << 1),
RT5640_DA_MONO_R_FILTER = (0x1 << 2),
RT5640_AD_STEREO_FILTER = (0x1 << 3),
RT5640_AD_MONO_L_FILTER = (0x1 << 4),
RT5640_AD_MONO_R_FILTER = (0x1 << 5),
};
struct rt5640_priv {
struct snd_soc_codec *codec;
struct rt5640_platform_data pdata;
......@@ -2095,9 +2109,12 @@ struct rt5640_priv {
int pll_out;
bool hp_mute;
bool asrc_en;
};
int rt5640_dmic_enable(struct snd_soc_codec *codec,
bool dmic1_data_pin, bool dmic2_data_pin);
int rt5640_sel_asrc_clk_src(struct snd_soc_codec *codec,
unsigned int filter_mask, unsigned int clk_src);
#endif
......@@ -222,12 +222,15 @@ static int fsl_asoc_card_set_bias_level(struct snd_soc_card *card,
enum snd_soc_bias_level level)
{
struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
struct codec_priv *codec_priv = &priv->codec_priv;
struct device *dev = card->dev;
unsigned int pll_out;
int ret;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
codec_dai = rtd->codec_dai;
if (dapm->dev != codec_dai->dev)
return 0;
......
......@@ -69,13 +69,16 @@ static int imx_wm8962_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
struct imx_priv *priv = &card_priv;
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
struct device *dev = &priv->pdev->dev;
unsigned int pll_out;
int ret;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
codec_dai = rtd->codec_dai;
if (dapm->dev != codec_dai->dev)
return 0;
......@@ -135,12 +138,15 @@ static int imx_wm8962_set_bias_level(struct snd_soc_card *card,
static int imx_wm8962_late_probe(struct snd_soc_card *card)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
struct imx_priv *priv = &card_priv;
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
struct device *dev = &priv->pdev->dev;
int ret;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
codec_dai = rtd->codec_dai;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
data->clk_frequency, SND_SOC_CLOCK_IN);
if (ret < 0)
......
......@@ -45,7 +45,7 @@ static int asoc_simple_card_startup(struct snd_pcm_substream *substream)
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
struct simple_dai_props *dai_props =
&priv->dai_props[rtd - rtd->card->rtd];
&priv->dai_props[rtd->num];
int ret;
ret = clk_prepare_enable(dai_props->cpu_dai.clk);
......@@ -64,7 +64,7 @@ static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream)
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
struct simple_dai_props *dai_props =
&priv->dai_props[rtd - rtd->card->rtd];
&priv->dai_props[rtd->num];
clk_disable_unprepare(dai_props->cpu_dai.clk);
......@@ -78,8 +78,7 @@ static int asoc_simple_card_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
struct simple_dai_props *dai_props =
&priv->dai_props[rtd - rtd->card->rtd];
struct simple_dai_props *dai_props = &priv->dai_props[rtd->num];
unsigned int mclk, mclk_fs = 0;
int ret = 0;
......@@ -174,10 +173,9 @@ static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd)
struct snd_soc_dai *codec = rtd->codec_dai;
struct snd_soc_dai *cpu = rtd->cpu_dai;
struct simple_dai_props *dai_props;
int num, ret;
int ret;
num = rtd - rtd->card->rtd;
dai_props = &priv->dai_props[num];
dai_props = &priv->dai_props[rtd->num];
ret = __asoc_simple_card_dai_init(codec, &dai_props->codec_dai);
if (ret < 0)
return ret;
......
......@@ -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,26 @@ 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".
config SND_SOC_INTEL_BYTCR_RT5651_MACH
tristate "ASoC Audio driver for Intel Baytrail and Baytrail-CR with RT5651 codec"
depends on X86 && I2C
select SND_SOC_RT5651
select SND_SST_MFLD_PLATFORM
select SND_SST_IPC_ACPI
help
This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR
platforms with RT5651 audio codec.
Say Y if you have such a device
If unsure select "N".
......@@ -154,3 +168,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".
......@@ -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"},
......
......@@ -28,6 +28,7 @@
enum {
MERR_DPCM_AUDIO = 0,
MERR_DPCM_DEEP_BUFFER,
MERR_DPCM_COMPR,
};
......
......@@ -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 */
......@@ -760,15 +768,15 @@ static int sst_platform_remove(struct platform_device *pdev)
static int sst_soc_prepare(struct device *dev)
{
struct sst_data *drv = dev_get_drvdata(dev);
int i;
struct snd_soc_pcm_runtime *rtd;
/* suspend all pcms first */
snd_soc_suspend(drv->soc_card->dev);
snd_soc_poweroff(drv->soc_card->dev);
/* set the SSPs to idle */
for (i = 0; i < drv->soc_card->num_rtd; i++) {
struct snd_soc_dai *dai = drv->soc_card->rtd[i].cpu_dai;
list_for_each_entry(rtd, &drv->soc_card->rtd_list, list) {
struct snd_soc_dai *dai = rtd->cpu_dai;
if (dai->active) {
send_ssp_cmd(dai, dai->name, 0);
......@@ -782,11 +790,11 @@ static int sst_soc_prepare(struct device *dev)
static void sst_soc_complete(struct device *dev)
{
struct sst_data *drv = dev_get_drvdata(dev);
int i;
struct snd_soc_pcm_runtime *rtd;
/* restart SSPs */
for (i = 0; i < drv->soc_card->num_rtd; i++) {
struct snd_soc_dai *dai = drv->soc_card->rtd[i].cpu_dai;
list_for_each_entry(rtd, &drv->soc_card->rtd_list, list) {
struct snd_soc_dai *dai = rtd->cpu_dai;
if (dai->active) {
sst_handle_vb_timer(dai, true);
......
......@@ -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,23 @@ 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);
/*
* Create platform device for sst machine driver,
* pass machine info as pdata
*/
mdev = platform_device_register_data(dev, mach->drv_name, -1,
(const void *)mach, sizeof(*mach));
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 +272,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 +320,28 @@ 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 },
{"10EC5642", "bytcr_rt5640", "intel/fw_sst_0f28.bin", "bytcr_rt5640", NULL,
&byt_rvp_platform_data },
{"INTCCFFD", "bytcr_rt5640", "intel/fw_sst_0f28.bin", "bytcr_rt5640", NULL,
&byt_rvp_platform_data },
{"10EC5651", "bytcr_rt5651", "intel/fw_sst_0f28.bin", "bytcr_rt5651", 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-rt5645", "intel/fw_sst_22a8.bin", "cht-bsw", NULL,
&chv_platform_data },
{"10EC5645", "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 },
{"10EC5650", "cht-bsw", "cht-bsw-rt5645", NULL, "intel/fw_sst_22a8.bin",
{"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 },
{},
};
......
......@@ -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);
......
......@@ -3,17 +3,23 @@ snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o
snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o
snd-soc-sst-broadwell-objs := broadwell.o
snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o
snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o
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
obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o
obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o
obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o
obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o
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
......@@ -20,51 +20,76 @@
#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"
#include "../common/sst-acpi.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_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,
};
static const struct snd_kcontrol_new byt_mc_controls[] = {
#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 +117,95 @@ 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),
},
{
.callback = byt_rt5640_quirk_cb,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP ElitePad 1000 G2"),
},
.driver_data = (unsigned long *)BYT_RT5640_IN1_MAP,
},
{}
};
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;
rt5640_sel_asrc_clk_src(codec,
RT5640_DA_STEREO_FILTER |
RT5640_AD_STEREO_FILTER,
RT5640_CLK_SEL_ASRC);
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 +213,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 +228,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 +279,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",
......@@ -161,58 +310,69 @@ static struct snd_soc_dai_link byt_dailink[] = {
.platform_name = "sst-mfld-platform",
.no_pcm = 1,
.codec_dai_name = "rt5640-aif1",
.codec_name = "i2c-10EC5640:00",
.codec_name = "i2c-10EC5640:00", /* overwritten with HID */
.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 char byt_rt5640_codec_name[16]; /* i2c-<HID>:00 with HID being 8 chars */
static int snd_byt_rt5640_mc_probe(struct platform_device *pdev)
{
int ret_val = 0;
struct sst_acpi_mach *mach;
/* register the soc card */
snd_soc_card_byt.dev = &pdev->dev;
byt_rt5640_card.dev = &pdev->dev;
mach = byt_rt5640_card.dev->platform_data;
/* fixup codec name based on HID */
snprintf(byt_rt5640_codec_name, sizeof(byt_rt5640_codec_name),
"%s%s%s", "i2c-", mach->id, ":00");
byt_rt5640_dais[MERR_DPCM_COMPR+1].codec_name = byt_rt5640_codec_name;
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");
/*
* bytcr_rt5651.c - ASoc Machine driver for Intel Byt CR platform
* (derived from bytcr_rt5640.c)
*
* Copyright (C) 2015 Intel Corp
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "../../codecs/rt5651.h"
#include "../atom/sst-atom-controls.h"
static const struct snd_soc_dapm_widget byt_rt5651_widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Internal Mic", NULL),
SND_SOC_DAPM_SPK("Speaker", NULL),
};
static const struct snd_soc_dapm_route byt_rt5651_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"}, /* lowercase for rt5651 */
{"IN2P", NULL, "Headset Mic"},
{"Headphone", NULL, "HPOL"},
{"Headphone", NULL, "HPOR"},
{"Speaker", NULL, "LOUTL"},
{"Speaker", NULL, "LOUTR"},
};
static const struct snd_soc_dapm_route byt_rt5651_intmic_dmic1_map[] = {
{"DMIC1", NULL, "Internal Mic"},
};
static const struct snd_soc_dapm_route byt_rt5651_intmic_dmic2_map[] = {
{"DMIC2", NULL, "Internal Mic"},
};
static const struct snd_soc_dapm_route byt_rt5651_intmic_in1_map[] = {
{"Internal Mic", NULL, "micbias1"},
{"IN1P", NULL, "Internal Mic"},
};
enum {
BYT_RT5651_DMIC1_MAP,
BYT_RT5651_DMIC2_MAP,
BYT_RT5651_IN1_MAP,
};
#define BYT_RT5651_MAP(quirk) ((quirk) & 0xff)
#define BYT_RT5651_DMIC_EN BIT(16)
static unsigned long byt_rt5651_quirk = BYT_RT5651_DMIC1_MAP |
BYT_RT5651_DMIC_EN;
static const struct snd_kcontrol_new byt_rt5651_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Internal Mic"),
SOC_DAPM_PIN_SWITCH("Speaker"),
};
static int byt_rt5651_aif1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
snd_soc_dai_set_bclk_ratio(codec_dai, 50);
ret = snd_soc_dai_set_sysclk(codec_dai, RT5651_SCLK_S_PLL1,
params_rate(params) * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec clock %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, 0, RT5651_PLL1_S_BCLK1,
params_rate(params) * 50,
params_rate(params) * 512);
if (ret < 0) {
dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
return ret;
}
return 0;
}
static const struct dmi_system_id byt_rt5651_quirk_table[] = {
{}
};
static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime)
{
int ret;
struct snd_soc_card *card = runtime->card;
const struct snd_soc_dapm_route *custom_map;
int num_routes;
card->dapm.idle_bias_off = true;
dmi_check_system(byt_rt5651_quirk_table);
switch (BYT_RT5651_MAP(byt_rt5651_quirk)) {
case BYT_RT5651_IN1_MAP:
custom_map = byt_rt5651_intmic_in1_map;
num_routes = ARRAY_SIZE(byt_rt5651_intmic_in1_map);
break;
case BYT_RT5651_DMIC2_MAP:
custom_map = byt_rt5651_intmic_dmic2_map;
num_routes = ARRAY_SIZE(byt_rt5651_intmic_dmic2_map);
break;
default:
custom_map = byt_rt5651_intmic_dmic1_map;
num_routes = ARRAY_SIZE(byt_rt5651_intmic_dmic1_map);
}
ret = snd_soc_add_card_controls(card, byt_rt5651_controls,
ARRAY_SIZE(byt_rt5651_controls));
if (ret) {
dev_err(card->dev, "unable to add card controls\n");
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_rt5651_dai_params = {
.formats = SNDRV_PCM_FMTBIT_S24_LE,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
};
static int byt_rt5651_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;
channels->min = channels->max = 2;
/* 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 unsigned int rates_48000[] = {
48000,
};
static struct snd_pcm_hw_constraint_list constraints_48000 = {
.count = ARRAY_SIZE(rates_48000),
.list = rates_48000,
};
static int byt_rt5651_aif1_startup(struct snd_pcm_substream *substream)
{
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_48000);
}
static struct snd_soc_ops byt_rt5651_aif1_ops = {
.startup = byt_rt5651_aif1_startup,
};
static struct snd_soc_ops byt_rt5651_be_ssp2_ops = {
.hw_params = byt_rt5651_aif1_hw_params,
};
static struct snd_soc_dai_link byt_rt5651_dais[] = {
[MERR_DPCM_AUDIO] = {
.name = "Audio Port",
.stream_name = "Audio",
.cpu_dai_name = "media-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
.ignore_suspend = 1,
.nonatomic = true,
.dynamic = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &byt_rt5651_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_rt5651_aif1_ops,
},
[MERR_DPCM_COMPR] = {
.name = "Compressed Port",
.stream_name = "Compress",
.cpu_dai_name = "compress-cpu-dai",
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.platform_name = "sst-mfld-platform",
},
/* CODEC<->CODEC link */
/* back ends */
{
.name = "SSP2-Codec",
.be_id = 1,
.cpu_dai_name = "ssp2-port",
.platform_name = "sst-mfld-platform",
.no_pcm = 1,
.codec_dai_name = "rt5651-aif1",
.codec_name = "i2c-10EC5651:00",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.be_hw_params_fixup = byt_rt5651_codec_fixup,
.ignore_suspend = 1,
.nonatomic = true,
.dpcm_playback = 1,
.dpcm_capture = 1,
.init = byt_rt5651_init,
.ops = &byt_rt5651_be_ssp2_ops,
},
};
/* SoC card */
static struct snd_soc_card byt_rt5651_card = {
.name = "bytcr-rt5651",
.owner = THIS_MODULE,
.dai_link = byt_rt5651_dais,
.num_links = ARRAY_SIZE(byt_rt5651_dais),
.dapm_widgets = byt_rt5651_widgets,
.num_dapm_widgets = ARRAY_SIZE(byt_rt5651_widgets),
.dapm_routes = byt_rt5651_audio_map,
.num_dapm_routes = ARRAY_SIZE(byt_rt5651_audio_map),
.fully_routed = true,
};
static int snd_byt_rt5651_mc_probe(struct platform_device *pdev)
{
int ret_val = 0;
/* register the soc card */
byt_rt5651_card.dev = &pdev->dev;
ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5651_card);
if (ret_val) {
dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n",
ret_val);
return ret_val;
}
platform_set_drvdata(pdev, &byt_rt5651_card);
return ret_val;
}
static struct platform_driver snd_byt_rt5651_mc_driver = {
.driver = {
.name = "bytcr_rt5651",
.pm = &snd_soc_pm_ops,
},
.probe = snd_byt_rt5651_mc_probe,
};
module_platform_driver(snd_byt_rt5651_mc_driver);
MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver for RT5651");
MODULE_AUTHOR("Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:bytcr_rt5651");
......@@ -41,12 +41,9 @@ struct cht_mc_private {
static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card)
{
int i;
struct snd_soc_pcm_runtime *rtd;
for (i = 0; i < card->num_rtd; i++) {
struct snd_soc_pcm_runtime *rtd;
rtd = card->rtd + i;
list_for_each_entry(rtd, &card->rtd_list, list) {
if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI,
strlen(CHT_CODEC_DAI)))
return rtd->codec_dai;
......@@ -235,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",
......
......@@ -47,12 +47,9 @@ struct cht_mc_private {
static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card)
{
int i;
for (i = 0; i < card->num_rtd; i++) {
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_pcm_runtime *rtd;
rtd = card->rtd + i;
list_for_each_entry(rtd, &card->rtd_list, list) {
if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI,
strlen(CHT_CODEC_DAI)))
return rtd->codec_dai;
......@@ -263,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",
......
......@@ -46,12 +46,9 @@ static struct snd_soc_jack_pin cht_bsw_headset_pins[] = {
static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card)
{
int i;
struct snd_soc_pcm_runtime *rtd;
for (i = 0; i < card->num_rtd; i++) {
struct snd_soc_pcm_runtime *rtd;
rtd = card->rtd + i;
list_for_each_entry(rtd, &card->rtd_list, list) {
if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI,
strlen(CHT_CODEC_DAI)))
return rtd->codec_dai;
......@@ -251,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",
......
/*
* 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");
/*
* 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");
......@@ -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,
},
};
......
snd-soc-sst-dsp-objs := sst-dsp.o
snd-soc-sst-acpi-objs := sst-acpi.o
ifneq ($(CONFIG_SND_SST_IPC_ACPI),)
snd-soc-sst-acpi-objs := sst-match-acpi.o
else
snd-soc-sst-acpi-objs := sst-acpi.o sst-match-acpi.o
endif
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
......@@ -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 },
{}
};
......
/*
* 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;
};
......@@ -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 */
......
......@@ -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)
{
......
......@@ -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);
......
......@@ -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;
......
/*
* 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);
......@@ -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,
};
......
......@@ -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);
......
......@@ -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);
}
......@@ -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;
}
......
......@@ -25,9 +25,12 @@
#include <sound/soc.h>
#include "skl.h"
#include "skl-topology.h"
#include "skl-sst-dsp.h"
#include "skl-sst-ipc.h"
#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 |
......@@ -35,16 +38,20 @@ static struct snd_pcm_hardware azx_pcm_hw = {
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_SYNC_START |
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 +112,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 +144,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 +174,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 +214,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",
......@@ -250,6 +275,7 @@ static void skl_pcm_close(struct snd_pcm_substream *substream,
struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
struct hdac_ext_bus *ebus = dev_get_drvdata(dai->dev);
struct skl_dma_params *dma_params = NULL;
struct skl *skl = ebus_to_skl(ebus);
dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
......@@ -261,9 +287,18 @@ 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);
/*
* check if close is for "Reference Pin" and set back the
* CGCTL.MISCBDCGE if disabled by driver
*/
if (!strncmp(dai->name, "Reference Pin", 13) &&
skl->skl_sst->miscbdcg_disabled) {
skl->skl_sst->enable_miscbdcge(dai->dev, true);
skl->skl_sst->miscbdcg_disabled = false;
}
pm_runtime_mark_last_busy(dai->dev);
pm_runtime_put_autosuspend(dai->dev);
kfree(dma_params);
}
......@@ -291,7 +326,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 +383,72 @@ 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);
/*
* enable DMA Resume enable bit for the stream, set the dpib
* & lpib position to resune before starting the DMA
*/
snd_hdac_ext_stream_drsm_enable(ebus, true,
hdac_stream(stream)->index);
snd_hdac_ext_stream_set_dpibr(ebus, stream, stream->dpib);
snd_hdac_ext_stream_set_lpib(stream, stream->lpib);
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) {
/* save the dpib and lpib positions */
stream->dpib = readl(ebus->bus.remap_addr +
AZX_REG_VS_SDXDPIB_XBASE +
(AZX_REG_VS_SDXDPIB_XINTERVAL *
hdac_stream(stream)->index));
stream->lpib = snd_hdac_stream_get_pos_lpib(
hdac_stream(stream));
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 +482,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,
......@@ -369,11 +497,6 @@ static int skl_link_pcm_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct hdac_ext_link *link;
if (link_dev->link_prepared) {
dev_dbg(dai->dev, "already stream is prepared - returning\n");
return 0;
}
dma_params = (struct skl_dma_params *)
snd_soc_dai_get_dma_data(codec_dai, substream);
if (dma_params)
......@@ -381,14 +504,15 @@ static int skl_link_pcm_prepare(struct snd_pcm_substream *substream,
dev_dbg(dai->dev, "stream_tag=%d formatvalue=%d codec_dai_name=%s\n",
hdac_stream(link_dev)->stream_tag, format_val, codec_dai->name);
snd_hdac_ext_link_stream_reset(link_dev);
snd_hdac_ext_link_stream_setup(link_dev, format_val);
link = snd_hdac_ext_bus_get_link(ebus, rtd->codec->component.name);
if (!link)
return -EINVAL;
snd_hdac_ext_bus_link_power_up(link);
snd_hdac_ext_link_stream_reset(link_dev);
snd_hdac_ext_link_stream_setup(link_dev, format_val);
snd_hdac_ext_link_set_stream_id(link, hdac_stream(link_dev)->stream_tag);
link_dev->link_prepared = 1;
......@@ -400,12 +524,16 @@ static int skl_link_pcm_trigger(struct snd_pcm_substream *substream,
{
struct hdac_ext_stream *link_dev =
snd_soc_dai_get_dma_data(dai, substream);
struct hdac_ext_bus *ebus = get_bus_ctx(substream);
struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
skl_link_pcm_prepare(substream, dai);
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
snd_hdac_ext_stream_decouple(ebus, stream, true);
snd_hdac_ext_link_stream_start(link_dev);
break;
......@@ -413,6 +541,8 @@ static int skl_link_pcm_trigger(struct snd_pcm_substream *substream,
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
snd_hdac_ext_link_stream_clear(link_dev);
if (cmd == SNDRV_PCM_TRIGGER_SUSPEND)
snd_hdac_ext_stream_decouple(ebus, stream, false);
break;
default:
......@@ -443,19 +573,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 +583,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 +622,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 +649,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 +680,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 +714,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 +829,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 +879,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 +890,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 +1032,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) {
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -81,8 +81,12 @@ static int rear_amp_power(struct snd_soc_codec *codec, int power)
static int rear_amp_event(struct snd_soc_dapm_widget *widget,
struct snd_kcontrol *kctl, int event)
{
struct snd_soc_codec *codec = widget->dapm->card->rtd[0].codec;
struct snd_soc_card *card = widget->dapm->card;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_codec *codec;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
codec = rtd->codec;
return rear_amp_power(codec, SND_SOC_DAPM_EVENT_ON(event));
}
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册