提交 1fa1e073 编写于 作者: M Mark Brown

Merge remote-tracking branches 'asoc/topic/omap', 'asoc/topic/qcom',...

Merge remote-tracking branches 'asoc/topic/omap', 'asoc/topic/qcom', 'asoc/topic/rcar' and 'asoc/topic/rt286' into asoc-next
* 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";
};
};
......
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>;
};
};
* 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;
......
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.
# 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
/*
* 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");
/*
* 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__ */
/*
* 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");
/*
* 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__ */
/*
* 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
##
......
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->ops->dma_name)
return mod->ops->name;
if (!mod || !mod->ops || !mod->ops->dma_req)
return NULL;
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,
......
/*
* 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 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;
enum dma_transfer_direction dir;
dma_addr_t addr;
};
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
......
/*
* 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,13 +725,21 @@ 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:
case 6:
......@@ -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;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册