Merge remote-tracking branches 'asoc/topic/omap', 'asoc/topic/qcom', 'asoc/topic/rcar' and 'asoc/topic/rt286' into asoc-next
This commit is contained in:
commit
1fa1e0731d
29 changed files with 3187 additions and 529 deletions
43
Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt
Normal file
43
Documentation/devicetree/bindings/sound/qcom,lpass-cpu.txt
Normal file
|
@ -0,0 +1,43 @@
|
|||
* Qualcomm Technologies LPASS CPU DAI
|
||||
|
||||
This node models the Qualcomm Technologies Low-Power Audio SubSystem (LPASS).
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "qcom,lpass-cpu"
|
||||
- clocks : Must contain an entry for each entry in clock-names.
|
||||
- clock-names : A list which must include the following entries:
|
||||
* "ahbix-clk"
|
||||
* "mi2s-osr-clk"
|
||||
* "mi2s-bit-clk"
|
||||
- interrupts : Must contain an entry for each entry in
|
||||
interrupt-names.
|
||||
- interrupt-names : A list which must include the following entries:
|
||||
* "lpass-irq-lpaif"
|
||||
- pinctrl-N : One property must exist for each entry in
|
||||
pinctrl-names. See ../pinctrl/pinctrl-bindings.txt
|
||||
for details of the property values.
|
||||
- pinctrl-names : Must contain a "default" entry.
|
||||
- reg : Must contain an address for each entry in reg-names.
|
||||
- reg-names : A list which must include the following entries:
|
||||
* "lpass-lpaif"
|
||||
|
||||
Optional properties:
|
||||
|
||||
- qcom,adsp : Phandle for the audio DSP node
|
||||
|
||||
Example:
|
||||
|
||||
lpass@28100000 {
|
||||
compatible = "qcom,lpass-cpu";
|
||||
clocks = <&lcc AHBIX_CLK>, <&lcc MI2S_OSR_CLK>, <&lcc MI2S_BIT_CLK>;
|
||||
clock-names = "ahbix-clk", "mi2s-osr-clk", "mi2s-bit-clk";
|
||||
interrupts = <0 85 1>;
|
||||
interrupt-names = "lpass-irq-lpaif";
|
||||
pinctrl-names = "default", "idle";
|
||||
pinctrl-0 = <&mi2s_default>;
|
||||
pinctrl-1 = <&mi2s_idle>;
|
||||
reg = <0x28100000 0x10000>;
|
||||
reg-names = "lpass-lpaif";
|
||||
qcom,adsp = <&adsp>;
|
||||
};
|
|
@ -29,9 +29,17 @@ SSI subnode properties:
|
|||
- shared-pin : if shared clock pin
|
||||
- pio-transfer : use PIO transfer mode
|
||||
- no-busif : BUSIF is not ussed when [mem -> SSI] via DMA case
|
||||
- dma : Should contain Audio DMAC entry
|
||||
- dma-names : SSI case "rx" (=playback), "tx" (=capture)
|
||||
SSIU case "rxu" (=playback), "txu" (=capture)
|
||||
|
||||
SRC subnode properties:
|
||||
no properties at this point
|
||||
- dma : Should contain Audio DMAC entry
|
||||
- dma-names : "rx" (=playback), "tx" (=capture)
|
||||
|
||||
DVC subnode properties:
|
||||
- dma : Should contain Audio DMAC entry
|
||||
- dma-names : "tx" (=playback/capture)
|
||||
|
||||
DAI subnode properties:
|
||||
- playback : list of playback modules
|
||||
|
@ -45,56 +53,145 @@ rcar_sound: rcar_sound@ec500000 {
|
|||
reg = <0 0xec500000 0 0x1000>, /* SCU */
|
||||
<0 0xec5a0000 0 0x100>, /* ADG */
|
||||
<0 0xec540000 0 0x1000>, /* SSIU */
|
||||
<0 0xec541000 0 0x1280>; /* SSI */
|
||||
<0 0xec541000 0 0x1280>, /* SSI */
|
||||
<0 0xec740000 0 0x200>; /* Audio DMAC peri peri*/
|
||||
reg-names = "scu", "adg", "ssiu", "ssi", "audmapp";
|
||||
|
||||
clocks = <&mstp10_clks R8A7790_CLK_SSI_ALL>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI9>, <&mstp10_clks R8A7790_CLK_SSI8>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI7>, <&mstp10_clks R8A7790_CLK_SSI6>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI5>, <&mstp10_clks R8A7790_CLK_SSI4>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI3>, <&mstp10_clks R8A7790_CLK_SSI2>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI1>, <&mstp10_clks R8A7790_CLK_SSI0>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC9>, <&mstp10_clks R8A7790_CLK_SCU_SRC8>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC7>, <&mstp10_clks R8A7790_CLK_SCU_SRC6>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC5>, <&mstp10_clks R8A7790_CLK_SCU_SRC4>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC3>, <&mstp10_clks R8A7790_CLK_SCU_SRC2>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC1>, <&mstp10_clks R8A7790_CLK_SCU_SRC0>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_DVC0>, <&mstp10_clks R8A7790_CLK_SCU_DVC1>,
|
||||
<&audio_clk_a>, <&audio_clk_b>, <&audio_clk_c>, <&m2_clk>;
|
||||
clock-names = "ssi-all",
|
||||
"ssi.9", "ssi.8", "ssi.7", "ssi.6", "ssi.5",
|
||||
"ssi.4", "ssi.3", "ssi.2", "ssi.1", "ssi.0",
|
||||
"src.9", "src.8", "src.7", "src.6", "src.5",
|
||||
"src.4", "src.3", "src.2", "src.1", "src.0",
|
||||
"dvc.0", "dvc.1",
|
||||
"clk_a", "clk_b", "clk_c", "clk_i";
|
||||
|
||||
rcar_sound,dvc {
|
||||
dvc0: dvc@0 { };
|
||||
dvc1: dvc@1 { };
|
||||
dvc0: dvc@0 {
|
||||
dmas = <&audma0 0xbc>;
|
||||
dma-names = "tx";
|
||||
};
|
||||
dvc1: dvc@1 {
|
||||
dmas = <&audma0 0xbe>;
|
||||
dma-names = "tx";
|
||||
};
|
||||
};
|
||||
|
||||
rcar_sound,src {
|
||||
src0: src@0 { };
|
||||
src1: src@1 { };
|
||||
src2: src@2 { };
|
||||
src3: src@3 { };
|
||||
src4: src@4 { };
|
||||
src5: src@5 { };
|
||||
src6: src@6 { };
|
||||
src7: src@7 { };
|
||||
src8: src@8 { };
|
||||
src9: src@9 { };
|
||||
src0: src@0 {
|
||||
interrupts = <0 352 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x85>, <&audma1 0x9a>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src1: src@1 {
|
||||
interrupts = <0 353 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x87>, <&audma1 0x9c>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src2: src@2 {
|
||||
interrupts = <0 354 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x89>, <&audma1 0x9e>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src3: src@3 {
|
||||
interrupts = <0 355 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x8b>, <&audma1 0xa0>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src4: src@4 {
|
||||
interrupts = <0 356 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x8d>, <&audma1 0xb0>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src5: src@5 {
|
||||
interrupts = <0 357 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x8f>, <&audma1 0xb2>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src6: src@6 {
|
||||
interrupts = <0 358 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x91>, <&audma1 0xb4>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src7: src@7 {
|
||||
interrupts = <0 359 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x93>, <&audma1 0xb6>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src8: src@8 {
|
||||
interrupts = <0 360 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x95>, <&audma1 0xb8>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src9: src@9 {
|
||||
interrupts = <0 361 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x97>, <&audma1 0xba>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
};
|
||||
|
||||
rcar_sound,ssi {
|
||||
ssi0: ssi@0 {
|
||||
interrupts = <0 370 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x01>, <&audma1 0x02>, <&audma0 0x15>, <&audma1 0x16>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi1: ssi@1 {
|
||||
interrupts = <0 371 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x03>, <&audma1 0x04>, <&audma0 0x49>, <&audma1 0x4a>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi2: ssi@2 {
|
||||
interrupts = <0 372 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x05>, <&audma1 0x06>, <&audma0 0x63>, <&audma1 0x64>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi3: ssi@3 {
|
||||
interrupts = <0 373 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x07>, <&audma1 0x08>, <&audma0 0x6f>, <&audma1 0x70>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi4: ssi@4 {
|
||||
interrupts = <0 374 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x09>, <&audma1 0x0a>, <&audma0 0x71>, <&audma1 0x72>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi5: ssi@5 {
|
||||
interrupts = <0 375 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x0b>, <&audma1 0x0c>, <&audma0 0x73>, <&audma1 0x74>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi6: ssi@6 {
|
||||
interrupts = <0 376 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x0d>, <&audma1 0x0e>, <&audma0 0x75>, <&audma1 0x76>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi7: ssi@7 {
|
||||
interrupts = <0 377 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x0f>, <&audma1 0x10>, <&audma0 0x79>, <&audma1 0x7a>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi8: ssi@8 {
|
||||
interrupts = <0 378 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x11>, <&audma1 0x12>, <&audma0 0x7b>, <&audma1 0x7c>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi9: ssi@9 {
|
||||
interrupts = <0 379 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x13>, <&audma1 0x14>, <&audma0 0x7d>, <&audma1 0x7e>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
Renesas Sampling Rate Convert Sound Card:
|
||||
|
||||
Renesas Sampling Rate Convert Sound Card specifies audio DAI connections of SoC <-> codec.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "renesas,rsrc-card,<board>"
|
||||
Examples with soctypes are:
|
||||
- "renesas,rsrc-card,lager"
|
||||
- "renesas,rsrc-card,koelsch"
|
||||
Optional properties:
|
||||
|
||||
- card_name : User specified audio sound card name, one string
|
||||
property.
|
||||
- cpu : CPU sub-node
|
||||
- codec : CODEC sub-node
|
||||
|
||||
Optional subnode properties:
|
||||
|
||||
- format : CPU/CODEC common audio format.
|
||||
"i2s", "right_j", "left_j" , "dsp_a"
|
||||
"dsp_b", "ac97", "pdm", "msb", "lsb"
|
||||
- frame-master : Indicates dai-link frame master.
|
||||
phandle to a cpu or codec subnode.
|
||||
- bitclock-master : Indicates dai-link bit clock master.
|
||||
phandle to a cpu or codec subnode.
|
||||
- bitclock-inversion : bool property. Add this if the
|
||||
dai-link uses bit clock inversion.
|
||||
- frame-inversion : bool property. Add this if the
|
||||
dai-link uses frame clock inversion.
|
||||
- convert-rate : platform specified sampling rate convert
|
||||
|
||||
Required CPU/CODEC subnodes properties:
|
||||
|
||||
- sound-dai : phandle and port of CPU/CODEC
|
||||
|
||||
Optional CPU/CODEC subnodes properties:
|
||||
|
||||
- clocks / system-clock-frequency : specify subnode's clock if needed.
|
||||
it can be specified via "clocks" if system has
|
||||
clock node (= common clock), or "system-clock-frequency"
|
||||
(if system doens't support common clock)
|
||||
If a clock is specified, it is
|
||||
enabled with clk_prepare_enable()
|
||||
in dai startup() and disabled with
|
||||
clk_disable_unprepare() in dai
|
||||
shutdown().
|
||||
|
||||
Example
|
||||
|
||||
sound {
|
||||
compatible = "renesas,rsrc-card,lager";
|
||||
|
||||
card-name = "rsnd-ak4643";
|
||||
format = "left_j";
|
||||
bitclock-master = <&sndcodec>;
|
||||
frame-master = <&sndcodec>;
|
||||
|
||||
sndcpu: cpu {
|
||||
sound-dai = <&rcar_sound>;
|
||||
};
|
||||
|
||||
sndcodec: codec {
|
||||
sound-dai = <&ak4643>;
|
||||
system-clock-frequency = <11289600>;
|
||||
};
|
||||
};
|
23
Documentation/devicetree/bindings/sound/storm.txt
Normal file
23
Documentation/devicetree/bindings/sound/storm.txt
Normal file
|
@ -0,0 +1,23 @@
|
|||
* Sound complex for Storm boards
|
||||
|
||||
Models a soundcard for Storm boards with the Qualcomm Technologies IPQ806x SOC
|
||||
connected to a MAX98357A DAC via I2S.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "google,storm-audio"
|
||||
- cpu : Phandle of the CPU DAI
|
||||
- codec : Phandle of the codec DAI
|
||||
|
||||
Optional properties:
|
||||
|
||||
- qcom,model : The user-visible name of this sound card.
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "google,storm-audio";
|
||||
qcom,model = "ipq806x-storm";
|
||||
cpu = <&lpass_cpu>;
|
||||
codec = <&max98357a>;
|
||||
};
|
|
@ -5271,6 +5271,13 @@ F: drivers/char/ipmi/
|
|||
F: include/linux/ipmi*
|
||||
F: include/uapi/linux/ipmi*
|
||||
|
||||
QCOM AUDIO (ASoC) DRIVERS
|
||||
M: Patrick Lai <plai@codeaurora.org>
|
||||
M: Banajit Goswami <bgoswami@codeaurora.org>
|
||||
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||
S: Supported
|
||||
F: sound/soc/qcom/
|
||||
|
||||
IPS SCSI RAID DRIVER
|
||||
M: Adaptec OEM Raid Solutions <aacraid@adaptec.com>
|
||||
L: linux-scsi@vger.kernel.org
|
||||
|
|
|
@ -47,6 +47,7 @@ source "sound/soc/kirkwood/Kconfig"
|
|||
source "sound/soc/intel/Kconfig"
|
||||
source "sound/soc/mxs/Kconfig"
|
||||
source "sound/soc/pxa/Kconfig"
|
||||
source "sound/soc/qcom/Kconfig"
|
||||
source "sound/soc/rockchip/Kconfig"
|
||||
source "sound/soc/samsung/Kconfig"
|
||||
source "sound/soc/sh/Kconfig"
|
||||
|
|
|
@ -28,6 +28,7 @@ obj-$(CONFIG_SND_SOC) += nuc900/
|
|||
obj-$(CONFIG_SND_SOC) += omap/
|
||||
obj-$(CONFIG_SND_SOC) += kirkwood/
|
||||
obj-$(CONFIG_SND_SOC) += pxa/
|
||||
obj-$(CONFIG_SND_SOC) += qcom/
|
||||
obj-$(CONFIG_SND_SOC) += rockchip/
|
||||
obj-$(CONFIG_SND_SOC) += samsung/
|
||||
obj-$(CONFIG_SND_SOC) += sh/
|
||||
|
|
|
@ -397,7 +397,7 @@ int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack)
|
|||
|
||||
if (jack) {
|
||||
/* enable IRQ */
|
||||
if (rt286->jack->status | SND_JACK_HEADPHONE)
|
||||
if (rt286->jack->status & SND_JACK_HEADPHONE)
|
||||
snd_soc_dapm_force_enable_pin(&codec->dapm, "LDO1");
|
||||
regmap_update_bits(rt286->regmap, RT286_IRQ_CTRL, 0x2, 0x2);
|
||||
/* Send an initial empty report */
|
||||
|
@ -1048,7 +1048,6 @@ static int rt286_probe(struct snd_soc_codec *codec)
|
|||
struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
rt286->codec = codec;
|
||||
codec->dapm.bias_level = SND_SOC_BIAS_OFF;
|
||||
|
||||
if (rt286->i2c->irq) {
|
||||
regmap_update_bits(rt286->regmap,
|
||||
|
@ -1220,7 +1219,7 @@ static int rt286_i2c_probe(struct i2c_client *i2c,
|
|||
{
|
||||
struct rt286_platform_data *pdata = dev_get_platdata(&i2c->dev);
|
||||
struct rt286_priv *rt286;
|
||||
int i, ret;
|
||||
int i, ret, val;
|
||||
|
||||
rt286 = devm_kzalloc(&i2c->dev, sizeof(*rt286),
|
||||
GFP_KERNEL);
|
||||
|
@ -1235,11 +1234,15 @@ static int rt286_i2c_probe(struct i2c_client *i2c,
|
|||
return ret;
|
||||
}
|
||||
|
||||
regmap_read(rt286->regmap,
|
||||
RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &ret);
|
||||
if (ret != RT286_VENDOR_ID && ret != RT288_VENDOR_ID) {
|
||||
ret = regmap_read(rt286->regmap,
|
||||
RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &val);
|
||||
if (ret != 0) {
|
||||
dev_err(&i2c->dev, "I2C error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
if (val != RT286_VENDOR_ID && val != RT288_VENDOR_ID) {
|
||||
dev_err(&i2c->dev,
|
||||
"Device with ID register %x is not rt286\n", ret);
|
||||
"Device with ID register %x is not rt286\n", val);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
@ -1247,6 +1250,14 @@ static int rt286_i2c_probe(struct i2c_client *i2c,
|
|||
rt286->i2c = i2c;
|
||||
i2c_set_clientdata(i2c, rt286);
|
||||
|
||||
/* restore codec default */
|
||||
for (i = 0; i < INDEX_CACHE_SIZE; i++)
|
||||
regmap_write(rt286->regmap, rt286->index_cache[i].reg,
|
||||
rt286->index_cache[i].def);
|
||||
for (i = 0; i < ARRAY_SIZE(rt286_reg); i++)
|
||||
regmap_write(rt286->regmap, rt286_reg[i].reg,
|
||||
rt286_reg[i].def);
|
||||
|
||||
if (pdata)
|
||||
rt286->pdata = *pdata;
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ config SND_OMAP_SOC_OMAP_ABE_TWL6040
|
|||
select SND_OMAP_SOC_MCPDM
|
||||
select SND_SOC_TWL6040
|
||||
select SND_SOC_DMIC
|
||||
select COMMON_CLK_PALMAS if SOC_OMAP5
|
||||
select COMMON_CLK_PALMAS if MFD_PALMAS
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on OMAP boards using
|
||||
ABE and twl6040 codec. This driver currently supports:
|
||||
|
|
|
@ -98,12 +98,11 @@ static int n810_startup(struct snd_pcm_substream *substream)
|
|||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
|
||||
snd_pcm_hw_constraint_minmax(runtime,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
|
||||
|
||||
n810_ext_control(&codec->dapm);
|
||||
n810_ext_control(&rtd->card->dapm);
|
||||
return clk_prepare_enable(sys_clkout2);
|
||||
}
|
||||
|
||||
|
@ -255,24 +254,6 @@ static const struct snd_kcontrol_new aic33_n810_controls[] = {
|
|||
n810_get_input, n810_set_input),
|
||||
};
|
||||
|
||||
static int n810_aic33_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
|
||||
/* Not connected */
|
||||
snd_soc_dapm_nc_pin(dapm, "MONO_LOUT");
|
||||
snd_soc_dapm_nc_pin(dapm, "HPLCOM");
|
||||
snd_soc_dapm_nc_pin(dapm, "HPRCOM");
|
||||
snd_soc_dapm_nc_pin(dapm, "MIC3L");
|
||||
snd_soc_dapm_nc_pin(dapm, "MIC3R");
|
||||
snd_soc_dapm_nc_pin(dapm, "LINE1R");
|
||||
snd_soc_dapm_nc_pin(dapm, "LINE2L");
|
||||
snd_soc_dapm_nc_pin(dapm, "LINE2R");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link n810_dai = {
|
||||
.name = "TLV320AIC33",
|
||||
|
@ -283,7 +264,6 @@ static struct snd_soc_dai_link n810_dai = {
|
|||
.codec_dai_name = "tlv320aic3x-hifi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM,
|
||||
.init = n810_aic33_init,
|
||||
.ops = &n810_ops,
|
||||
};
|
||||
|
||||
|
@ -300,6 +280,7 @@ static struct snd_soc_card snd_soc_n810 = {
|
|||
.num_dapm_widgets = ARRAY_SIZE(aic33_dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
.fully_routed = true,
|
||||
};
|
||||
|
||||
static struct platform_device *n810_snd_device;
|
||||
|
|
|
@ -142,8 +142,6 @@ static int hdmi_dai_hw_params(struct snd_pcm_substream *substream,
|
|||
|
||||
iec->status[0] |= IEC958_AES0_CON_EMPHASIS_NONE;
|
||||
|
||||
iec->status[0] |= IEC958_AES1_PRO_MODE_NOTID;
|
||||
|
||||
iec->status[1] = IEC958_AES1_CON_GENERAL;
|
||||
|
||||
iec->status[2] |= IEC958_AES2_CON_SOURCE_UNSPEC;
|
||||
|
|
25
sound/soc/qcom/Kconfig
Normal file
25
sound/soc/qcom/Kconfig
Normal file
|
@ -0,0 +1,25 @@
|
|||
config SND_SOC_QCOM
|
||||
tristate "ASoC support for QCOM platforms"
|
||||
help
|
||||
Say Y or M if you want to add support to use audio devices
|
||||
in Qualcomm Technologies SOC-based platforms.
|
||||
|
||||
config SND_SOC_LPASS_CPU
|
||||
tristate
|
||||
depends on SND_SOC_QCOM
|
||||
select REGMAP_MMIO
|
||||
|
||||
config SND_SOC_LPASS_PLATFORM
|
||||
tristate
|
||||
depends on SND_SOC_QCOM
|
||||
select REGMAP_MMIO
|
||||
|
||||
config SND_SOC_STORM
|
||||
tristate "ASoC I2S support for Storm boards"
|
||||
depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST
|
||||
select SND_SOC_LPASS_CPU
|
||||
select SND_SOC_LPASS_PLATFORM
|
||||
select SND_SOC_MAX98357A
|
||||
help
|
||||
Say Y or M if you want add support for SoC audio on the
|
||||
Qualcomm Technologies IPQ806X-based Storm board.
|
11
sound/soc/qcom/Makefile
Normal file
11
sound/soc/qcom/Makefile
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Platform
|
||||
snd-soc-lpass-cpu-objs := lpass-cpu.o
|
||||
snd-soc-lpass-platform-objs := lpass-platform.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o
|
||||
obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o
|
||||
|
||||
# Machine
|
||||
snd-soc-storm-objs := storm.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o
|
491
sound/soc/qcom/lpass-cpu.c
Normal file
491
sound/soc/qcom/lpass-cpu.c
Normal file
|
@ -0,0 +1,491 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2011,2013-2015 The Linux Foundation. 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 and
|
||||
* only 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.
|
||||
*
|
||||
* lpass-cpu.c -- ALSA SoC CPU DAI driver for QTi LPASS
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dai.h>
|
||||
#include "lpass-lpaif-ipq806x.h"
|
||||
#include "lpass.h"
|
||||
|
||||
static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id,
|
||||
unsigned int freq, int dir)
|
||||
{
|
||||
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
|
||||
int ret;
|
||||
|
||||
ret = clk_set_rate(drvdata->mi2s_osr_clk, freq);
|
||||
if (ret)
|
||||
dev_err(dai->dev, "%s() error setting mi2s osrclk to %u: %d\n",
|
||||
__func__, freq, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(drvdata->mi2s_osr_clk);
|
||||
if (ret) {
|
||||
dev_err(dai->dev, "%s() error in enabling mi2s osr clk: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(drvdata->mi2s_bit_clk);
|
||||
if (ret) {
|
||||
dev_err(dai->dev, "%s() error in enabling mi2s bit clk: %d\n",
|
||||
__func__, ret);
|
||||
clk_disable_unprepare(drvdata->mi2s_osr_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
clk_disable_unprepare(drvdata->mi2s_bit_clk);
|
||||
clk_disable_unprepare(drvdata->mi2s_osr_clk);
|
||||
}
|
||||
|
||||
static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
|
||||
snd_pcm_format_t format = params_format(params);
|
||||
unsigned int channels = params_channels(params);
|
||||
unsigned int rate = params_rate(params);
|
||||
unsigned int regval;
|
||||
int bitwidth, ret;
|
||||
|
||||
bitwidth = snd_pcm_format_width(format);
|
||||
if (bitwidth < 0) {
|
||||
dev_err(dai->dev, "%s() invalid bit width given: %d\n",
|
||||
__func__, bitwidth);
|
||||
return bitwidth;
|
||||
}
|
||||
|
||||
regval = LPAIF_I2SCTL_LOOPBACK_DISABLE |
|
||||
LPAIF_I2SCTL_WSSRC_INTERNAL;
|
||||
|
||||
switch (bitwidth) {
|
||||
case 16:
|
||||
regval |= LPAIF_I2SCTL_BITWIDTH_16;
|
||||
break;
|
||||
case 24:
|
||||
regval |= LPAIF_I2SCTL_BITWIDTH_24;
|
||||
break;
|
||||
case 32:
|
||||
regval |= LPAIF_I2SCTL_BITWIDTH_32;
|
||||
break;
|
||||
default:
|
||||
dev_err(dai->dev, "%s() invalid bitwidth given: %d\n",
|
||||
__func__, bitwidth);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (channels) {
|
||||
case 1:
|
||||
regval |= LPAIF_I2SCTL_SPKMODE_SD0;
|
||||
regval |= LPAIF_I2SCTL_SPKMONO_MONO;
|
||||
break;
|
||||
case 2:
|
||||
regval |= LPAIF_I2SCTL_SPKMODE_SD0;
|
||||
regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
|
||||
break;
|
||||
case 4:
|
||||
regval |= LPAIF_I2SCTL_SPKMODE_QUAD01;
|
||||
regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
|
||||
break;
|
||||
case 6:
|
||||
regval |= LPAIF_I2SCTL_SPKMODE_6CH;
|
||||
regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
|
||||
break;
|
||||
case 8:
|
||||
regval |= LPAIF_I2SCTL_SPKMODE_8CH;
|
||||
regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
|
||||
break;
|
||||
default:
|
||||
dev_err(dai->dev, "%s() invalid channels given: %u\n",
|
||||
__func__, channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), regval);
|
||||
if (ret) {
|
||||
dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_set_rate(drvdata->mi2s_bit_clk, rate * bitwidth * 2);
|
||||
if (ret) {
|
||||
dev_err(dai->dev, "%s() error setting mi2s bitclk to %u: %d\n",
|
||||
__func__, rate * bitwidth * 2, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpass_cpu_daiops_hw_free(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), 0);
|
||||
if (ret)
|
||||
dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(drvdata->lpaif_map,
|
||||
LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S),
|
||||
LPAIF_I2SCTL_SPKEN_MASK, LPAIF_I2SCTL_SPKEN_ENABLE);
|
||||
if (ret)
|
||||
dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
|
||||
int ret;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ret = regmap_update_bits(drvdata->lpaif_map,
|
||||
LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S),
|
||||
LPAIF_I2SCTL_SPKEN_MASK,
|
||||
LPAIF_I2SCTL_SPKEN_ENABLE);
|
||||
if (ret)
|
||||
dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
|
||||
__func__, ret);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
ret = regmap_update_bits(drvdata->lpaif_map,
|
||||
LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S),
|
||||
LPAIF_I2SCTL_SPKEN_MASK,
|
||||
LPAIF_I2SCTL_SPKEN_DISABLE);
|
||||
if (ret)
|
||||
dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
|
||||
__func__, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops lpass_cpu_dai_ops = {
|
||||
.set_sysclk = lpass_cpu_daiops_set_sysclk,
|
||||
.startup = lpass_cpu_daiops_startup,
|
||||
.shutdown = lpass_cpu_daiops_shutdown,
|
||||
.hw_params = lpass_cpu_daiops_hw_params,
|
||||
.hw_free = lpass_cpu_daiops_hw_free,
|
||||
.prepare = lpass_cpu_daiops_prepare,
|
||||
.trigger = lpass_cpu_daiops_trigger,
|
||||
};
|
||||
|
||||
static int lpass_cpu_dai_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
|
||||
int ret;
|
||||
|
||||
/* ensure audio hardware is disabled */
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), 0);
|
||||
if (ret)
|
||||
dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_driver lpass_cpu_dai_driver = {
|
||||
.playback = {
|
||||
.stream_name = "lpass-cpu-playback",
|
||||
.formats = SNDRV_PCM_FMTBIT_S16 |
|
||||
SNDRV_PCM_FMTBIT_S24 |
|
||||
SNDRV_PCM_FMTBIT_S32,
|
||||
.rates = SNDRV_PCM_RATE_8000 |
|
||||
SNDRV_PCM_RATE_16000 |
|
||||
SNDRV_PCM_RATE_32000 |
|
||||
SNDRV_PCM_RATE_48000 |
|
||||
SNDRV_PCM_RATE_96000,
|
||||
.rate_min = 8000,
|
||||
.rate_max = 96000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 8,
|
||||
},
|
||||
.probe = &lpass_cpu_dai_probe,
|
||||
.ops = &lpass_cpu_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver lpass_cpu_comp_driver = {
|
||||
.name = "lpass-cpu",
|
||||
};
|
||||
|
||||
static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < LPAIF_I2S_PORT_NUM; ++i)
|
||||
if (reg == LPAIF_I2SCTL_REG(i))
|
||||
return true;
|
||||
|
||||
for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) {
|
||||
if (reg == LPAIF_IRQEN_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_IRQCLEAR_REG(i))
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) {
|
||||
if (reg == LPAIF_RDMACTL_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_RDMABASE_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_RDMABUFF_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_RDMAPER_REG(i))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < LPAIF_I2S_PORT_NUM; ++i)
|
||||
if (reg == LPAIF_I2SCTL_REG(i))
|
||||
return true;
|
||||
|
||||
for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) {
|
||||
if (reg == LPAIF_IRQEN_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_IRQSTAT_REG(i))
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) {
|
||||
if (reg == LPAIF_RDMACTL_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_RDMABASE_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_RDMABUFF_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_RDMACURR_REG(i))
|
||||
return true;
|
||||
if (reg == LPAIF_RDMAPER_REG(i))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i)
|
||||
if (reg == LPAIF_IRQSTAT_REG(i))
|
||||
return true;
|
||||
|
||||
for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i)
|
||||
if (reg == LPAIF_RDMACURR_REG(i))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static const struct regmap_config lpass_cpu_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.val_bits = 32,
|
||||
.max_register = LPAIF_RDMAPER_REG(LPAIF_RDMA_CHAN_MAX),
|
||||
.writeable_reg = lpass_cpu_regmap_writeable,
|
||||
.readable_reg = lpass_cpu_regmap_readable,
|
||||
.volatile_reg = lpass_cpu_regmap_volatile,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
};
|
||||
|
||||
static int lpass_cpu_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct lpass_data *drvdata;
|
||||
struct device_node *dsp_of_node;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0);
|
||||
if (dsp_of_node) {
|
||||
dev_err(&pdev->dev, "%s() DSP exists and holds audio resources\n",
|
||||
__func__);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_data),
|
||||
GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, drvdata);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif");
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "%s() error getting resource\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
drvdata->lpaif = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR((void const __force *)drvdata->lpaif)) {
|
||||
dev_err(&pdev->dev, "%s() error mapping reg resource: %ld\n",
|
||||
__func__,
|
||||
PTR_ERR((void const __force *)drvdata->lpaif));
|
||||
return PTR_ERR((void const __force *)drvdata->lpaif);
|
||||
}
|
||||
|
||||
drvdata->lpaif_map = devm_regmap_init_mmio(&pdev->dev, drvdata->lpaif,
|
||||
&lpass_cpu_regmap_config);
|
||||
if (IS_ERR(drvdata->lpaif_map)) {
|
||||
dev_err(&pdev->dev, "%s() error initializing regmap: %ld\n",
|
||||
__func__, PTR_ERR(drvdata->lpaif_map));
|
||||
return PTR_ERR(drvdata->lpaif_map);
|
||||
}
|
||||
|
||||
drvdata->mi2s_osr_clk = devm_clk_get(&pdev->dev, "mi2s-osr-clk");
|
||||
if (IS_ERR(drvdata->mi2s_osr_clk)) {
|
||||
dev_err(&pdev->dev, "%s() error getting mi2s-osr-clk: %ld\n",
|
||||
__func__, PTR_ERR(drvdata->mi2s_osr_clk));
|
||||
return PTR_ERR(drvdata->mi2s_osr_clk);
|
||||
}
|
||||
|
||||
drvdata->mi2s_bit_clk = devm_clk_get(&pdev->dev, "mi2s-bit-clk");
|
||||
if (IS_ERR(drvdata->mi2s_bit_clk)) {
|
||||
dev_err(&pdev->dev, "%s() error getting mi2s-bit-clk: %ld\n",
|
||||
__func__, PTR_ERR(drvdata->mi2s_bit_clk));
|
||||
return PTR_ERR(drvdata->mi2s_bit_clk);
|
||||
}
|
||||
|
||||
drvdata->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix-clk");
|
||||
if (IS_ERR(drvdata->ahbix_clk)) {
|
||||
dev_err(&pdev->dev, "%s() error getting ahbix-clk: %ld\n",
|
||||
__func__, PTR_ERR(drvdata->ahbix_clk));
|
||||
return PTR_ERR(drvdata->ahbix_clk);
|
||||
}
|
||||
|
||||
ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s() error setting rate on ahbix_clk: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
dev_dbg(&pdev->dev, "%s() set ahbix_clk rate to %lu\n", __func__,
|
||||
clk_get_rate(drvdata->ahbix_clk));
|
||||
|
||||
ret = clk_prepare_enable(drvdata->ahbix_clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s() error enabling ahbix_clk: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_component(&pdev->dev,
|
||||
&lpass_cpu_comp_driver, &lpass_cpu_dai_driver, 1);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s() error registering cpu driver: %d\n",
|
||||
__func__, ret);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
ret = asoc_qcom_lpass_platform_register(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s() error registering platform driver: %d\n",
|
||||
__func__, ret);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_clk:
|
||||
clk_disable_unprepare(drvdata->ahbix_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpass_cpu_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct lpass_data *drvdata = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable_unprepare(drvdata->ahbix_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id lpass_cpu_device_id[] = {
|
||||
{ .compatible = "qcom,lpass-cpu" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lpass_cpu_device_id);
|
||||
#endif
|
||||
|
||||
static struct platform_driver lpass_cpu_platform_driver = {
|
||||
.driver = {
|
||||
.name = "lpass-cpu",
|
||||
.of_match_table = of_match_ptr(lpass_cpu_device_id),
|
||||
},
|
||||
.probe = lpass_cpu_platform_probe,
|
||||
.remove = lpass_cpu_platform_remove,
|
||||
};
|
||||
module_platform_driver(lpass_cpu_platform_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QTi LPASS CPU Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
172
sound/soc/qcom/lpass-lpaif-ipq806x.h
Normal file
172
sound/soc/qcom/lpass-lpaif-ipq806x.h
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2011,2013-2015 The Linux Foundation. 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 and
|
||||
* only 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.
|
||||
*
|
||||
* lpass-lpaif-ipq806x.h -- Definitions for the QTi LPAIF in the ipq806x LPASS
|
||||
*/
|
||||
|
||||
#ifndef __LPASS_LPAIF_H__
|
||||
#define __LPASS_LPAIF_H__
|
||||
|
||||
#define LPAIF_BANK_OFFSET 0x1000
|
||||
|
||||
/* LPAIF I2S */
|
||||
|
||||
#define LPAIF_I2SCTL_REG_BASE 0x0010
|
||||
#define LPAIF_I2SCTL_REG_STRIDE 0x4
|
||||
#define LPAIF_I2SCTL_REG_ADDR(addr, port) \
|
||||
(LPAIF_I2SCTL_REG_BASE + (addr) + (LPAIF_I2SCTL_REG_STRIDE * (port)))
|
||||
|
||||
enum lpaif_i2s_ports {
|
||||
LPAIF_I2S_PORT_MIN = 0,
|
||||
|
||||
LPAIF_I2S_PORT_CODEC_SPK = 0,
|
||||
LPAIF_I2S_PORT_CODEC_MIC = 1,
|
||||
LPAIF_I2S_PORT_SEC_SPK = 2,
|
||||
LPAIF_I2S_PORT_SEC_MIC = 3,
|
||||
LPAIF_I2S_PORT_MI2S = 4,
|
||||
|
||||
LPAIF_I2S_PORT_MAX = 4,
|
||||
LPAIF_I2S_PORT_NUM = 5,
|
||||
};
|
||||
|
||||
#define LPAIF_I2SCTL_REG(port) LPAIF_I2SCTL_REG_ADDR(0x0, (port))
|
||||
|
||||
#define LPAIF_I2SCTL_LOOPBACK_MASK 0x8000
|
||||
#define LPAIF_I2SCTL_LOOPBACK_SHIFT 15
|
||||
#define LPAIF_I2SCTL_LOOPBACK_DISABLE (0 << LPAIF_I2SCTL_LOOPBACK_SHIFT)
|
||||
#define LPAIF_I2SCTL_LOOPBACK_ENABLE (1 << LPAIF_I2SCTL_LOOPBACK_SHIFT)
|
||||
|
||||
#define LPAIF_I2SCTL_SPKEN_MASK 0x4000
|
||||
#define LPAIF_I2SCTL_SPKEN_SHIFT 14
|
||||
#define LPAIF_I2SCTL_SPKEN_DISABLE (0 << LPAIF_I2SCTL_SPKEN_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKEN_ENABLE (1 << LPAIF_I2SCTL_SPKEN_SHIFT)
|
||||
|
||||
#define LPAIF_I2SCTL_SPKMODE_MASK 0x3C00
|
||||
#define LPAIF_I2SCTL_SPKMODE_SHIFT 10
|
||||
#define LPAIF_I2SCTL_SPKMODE_NONE (0 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMODE_SD0 (1 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMODE_SD1 (2 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMODE_SD2 (3 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMODE_SD3 (4 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMODE_QUAD01 (5 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMODE_QUAD23 (6 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMODE_6CH (7 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMODE_8CH (8 << LPAIF_I2SCTL_SPKMODE_SHIFT)
|
||||
|
||||
#define LPAIF_I2SCTL_SPKMONO_MASK 0x0200
|
||||
#define LPAIF_I2SCTL_SPKMONO_SHIFT 9
|
||||
#define LPAIF_I2SCTL_SPKMONO_STEREO (0 << LPAIF_I2SCTL_SPKMONO_SHIFT)
|
||||
#define LPAIF_I2SCTL_SPKMONO_MONO (1 << LPAIF_I2SCTL_SPKMONO_SHIFT)
|
||||
|
||||
#define LPAIF_I2SCTL_WSSRC_MASK 0x0004
|
||||
#define LPAIF_I2SCTL_WSSRC_SHIFT 2
|
||||
#define LPAIF_I2SCTL_WSSRC_INTERNAL (0 << LPAIF_I2SCTL_WSSRC_SHIFT)
|
||||
#define LPAIF_I2SCTL_WSSRC_EXTERNAL (1 << LPAIF_I2SCTL_WSSRC_SHIFT)
|
||||
|
||||
#define LPAIF_I2SCTL_BITWIDTH_MASK 0x0003
|
||||
#define LPAIF_I2SCTL_BITWIDTH_SHIFT 0
|
||||
#define LPAIF_I2SCTL_BITWIDTH_16 (0 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
|
||||
#define LPAIF_I2SCTL_BITWIDTH_24 (1 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
|
||||
#define LPAIF_I2SCTL_BITWIDTH_32 (2 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
|
||||
|
||||
/* LPAIF IRQ */
|
||||
|
||||
#define LPAIF_IRQ_REG_BASE 0x3000
|
||||
#define LPAIF_IRQ_REG_STRIDE 0x1000
|
||||
#define LPAIF_IRQ_REG_ADDR(addr, port) \
|
||||
(LPAIF_IRQ_REG_BASE + (addr) + (LPAIF_IRQ_REG_STRIDE * (port)))
|
||||
|
||||
enum lpaif_irq_ports {
|
||||
LPAIF_IRQ_PORT_MIN = 0,
|
||||
|
||||
LPAIF_IRQ_PORT_HOST = 0,
|
||||
LPAIF_IRQ_PORT_ADSP = 1,
|
||||
|
||||
LPAIF_IRQ_PORT_MAX = 2,
|
||||
LPAIF_IRQ_PORT_NUM = 3,
|
||||
};
|
||||
|
||||
#define LPAIF_IRQEN_REG(port) LPAIF_IRQ_REG_ADDR(0x0, (port))
|
||||
#define LPAIF_IRQSTAT_REG(port) LPAIF_IRQ_REG_ADDR(0x4, (port))
|
||||
#define LPAIF_IRQCLEAR_REG(port) LPAIF_IRQ_REG_ADDR(0xC, (port))
|
||||
|
||||
#define LPAIF_IRQ_BITSTRIDE 3
|
||||
#define LPAIF_IRQ_PER(chan) (1 << (LPAIF_IRQ_BITSTRIDE * (chan)))
|
||||
#define LPAIF_IRQ_XRUN(chan) (2 << (LPAIF_IRQ_BITSTRIDE * (chan)))
|
||||
#define LPAIF_IRQ_ERR(chan) (4 << (LPAIF_IRQ_BITSTRIDE * (chan)))
|
||||
#define LPAIF_IRQ_ALL(chan) (7 << (LPAIF_IRQ_BITSTRIDE * (chan)))
|
||||
|
||||
/* LPAIF DMA */
|
||||
|
||||
#define LPAIF_RDMA_REG_BASE 0x6000
|
||||
#define LPAIF_RDMA_REG_STRIDE 0x1000
|
||||
#define LPAIF_RDMA_REG_ADDR(addr, chan) \
|
||||
(LPAIF_RDMA_REG_BASE + (addr) + (LPAIF_RDMA_REG_STRIDE * (chan)))
|
||||
|
||||
enum lpaif_dma_channels {
|
||||
LPAIF_RDMA_CHAN_MIN = 0,
|
||||
|
||||
LPAIF_RDMA_CHAN_MI2S = 0,
|
||||
LPAIF_RDMA_CHAN_PCM0 = 1,
|
||||
LPAIF_RDMA_CHAN_PCM1 = 2,
|
||||
|
||||
LPAIF_RDMA_CHAN_MAX = 4,
|
||||
LPAIF_RDMA_CHAN_NUM = 5,
|
||||
};
|
||||
|
||||
#define LPAIF_RDMACTL_REG(chan) LPAIF_RDMA_REG_ADDR(0x00, (chan))
|
||||
#define LPAIF_RDMABASE_REG(chan) LPAIF_RDMA_REG_ADDR(0x04, (chan))
|
||||
#define LPAIF_RDMABUFF_REG(chan) LPAIF_RDMA_REG_ADDR(0x08, (chan))
|
||||
#define LPAIF_RDMACURR_REG(chan) LPAIF_RDMA_REG_ADDR(0x0C, (chan))
|
||||
#define LPAIF_RDMAPER_REG(chan) LPAIF_RDMA_REG_ADDR(0x10, (chan))
|
||||
|
||||
#define LPAIF_RDMACTL_BURSTEN_MASK 0x800
|
||||
#define LPAIF_RDMACTL_BURSTEN_SHIFT 11
|
||||
#define LPAIF_RDMACTL_BURSTEN_SINGLE (0 << LPAIF_RDMACTL_BURSTEN_SHIFT)
|
||||
#define LPAIF_RDMACTL_BURSTEN_INCR4 (1 << LPAIF_RDMACTL_BURSTEN_SHIFT)
|
||||
|
||||
#define LPAIF_RDMACTL_WPSCNT_MASK 0x700
|
||||
#define LPAIF_RDMACTL_WPSCNT_SHIFT 8
|
||||
#define LPAIF_RDMACTL_WPSCNT_ONE (0 << LPAIF_RDMACTL_WPSCNT_SHIFT)
|
||||
#define LPAIF_RDMACTL_WPSCNT_TWO (1 << LPAIF_RDMACTL_WPSCNT_SHIFT)
|
||||
#define LPAIF_RDMACTL_WPSCNT_THREE (2 << LPAIF_RDMACTL_WPSCNT_SHIFT)
|
||||
#define LPAIF_RDMACTL_WPSCNT_FOUR (3 << LPAIF_RDMACTL_WPSCNT_SHIFT)
|
||||
#define LPAIF_RDMACTL_WPSCNT_SIX (5 << LPAIF_RDMACTL_WPSCNT_SHIFT)
|
||||
#define LPAIF_RDMACTL_WPSCNT_EIGHT (7 << LPAIF_RDMACTL_WPSCNT_SHIFT)
|
||||
|
||||
#define LPAIF_RDMACTL_AUDINTF_MASK 0x0F0
|
||||
#define LPAIF_RDMACTL_AUDINTF_SHIFT 4
|
||||
#define LPAIF_RDMACTL_AUDINTF_NONE (0 << LPAIF_RDMACTL_AUDINTF_SHIFT)
|
||||
#define LPAIF_RDMACTL_AUDINTF_CODEC (1 << LPAIF_RDMACTL_AUDINTF_SHIFT)
|
||||
#define LPAIF_RDMACTL_AUDINTF_PCM (2 << LPAIF_RDMACTL_AUDINTF_SHIFT)
|
||||
#define LPAIF_RDMACTL_AUDINTF_SEC_I2S (3 << LPAIF_RDMACTL_AUDINTF_SHIFT)
|
||||
#define LPAIF_RDMACTL_AUDINTF_MI2S (4 << LPAIF_RDMACTL_AUDINTF_SHIFT)
|
||||
#define LPAIF_RDMACTL_AUDINTF_HDMI (5 << LPAIF_RDMACTL_AUDINTF_SHIFT)
|
||||
#define LPAIF_RDMACTL_AUDINTF_SEC_PCM (7 << LPAIF_RDMACTL_AUDINTF_SHIFT)
|
||||
|
||||
#define LPAIF_RDMACTL_FIFOWM_MASK 0x00E
|
||||
#define LPAIF_RDMACTL_FIFOWM_SHIFT 1
|
||||
#define LPAIF_RDMACTL_FIFOWM_1 (0 << LPAIF_RDMACTL_FIFOWM_SHIFT)
|
||||
#define LPAIF_RDMACTL_FIFOWM_2 (1 << LPAIF_RDMACTL_FIFOWM_SHIFT)
|
||||
#define LPAIF_RDMACTL_FIFOWM_3 (2 << LPAIF_RDMACTL_FIFOWM_SHIFT)
|
||||
#define LPAIF_RDMACTL_FIFOWM_4 (3 << LPAIF_RDMACTL_FIFOWM_SHIFT)
|
||||
#define LPAIF_RDMACTL_FIFOWM_5 (4 << LPAIF_RDMACTL_FIFOWM_SHIFT)
|
||||
#define LPAIF_RDMACTL_FIFOWM_6 (5 << LPAIF_RDMACTL_FIFOWM_SHIFT)
|
||||
#define LPAIF_RDMACTL_FIFOWM_7 (6 << LPAIF_RDMACTL_FIFOWM_SHIFT)
|
||||
#define LPAIF_RDMACTL_FIFOWM_8 (7 << LPAIF_RDMACTL_FIFOWM_SHIFT)
|
||||
|
||||
#define LPAIF_RDMACTL_ENABLE_MASK 0x1
|
||||
#define LPAIF_RDMACTL_ENABLE_SHIFT 0
|
||||
#define LPAIF_RDMACTL_ENABLE_OFF (0 << LPAIF_RDMACTL_ENABLE_SHIFT)
|
||||
#define LPAIF_RDMACTL_ENABLE_ON (1 << LPAIF_RDMACTL_ENABLE_SHIFT)
|
||||
|
||||
#endif /* __LPASS_LPAIF_H__ */
|
526
sound/soc/qcom/lpass-platform.c
Normal file
526
sound/soc/qcom/lpass-platform.c
Normal file
|
@ -0,0 +1,526 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2011,2013-2015 The Linux Foundation. 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 and
|
||||
* only 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.
|
||||
*
|
||||
* lpass-platform.c -- ALSA SoC platform driver for QTi LPASS
|
||||
*/
|
||||
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/memalloc.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <sound/soc.h>
|
||||
#include "lpass-lpaif-ipq806x.h"
|
||||
#include "lpass.h"
|
||||
|
||||
#define LPASS_PLATFORM_BUFFER_SIZE (16 * 1024)
|
||||
#define LPASS_PLATFORM_PERIODS 2
|
||||
|
||||
static struct snd_pcm_hardware lpass_platform_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16 |
|
||||
SNDRV_PCM_FMTBIT_S24 |
|
||||
SNDRV_PCM_FMTBIT_S32,
|
||||
.rates = SNDRV_PCM_RATE_8000_192000,
|
||||
.rate_min = 8000,
|
||||
.rate_max = 192000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 8,
|
||||
.buffer_bytes_max = LPASS_PLATFORM_BUFFER_SIZE,
|
||||
.period_bytes_max = LPASS_PLATFORM_BUFFER_SIZE /
|
||||
LPASS_PLATFORM_PERIODS,
|
||||
.period_bytes_min = LPASS_PLATFORM_BUFFER_SIZE /
|
||||
LPASS_PLATFORM_PERIODS,
|
||||
.periods_min = LPASS_PLATFORM_PERIODS,
|
||||
.periods_max = LPASS_PLATFORM_PERIODS,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static int lpass_platform_pcmops_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
int ret;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &lpass_platform_pcm_hardware);
|
||||
|
||||
runtime->dma_bytes = lpass_platform_pcm_hardware.buffer_bytes_max;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0) {
|
||||
dev_err(soc_runtime->dev, "%s() setting constraints failed: %d\n",
|
||||
__func__, ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpass_platform_pcmops_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct lpass_data *drvdata =
|
||||
snd_soc_platform_get_drvdata(soc_runtime->platform);
|
||||
snd_pcm_format_t format = params_format(params);
|
||||
unsigned int channels = params_channels(params);
|
||||
unsigned int regval;
|
||||
int bitwidth;
|
||||
int ret;
|
||||
|
||||
bitwidth = snd_pcm_format_width(format);
|
||||
if (bitwidth < 0) {
|
||||
dev_err(soc_runtime->dev, "%s() invalid bit width given: %d\n",
|
||||
__func__, bitwidth);
|
||||
return bitwidth;
|
||||
}
|
||||
|
||||
regval = LPAIF_RDMACTL_BURSTEN_INCR4 |
|
||||
LPAIF_RDMACTL_AUDINTF_MI2S |
|
||||
LPAIF_RDMACTL_FIFOWM_8;
|
||||
|
||||
switch (bitwidth) {
|
||||
case 16:
|
||||
switch (channels) {
|
||||
case 1:
|
||||
case 2:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_ONE;
|
||||
break;
|
||||
case 4:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_TWO;
|
||||
break;
|
||||
case 6:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_THREE;
|
||||
break;
|
||||
case 8:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_FOUR;
|
||||
break;
|
||||
default:
|
||||
dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n",
|
||||
__func__, bitwidth, channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case 24:
|
||||
case 32:
|
||||
switch (channels) {
|
||||
case 1:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_ONE;
|
||||
break;
|
||||
case 2:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_TWO;
|
||||
break;
|
||||
case 4:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_FOUR;
|
||||
break;
|
||||
case 6:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_SIX;
|
||||
break;
|
||||
case 8:
|
||||
regval |= LPAIF_RDMACTL_WPSCNT_EIGHT;
|
||||
break;
|
||||
default:
|
||||
dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n",
|
||||
__func__, bitwidth, channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n",
|
||||
__func__, bitwidth, channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), regval);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpass_platform_pcmops_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct lpass_data *drvdata =
|
||||
snd_soc_platform_get_drvdata(soc_runtime->platform);
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), 0);
|
||||
if (ret)
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpass_platform_pcmops_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct lpass_data *drvdata =
|
||||
snd_soc_platform_get_drvdata(soc_runtime->platform);
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_RDMABASE_REG(LPAIF_RDMA_CHAN_MI2S),
|
||||
runtime->dma_addr);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmabase reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_RDMABUFF_REG(LPAIF_RDMA_CHAN_MI2S),
|
||||
(snd_pcm_lib_buffer_bytes(substream) >> 2) - 1);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmabuff reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_RDMAPER_REG(LPAIF_RDMA_CHAN_MI2S),
|
||||
(snd_pcm_lib_period_bytes(substream) >> 2) - 1);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmaper reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(drvdata->lpaif_map,
|
||||
LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S),
|
||||
LPAIF_RDMACTL_ENABLE_MASK, LPAIF_RDMACTL_ENABLE_ON);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpass_platform_pcmops_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct lpass_data *drvdata =
|
||||
snd_soc_platform_get_drvdata(soc_runtime->platform);
|
||||
int ret;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
/* clear status before enabling interrupts */
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST),
|
||||
LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S));
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(drvdata->lpaif_map,
|
||||
LPAIF_IRQEN_REG(LPAIF_IRQ_PORT_HOST),
|
||||
LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S),
|
||||
LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S));
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(drvdata->lpaif_map,
|
||||
LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S),
|
||||
LPAIF_RDMACTL_ENABLE_MASK,
|
||||
LPAIF_RDMACTL_ENABLE_ON);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
ret = regmap_update_bits(drvdata->lpaif_map,
|
||||
LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S),
|
||||
LPAIF_RDMACTL_ENABLE_MASK,
|
||||
LPAIF_RDMACTL_ENABLE_OFF);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(drvdata->lpaif_map,
|
||||
LPAIF_IRQEN_REG(LPAIF_IRQ_PORT_HOST),
|
||||
LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S), 0);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t lpass_platform_pcmops_pointer(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct lpass_data *drvdata =
|
||||
snd_soc_platform_get_drvdata(soc_runtime->platform);
|
||||
unsigned int base_addr, curr_addr;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(drvdata->lpaif_map,
|
||||
LPAIF_RDMABASE_REG(LPAIF_RDMA_CHAN_MI2S), &base_addr);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error reading from rdmabase reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_read(drvdata->lpaif_map,
|
||||
LPAIF_RDMACURR_REG(LPAIF_RDMA_CHAN_MI2S), &curr_addr);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error reading from rdmacurr reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bytes_to_frames(substream->runtime, curr_addr - base_addr);
|
||||
}
|
||||
|
||||
static int lpass_platform_pcmops_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
return dma_mmap_coherent(substream->pcm->card->dev, vma,
|
||||
runtime->dma_area, runtime->dma_addr,
|
||||
runtime->dma_bytes);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops lpass_platform_pcm_ops = {
|
||||
.open = lpass_platform_pcmops_open,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = lpass_platform_pcmops_hw_params,
|
||||
.hw_free = lpass_platform_pcmops_hw_free,
|
||||
.prepare = lpass_platform_pcmops_prepare,
|
||||
.trigger = lpass_platform_pcmops_trigger,
|
||||
.pointer = lpass_platform_pcmops_pointer,
|
||||
.mmap = lpass_platform_pcmops_mmap,
|
||||
};
|
||||
|
||||
static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data)
|
||||
{
|
||||
struct snd_pcm_substream *substream = data;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct lpass_data *drvdata =
|
||||
snd_soc_platform_get_drvdata(soc_runtime->platform);
|
||||
unsigned int interrupts;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
int rv;
|
||||
|
||||
rv = regmap_read(drvdata->lpaif_map,
|
||||
LPAIF_IRQSTAT_REG(LPAIF_IRQ_PORT_HOST), &interrupts);
|
||||
if (rv) {
|
||||
dev_err(soc_runtime->dev, "%s() error reading from irqstat reg: %d\n",
|
||||
__func__, rv);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
interrupts &= LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S);
|
||||
|
||||
if (interrupts & LPAIF_IRQ_PER(LPAIF_RDMA_CHAN_MI2S)) {
|
||||
rv = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST),
|
||||
LPAIF_IRQ_PER(LPAIF_RDMA_CHAN_MI2S));
|
||||
if (rv) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n",
|
||||
__func__, rv);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
snd_pcm_period_elapsed(substream);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (interrupts & LPAIF_IRQ_XRUN(LPAIF_RDMA_CHAN_MI2S)) {
|
||||
rv = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST),
|
||||
LPAIF_IRQ_XRUN(LPAIF_RDMA_CHAN_MI2S));
|
||||
if (rv) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n",
|
||||
__func__, rv);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
dev_warn(soc_runtime->dev, "%s() xrun warning\n", __func__);
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (interrupts & LPAIF_IRQ_ERR(LPAIF_RDMA_CHAN_MI2S)) {
|
||||
rv = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST),
|
||||
LPAIF_IRQ_ERR(LPAIF_RDMA_CHAN_MI2S));
|
||||
if (rv) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n",
|
||||
__func__, rv);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
dev_err(soc_runtime->dev, "%s() bus access error\n", __func__);
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpass_platform_alloc_buffer(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_pcm_runtime *soc_runtime)
|
||||
{
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = lpass_platform_pcm_hardware.buffer_bytes_max;
|
||||
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = soc_runtime->dev;
|
||||
buf->private_data = NULL;
|
||||
buf->area = dma_alloc_coherent(soc_runtime->dev, size, &buf->addr,
|
||||
GFP_KERNEL);
|
||||
if (!buf->area) {
|
||||
dev_err(soc_runtime->dev, "%s: Could not allocate DMA buffer\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
buf->bytes = size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lpass_platform_free_buffer(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_pcm_runtime *soc_runtime)
|
||||
{
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
|
||||
if (buf->area) {
|
||||
dma_free_coherent(soc_runtime->dev, buf->bytes, buf->area,
|
||||
buf->addr);
|
||||
}
|
||||
buf->area = NULL;
|
||||
}
|
||||
|
||||
static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime)
|
||||
{
|
||||
struct snd_pcm *pcm = soc_runtime->pcm;
|
||||
struct snd_pcm_substream *substream =
|
||||
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
|
||||
struct lpass_data *drvdata =
|
||||
snd_soc_platform_get_drvdata(soc_runtime->platform);
|
||||
int ret;
|
||||
|
||||
soc_runtime->dev->coherent_dma_mask = DMA_BIT_MASK(32);
|
||||
soc_runtime->dev->dma_mask = &soc_runtime->dev->coherent_dma_mask;
|
||||
|
||||
ret = lpass_platform_alloc_buffer(substream, soc_runtime);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_request_irq(soc_runtime->dev, drvdata->lpaif_irq,
|
||||
lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING,
|
||||
"lpass-irq-lpaif", substream);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() irq request failed: %d\n",
|
||||
__func__, ret);
|
||||
goto err_buf;
|
||||
}
|
||||
|
||||
/* ensure audio hardware is disabled */
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_IRQEN_REG(LPAIF_IRQ_PORT_HOST), 0);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
ret = regmap_write(drvdata->lpaif_map,
|
||||
LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), 0);
|
||||
if (ret) {
|
||||
dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_buf:
|
||||
lpass_platform_free_buffer(substream, soc_runtime);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lpass_platform_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream =
|
||||
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
|
||||
lpass_platform_free_buffer(substream, soc_runtime);
|
||||
}
|
||||
|
||||
static struct snd_soc_platform_driver lpass_platform_driver = {
|
||||
.pcm_new = lpass_platform_pcm_new,
|
||||
.pcm_free = lpass_platform_pcm_free,
|
||||
.ops = &lpass_platform_pcm_ops,
|
||||
};
|
||||
|
||||
int asoc_qcom_lpass_platform_register(struct platform_device *pdev)
|
||||
{
|
||||
struct lpass_data *drvdata = platform_get_drvdata(pdev);
|
||||
|
||||
drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif");
|
||||
if (drvdata->lpaif_irq < 0) {
|
||||
dev_err(&pdev->dev, "%s() error getting irq handle: %d\n",
|
||||
__func__, drvdata->lpaif_irq);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return devm_snd_soc_register_platform(&pdev->dev,
|
||||
&lpass_platform_driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(asoc_qcom_lpass_platform_register);
|
||||
|
||||
MODULE_DESCRIPTION("QTi LPASS Platform Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
51
sound/soc/qcom/lpass.h
Normal file
51
sound/soc/qcom/lpass.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2011,2013-2015 The Linux Foundation. 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 and
|
||||
* only 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.
|
||||
*
|
||||
* lpass.h - Definitions for the QTi LPASS
|
||||
*/
|
||||
|
||||
#ifndef __LPASS_H__
|
||||
#define __LPASS_H__
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000
|
||||
|
||||
/* Both the CPU DAI and platform drivers will access this data */
|
||||
struct lpass_data {
|
||||
|
||||
/* AHB-I/X bus clocks inside the low-power audio subsystem (LPASS) */
|
||||
struct clk *ahbix_clk;
|
||||
|
||||
/* MI2S system clock */
|
||||
struct clk *mi2s_osr_clk;
|
||||
|
||||
/* MI2S bit clock (derived from system clock by a divider */
|
||||
struct clk *mi2s_bit_clk;
|
||||
|
||||
/* low-power audio interface (LPAIF) registers */
|
||||
void __iomem *lpaif;
|
||||
|
||||
/* regmap backed by the low-power audio interface (LPAIF) registers */
|
||||
struct regmap *lpaif_map;
|
||||
|
||||
/* interrupts from the low-power audio interface (LPAIF) */
|
||||
int lpaif_irq;
|
||||
};
|
||||
|
||||
/* register the platform driver from the CPU DAI driver */
|
||||
int asoc_qcom_lpass_platform_register(struct platform_device *);
|
||||
|
||||
#endif /* __LPASS_H__ */
|
162
sound/soc/qcom/storm.c
Normal file
162
sound/soc/qcom/storm.c
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2011,2013-2015 The Linux Foundation. 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 and
|
||||
* only 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.
|
||||
*
|
||||
* storm.c -- ALSA SoC machine driver for QTi ipq806x-based Storm board
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#define STORM_SYSCLK_MULT 4
|
||||
|
||||
static int storm_ops_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
|
||||
struct snd_soc_card *card = soc_runtime->card;
|
||||
snd_pcm_format_t format = params_format(params);
|
||||
unsigned int rate = params_rate(params);
|
||||
unsigned int sysclk_freq;
|
||||
int bitwidth, ret;
|
||||
|
||||
bitwidth = snd_pcm_format_width(format);
|
||||
if (bitwidth < 0) {
|
||||
dev_err(card->dev, "%s() invalid bit width given: %d\n",
|
||||
__func__, bitwidth);
|
||||
return bitwidth;
|
||||
}
|
||||
|
||||
/*
|
||||
* as the CPU DAI is the I2S bus master and no system clock is needed by
|
||||
* the MAX98357a DAC, simply set the system clock to be a constant
|
||||
* multiple of the bit clock for the clock divider
|
||||
*/
|
||||
sysclk_freq = rate * bitwidth * 2 * STORM_SYSCLK_MULT;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(soc_runtime->cpu_dai, 0, sysclk_freq, 0);
|
||||
if (ret) {
|
||||
dev_err(card->dev, "%s() error setting sysclk to %u: %d\n",
|
||||
__func__, sysclk_freq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops storm_soc_ops = {
|
||||
.hw_params = storm_ops_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link storm_dai_link = {
|
||||
.name = "Primary",
|
||||
.stream_name = "Primary",
|
||||
.codec_dai_name = "HiFi",
|
||||
.ops = &storm_soc_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card storm_soc_card = {
|
||||
.name = "ipq806x-storm",
|
||||
.dev = NULL,
|
||||
};
|
||||
|
||||
static int storm_parse_of(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_dai_link *dai_link = card->dai_link;
|
||||
struct device_node *np = card->dev->of_node;
|
||||
|
||||
dai_link->cpu_of_node = of_parse_phandle(np, "cpu", 0);
|
||||
if (!dai_link->cpu_of_node) {
|
||||
dev_err(card->dev, "%s() error getting cpu phandle\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
dai_link->platform_of_node = dai_link->cpu_of_node;
|
||||
|
||||
dai_link->codec_of_node = of_parse_phandle(np, "codec", 0);
|
||||
if (!dai_link->codec_of_node) {
|
||||
dev_err(card->dev, "%s() error getting codec phandle\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int storm_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &storm_soc_card;
|
||||
int ret;
|
||||
|
||||
if (card->dev) {
|
||||
dev_err(&pdev->dev, "%s() error, existing soundcard\n",
|
||||
__func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
card->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, card);
|
||||
|
||||
ret = snd_soc_of_parse_card_name(card, "qcom,model");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s() error parsing card name: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
card->dai_link = &storm_dai_link;
|
||||
card->num_links = 1;
|
||||
|
||||
ret = storm_parse_of(card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s() error resolving dai links: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, card);
|
||||
if (ret == -EPROBE_DEFER) {
|
||||
card->dev = NULL;
|
||||
return ret;
|
||||
} else if (ret) {
|
||||
dev_err(&pdev->dev, "%s() error registering soundcard: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id storm_device_id[] = {
|
||||
{ .compatible = "google,storm-audio" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, storm_device_id);
|
||||
#endif
|
||||
|
||||
static struct platform_driver storm_platform_driver = {
|
||||
.driver = {
|
||||
.name = "storm-audio",
|
||||
.of_match_table =
|
||||
of_match_ptr(storm_device_id),
|
||||
},
|
||||
.probe = storm_platform_probe,
|
||||
};
|
||||
module_platform_driver(storm_platform_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QTi IPQ806x-based Storm Machine Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -36,11 +36,17 @@ config SND_SOC_SH4_SIU
|
|||
|
||||
config SND_SOC_RCAR
|
||||
tristate "R-Car series SRU/SCU/SSIU/SSI support"
|
||||
depends on DMA_OF
|
||||
select SND_SIMPLE_CARD
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
This option enables R-Car SUR/SCU/SSIU/SSI sound support
|
||||
|
||||
config SND_SOC_RSRC_CARD
|
||||
tristate "Renesas Sampling Rate Convert Sound Card"
|
||||
help
|
||||
This option enables simple sound if you need sampling rate convert
|
||||
|
||||
##
|
||||
## Boards
|
||||
##
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
snd-soc-rcar-objs := core.o gen.o src.o adg.o ssi.o dvc.o
|
||||
snd-soc-rcar-objs := core.o gen.o dma.o src.o adg.o ssi.o dvc.o
|
||||
obj-$(CONFIG_SND_SOC_RCAR) += snd-soc-rcar.o
|
||||
|
||||
snd-soc-rsrc-card-objs := rsrc-card.o
|
||||
obj-$(CONFIG_SND_SOC_RSRC_CARD) += snd-soc-rsrc-card.o
|
||||
|
|
|
@ -183,6 +183,8 @@ int rsnd_adg_set_convert_clk_gen2(struct rsnd_mod *mod,
|
|||
|
||||
rsnd_mod_bset(mod, DIV_EN, en, en);
|
||||
|
||||
dev_dbg(dev, "convert rate %d <-> %d\n", src_rate, dst_rate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -432,7 +434,5 @@ int rsnd_adg_probe(struct platform_device *pdev,
|
|||
|
||||
priv->adg = adg;
|
||||
|
||||
dev_dbg(dev, "adg probed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -94,21 +94,20 @@
|
|||
*
|
||||
*/
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/shdma-base.h>
|
||||
#include "rsnd.h"
|
||||
|
||||
#define RSND_RATES SNDRV_PCM_RATE_8000_96000
|
||||
#define RSND_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
|
||||
|
||||
static struct rsnd_of_data rsnd_of_data_gen1 = {
|
||||
static const struct rsnd_of_data rsnd_of_data_gen1 = {
|
||||
.flags = RSND_GEN1,
|
||||
};
|
||||
|
||||
static struct rsnd_of_data rsnd_of_data_gen2 = {
|
||||
static const struct rsnd_of_data rsnd_of_data_gen2 = {
|
||||
.flags = RSND_GEN2,
|
||||
};
|
||||
|
||||
static struct of_device_id rsnd_of_match[] = {
|
||||
static const struct of_device_id rsnd_of_match[] = {
|
||||
{ .compatible = "renesas,rcar_sound-gen1", .data = &rsnd_of_data_gen1 },
|
||||
{ .compatible = "renesas,rcar_sound-gen2", .data = &rsnd_of_data_gen2 },
|
||||
{},
|
||||
|
@ -138,15 +137,12 @@ char *rsnd_mod_name(struct rsnd_mod *mod)
|
|||
return mod->ops->name;
|
||||
}
|
||||
|
||||
char *rsnd_mod_dma_name(struct rsnd_mod *mod)
|
||||
struct dma_chan *rsnd_mod_dma_req(struct rsnd_mod *mod)
|
||||
{
|
||||
if (!mod || !mod->ops)
|
||||
return "unknown";
|
||||
if (!mod || !mod->ops || !mod->ops->dma_req)
|
||||
return NULL;
|
||||
|
||||
if (!mod->ops->dma_name)
|
||||
return mod->ops->name;
|
||||
|
||||
return mod->ops->dma_name(mod);
|
||||
return mod->ops->dma_req(mod);
|
||||
}
|
||||
|
||||
int rsnd_mod_init(struct rsnd_mod *mod,
|
||||
|
@ -174,228 +170,6 @@ void rsnd_mod_quit(struct rsnd_mod *mod)
|
|||
clk_unprepare(mod->clk);
|
||||
}
|
||||
|
||||
/*
|
||||
* rsnd_dma functions
|
||||
*/
|
||||
void rsnd_dma_stop(struct rsnd_dma *dma)
|
||||
{
|
||||
dmaengine_terminate_all(dma->chan);
|
||||
}
|
||||
|
||||
static void rsnd_dma_complete(void *data)
|
||||
{
|
||||
struct rsnd_dma *dma = (struct rsnd_dma *)data;
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
|
||||
/*
|
||||
* Renesas sound Gen1 needs 1 DMAC,
|
||||
* Gen2 needs 2 DMAC.
|
||||
* In Gen2 case, it are Audio-DMAC, and Audio-DMAC-peri-peri.
|
||||
* But, Audio-DMAC-peri-peri doesn't have interrupt,
|
||||
* and this driver is assuming that here.
|
||||
*
|
||||
* If Audio-DMAC-peri-peri has interrpt,
|
||||
* rsnd_dai_pointer_update() will be called twice,
|
||||
* ant it will breaks io->byte_pos
|
||||
*/
|
||||
|
||||
rsnd_dai_pointer_update(io, io->byte_per_period);
|
||||
}
|
||||
|
||||
void rsnd_dma_start(struct rsnd_dma *dma)
|
||||
{
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
struct snd_pcm_substream *substream = io->substream;
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
|
||||
desc = dmaengine_prep_dma_cyclic(dma->chan,
|
||||
(dma->addr) ? dma->addr :
|
||||
substream->runtime->dma_addr,
|
||||
snd_pcm_lib_buffer_bytes(substream),
|
||||
snd_pcm_lib_period_bytes(substream),
|
||||
dma->dir,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
|
||||
if (!desc) {
|
||||
dev_err(dev, "dmaengine_prep_slave_sg() fail\n");
|
||||
return;
|
||||
}
|
||||
|
||||
desc->callback = rsnd_dma_complete;
|
||||
desc->callback_param = dma;
|
||||
|
||||
if (dmaengine_submit(desc) < 0) {
|
||||
dev_err(dev, "dmaengine_submit() fail\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dma_async_issue_pending(dma->chan);
|
||||
}
|
||||
|
||||
int rsnd_dma_available(struct rsnd_dma *dma)
|
||||
{
|
||||
return !!dma->chan;
|
||||
}
|
||||
|
||||
#define DMA_NAME_SIZE 16
|
||||
#define MOD_MAX 4 /* MEM/SSI/SRC/DVC */
|
||||
static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod)
|
||||
{
|
||||
if (mod)
|
||||
return snprintf(dma_name, DMA_NAME_SIZE / 2, "%s%d",
|
||||
rsnd_mod_dma_name(mod), rsnd_mod_id(mod));
|
||||
else
|
||||
return snprintf(dma_name, DMA_NAME_SIZE / 2, "mem");
|
||||
|
||||
}
|
||||
|
||||
static void rsnd_dma_of_name(struct rsnd_mod *mod_from,
|
||||
struct rsnd_mod *mod_to,
|
||||
char *dma_name)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
index = _rsnd_dma_of_name(dma_name + index, mod_from);
|
||||
*(dma_name + index++) = '_';
|
||||
index = _rsnd_dma_of_name(dma_name + index, mod_to);
|
||||
}
|
||||
|
||||
static void rsnd_dma_of_path(struct rsnd_dma *dma,
|
||||
int is_play,
|
||||
struct rsnd_mod **mod_from,
|
||||
struct rsnd_mod **mod_to)
|
||||
{
|
||||
struct rsnd_mod *this = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(this);
|
||||
struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io);
|
||||
struct rsnd_mod *src = rsnd_io_to_mod_src(io);
|
||||
struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io);
|
||||
struct rsnd_mod *mod[MOD_MAX];
|
||||
int i, index;
|
||||
|
||||
|
||||
for (i = 0; i < MOD_MAX; i++)
|
||||
mod[i] = NULL;
|
||||
|
||||
/*
|
||||
* in play case...
|
||||
*
|
||||
* src -> dst
|
||||
*
|
||||
* mem -> SSI
|
||||
* mem -> SRC -> SSI
|
||||
* mem -> SRC -> DVC -> SSI
|
||||
*/
|
||||
mod[0] = NULL; /* for "mem" */
|
||||
index = 1;
|
||||
for (i = 1; i < MOD_MAX; i++) {
|
||||
if (!src) {
|
||||
mod[i] = ssi;
|
||||
} else if (!dvc) {
|
||||
mod[i] = src;
|
||||
src = NULL;
|
||||
} else {
|
||||
if ((!is_play) && (this == src))
|
||||
this = dvc;
|
||||
|
||||
mod[i] = (is_play) ? src : dvc;
|
||||
i++;
|
||||
mod[i] = (is_play) ? dvc : src;
|
||||
src = NULL;
|
||||
dvc = NULL;
|
||||
}
|
||||
|
||||
if (mod[i] == this)
|
||||
index = i;
|
||||
|
||||
if (mod[i] == ssi)
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_play) {
|
||||
*mod_from = mod[index - 1];
|
||||
*mod_to = mod[index];
|
||||
} else {
|
||||
*mod_from = mod[index];
|
||||
*mod_to = mod[index - 1];
|
||||
}
|
||||
}
|
||||
|
||||
int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma,
|
||||
int is_play, int id)
|
||||
{
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct dma_slave_config cfg;
|
||||
struct rsnd_mod *mod_from;
|
||||
struct rsnd_mod *mod_to;
|
||||
char dma_name[DMA_NAME_SIZE];
|
||||
dma_cap_mask_t mask;
|
||||
int ret;
|
||||
|
||||
if (dma->chan) {
|
||||
dev_err(dev, "it already has dma channel\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
|
||||
rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to);
|
||||
rsnd_dma_of_name(mod_from, mod_to, dma_name);
|
||||
|
||||
cfg.slave_id = id;
|
||||
cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
|
||||
cfg.src_addr = rsnd_gen_dma_addr(priv, mod_from, is_play, 1);
|
||||
cfg.dst_addr = rsnd_gen_dma_addr(priv, mod_to, is_play, 0);
|
||||
cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
|
||||
dev_dbg(dev, "dma : %s %pad -> %pad\n",
|
||||
dma_name, &cfg.src_addr, &cfg.dst_addr);
|
||||
|
||||
dma->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter,
|
||||
(void *)id, dev,
|
||||
dma_name);
|
||||
if (!dma->chan) {
|
||||
dev_err(dev, "can't get dma channel\n");
|
||||
goto rsnd_dma_channel_err;
|
||||
}
|
||||
|
||||
ret = dmaengine_slave_config(dma->chan, &cfg);
|
||||
if (ret < 0)
|
||||
goto rsnd_dma_init_err;
|
||||
|
||||
dma->addr = is_play ? cfg.src_addr : cfg.dst_addr;
|
||||
dma->dir = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
|
||||
|
||||
return 0;
|
||||
|
||||
rsnd_dma_init_err:
|
||||
rsnd_dma_quit(priv, dma);
|
||||
rsnd_dma_channel_err:
|
||||
|
||||
/*
|
||||
* DMA failed. try to PIO mode
|
||||
* see
|
||||
* rsnd_ssi_fallback()
|
||||
* rsnd_rdai_continuance_probe()
|
||||
*/
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
void rsnd_dma_quit(struct rsnd_priv *priv,
|
||||
struct rsnd_dma *dma)
|
||||
{
|
||||
if (dma->chan)
|
||||
dma_release_channel(dma->chan);
|
||||
|
||||
dma->chan = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* settting function
|
||||
*/
|
||||
|
@ -429,7 +203,7 @@ u32 rsnd_get_adinr(struct rsnd_mod *mod)
|
|||
({ \
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod); \
|
||||
struct device *dev = rsnd_priv_to_dev(priv); \
|
||||
u32 mask = 1 << __rsnd_mod_shift_##func; \
|
||||
u32 mask = (1 << __rsnd_mod_shift_##func) & ~(1 << 31); \
|
||||
u32 call = __rsnd_mod_call_##func << __rsnd_mod_shift_##func; \
|
||||
int ret = 0; \
|
||||
if ((mod->status & mask) == call) { \
|
||||
|
@ -471,7 +245,7 @@ static int rsnd_dai_connect(struct rsnd_mod *mod,
|
|||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
|
||||
dev_err(dev, "%s%d is not empty\n",
|
||||
dev_err(dev, "%s[%d] is not empty\n",
|
||||
rsnd_mod_name(mod),
|
||||
rsnd_mod_id(mod));
|
||||
return -EIO;
|
||||
|
@ -887,20 +661,28 @@ static int rsnd_dai_probe(struct platform_device *pdev,
|
|||
drv[i].name = rdai[i].name;
|
||||
drv[i].ops = &rsnd_soc_dai_ops;
|
||||
if (pmod) {
|
||||
snprintf(rdai[i].playback.name, RSND_DAI_NAME_SIZE,
|
||||
"DAI%d Playback", i);
|
||||
|
||||
drv[i].playback.rates = RSND_RATES;
|
||||
drv[i].playback.formats = RSND_FMTS;
|
||||
drv[i].playback.channels_min = 2;
|
||||
drv[i].playback.channels_max = 2;
|
||||
drv[i].playback.stream_name = rdai[i].playback.name;
|
||||
|
||||
rdai[i].playback.info = &info->dai_info[i].playback;
|
||||
rdai[i].playback.rdai = rdai + i;
|
||||
rsnd_path_init(priv, &rdai[i], &rdai[i].playback);
|
||||
}
|
||||
if (cmod) {
|
||||
snprintf(rdai[i].capture.name, RSND_DAI_NAME_SIZE,
|
||||
"DAI%d Capture", i);
|
||||
|
||||
drv[i].capture.rates = RSND_RATES;
|
||||
drv[i].capture.formats = RSND_FMTS;
|
||||
drv[i].capture.channels_min = 2;
|
||||
drv[i].capture.channels_max = 2;
|
||||
drv[i].capture.stream_name = rdai[i].capture.name;
|
||||
|
||||
rdai[i].capture.info = &info->dai_info[i].capture;
|
||||
rdai[i].capture.rdai = rdai + i;
|
||||
|
@ -946,6 +728,15 @@ static int rsnd_pcm_open(struct snd_pcm_substream *substream)
|
|||
static int rsnd_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_soc_dai *dai = rsnd_substream_to_dai(substream);
|
||||
struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai);
|
||||
struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream);
|
||||
int ret;
|
||||
|
||||
ret = rsnd_dai_call(hw_params, io, substream, hw_params);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
}
|
||||
|
@ -1210,6 +1001,7 @@ static int rsnd_probe(struct platform_device *pdev)
|
|||
const struct rsnd_of_data *of_data,
|
||||
struct rsnd_priv *priv) = {
|
||||
rsnd_gen_probe,
|
||||
rsnd_dma_probe,
|
||||
rsnd_ssi_probe,
|
||||
rsnd_src_probe,
|
||||
rsnd_dvc_probe,
|
||||
|
|
616
sound/soc/sh/rcar/dma.c
Normal file
616
sound/soc/sh/rcar/dma.c
Normal file
|
@ -0,0 +1,616 @@
|
|||
/*
|
||||
* Renesas R-Car Audio DMAC support
|
||||
*
|
||||
* Copyright (C) 2015 Renesas Electronics Corp.
|
||||
* Copyright (c) 2015 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of_dma.h>
|
||||
#include "rsnd.h"
|
||||
|
||||
/*
|
||||
* Audio DMAC peri peri register
|
||||
*/
|
||||
#define PDMASAR 0x00
|
||||
#define PDMADAR 0x04
|
||||
#define PDMACHCR 0x0c
|
||||
|
||||
/* PDMACHCR */
|
||||
#define PDMACHCR_DE (1 << 0)
|
||||
|
||||
struct rsnd_dma_ctrl {
|
||||
void __iomem *base;
|
||||
int dmapp_num;
|
||||
};
|
||||
|
||||
#define rsnd_priv_to_dmac(p) ((struct rsnd_dma_ctrl *)(p)->dma)
|
||||
|
||||
/*
|
||||
* Audio DMAC
|
||||
*/
|
||||
static void rsnd_dmaen_complete(void *data)
|
||||
{
|
||||
struct rsnd_dma *dma = (struct rsnd_dma *)data;
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
|
||||
/*
|
||||
* Renesas sound Gen1 needs 1 DMAC,
|
||||
* Gen2 needs 2 DMAC.
|
||||
* In Gen2 case, it are Audio-DMAC, and Audio-DMAC-peri-peri.
|
||||
* But, Audio-DMAC-peri-peri doesn't have interrupt,
|
||||
* and this driver is assuming that here.
|
||||
*
|
||||
* If Audio-DMAC-peri-peri has interrpt,
|
||||
* rsnd_dai_pointer_update() will be called twice,
|
||||
* ant it will breaks io->byte_pos
|
||||
*/
|
||||
|
||||
rsnd_dai_pointer_update(io, io->byte_per_period);
|
||||
}
|
||||
|
||||
static void rsnd_dmaen_stop(struct rsnd_dma *dma)
|
||||
{
|
||||
struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma);
|
||||
|
||||
dmaengine_terminate_all(dmaen->chan);
|
||||
}
|
||||
|
||||
static void rsnd_dmaen_start(struct rsnd_dma *dma)
|
||||
{
|
||||
struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma);
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
struct snd_pcm_substream *substream = io->substream;
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
int is_play = rsnd_io_is_play(io);
|
||||
|
||||
desc = dmaengine_prep_dma_cyclic(dmaen->chan,
|
||||
substream->runtime->dma_addr,
|
||||
snd_pcm_lib_buffer_bytes(substream),
|
||||
snd_pcm_lib_period_bytes(substream),
|
||||
is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
|
||||
if (!desc) {
|
||||
dev_err(dev, "dmaengine_prep_slave_sg() fail\n");
|
||||
return;
|
||||
}
|
||||
|
||||
desc->callback = rsnd_dmaen_complete;
|
||||
desc->callback_param = dma;
|
||||
|
||||
if (dmaengine_submit(desc) < 0) {
|
||||
dev_err(dev, "dmaengine_submit() fail\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dma_async_issue_pending(dmaen->chan);
|
||||
}
|
||||
|
||||
struct dma_chan *rsnd_dma_request_channel(struct device_node *of_node,
|
||||
struct rsnd_mod *mod, char *name)
|
||||
{
|
||||
struct dma_chan *chan;
|
||||
struct device_node *np;
|
||||
int i = 0;
|
||||
|
||||
for_each_child_of_node(of_node, np) {
|
||||
if (i == rsnd_mod_id(mod))
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
chan = of_dma_request_slave_channel(np, name);
|
||||
|
||||
of_node_put(np);
|
||||
of_node_put(of_node);
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
static struct dma_chan *rsnd_dmaen_request_channel(struct rsnd_mod *mod_from,
|
||||
struct rsnd_mod *mod_to)
|
||||
{
|
||||
if ((!mod_from && !mod_to) ||
|
||||
(mod_from && mod_to))
|
||||
return NULL;
|
||||
|
||||
if (mod_from)
|
||||
return rsnd_mod_dma_req(mod_from);
|
||||
else
|
||||
return rsnd_mod_dma_req(mod_to);
|
||||
}
|
||||
|
||||
static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id,
|
||||
struct rsnd_mod *mod_from, struct rsnd_mod *mod_to)
|
||||
{
|
||||
struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma);
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct dma_slave_config cfg = {};
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
int is_play = rsnd_io_is_play(io);
|
||||
int ret;
|
||||
|
||||
if (dmaen->chan) {
|
||||
dev_err(dev, "it already has dma channel\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (dev->of_node) {
|
||||
dmaen->chan = rsnd_dmaen_request_channel(mod_from, mod_to);
|
||||
} else {
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
|
||||
dmaen->chan = dma_request_channel(mask, shdma_chan_filter,
|
||||
(void *)id);
|
||||
}
|
||||
if (IS_ERR_OR_NULL(dmaen->chan)) {
|
||||
dev_err(dev, "can't get dma channel\n");
|
||||
goto rsnd_dma_channel_err;
|
||||
}
|
||||
|
||||
cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
|
||||
cfg.src_addr = dma->src_addr;
|
||||
cfg.dst_addr = dma->dst_addr;
|
||||
cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
|
||||
dev_dbg(dev, "dma : %pad -> %pad\n",
|
||||
&cfg.src_addr, &cfg.dst_addr);
|
||||
|
||||
ret = dmaengine_slave_config(dmaen->chan, &cfg);
|
||||
if (ret < 0)
|
||||
goto rsnd_dma_init_err;
|
||||
|
||||
return 0;
|
||||
|
||||
rsnd_dma_init_err:
|
||||
rsnd_dma_quit(dma);
|
||||
rsnd_dma_channel_err:
|
||||
|
||||
/*
|
||||
* DMA failed. try to PIO mode
|
||||
* see
|
||||
* rsnd_ssi_fallback()
|
||||
* rsnd_rdai_continuance_probe()
|
||||
*/
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
static void rsnd_dmaen_quit(struct rsnd_dma *dma)
|
||||
{
|
||||
struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma);
|
||||
|
||||
if (dmaen->chan)
|
||||
dma_release_channel(dmaen->chan);
|
||||
|
||||
dmaen->chan = NULL;
|
||||
}
|
||||
|
||||
static struct rsnd_dma_ops rsnd_dmaen_ops = {
|
||||
.start = rsnd_dmaen_start,
|
||||
.stop = rsnd_dmaen_stop,
|
||||
.init = rsnd_dmaen_init,
|
||||
.quit = rsnd_dmaen_quit,
|
||||
};
|
||||
|
||||
/*
|
||||
* Audio DMAC peri peri
|
||||
*/
|
||||
static const u8 gen2_id_table_ssiu[] = {
|
||||
0x00, /* SSI00 */
|
||||
0x04, /* SSI10 */
|
||||
0x08, /* SSI20 */
|
||||
0x0c, /* SSI3 */
|
||||
0x0d, /* SSI4 */
|
||||
0x0e, /* SSI5 */
|
||||
0x0f, /* SSI6 */
|
||||
0x10, /* SSI7 */
|
||||
0x11, /* SSI8 */
|
||||
0x12, /* SSI90 */
|
||||
};
|
||||
static const u8 gen2_id_table_scu[] = {
|
||||
0x2d, /* SCU_SRCI0 */
|
||||
0x2e, /* SCU_SRCI1 */
|
||||
0x2f, /* SCU_SRCI2 */
|
||||
0x30, /* SCU_SRCI3 */
|
||||
0x31, /* SCU_SRCI4 */
|
||||
0x32, /* SCU_SRCI5 */
|
||||
0x33, /* SCU_SRCI6 */
|
||||
0x34, /* SCU_SRCI7 */
|
||||
0x35, /* SCU_SRCI8 */
|
||||
0x36, /* SCU_SRCI9 */
|
||||
};
|
||||
static const u8 gen2_id_table_cmd[] = {
|
||||
0x37, /* SCU_CMD0 */
|
||||
0x38, /* SCU_CMD1 */
|
||||
};
|
||||
|
||||
static u32 rsnd_dmapp_get_id(struct rsnd_mod *mod)
|
||||
{
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io);
|
||||
struct rsnd_mod *src = rsnd_io_to_mod_src(io);
|
||||
struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io);
|
||||
const u8 *entry = NULL;
|
||||
int id = rsnd_mod_id(mod);
|
||||
int size = 0;
|
||||
|
||||
if (mod == ssi) {
|
||||
entry = gen2_id_table_ssiu;
|
||||
size = ARRAY_SIZE(gen2_id_table_ssiu);
|
||||
} else if (mod == src) {
|
||||
entry = gen2_id_table_scu;
|
||||
size = ARRAY_SIZE(gen2_id_table_scu);
|
||||
} else if (mod == dvc) {
|
||||
entry = gen2_id_table_cmd;
|
||||
size = ARRAY_SIZE(gen2_id_table_cmd);
|
||||
}
|
||||
|
||||
if (!entry)
|
||||
return 0xFF;
|
||||
|
||||
if (size <= id)
|
||||
return 0xFF;
|
||||
|
||||
return entry[id];
|
||||
}
|
||||
|
||||
static u32 rsnd_dmapp_get_chcr(struct rsnd_mod *mod_from,
|
||||
struct rsnd_mod *mod_to)
|
||||
{
|
||||
return (rsnd_dmapp_get_id(mod_from) << 24) +
|
||||
(rsnd_dmapp_get_id(mod_to) << 16);
|
||||
}
|
||||
|
||||
#define rsnd_dmapp_addr(dmac, dma, reg) \
|
||||
(dmac->base + 0x20 + reg + \
|
||||
(0x10 * rsnd_dma_to_dmapp(dma)->dmapp_id))
|
||||
static void rsnd_dmapp_write(struct rsnd_dma *dma, u32 data, u32 reg)
|
||||
{
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv);
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
|
||||
dev_dbg(dev, "w %p : %08x\n", rsnd_dmapp_addr(dmac, dma, reg), data);
|
||||
|
||||
iowrite32(data, rsnd_dmapp_addr(dmac, dma, reg));
|
||||
}
|
||||
|
||||
static u32 rsnd_dmapp_read(struct rsnd_dma *dma, u32 reg)
|
||||
{
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv);
|
||||
|
||||
return ioread32(rsnd_dmapp_addr(dmac, dma, reg));
|
||||
}
|
||||
|
||||
static void rsnd_dmapp_stop(struct rsnd_dma *dma)
|
||||
{
|
||||
int i;
|
||||
|
||||
rsnd_dmapp_write(dma, 0, PDMACHCR);
|
||||
|
||||
for (i = 0; i < 1024; i++) {
|
||||
if (0 == rsnd_dmapp_read(dma, PDMACHCR))
|
||||
return;
|
||||
udelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void rsnd_dmapp_start(struct rsnd_dma *dma)
|
||||
{
|
||||
struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma);
|
||||
|
||||
rsnd_dmapp_write(dma, dma->src_addr, PDMASAR);
|
||||
rsnd_dmapp_write(dma, dma->dst_addr, PDMADAR);
|
||||
rsnd_dmapp_write(dma, dmapp->chcr, PDMACHCR);
|
||||
}
|
||||
|
||||
static int rsnd_dmapp_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id,
|
||||
struct rsnd_mod *mod_from, struct rsnd_mod *mod_to)
|
||||
{
|
||||
struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma);
|
||||
struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv);
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
|
||||
dmapp->dmapp_id = dmac->dmapp_num;
|
||||
dmapp->chcr = rsnd_dmapp_get_chcr(mod_from, mod_to) | PDMACHCR_DE;
|
||||
|
||||
dmac->dmapp_num++;
|
||||
|
||||
rsnd_dmapp_stop(dma);
|
||||
|
||||
dev_dbg(dev, "id/src/dst/chcr = %d/%pad/%pad/%08x\n",
|
||||
dmapp->dmapp_id, &dma->src_addr, &dma->dst_addr, dmapp->chcr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct rsnd_dma_ops rsnd_dmapp_ops = {
|
||||
.start = rsnd_dmapp_start,
|
||||
.stop = rsnd_dmapp_stop,
|
||||
.init = rsnd_dmapp_init,
|
||||
.quit = rsnd_dmapp_stop,
|
||||
};
|
||||
|
||||
/*
|
||||
* Common DMAC Interface
|
||||
*/
|
||||
|
||||
/*
|
||||
* DMA read/write register offset
|
||||
*
|
||||
* RSND_xxx_I_N for Audio DMAC input
|
||||
* RSND_xxx_O_N for Audio DMAC output
|
||||
* RSND_xxx_I_P for Audio DMAC peri peri input
|
||||
* RSND_xxx_O_P for Audio DMAC peri peri output
|
||||
*
|
||||
* ex) R-Car H2 case
|
||||
* mod / DMAC in / DMAC out / DMAC PP in / DMAC pp out
|
||||
* SSI : 0xec541000 / 0xec241008 / 0xec24100c
|
||||
* SSIU: 0xec541000 / 0xec100000 / 0xec100000 / 0xec400000 / 0xec400000
|
||||
* SCU : 0xec500000 / 0xec000000 / 0xec004000 / 0xec300000 / 0xec304000
|
||||
* CMD : 0xec500000 / / 0xec008000 0xec308000
|
||||
*/
|
||||
#define RDMA_SSI_I_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0x8)
|
||||
#define RDMA_SSI_O_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0xc)
|
||||
|
||||
#define RDMA_SSIU_I_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i))
|
||||
#define RDMA_SSIU_O_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i))
|
||||
|
||||
#define RDMA_SSIU_I_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i))
|
||||
#define RDMA_SSIU_O_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i))
|
||||
|
||||
#define RDMA_SRC_I_N(addr, i) (addr ##_reg - 0x00500000 + (0x400 * i))
|
||||
#define RDMA_SRC_O_N(addr, i) (addr ##_reg - 0x004fc000 + (0x400 * i))
|
||||
|
||||
#define RDMA_SRC_I_P(addr, i) (addr ##_reg - 0x00200000 + (0x400 * i))
|
||||
#define RDMA_SRC_O_P(addr, i) (addr ##_reg - 0x001fc000 + (0x400 * i))
|
||||
|
||||
#define RDMA_CMD_O_N(addr, i) (addr ##_reg - 0x004f8000 + (0x400 * i))
|
||||
#define RDMA_CMD_O_P(addr, i) (addr ##_reg - 0x001f8000 + (0x400 * i))
|
||||
|
||||
static dma_addr_t
|
||||
rsnd_gen2_dma_addr(struct rsnd_priv *priv,
|
||||
struct rsnd_mod *mod,
|
||||
int is_play, int is_from)
|
||||
{
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
phys_addr_t ssi_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SSI);
|
||||
phys_addr_t src_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SCU);
|
||||
int is_ssi = !!(rsnd_io_to_mod_ssi(io) == mod);
|
||||
int use_src = !!rsnd_io_to_mod_src(io);
|
||||
int use_dvc = !!rsnd_io_to_mod_dvc(io);
|
||||
int id = rsnd_mod_id(mod);
|
||||
struct dma_addr {
|
||||
dma_addr_t out_addr;
|
||||
dma_addr_t in_addr;
|
||||
} dma_addrs[3][2][3] = {
|
||||
/* SRC */
|
||||
{{{ 0, 0 },
|
||||
/* Capture */
|
||||
{ RDMA_SRC_O_N(src, id), RDMA_SRC_I_P(src, id) },
|
||||
{ RDMA_CMD_O_N(src, id), RDMA_SRC_I_P(src, id) } },
|
||||
/* Playback */
|
||||
{{ 0, 0, },
|
||||
{ RDMA_SRC_O_P(src, id), RDMA_SRC_I_N(src, id) },
|
||||
{ RDMA_CMD_O_P(src, id), RDMA_SRC_I_N(src, id) } }
|
||||
},
|
||||
/* SSI */
|
||||
/* Capture */
|
||||
{{{ RDMA_SSI_O_N(ssi, id), 0 },
|
||||
{ RDMA_SSIU_O_P(ssi, id), 0 },
|
||||
{ RDMA_SSIU_O_P(ssi, id), 0 } },
|
||||
/* Playback */
|
||||
{{ 0, RDMA_SSI_I_N(ssi, id) },
|
||||
{ 0, RDMA_SSIU_I_P(ssi, id) },
|
||||
{ 0, RDMA_SSIU_I_P(ssi, id) } }
|
||||
},
|
||||
/* SSIU */
|
||||
/* Capture */
|
||||
{{{ RDMA_SSIU_O_N(ssi, id), 0 },
|
||||
{ RDMA_SSIU_O_P(ssi, id), 0 },
|
||||
{ RDMA_SSIU_O_P(ssi, id), 0 } },
|
||||
/* Playback */
|
||||
{{ 0, RDMA_SSIU_I_N(ssi, id) },
|
||||
{ 0, RDMA_SSIU_I_P(ssi, id) },
|
||||
{ 0, RDMA_SSIU_I_P(ssi, id) } } },
|
||||
};
|
||||
|
||||
/* it shouldn't happen */
|
||||
if (use_dvc && !use_src)
|
||||
dev_err(dev, "DVC is selected without SRC\n");
|
||||
|
||||
/* use SSIU or SSI ? */
|
||||
if (is_ssi && rsnd_ssi_use_busif(mod))
|
||||
is_ssi++;
|
||||
|
||||
return (is_from) ?
|
||||
dma_addrs[is_ssi][is_play][use_src + use_dvc].out_addr :
|
||||
dma_addrs[is_ssi][is_play][use_src + use_dvc].in_addr;
|
||||
}
|
||||
|
||||
static dma_addr_t rsnd_dma_addr(struct rsnd_priv *priv,
|
||||
struct rsnd_mod *mod,
|
||||
int is_play, int is_from)
|
||||
{
|
||||
/*
|
||||
* gen1 uses default DMA addr
|
||||
*/
|
||||
if (rsnd_is_gen1(priv))
|
||||
return 0;
|
||||
|
||||
if (!mod)
|
||||
return 0;
|
||||
|
||||
return rsnd_gen2_dma_addr(priv, mod, is_play, is_from);
|
||||
}
|
||||
|
||||
#define MOD_MAX 4 /* MEM/SSI/SRC/DVC */
|
||||
static void rsnd_dma_of_path(struct rsnd_dma *dma,
|
||||
int is_play,
|
||||
struct rsnd_mod **mod_from,
|
||||
struct rsnd_mod **mod_to)
|
||||
{
|
||||
struct rsnd_mod *this = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(this);
|
||||
struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io);
|
||||
struct rsnd_mod *src = rsnd_io_to_mod_src(io);
|
||||
struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io);
|
||||
struct rsnd_mod *mod[MOD_MAX];
|
||||
int i, index;
|
||||
|
||||
|
||||
for (i = 0; i < MOD_MAX; i++)
|
||||
mod[i] = NULL;
|
||||
|
||||
/*
|
||||
* in play case...
|
||||
*
|
||||
* src -> dst
|
||||
*
|
||||
* mem -> SSI
|
||||
* mem -> SRC -> SSI
|
||||
* mem -> SRC -> DVC -> SSI
|
||||
*/
|
||||
mod[0] = NULL; /* for "mem" */
|
||||
index = 1;
|
||||
for (i = 1; i < MOD_MAX; i++) {
|
||||
if (!src) {
|
||||
mod[i] = ssi;
|
||||
} else if (!dvc) {
|
||||
mod[i] = src;
|
||||
src = NULL;
|
||||
} else {
|
||||
if ((!is_play) && (this == src))
|
||||
this = dvc;
|
||||
|
||||
mod[i] = (is_play) ? src : dvc;
|
||||
i++;
|
||||
mod[i] = (is_play) ? dvc : src;
|
||||
src = NULL;
|
||||
dvc = NULL;
|
||||
}
|
||||
|
||||
if (mod[i] == this)
|
||||
index = i;
|
||||
|
||||
if (mod[i] == ssi)
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_play) {
|
||||
*mod_from = mod[index - 1];
|
||||
*mod_to = mod[index];
|
||||
} else {
|
||||
*mod_from = mod[index];
|
||||
*mod_to = mod[index - 1];
|
||||
}
|
||||
}
|
||||
|
||||
void rsnd_dma_stop(struct rsnd_dma *dma)
|
||||
{
|
||||
dma->ops->stop(dma);
|
||||
}
|
||||
|
||||
void rsnd_dma_start(struct rsnd_dma *dma)
|
||||
{
|
||||
dma->ops->start(dma);
|
||||
}
|
||||
|
||||
void rsnd_dma_quit(struct rsnd_dma *dma)
|
||||
{
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv);
|
||||
|
||||
if (!dmac)
|
||||
return;
|
||||
|
||||
dma->ops->quit(dma);
|
||||
}
|
||||
|
||||
int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id)
|
||||
{
|
||||
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
||||
struct rsnd_mod *mod_from;
|
||||
struct rsnd_mod *mod_to;
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv);
|
||||
int is_play = rsnd_io_is_play(io);
|
||||
|
||||
/*
|
||||
* DMA failed. try to PIO mode
|
||||
* see
|
||||
* rsnd_ssi_fallback()
|
||||
* rsnd_rdai_continuance_probe()
|
||||
*/
|
||||
if (!dmac)
|
||||
return -EAGAIN;
|
||||
|
||||
rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to);
|
||||
|
||||
dma->src_addr = rsnd_dma_addr(priv, mod_from, is_play, 1);
|
||||
dma->dst_addr = rsnd_dma_addr(priv, mod_to, is_play, 0);
|
||||
|
||||
/* for Gen2 */
|
||||
if (mod_from && mod_to)
|
||||
dma->ops = &rsnd_dmapp_ops;
|
||||
else
|
||||
dma->ops = &rsnd_dmaen_ops;
|
||||
|
||||
/* for Gen1, overwrite */
|
||||
if (rsnd_is_gen1(priv))
|
||||
dma->ops = &rsnd_dmaen_ops;
|
||||
|
||||
return dma->ops->init(priv, dma, id, mod_from, mod_to);
|
||||
}
|
||||
|
||||
int rsnd_dma_probe(struct platform_device *pdev,
|
||||
const struct rsnd_of_data *of_data,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct rsnd_dma_ctrl *dmac;
|
||||
struct resource *res;
|
||||
|
||||
/*
|
||||
* for Gen1
|
||||
*/
|
||||
if (rsnd_is_gen1(priv))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* for Gen2
|
||||
*/
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audmapp");
|
||||
dmac = devm_kzalloc(dev, sizeof(*dmac), GFP_KERNEL);
|
||||
if (!dmac || !res) {
|
||||
dev_err(dev, "dma allocate failed\n");
|
||||
return 0; /* it will be PIO mode */
|
||||
}
|
||||
|
||||
dmac->dmapp_num = 0;
|
||||
dmac->base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(dmac->base))
|
||||
return PTR_ERR(dmac->base);
|
||||
|
||||
priv->dma = dmac;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -24,6 +24,9 @@ struct rsnd_dvc {
|
|||
struct rsnd_kctrl_cfg_s rdown; /* Ramp Rate Down */
|
||||
};
|
||||
|
||||
#define rsnd_dvc_of_node(priv) \
|
||||
of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,dvc")
|
||||
|
||||
#define rsnd_mod_to_dvc(_mod) \
|
||||
container_of((_mod), struct rsnd_dvc, mod)
|
||||
|
||||
|
@ -33,7 +36,7 @@ struct rsnd_dvc {
|
|||
((pos) = (struct rsnd_dvc *)(priv)->dvc + i); \
|
||||
i++)
|
||||
|
||||
static const char const *dvc_ramp_rate[] = {
|
||||
static const char * const dvc_ramp_rate[] = {
|
||||
"128 dB/1 step", /* 00000 */
|
||||
"64 dB/1 step", /* 00001 */
|
||||
"32 dB/1 step", /* 00010 */
|
||||
|
@ -116,17 +119,6 @@ static void rsnd_dvc_volume_update(struct rsnd_mod *mod)
|
|||
rsnd_mod_write(mod, DVC_DVUER, 1);
|
||||
}
|
||||
|
||||
static int rsnd_dvc_probe_gen2(struct rsnd_mod *mod,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
|
||||
dev_dbg(dev, "%s[%d] (Gen2) is probed\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rsnd_dvc_remove_gen2(struct rsnd_mod *mod,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
|
@ -269,9 +261,17 @@ static int rsnd_dvc_pcm_new(struct rsnd_mod *mod,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct dma_chan *rsnd_dvc_dma_req(struct rsnd_mod *mod)
|
||||
{
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
|
||||
return rsnd_dma_request_channel(rsnd_dvc_of_node(priv),
|
||||
mod, "tx");
|
||||
}
|
||||
|
||||
static struct rsnd_mod_ops rsnd_dvc_ops = {
|
||||
.name = DVC_NAME,
|
||||
.probe = rsnd_dvc_probe_gen2,
|
||||
.dma_req = rsnd_dvc_dma_req,
|
||||
.remove = rsnd_dvc_remove_gen2,
|
||||
.init = rsnd_dvc_init,
|
||||
.quit = rsnd_dvc_quit,
|
||||
|
@ -370,8 +370,6 @@ int rsnd_dvc_probe(struct platform_device *pdev,
|
|||
clk, RSND_MOD_DVC, i);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_dbg(dev, "CMD%d probed\n", i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -28,6 +28,7 @@ struct rsnd_gen {
|
|||
|
||||
struct regmap *regmap[RSND_BASE_MAX];
|
||||
struct regmap_field *regs[RSND_REG_MAX];
|
||||
phys_addr_t res[RSND_REG_MAX];
|
||||
};
|
||||
|
||||
#define rsnd_priv_to_gen(p) ((struct rsnd_gen *)(p)->gen)
|
||||
|
@ -118,11 +119,19 @@ void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod,
|
|||
mask, data);
|
||||
}
|
||||
|
||||
#define rsnd_gen_regmap_init(priv, id_size, reg_id, conf) \
|
||||
_rsnd_gen_regmap_init(priv, id_size, reg_id, conf, ARRAY_SIZE(conf))
|
||||
phys_addr_t rsnd_gen_get_phy_addr(struct rsnd_priv *priv, int reg_id)
|
||||
{
|
||||
struct rsnd_gen *gen = rsnd_priv_to_gen(priv);
|
||||
|
||||
return gen->res[reg_id];
|
||||
}
|
||||
|
||||
#define rsnd_gen_regmap_init(priv, id_size, reg_id, name, conf) \
|
||||
_rsnd_gen_regmap_init(priv, id_size, reg_id, name, conf, ARRAY_SIZE(conf))
|
||||
static int _rsnd_gen_regmap_init(struct rsnd_priv *priv,
|
||||
int id_size,
|
||||
int reg_id,
|
||||
const char *name,
|
||||
struct rsnd_regmap_field_conf *conf,
|
||||
int conf_size)
|
||||
{
|
||||
|
@ -141,7 +150,10 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv,
|
|||
regc.reg_bits = 32;
|
||||
regc.val_bits = 32;
|
||||
regc.reg_stride = 4;
|
||||
regc.name = name;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
|
||||
if (!res)
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, reg_id);
|
||||
if (!res)
|
||||
return -ENODEV;
|
||||
|
@ -156,6 +168,7 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv,
|
|||
|
||||
gen->base[reg_id] = base;
|
||||
gen->regmap[reg_id] = regmap;
|
||||
gen->res[reg_id] = res->start;
|
||||
|
||||
for (i = 0; i < conf_size; i++) {
|
||||
|
||||
|
@ -175,126 +188,12 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* DMA read/write register offset
|
||||
*
|
||||
* RSND_xxx_I_N for Audio DMAC input
|
||||
* RSND_xxx_O_N for Audio DMAC output
|
||||
* RSND_xxx_I_P for Audio DMAC peri peri input
|
||||
* RSND_xxx_O_P for Audio DMAC peri peri output
|
||||
*
|
||||
* ex) R-Car H2 case
|
||||
* mod / DMAC in / DMAC out / DMAC PP in / DMAC pp out
|
||||
* SSI : 0xec541000 / 0xec241008 / 0xec24100c
|
||||
* SSIU: 0xec541000 / 0xec100000 / 0xec100000 / 0xec400000 / 0xec400000
|
||||
* SCU : 0xec500000 / 0xec000000 / 0xec004000 / 0xec300000 / 0xec304000
|
||||
* CMD : 0xec500000 / / 0xec008000 0xec308000
|
||||
*/
|
||||
#define RDMA_SSI_I_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0x8)
|
||||
#define RDMA_SSI_O_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0xc)
|
||||
|
||||
#define RDMA_SSIU_I_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i))
|
||||
#define RDMA_SSIU_O_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i))
|
||||
|
||||
#define RDMA_SSIU_I_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i))
|
||||
#define RDMA_SSIU_O_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i))
|
||||
|
||||
#define RDMA_SRC_I_N(addr, i) (addr ##_reg - 0x00500000 + (0x400 * i))
|
||||
#define RDMA_SRC_O_N(addr, i) (addr ##_reg - 0x004fc000 + (0x400 * i))
|
||||
|
||||
#define RDMA_SRC_I_P(addr, i) (addr ##_reg - 0x00200000 + (0x400 * i))
|
||||
#define RDMA_SRC_O_P(addr, i) (addr ##_reg - 0x001fc000 + (0x400 * i))
|
||||
|
||||
#define RDMA_CMD_O_N(addr, i) (addr ##_reg - 0x004f8000 + (0x400 * i))
|
||||
#define RDMA_CMD_O_P(addr, i) (addr ##_reg - 0x001f8000 + (0x400 * i))
|
||||
|
||||
static dma_addr_t
|
||||
rsnd_gen2_dma_addr(struct rsnd_priv *priv,
|
||||
struct rsnd_mod *mod,
|
||||
int is_play, int is_from)
|
||||
{
|
||||
struct platform_device *pdev = rsnd_priv_to_pdev(priv);
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
dma_addr_t ssi_reg = platform_get_resource(pdev,
|
||||
IORESOURCE_MEM, RSND_GEN2_SSI)->start;
|
||||
dma_addr_t src_reg = platform_get_resource(pdev,
|
||||
IORESOURCE_MEM, RSND_GEN2_SCU)->start;
|
||||
int is_ssi = !!(rsnd_io_to_mod_ssi(io) == mod);
|
||||
int use_src = !!rsnd_io_to_mod_src(io);
|
||||
int use_dvc = !!rsnd_io_to_mod_dvc(io);
|
||||
int id = rsnd_mod_id(mod);
|
||||
struct dma_addr {
|
||||
dma_addr_t out_addr;
|
||||
dma_addr_t in_addr;
|
||||
} dma_addrs[3][2][3] = {
|
||||
/* SRC */
|
||||
{{{ 0, 0 },
|
||||
/* Capture */
|
||||
{ RDMA_SRC_O_N(src, id), RDMA_SRC_I_P(src, id) },
|
||||
{ RDMA_CMD_O_N(src, id), RDMA_SRC_I_P(src, id) } },
|
||||
/* Playback */
|
||||
{{ 0, 0, },
|
||||
{ RDMA_SRC_O_P(src, id), RDMA_SRC_I_N(src, id) },
|
||||
{ RDMA_CMD_O_P(src, id), RDMA_SRC_I_N(src, id) } }
|
||||
},
|
||||
/* SSI */
|
||||
/* Capture */
|
||||
{{{ RDMA_SSI_O_N(ssi, id), 0 },
|
||||
{ RDMA_SSIU_O_P(ssi, id), 0 },
|
||||
{ RDMA_SSIU_O_P(ssi, id), 0 } },
|
||||
/* Playback */
|
||||
{{ 0, RDMA_SSI_I_N(ssi, id) },
|
||||
{ 0, RDMA_SSIU_I_P(ssi, id) },
|
||||
{ 0, RDMA_SSIU_I_P(ssi, id) } }
|
||||
},
|
||||
/* SSIU */
|
||||
/* Capture */
|
||||
{{{ RDMA_SSIU_O_N(ssi, id), 0 },
|
||||
{ RDMA_SSIU_O_P(ssi, id), 0 },
|
||||
{ RDMA_SSIU_O_P(ssi, id), 0 } },
|
||||
/* Playback */
|
||||
{{ 0, RDMA_SSIU_I_N(ssi, id) },
|
||||
{ 0, RDMA_SSIU_I_P(ssi, id) },
|
||||
{ 0, RDMA_SSIU_I_P(ssi, id) } } },
|
||||
};
|
||||
|
||||
/* it shouldn't happen */
|
||||
if (use_dvc && !use_src)
|
||||
dev_err(dev, "DVC is selected without SRC\n");
|
||||
|
||||
/* use SSIU or SSI ? */
|
||||
if (is_ssi && (0 == strcmp(rsnd_mod_dma_name(mod), "ssiu")))
|
||||
is_ssi++;
|
||||
|
||||
return (is_from) ?
|
||||
dma_addrs[is_ssi][is_play][use_src + use_dvc].out_addr :
|
||||
dma_addrs[is_ssi][is_play][use_src + use_dvc].in_addr;
|
||||
}
|
||||
|
||||
dma_addr_t rsnd_gen_dma_addr(struct rsnd_priv *priv,
|
||||
struct rsnd_mod *mod,
|
||||
int is_play, int is_from)
|
||||
{
|
||||
/*
|
||||
* gen1 uses default DMA addr
|
||||
*/
|
||||
if (rsnd_is_gen1(priv))
|
||||
return 0;
|
||||
|
||||
if (!mod)
|
||||
return 0;
|
||||
|
||||
return rsnd_gen2_dma_addr(priv, mod, is_play, is_from);
|
||||
}
|
||||
|
||||
/*
|
||||
* Gen2
|
||||
*/
|
||||
static int rsnd_gen2_probe(struct platform_device *pdev,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct rsnd_regmap_field_conf conf_ssiu[] = {
|
||||
RSND_GEN_S_REG(SSI_MODE0, 0x800),
|
||||
RSND_GEN_S_REG(SSI_MODE1, 0x804),
|
||||
|
@ -368,18 +267,16 @@ static int rsnd_gen2_probe(struct platform_device *pdev,
|
|||
int ret_adg;
|
||||
int ret_ssi;
|
||||
|
||||
ret_ssiu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSIU, conf_ssiu);
|
||||
ret_scu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SCU, conf_scu);
|
||||
ret_adg = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_ADG, conf_adg);
|
||||
ret_ssi = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSI, conf_ssi);
|
||||
ret_ssiu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSIU, "ssiu", conf_ssiu);
|
||||
ret_scu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SCU, "scu", conf_scu);
|
||||
ret_adg = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_ADG, "adg", conf_adg);
|
||||
ret_ssi = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSI, "ssi", conf_ssi);
|
||||
if (ret_ssiu < 0 ||
|
||||
ret_scu < 0 ||
|
||||
ret_adg < 0 ||
|
||||
ret_ssi < 0)
|
||||
return ret_ssiu | ret_scu | ret_adg | ret_ssi;
|
||||
|
||||
dev_dbg(dev, "Gen2 is probed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -390,7 +287,6 @@ static int rsnd_gen2_probe(struct platform_device *pdev,
|
|||
static int rsnd_gen1_probe(struct platform_device *pdev,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
struct rsnd_regmap_field_conf conf_sru[] = {
|
||||
RSND_GEN_S_REG(SRC_ROUTE_SEL, 0x00),
|
||||
RSND_GEN_S_REG(SRC_TMG_SEL0, 0x08),
|
||||
|
@ -440,16 +336,14 @@ static int rsnd_gen1_probe(struct platform_device *pdev,
|
|||
int ret_adg;
|
||||
int ret_ssi;
|
||||
|
||||
ret_sru = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SRU, conf_sru);
|
||||
ret_adg = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_ADG, conf_adg);
|
||||
ret_ssi = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SSI, conf_ssi);
|
||||
ret_sru = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SRU, "sru", conf_sru);
|
||||
ret_adg = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_ADG, "adg", conf_adg);
|
||||
ret_ssi = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SSI, "ssi", conf_ssi);
|
||||
if (ret_sru < 0 ||
|
||||
ret_adg < 0 ||
|
||||
ret_ssi < 0)
|
||||
return ret_sru | ret_adg | ret_ssi;
|
||||
|
||||
dev_dbg(dev, "Gen1 is probed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -170,21 +170,47 @@ u32 rsnd_get_adinr(struct rsnd_mod *mod);
|
|||
/*
|
||||
* R-Car DMA
|
||||
*/
|
||||
struct rsnd_dma {
|
||||
struct sh_dmae_slave slave;
|
||||
struct dma_chan *chan;
|
||||
enum dma_transfer_direction dir;
|
||||
dma_addr_t addr;
|
||||
struct rsnd_dma;
|
||||
struct rsnd_dma_ops {
|
||||
void (*start)(struct rsnd_dma *dma);
|
||||
void (*stop)(struct rsnd_dma *dma);
|
||||
int (*init)(struct rsnd_priv *priv, struct rsnd_dma *dma, int id,
|
||||
struct rsnd_mod *mod_from, struct rsnd_mod *mod_to);
|
||||
void (*quit)(struct rsnd_dma *dma);
|
||||
};
|
||||
|
||||
struct rsnd_dmaen {
|
||||
struct dma_chan *chan;
|
||||
};
|
||||
|
||||
struct rsnd_dmapp {
|
||||
int dmapp_id;
|
||||
u32 chcr;
|
||||
};
|
||||
|
||||
struct rsnd_dma {
|
||||
struct rsnd_dma_ops *ops;
|
||||
dma_addr_t src_addr;
|
||||
dma_addr_t dst_addr;
|
||||
union {
|
||||
struct rsnd_dmaen en;
|
||||
struct rsnd_dmapp pp;
|
||||
} dma;
|
||||
};
|
||||
#define rsnd_dma_to_dmaen(dma) (&(dma)->dma.en)
|
||||
#define rsnd_dma_to_dmapp(dma) (&(dma)->dma.pp)
|
||||
|
||||
void rsnd_dma_start(struct rsnd_dma *dma);
|
||||
void rsnd_dma_stop(struct rsnd_dma *dma);
|
||||
int rsnd_dma_available(struct rsnd_dma *dma);
|
||||
int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma,
|
||||
int is_play, int id);
|
||||
void rsnd_dma_quit(struct rsnd_priv *priv,
|
||||
struct rsnd_dma *dma);
|
||||
int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id);
|
||||
void rsnd_dma_quit(struct rsnd_dma *dma);
|
||||
int rsnd_dma_probe(struct platform_device *pdev,
|
||||
const struct rsnd_of_data *of_data,
|
||||
struct rsnd_priv *priv);
|
||||
struct dma_chan *rsnd_dma_request_channel(struct device_node *of_node,
|
||||
struct rsnd_mod *mod, char *name);
|
||||
|
||||
#define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma)
|
||||
|
||||
/*
|
||||
* R-Car sound mod
|
||||
|
@ -198,7 +224,7 @@ enum rsnd_mod_type {
|
|||
|
||||
struct rsnd_mod_ops {
|
||||
char *name;
|
||||
char* (*dma_name)(struct rsnd_mod *mod);
|
||||
struct dma_chan* (*dma_req)(struct rsnd_mod *mod);
|
||||
int (*probe)(struct rsnd_mod *mod,
|
||||
struct rsnd_priv *priv);
|
||||
int (*remove)(struct rsnd_mod *mod,
|
||||
|
@ -213,6 +239,9 @@ struct rsnd_mod_ops {
|
|||
struct rsnd_priv *priv);
|
||||
int (*pcm_new)(struct rsnd_mod *mod,
|
||||
struct snd_soc_pcm_runtime *rtd);
|
||||
int (*hw_params)(struct rsnd_mod *mod,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params);
|
||||
int (*fallback)(struct rsnd_mod *mod,
|
||||
struct rsnd_priv *priv);
|
||||
};
|
||||
|
@ -236,6 +265,9 @@ struct rsnd_mod {
|
|||
* 2 0: start 1: stop
|
||||
* 3 0: pcm_new
|
||||
* 4 0: fallback
|
||||
*
|
||||
* 31 bit is always called (see __rsnd_mod_call)
|
||||
* 31 0: hw_params
|
||||
*/
|
||||
#define __rsnd_mod_shift_probe 0
|
||||
#define __rsnd_mod_shift_remove 0
|
||||
|
@ -245,6 +277,7 @@ struct rsnd_mod {
|
|||
#define __rsnd_mod_shift_stop 2
|
||||
#define __rsnd_mod_shift_pcm_new 3
|
||||
#define __rsnd_mod_shift_fallback 4
|
||||
#define __rsnd_mod_shift_hw_params 31 /* always called */
|
||||
|
||||
#define __rsnd_mod_call_probe 0
|
||||
#define __rsnd_mod_call_remove 1
|
||||
|
@ -254,10 +287,10 @@ struct rsnd_mod {
|
|||
#define __rsnd_mod_call_stop 1
|
||||
#define __rsnd_mod_call_pcm_new 0
|
||||
#define __rsnd_mod_call_fallback 0
|
||||
#define __rsnd_mod_call_hw_params 0
|
||||
|
||||
#define rsnd_mod_to_priv(mod) (rsnd_io_to_priv(rsnd_mod_to_io(mod)))
|
||||
#define rsnd_mod_to_dma(mod) (&(mod)->dma)
|
||||
#define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma)
|
||||
#define rsnd_mod_to_io(mod) ((mod)->io)
|
||||
#define rsnd_mod_id(mod) ((mod)->id)
|
||||
#define rsnd_mod_hw_start(mod) clk_enable((mod)->clk)
|
||||
|
@ -270,13 +303,14 @@ int rsnd_mod_init(struct rsnd_mod *mod,
|
|||
int id);
|
||||
void rsnd_mod_quit(struct rsnd_mod *mod);
|
||||
char *rsnd_mod_name(struct rsnd_mod *mod);
|
||||
char *rsnd_mod_dma_name(struct rsnd_mod *mod);
|
||||
struct dma_chan *rsnd_mod_dma_req(struct rsnd_mod *mod);
|
||||
|
||||
/*
|
||||
* R-Car sound DAI
|
||||
*/
|
||||
#define RSND_DAI_NAME_SIZE 16
|
||||
struct rsnd_dai_stream {
|
||||
char name[RSND_DAI_NAME_SIZE];
|
||||
struct snd_pcm_substream *substream;
|
||||
struct rsnd_mod *mod[RSND_MOD_MAX];
|
||||
struct rsnd_dai_path_info *info; /* rcar_snd.h */
|
||||
|
@ -332,9 +366,7 @@ int rsnd_gen_probe(struct platform_device *pdev,
|
|||
void __iomem *rsnd_gen_reg_get(struct rsnd_priv *priv,
|
||||
struct rsnd_mod *mod,
|
||||
enum rsnd_reg reg);
|
||||
dma_addr_t rsnd_gen_dma_addr(struct rsnd_priv *priv,
|
||||
struct rsnd_mod *mod,
|
||||
int is_play, int is_from);
|
||||
phys_addr_t rsnd_gen_get_phy_addr(struct rsnd_priv *priv, int reg_id);
|
||||
|
||||
#define rsnd_is_gen1(s) (((s)->info->flags & RSND_GEN_MASK) == RSND_GEN1)
|
||||
#define rsnd_is_gen2(s) (((s)->info->flags & RSND_GEN_MASK) == RSND_GEN2)
|
||||
|
@ -389,6 +421,11 @@ struct rsnd_priv {
|
|||
*/
|
||||
void *adg;
|
||||
|
||||
/*
|
||||
* below value will be filled on rsnd_dma_probe()
|
||||
*/
|
||||
void *dma;
|
||||
|
||||
/*
|
||||
* below value will be filled on rsnd_ssi_probe()
|
||||
*/
|
||||
|
@ -415,19 +452,6 @@ struct rsnd_priv {
|
|||
#define rsnd_lock(priv, flags) spin_lock_irqsave(&priv->lock, flags)
|
||||
#define rsnd_unlock(priv, flags) spin_unlock_irqrestore(&priv->lock, flags)
|
||||
|
||||
#define rsnd_info_is_playback(priv, type) \
|
||||
({ \
|
||||
struct rcar_snd_info *info = rsnd_priv_to_info(priv); \
|
||||
int i, is_play = 0; \
|
||||
for (i = 0; i < info->dai_info_nr; i++) { \
|
||||
if (info->dai_info[i].playback.type == (type)->info) { \
|
||||
is_play = 1; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
is_play; \
|
||||
})
|
||||
|
||||
/*
|
||||
* rsnd_kctrl
|
||||
*/
|
||||
|
@ -506,6 +530,7 @@ void rsnd_ssi_remove(struct platform_device *pdev,
|
|||
struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id);
|
||||
int rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod);
|
||||
int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod);
|
||||
int rsnd_ssi_use_busif(struct rsnd_mod *mod);
|
||||
|
||||
/*
|
||||
* R-Car DVC
|
||||
|
|
512
sound/soc/sh/rcar/rsrc-card.c
Normal file
512
sound/soc/sh/rcar/rsrc-card.c
Normal file
|
@ -0,0 +1,512 @@
|
|||
/*
|
||||
* Renesas Sampling Rate Convert Sound Card for DPCM
|
||||
*
|
||||
* Copyright (C) 2015 Renesas Solutions Corp.
|
||||
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
||||
*
|
||||
* based on ${LINUX}/sound/soc/generic/simple-card.c
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/string.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dai.h>
|
||||
|
||||
struct rsrc_card_of_data {
|
||||
const char *prefix;
|
||||
const struct snd_soc_dapm_route *routes;
|
||||
int num_routes;
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route routes_ssi0_ak4642[] = {
|
||||
{"ak4642 Playback", NULL, "DAI0 Playback"},
|
||||
{"DAI0 Capture", NULL, "ak4642 Capture"},
|
||||
};
|
||||
|
||||
static const struct rsrc_card_of_data routes_of_ssi0_ak4642 = {
|
||||
.prefix = "ak4642",
|
||||
.routes = routes_ssi0_ak4642,
|
||||
.num_routes = ARRAY_SIZE(routes_ssi0_ak4642),
|
||||
};
|
||||
|
||||
static const struct of_device_id rsrc_card_of_match[] = {
|
||||
{ .compatible = "renesas,rsrc-card,lager", .data = &routes_of_ssi0_ak4642 },
|
||||
{ .compatible = "renesas,rsrc-card,koelsch", .data = &routes_of_ssi0_ak4642 },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rsrc_card_of_match);
|
||||
|
||||
struct rsrc_card_dai {
|
||||
const char *name;
|
||||
unsigned int fmt;
|
||||
unsigned int sysclk;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
#define RSRC_FB_NUM 2 /* FE/BE */
|
||||
#define IDX_CPU 0
|
||||
#define IDX_CODEC 1
|
||||
struct rsrc_card_priv {
|
||||
struct snd_soc_card snd_card;
|
||||
struct rsrc_card_dai_props {
|
||||
struct rsrc_card_dai cpu_dai;
|
||||
struct rsrc_card_dai codec_dai;
|
||||
} dai_props[RSRC_FB_NUM];
|
||||
struct snd_soc_codec_conf codec_conf;
|
||||
struct snd_soc_dai_link dai_link[RSRC_FB_NUM];
|
||||
u32 convert_rate;
|
||||
};
|
||||
|
||||
#define rsrc_priv_to_dev(priv) ((priv)->snd_card.dev)
|
||||
#define rsrc_priv_to_link(priv, i) ((priv)->snd_card.dai_link + i)
|
||||
#define rsrc_priv_to_props(priv, i) ((priv)->dai_props + i)
|
||||
#define rsrc_dev_to_of_data(dev) (of_match_device(rsrc_card_of_match, (dev))->data)
|
||||
|
||||
static int rsrc_card_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
||||
struct rsrc_card_dai_props *dai_props =
|
||||
&priv->dai_props[rtd - rtd->card->rtd];
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(dai_props->cpu_dai.clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(dai_props->codec_dai.clk);
|
||||
if (ret)
|
||||
clk_disable_unprepare(dai_props->cpu_dai.clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rsrc_card_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
||||
struct rsrc_card_dai_props *dai_props =
|
||||
&priv->dai_props[rtd - rtd->card->rtd];
|
||||
|
||||
clk_disable_unprepare(dai_props->cpu_dai.clk);
|
||||
|
||||
clk_disable_unprepare(dai_props->codec_dai.clk);
|
||||
}
|
||||
|
||||
static struct snd_soc_ops rsrc_card_ops = {
|
||||
.startup = rsrc_card_startup,
|
||||
.shutdown = rsrc_card_shutdown,
|
||||
};
|
||||
|
||||
static int __rsrc_card_dai_init(struct snd_soc_dai *dai,
|
||||
struct rsrc_card_dai *set)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (set->fmt) {
|
||||
ret = snd_soc_dai_set_fmt(dai, set->fmt);
|
||||
if (ret && ret != -ENOTSUPP) {
|
||||
dev_err(dai->dev, "set_fmt error\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (set->sysclk) {
|
||||
ret = snd_soc_dai_set_sysclk(dai, 0, set->sysclk, 0);
|
||||
if (ret && ret != -ENOTSUPP) {
|
||||
dev_err(dai->dev, "set_sysclk error\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rsrc_card_dai_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
||||
struct snd_soc_dai *codec = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu = rtd->cpu_dai;
|
||||
struct rsrc_card_dai_props *dai_props;
|
||||
int num, ret;
|
||||
|
||||
num = rtd - rtd->card->rtd;
|
||||
dai_props = &priv->dai_props[num];
|
||||
ret = __rsrc_card_dai_init(codec, &dai_props->codec_dai);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __rsrc_card_dai_init(cpu, &dai_props->cpu_dai);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rsrc_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
||||
struct snd_interval *rate = hw_param_interval(params,
|
||||
SNDRV_PCM_HW_PARAM_RATE);
|
||||
|
||||
if (!priv->convert_rate)
|
||||
return 0;
|
||||
|
||||
rate->min = rate->max = priv->convert_rate;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rsrc_card_sub_parse_of(struct rsrc_card_priv *priv,
|
||||
struct device_node *np,
|
||||
struct rsrc_card_dai *dai,
|
||||
struct snd_soc_dai_link *dai_link,
|
||||
int *args_count)
|
||||
{
|
||||
struct device *dev = rsrc_priv_to_dev(priv);
|
||||
const struct rsrc_card_of_data *of_data = rsrc_dev_to_of_data(dev);
|
||||
struct of_phandle_args args;
|
||||
struct device_node **p_node;
|
||||
struct clk *clk;
|
||||
const char **dai_name;
|
||||
const char **name;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (args_count) {
|
||||
p_node = &dai_link->cpu_of_node;
|
||||
dai_name = &dai_link->cpu_dai_name;
|
||||
name = &dai_link->cpu_name;
|
||||
} else {
|
||||
p_node = &dai_link->codec_of_node;
|
||||
dai_name = &dai_link->codec_dai_name;
|
||||
name = &dai_link->codec_name;
|
||||
}
|
||||
|
||||
if (!np) {
|
||||
/* use snd-soc-dummy */
|
||||
*p_node = NULL;
|
||||
*dai_name = "snd-soc-dummy-dai";
|
||||
*name = "snd-soc-dummy";
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get node via "sound-dai = <&phandle port>"
|
||||
* it will be used as xxx_of_node on soc_bind_dai_link()
|
||||
*/
|
||||
ret = of_parse_phandle_with_args(np, "sound-dai",
|
||||
"#sound-dai-cells", 0, &args);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*p_node = args.np;
|
||||
|
||||
/* Get dai->name */
|
||||
ret = snd_soc_of_get_dai_name(np, dai_name);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* FIXME
|
||||
*
|
||||
* rsrc assumes DPCM playback/capture
|
||||
*/
|
||||
dai_link->dpcm_playback = 1;
|
||||
dai_link->dpcm_capture = 1;
|
||||
|
||||
if (args_count) {
|
||||
*args_count = args.args_count;
|
||||
dai_link->dynamic = 1;
|
||||
} else {
|
||||
dai_link->no_pcm = 1;
|
||||
priv->codec_conf.of_node = (*p_node);
|
||||
priv->codec_conf.name_prefix = of_data->prefix;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse dai->sysclk come from "clocks = <&xxx>"
|
||||
* (if system has common clock)
|
||||
* or "system-clock-frequency = <xxx>"
|
||||
* or device's module clock.
|
||||
*/
|
||||
if (of_property_read_bool(np, "clocks")) {
|
||||
clk = of_clk_get(np, 0);
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dai->sysclk = clk_get_rate(clk);
|
||||
dai->clk = clk;
|
||||
} else if (!of_property_read_u32(np, "system-clock-frequency", &val)) {
|
||||
dai->sysclk = val;
|
||||
} else {
|
||||
clk = of_clk_get(args.np, 0);
|
||||
if (!IS_ERR(clk))
|
||||
dai->sysclk = clk_get_rate(clk);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rsrc_card_parse_daifmt(struct device_node *node,
|
||||
struct rsrc_card_priv *priv,
|
||||
struct device_node *codec,
|
||||
int idx)
|
||||
{
|
||||
struct device_node *bitclkmaster = NULL;
|
||||
struct device_node *framemaster = NULL;
|
||||
struct rsrc_card_dai_props *dai_props = rsrc_priv_to_props(priv, idx);
|
||||
struct rsrc_card_dai *cpu_dai = &dai_props->cpu_dai;
|
||||
struct rsrc_card_dai *codec_dai = &dai_props->codec_dai;
|
||||
unsigned int daifmt;
|
||||
|
||||
daifmt = snd_soc_of_parse_daifmt(node, NULL,
|
||||
&bitclkmaster, &framemaster);
|
||||
daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
|
||||
|
||||
if (!bitclkmaster && !framemaster)
|
||||
return -EINVAL;
|
||||
|
||||
if (codec == bitclkmaster)
|
||||
daifmt |= (codec == framemaster) ?
|
||||
SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS;
|
||||
else
|
||||
daifmt |= (codec == framemaster) ?
|
||||
SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
cpu_dai->fmt = daifmt;
|
||||
codec_dai->fmt = daifmt;
|
||||
|
||||
of_node_put(bitclkmaster);
|
||||
of_node_put(framemaster);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rsrc_card_dai_link_of(struct device_node *node,
|
||||
struct rsrc_card_priv *priv,
|
||||
int idx)
|
||||
{
|
||||
struct device *dev = rsrc_priv_to_dev(priv);
|
||||
struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx);
|
||||
struct rsrc_card_dai_props *dai_props = rsrc_priv_to_props(priv, idx);
|
||||
struct device_node *cpu = NULL;
|
||||
struct device_node *codec = NULL;
|
||||
char *name;
|
||||
char prop[128];
|
||||
int ret, cpu_args;
|
||||
|
||||
cpu = of_get_child_by_name(node, "cpu");
|
||||
codec = of_get_child_by_name(node, "codec");
|
||||
|
||||
if (!cpu || !codec) {
|
||||
ret = -EINVAL;
|
||||
dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop);
|
||||
goto dai_link_of_err;
|
||||
}
|
||||
|
||||
ret = rsrc_card_parse_daifmt(node, priv, codec, idx);
|
||||
if (ret < 0)
|
||||
goto dai_link_of_err;
|
||||
|
||||
ret = rsrc_card_sub_parse_of(priv, (idx == IDX_CPU) ? cpu : NULL,
|
||||
&dai_props->cpu_dai,
|
||||
dai_link,
|
||||
&cpu_args);
|
||||
if (ret < 0)
|
||||
goto dai_link_of_err;
|
||||
|
||||
ret = rsrc_card_sub_parse_of(priv, (idx == IDX_CODEC) ? codec : NULL,
|
||||
&dai_props->codec_dai,
|
||||
dai_link,
|
||||
NULL);
|
||||
if (ret < 0)
|
||||
goto dai_link_of_err;
|
||||
|
||||
if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name) {
|
||||
ret = -EINVAL;
|
||||
goto dai_link_of_err;
|
||||
}
|
||||
|
||||
/* Simple Card assumes platform == cpu */
|
||||
dai_link->platform_of_node = dai_link->cpu_of_node;
|
||||
|
||||
/* DAI link name is created from CPU/CODEC dai name */
|
||||
name = devm_kzalloc(dev,
|
||||
strlen(dai_link->cpu_dai_name) +
|
||||
strlen(dai_link->codec_dai_name) + 2,
|
||||
GFP_KERNEL);
|
||||
if (!name) {
|
||||
ret = -ENOMEM;
|
||||
goto dai_link_of_err;
|
||||
}
|
||||
|
||||
sprintf(name, "%s-%s", dai_link->cpu_dai_name,
|
||||
dai_link->codec_dai_name);
|
||||
dai_link->name = dai_link->stream_name = name;
|
||||
dai_link->ops = &rsrc_card_ops;
|
||||
dai_link->init = rsrc_card_dai_init;
|
||||
|
||||
if (idx == IDX_CODEC)
|
||||
dai_link->be_hw_params_fixup = rsrc_card_be_hw_params_fixup;
|
||||
|
||||
dev_dbg(dev, "\tname : %s\n", dai_link->stream_name);
|
||||
dev_dbg(dev, "\tcpu : %s / %04x / %d\n",
|
||||
dai_link->cpu_dai_name,
|
||||
dai_props->cpu_dai.fmt,
|
||||
dai_props->cpu_dai.sysclk);
|
||||
dev_dbg(dev, "\tcodec : %s / %04x / %d\n",
|
||||
dai_link->codec_dai_name,
|
||||
dai_props->codec_dai.fmt,
|
||||
dai_props->codec_dai.sysclk);
|
||||
|
||||
/*
|
||||
* In soc_bind_dai_link() will check cpu name after
|
||||
* of_node matching if dai_link has cpu_dai_name.
|
||||
* but, it will never match if name was created by
|
||||
* fmt_single_name() remove cpu_dai_name if cpu_args
|
||||
* was 0. See:
|
||||
* fmt_single_name()
|
||||
* fmt_multiple_name()
|
||||
*/
|
||||
if (!cpu_args)
|
||||
dai_link->cpu_dai_name = NULL;
|
||||
|
||||
dai_link_of_err:
|
||||
of_node_put(cpu);
|
||||
of_node_put(codec);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rsrc_card_parse_of(struct device_node *node,
|
||||
struct rsrc_card_priv *priv)
|
||||
{
|
||||
struct device *dev = rsrc_priv_to_dev(priv);
|
||||
const struct rsrc_card_of_data *of_data = rsrc_dev_to_of_data(dev);
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (!node)
|
||||
return -EINVAL;
|
||||
|
||||
/* Parse the card name from DT */
|
||||
snd_soc_of_parse_card_name(&priv->snd_card, "card-name");
|
||||
|
||||
/* DAPM routes */
|
||||
priv->snd_card.of_dapm_routes = of_data->routes;
|
||||
priv->snd_card.num_of_dapm_routes = of_data->num_routes;
|
||||
|
||||
/* sampling rate convert */
|
||||
of_property_read_u32(node, "convert-rate", &priv->convert_rate);
|
||||
|
||||
dev_dbg(dev, "New rsrc-audio-card: %s (%d)\n",
|
||||
priv->snd_card.name ? priv->snd_card.name : "",
|
||||
priv->convert_rate);
|
||||
|
||||
/* FE/BE */
|
||||
for (i = 0; i < RSRC_FB_NUM; i++) {
|
||||
ret = rsrc_card_dai_link_of(node, priv, i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!priv->snd_card.name)
|
||||
priv->snd_card.name = priv->snd_card.dai_link->name;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decrease the reference count of the device nodes */
|
||||
static int rsrc_card_unref(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_dai_link *dai_link;
|
||||
int num_links;
|
||||
|
||||
for (num_links = 0, dai_link = card->dai_link;
|
||||
num_links < card->num_links;
|
||||
num_links++, dai_link++) {
|
||||
of_node_put(dai_link->cpu_of_node);
|
||||
of_node_put(dai_link->codec_of_node);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rsrc_card_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rsrc_card_priv *priv;
|
||||
struct snd_soc_dai_link *dai_link;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
/* Allocate the private data */
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Init snd_soc_card */
|
||||
priv->snd_card.owner = THIS_MODULE;
|
||||
priv->snd_card.dev = dev;
|
||||
dai_link = priv->dai_link;
|
||||
priv->snd_card.dai_link = dai_link;
|
||||
priv->snd_card.num_links = RSRC_FB_NUM;
|
||||
priv->snd_card.codec_conf = &priv->codec_conf;
|
||||
priv->snd_card.num_configs = 1;
|
||||
|
||||
ret = rsrc_card_parse_of(np, priv);
|
||||
if (ret < 0) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "parse error %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
snd_soc_card_set_drvdata(&priv->snd_card, priv);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card);
|
||||
if (ret >= 0)
|
||||
return ret;
|
||||
err:
|
||||
rsrc_card_unref(&priv->snd_card);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rsrc_card_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
return rsrc_card_unref(card);
|
||||
}
|
||||
|
||||
static struct platform_driver rsrc_card = {
|
||||
.driver = {
|
||||
.name = "renesas-src-audio-card",
|
||||
.of_match_table = rsrc_card_of_match,
|
||||
},
|
||||
.probe = rsrc_card_probe,
|
||||
.remove = rsrc_card_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(rsrc_card);
|
||||
|
||||
MODULE_ALIAS("platform:renesas-src-audio-card");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Renesas Sampling Rate Convert Sound Card");
|
||||
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
|
|
@ -22,16 +22,20 @@
|
|||
struct rsnd_src {
|
||||
struct rsnd_src_platform_info *info; /* rcar_snd.h */
|
||||
struct rsnd_mod mod;
|
||||
struct rsnd_kctrl_cfg_s sen; /* sync convert enable */
|
||||
struct rsnd_kctrl_cfg_s sync; /* sync convert */
|
||||
u32 convert_rate; /* sampling rate convert */
|
||||
int err;
|
||||
};
|
||||
|
||||
#define RSND_SRC_NAME_SIZE 16
|
||||
|
||||
#define rsnd_src_convert_rate(p) ((p)->info->convert_rate)
|
||||
#define rsnd_enable_sync_convert(src) ((src)->sen.val)
|
||||
#define rsnd_src_of_node(priv) \
|
||||
of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,src")
|
||||
|
||||
#define rsnd_mod_to_src(_mod) \
|
||||
container_of((_mod), struct rsnd_src, mod)
|
||||
#define rsnd_src_dma_available(src) \
|
||||
rsnd_dma_available(rsnd_mod_to_dma(&(src)->mod))
|
||||
|
||||
#define for_each_rsnd_src(pos, priv, i) \
|
||||
for ((i) = 0; \
|
||||
|
@ -113,6 +117,17 @@ struct rsnd_src {
|
|||
/*
|
||||
* Gen1/Gen2 common functions
|
||||
*/
|
||||
static struct dma_chan *rsnd_src_dma_req(struct rsnd_mod *mod)
|
||||
{
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
int is_play = rsnd_io_is_play(io);
|
||||
|
||||
return rsnd_dma_request_channel(rsnd_src_of_node(priv),
|
||||
mod,
|
||||
is_play ? "rx" : "tx");
|
||||
}
|
||||
|
||||
int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod,
|
||||
int use_busif)
|
||||
{
|
||||
|
@ -220,6 +235,30 @@ int rsnd_src_ssi_irq_disable(struct rsnd_mod *ssi_mod)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static u32 rsnd_src_convert_rate(struct rsnd_src *src)
|
||||
{
|
||||
struct rsnd_mod *mod = &src->mod;
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
|
||||
u32 convert_rate;
|
||||
|
||||
if (!runtime)
|
||||
return 0;
|
||||
|
||||
if (!rsnd_enable_sync_convert(src))
|
||||
return src->convert_rate;
|
||||
|
||||
convert_rate = src->sync.val;
|
||||
|
||||
if (!convert_rate)
|
||||
convert_rate = src->convert_rate;
|
||||
|
||||
if (!convert_rate)
|
||||
convert_rate = runtime->rate;
|
||||
|
||||
return convert_rate;
|
||||
}
|
||||
|
||||
unsigned int rsnd_src_get_ssi_rate(struct rsnd_priv *priv,
|
||||
struct rsnd_dai_stream *io,
|
||||
struct snd_pcm_runtime *runtime)
|
||||
|
@ -276,7 +315,43 @@ static int rsnd_src_set_convert_rate(struct rsnd_mod *mod)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int rsnd_src_init(struct rsnd_mod *mod)
|
||||
static int rsnd_src_hw_params(struct rsnd_mod *mod,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *fe_params)
|
||||
{
|
||||
struct rsnd_src *src = rsnd_mod_to_src(mod);
|
||||
struct snd_soc_pcm_runtime *fe = substream->private_data;
|
||||
|
||||
/* default value (mainly for non-DT) */
|
||||
src->convert_rate = src->info->convert_rate;
|
||||
|
||||
/*
|
||||
* SRC assumes that it is used under DPCM if user want to use
|
||||
* sampling rate convert. Then, SRC should be FE.
|
||||
* And then, this function will be called *after* BE settings.
|
||||
* this means, each BE already has fixuped hw_params.
|
||||
* see
|
||||
* dpcm_fe_dai_hw_params()
|
||||
* dpcm_be_dai_hw_params()
|
||||
*/
|
||||
if (fe->dai_link->dynamic) {
|
||||
int stream = substream->stream;
|
||||
struct snd_soc_dpcm *dpcm;
|
||||
struct snd_pcm_hw_params *be_params;
|
||||
|
||||
list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
|
||||
be_params = &dpcm->hw_params;
|
||||
|
||||
if (params_rate(fe_params) != params_rate(be_params))
|
||||
src->convert_rate = params_rate(be_params);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rsnd_src_init(struct rsnd_mod *mod,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
struct rsnd_src *src = rsnd_mod_to_src(mod);
|
||||
|
||||
|
@ -284,6 +359,9 @@ static int rsnd_src_init(struct rsnd_mod *mod)
|
|||
|
||||
src->err = 0;
|
||||
|
||||
/* reset sync convert_rate */
|
||||
src->sync.val = 0;
|
||||
|
||||
/*
|
||||
* Initialize the operation of the SRC internal circuits
|
||||
* see rsnd_src_start()
|
||||
|
@ -305,6 +383,11 @@ static int rsnd_src_quit(struct rsnd_mod *mod,
|
|||
dev_warn(dev, "%s[%d] under/over flow err = %d\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod), src->err);
|
||||
|
||||
src->convert_rate = 0;
|
||||
|
||||
/* reset sync convert_rate */
|
||||
src->sync.val = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -448,23 +531,12 @@ static int rsnd_src_set_convert_rate_gen1(struct rsnd_mod *mod)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int rsnd_src_probe_gen1(struct rsnd_mod *mod,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
|
||||
dev_dbg(dev, "%s[%d] (Gen1) is probed\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rsnd_src_init_gen1(struct rsnd_mod *mod,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = rsnd_src_init(mod);
|
||||
ret = rsnd_src_init(mod, priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -505,11 +577,12 @@ static int rsnd_src_stop_gen1(struct rsnd_mod *mod,
|
|||
|
||||
static struct rsnd_mod_ops rsnd_src_gen1_ops = {
|
||||
.name = SRC_NAME,
|
||||
.probe = rsnd_src_probe_gen1,
|
||||
.dma_req = rsnd_src_dma_req,
|
||||
.init = rsnd_src_init_gen1,
|
||||
.quit = rsnd_src_quit,
|
||||
.start = rsnd_src_start_gen1,
|
||||
.stop = rsnd_src_stop_gen1,
|
||||
.hw_params = rsnd_src_hw_params,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -607,13 +680,17 @@ static irqreturn_t rsnd_src_interrupt_gen2(int irq, void *data)
|
|||
|
||||
if (rsnd_src_error_record_gen2(mod)) {
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_src *src = rsnd_mod_to_src(mod);
|
||||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
|
||||
_rsnd_src_stop_gen2(mod);
|
||||
_rsnd_src_start_gen2(mod);
|
||||
|
||||
dev_dbg(dev, "%s[%d] restart\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
_rsnd_src_stop_gen2(mod);
|
||||
if (src->err < 1024)
|
||||
_rsnd_src_start_gen2(mod);
|
||||
else
|
||||
dev_warn(dev, "no more SRC restart\n");
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
|
@ -627,6 +704,7 @@ static int rsnd_src_set_convert_rate_gen2(struct rsnd_mod *mod)
|
|||
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
|
||||
struct rsnd_src *src = rsnd_mod_to_src(mod);
|
||||
u32 convert_rate = rsnd_src_convert_rate(src);
|
||||
u32 cr, route;
|
||||
uint ratio;
|
||||
int ret;
|
||||
|
||||
|
@ -647,12 +725,20 @@ static int rsnd_src_set_convert_rate_gen2(struct rsnd_mod *mod)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
rsnd_mod_write(mod, SRC_SRCCR, 0x00011110);
|
||||
|
||||
cr = 0x00011110;
|
||||
route = 0x0;
|
||||
if (convert_rate) {
|
||||
/* Gen1/Gen2 are not compatible */
|
||||
rsnd_mod_write(mod, SRC_ROUTE_MODE0, 1);
|
||||
route = 0x1;
|
||||
|
||||
if (rsnd_enable_sync_convert(src)) {
|
||||
cr |= 0x1;
|
||||
route |= rsnd_io_is_play(io) ?
|
||||
(0x1 << 24) : (0x1 << 25);
|
||||
}
|
||||
}
|
||||
|
||||
rsnd_mod_write(mod, SRC_SRCCR, cr);
|
||||
rsnd_mod_write(mod, SRC_ROUTE_MODE0, route);
|
||||
|
||||
switch (rsnd_mod_id(mod)) {
|
||||
case 5:
|
||||
|
@ -708,24 +794,12 @@ static int rsnd_src_probe_gen2(struct rsnd_mod *mod,
|
|||
IRQF_SHARED,
|
||||
dev_name(dev), mod);
|
||||
if (ret)
|
||||
goto rsnd_src_probe_gen2_fail;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = rsnd_dma_init(priv,
|
||||
rsnd_mod_to_dma(mod),
|
||||
rsnd_info_is_playback(priv, src),
|
||||
src->info->dma_id);
|
||||
if (ret)
|
||||
goto rsnd_src_probe_gen2_fail;
|
||||
|
||||
dev_dbg(dev, "%s[%d] (Gen2) is probed\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
return ret;
|
||||
|
||||
rsnd_src_probe_gen2_fail:
|
||||
dev_err(dev, "%s[%d] (Gen2) failed\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -733,7 +807,7 @@ static int rsnd_src_probe_gen2(struct rsnd_mod *mod,
|
|||
static int rsnd_src_remove_gen2(struct rsnd_mod *mod,
|
||||
struct rsnd_priv *priv)
|
||||
{
|
||||
rsnd_dma_quit(priv, rsnd_mod_to_dma(mod));
|
||||
rsnd_dma_quit(rsnd_mod_to_dma(mod));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -743,7 +817,7 @@ static int rsnd_src_init_gen2(struct rsnd_mod *mod,
|
|||
{
|
||||
int ret;
|
||||
|
||||
ret = rsnd_src_init(mod);
|
||||
ret = rsnd_src_init(mod, priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -778,14 +852,91 @@ static int rsnd_src_stop_gen2(struct rsnd_mod *mod,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void rsnd_src_reconvert_update(struct rsnd_mod *mod)
|
||||
{
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
|
||||
struct rsnd_src *src = rsnd_mod_to_src(mod);
|
||||
u32 convert_rate = rsnd_src_convert_rate(src);
|
||||
u32 fsrate;
|
||||
|
||||
if (!runtime)
|
||||
return;
|
||||
|
||||
if (!convert_rate)
|
||||
convert_rate = runtime->rate;
|
||||
|
||||
fsrate = 0x0400000 / convert_rate * runtime->rate;
|
||||
|
||||
/* update IFS */
|
||||
rsnd_mod_write(mod, SRC_IFSVR, fsrate);
|
||||
}
|
||||
|
||||
static int rsnd_src_pcm_new(struct rsnd_mod *mod,
|
||||
struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
|
||||
struct rsnd_src *src = rsnd_mod_to_src(mod);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* enable SRC sync convert if possible
|
||||
*/
|
||||
|
||||
/*
|
||||
* Gen1 is not supported
|
||||
*/
|
||||
if (rsnd_is_gen1(priv))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* SRC sync convert needs clock master
|
||||
*/
|
||||
if (!rsnd_rdai_is_clk_master(rdai))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* We can't use SRC sync convert
|
||||
* if it has DVC
|
||||
*/
|
||||
if (rsnd_io_to_mod_dvc(io))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* enable sync convert
|
||||
*/
|
||||
ret = rsnd_kctrl_new_s(mod, rtd,
|
||||
rsnd_io_is_play(io) ?
|
||||
"SRC Out Rate Switch" :
|
||||
"SRC In Rate Switch",
|
||||
rsnd_src_reconvert_update,
|
||||
&src->sen, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = rsnd_kctrl_new_s(mod, rtd,
|
||||
rsnd_io_is_play(io) ?
|
||||
"SRC Out Rate" :
|
||||
"SRC In Rate",
|
||||
rsnd_src_reconvert_update,
|
||||
&src->sync, 192000);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct rsnd_mod_ops rsnd_src_gen2_ops = {
|
||||
.name = SRC_NAME,
|
||||
.dma_req = rsnd_src_dma_req,
|
||||
.probe = rsnd_src_probe_gen2,
|
||||
.remove = rsnd_src_remove_gen2,
|
||||
.init = rsnd_src_init_gen2,
|
||||
.quit = rsnd_src_quit,
|
||||
.start = rsnd_src_start_gen2,
|
||||
.stop = rsnd_src_stop_gen2,
|
||||
.hw_params = rsnd_src_hw_params,
|
||||
.pcm_new = rsnd_src_pcm_new,
|
||||
};
|
||||
|
||||
struct rsnd_mod *rsnd_src_mod_get(struct rsnd_priv *priv, int id)
|
||||
|
@ -810,7 +961,7 @@ static void rsnd_of_parse_src(struct platform_device *pdev,
|
|||
if (!of_data)
|
||||
return;
|
||||
|
||||
src_node = of_get_child_by_name(dev->of_node, "rcar_sound,src");
|
||||
src_node = rsnd_src_of_node(priv);
|
||||
if (!src_node)
|
||||
return;
|
||||
|
||||
|
@ -893,8 +1044,6 @@ int rsnd_src_probe(struct platform_device *pdev,
|
|||
ret = rsnd_mod_init(&src->mod, ops, clk, RSND_MOD_SRC, i);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_dbg(dev, "SRC%d probed\n", i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -80,13 +80,13 @@ struct rsnd_ssi {
|
|||
#define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod)
|
||||
#define rsnd_dma_to_ssi(dma) rsnd_mod_to_ssi(rsnd_dma_to_mod(dma))
|
||||
#define rsnd_ssi_pio_available(ssi) ((ssi)->info->irq > 0)
|
||||
#define rsnd_ssi_dma_available(ssi) \
|
||||
rsnd_dma_available(rsnd_mod_to_dma(&(ssi)->mod))
|
||||
#define rsnd_ssi_clk_from_parent(ssi) ((ssi)->parent)
|
||||
#define rsnd_ssi_mode_flags(p) ((p)->info->flags)
|
||||
#define rsnd_ssi_dai_id(ssi) ((ssi)->info->dai_id)
|
||||
#define rsnd_ssi_of_node(priv) \
|
||||
of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,ssi")
|
||||
|
||||
static int rsnd_ssi_use_busif(struct rsnd_mod *mod)
|
||||
int rsnd_ssi_use_busif(struct rsnd_mod *mod)
|
||||
{
|
||||
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
|
@ -416,11 +416,14 @@ static irqreturn_t rsnd_ssi_interrupt(int irq, void *data)
|
|||
/*
|
||||
* restart SSI
|
||||
*/
|
||||
rsnd_ssi_stop(mod, priv);
|
||||
rsnd_ssi_start(mod, priv);
|
||||
|
||||
dev_dbg(dev, "%s[%d] restart\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
rsnd_ssi_stop(mod, priv);
|
||||
if (ssi->err < 1024)
|
||||
rsnd_ssi_start(mod, priv);
|
||||
else
|
||||
dev_warn(dev, "no more SSI restart\n");
|
||||
}
|
||||
|
||||
rsnd_ssi_record_error(ssi, status);
|
||||
|
@ -442,12 +445,6 @@ static int rsnd_ssi_pio_probe(struct rsnd_mod *mod,
|
|||
rsnd_ssi_interrupt,
|
||||
IRQF_SHARED,
|
||||
dev_name(dev), ssi);
|
||||
if (ret)
|
||||
dev_err(dev, "%s[%d] (PIO) request interrupt failed\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
else
|
||||
dev_dbg(dev, "%s[%d] (PIO) is probed\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -474,23 +471,11 @@ static int rsnd_ssi_dma_probe(struct rsnd_mod *mod,
|
|||
IRQF_SHARED,
|
||||
dev_name(dev), ssi);
|
||||
if (ret)
|
||||
goto rsnd_ssi_dma_probe_fail;
|
||||
return ret;
|
||||
|
||||
ret = rsnd_dma_init(
|
||||
priv, rsnd_mod_to_dma(mod),
|
||||
rsnd_info_is_playback(priv, ssi),
|
||||
dma_id);
|
||||
if (ret)
|
||||
goto rsnd_ssi_dma_probe_fail;
|
||||
|
||||
dev_dbg(dev, "%s[%d] (DMA) is probed\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
return ret;
|
||||
|
||||
rsnd_ssi_dma_probe_fail:
|
||||
dev_err(dev, "%s[%d] (DMA) is failed\n",
|
||||
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -502,7 +487,7 @@ static int rsnd_ssi_dma_remove(struct rsnd_mod *mod,
|
|||
struct device *dev = rsnd_priv_to_dev(priv);
|
||||
int irq = ssi->info->irq;
|
||||
|
||||
rsnd_dma_quit(priv, rsnd_mod_to_dma(mod));
|
||||
rsnd_dma_quit(rsnd_mod_to_dma(mod));
|
||||
|
||||
/* PIO will request IRQ again */
|
||||
devm_free_irq(dev, irq, ssi);
|
||||
|
@ -554,14 +539,25 @@ static int rsnd_ssi_dma_stop(struct rsnd_mod *mod,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static char *rsnd_ssi_dma_name(struct rsnd_mod *mod)
|
||||
static struct dma_chan *rsnd_ssi_dma_req(struct rsnd_mod *mod)
|
||||
{
|
||||
return rsnd_ssi_use_busif(mod) ? "ssiu" : SSI_NAME;
|
||||
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
||||
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
||||
int is_play = rsnd_io_is_play(io);
|
||||
char *name;
|
||||
|
||||
if (rsnd_ssi_use_busif(mod))
|
||||
name = is_play ? "rxu" : "txu";
|
||||
else
|
||||
name = is_play ? "rx" : "tx";
|
||||
|
||||
return rsnd_dma_request_channel(rsnd_ssi_of_node(priv),
|
||||
mod, name);
|
||||
}
|
||||
|
||||
static struct rsnd_mod_ops rsnd_ssi_dma_ops = {
|
||||
.name = SSI_NAME,
|
||||
.dma_name = rsnd_ssi_dma_name,
|
||||
.dma_req = rsnd_ssi_dma_req,
|
||||
.probe = rsnd_ssi_dma_probe,
|
||||
.remove = rsnd_ssi_dma_remove,
|
||||
.init = rsnd_ssi_init,
|
||||
|
@ -636,7 +632,7 @@ static void rsnd_of_parse_ssi(struct platform_device *pdev,
|
|||
if (!of_data)
|
||||
return;
|
||||
|
||||
node = of_get_child_by_name(dev->of_node, "rcar_sound,ssi");
|
||||
node = rsnd_ssi_of_node(priv);
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
|
|
Loading…
Reference in a new issue