forked from Minki/linux
Merge remote-tracking branch 'asoc/topic/samsung' into asoc-next
This commit is contained in:
commit
848272e9a3
@ -2,8 +2,10 @@ Samsung Exynos Odroid XU3/XU4 audio complex with MAX98090 codec
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible - "samsung,odroidxu3-audio" - for Odroid XU3 board,
|
||||
"samsung,odroidxu4-audio" - for Odroid XU4 board
|
||||
- compatible - "hardkernel,odroid-xu3-audio" - for Odroid XU3 board,
|
||||
"hardkernel,odroid-xu4-audio" - for Odroid XU4 board (deprecated),
|
||||
"samsung,odroid-xu3-audio" - for Odroid XU3 board (deprecated),
|
||||
"samsung,odroid-xu4-audio" - for Odroid XU4 board (deprecated)
|
||||
- model - the user-visible name of this sound complex
|
||||
- clocks - should contain entries matching clock names in the clock-names
|
||||
property
|
||||
@ -35,7 +37,7 @@ Required sub-nodes:
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "samsung,odroidxu3-audio";
|
||||
compatible = "hardkernel,odroid-xu3-audio";
|
||||
model = "Odroid-XU3";
|
||||
samsung,audio-routing =
|
||||
"Headphone Jack", "HPL",
|
||||
|
@ -4,9 +4,13 @@ Required properties:
|
||||
|
||||
- compatible : "samsung,tm2-audio"
|
||||
- model : the user-visible name of this sound complex
|
||||
- audio-codec : the phandle of the wm5110 audio codec node,
|
||||
as described in ../mfd/arizona.txt
|
||||
- i2s-controller : the phandle of the I2S controller
|
||||
- audio-codec : the first entry should be phandle of the wm5110 audio
|
||||
codec node, as described in ../mfd/arizona.txt;
|
||||
the second entry should be phandle of the HDMI
|
||||
transmitter node
|
||||
- i2s-controller : the list of phandle and argument tuples pointing to
|
||||
I2S controllers, the first entry should be I2S0 and
|
||||
the second one I2S1
|
||||
- audio-amplifier : the phandle of the MAX98504 amplifier
|
||||
- samsung,audio-routing : a list of the connections between audio components;
|
||||
each entry is a pair of strings, the first being the
|
||||
@ -22,8 +26,8 @@ Example:
|
||||
|
||||
sound {
|
||||
compatible = "samsung,tm2-audio";
|
||||
audio-codec = <&wm5110>;
|
||||
i2s-controller = <&i2s0>;
|
||||
audio-codec = <&wm5110>, <&hdmi>;
|
||||
i2s-controller = <&i2s0 0>, <&i2s1 0>;
|
||||
audio-amplifier = <&max98504>;
|
||||
mic-bias-gpios = <&gpr3 2 0>;
|
||||
model = "wm5110";
|
||||
|
@ -7,7 +7,7 @@ Required SoC Specific Properties:
|
||||
- samsung,s5pv210-i2s: for 8/16/24bit multichannel(5.1) I2S with
|
||||
secondary fifo, s/w reset control and internal mux for root clk src.
|
||||
- samsung,exynos5420-i2s: for 8/16/24bit multichannel(5.1) I2S for
|
||||
playback, sterio channel capture, secondary fifo using internal
|
||||
playback, stereo channel capture, secondary fifo using internal
|
||||
or external dma, s/w reset control, internal mux for root clk src
|
||||
and 7.1 channel TDM support for playback. TDM (Time division multiplexing)
|
||||
is to allow transfer of multiple channel audio data on single data line.
|
||||
@ -25,7 +25,7 @@ Required SoC Specific Properties:
|
||||
These strings correspond 1:1 with the ordered pairs in dmas.
|
||||
- clocks: Handle to iis clock and RCLK source clk.
|
||||
- clock-names:
|
||||
i2s0 uses some base clks from CMU and some are from audio subsystem internal
|
||||
i2s0 uses some base clocks from CMU and some are from audio subsystem internal
|
||||
clock controller. The clock names for i2s0 should be "iis", "i2s_opclk0" and
|
||||
"i2s_opclk1" as shown in the example below.
|
||||
i2s1 and i2s2 uses clocks from CMU. The clock names for i2s1 and i2s2 should
|
||||
@ -36,9 +36,9 @@ Required SoC Specific Properties:
|
||||
- #clock-cells: should be 1, this property must be present if the I2S device
|
||||
is a clock provider in terms of the common clock bindings, described in
|
||||
../clock/clock-bindings.txt.
|
||||
- clock-output-names: from the common clock bindings, names of the CDCLK
|
||||
I2S output clocks, suggested values are "i2s_cdclk0", "i2s_cdclk1",
|
||||
"i2s_cdclk3" for the I2S0, I2S1, I2S2 devices recpectively.
|
||||
- clock-output-names (deprecated): from the common clock bindings, names of
|
||||
the CDCLK I2S output clocks, suggested values are "i2s_cdclk0", "i2s_cdclk1",
|
||||
"i2s_cdclk3" for the I2S0, I2S1, I2S2 devices respectively.
|
||||
|
||||
There are following clocks available at the I2S device nodes:
|
||||
CLK_I2S_CDCLK - the CDCLK (CODECLKO) gate clock,
|
||||
@ -49,9 +49,10 @@ There are following clocks available at the I2S device nodes:
|
||||
|
||||
Refer to the SoC datasheet for availability of the above clocks.
|
||||
The CLK_I2S_RCLK_PSR and CLK_I2S_RCLK_SRC clocks are usually only available
|
||||
in the IIS Multi Audio Interface (I2S0).
|
||||
Note: Old DTs may not have the #clock-cells, clock-output-names properties
|
||||
and then not use the I2S node as a clock supplier.
|
||||
in the IIS Multi Audio Interface.
|
||||
|
||||
Note: Old DTs may not have the #clock-cells property and then not use the I2S
|
||||
node as a clock supplier.
|
||||
|
||||
Optional SoC Specific Properties:
|
||||
|
||||
@ -59,6 +60,7 @@ Optional SoC Specific Properties:
|
||||
sub system(used in secondary sound source).
|
||||
- pinctrl-0: Should specify pin control groups used for this controller.
|
||||
- pinctrl-names: Should contain only one value - "default".
|
||||
- #sound-dai-cells: should be 1.
|
||||
|
||||
|
||||
Example:
|
||||
@ -74,9 +76,9 @@ i2s0: i2s@3830000 {
|
||||
<&clock_audss EXYNOS_I2S_BUS>,
|
||||
<&clock_audss EXYNOS_SCLK_I2S>;
|
||||
clock-names = "iis", "i2s_opclk0", "i2s_opclk1";
|
||||
#clock-cells;
|
||||
clock-output-names = "i2s_cdclk0";
|
||||
#clock-cells = <1>;
|
||||
samsung,idma-addr = <0x03000000>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&i2s0_bus>;
|
||||
#sound-dai-cells = <1>;
|
||||
};
|
||||
|
@ -5,8 +5,17 @@ Required properties:
|
||||
"google,snow-audio-max98090" or
|
||||
"google,snow-audio-max98091" or
|
||||
"google,snow-audio-max98095"
|
||||
- samsung,i2s-controller: The phandle of the Samsung I2S controller
|
||||
- samsung,audio-codec: The phandle of the audio codec
|
||||
- samsung,i2s-controller (deprecated): The phandle of the Samsung I2S controller
|
||||
- samsung,audio-codec (deprecated): The phandle of the audio codec
|
||||
|
||||
Required sub-nodes:
|
||||
|
||||
- 'cpu' subnode with a 'sound-dai' property containing the phandle of the I2S
|
||||
controller
|
||||
- 'codec' subnode with a 'sound-dai' property containing list of phandles
|
||||
to the CODEC nodes, first entry must be the phandle of the MAX98090,
|
||||
MAX98091 or MAX98095 CODEC (exact device type is indicated by the compatible
|
||||
string) and the second entry must be the phandle of the HDMI IP block node
|
||||
|
||||
Optional:
|
||||
- samsung,model: The name of the sound-card
|
||||
|
@ -1807,6 +1807,7 @@ int snd_soc_of_get_dai_name(struct device_node *of_node,
|
||||
int snd_soc_of_get_dai_link_codecs(struct device *dev,
|
||||
struct device_node *of_node,
|
||||
struct snd_soc_dai_link *dai_link);
|
||||
void snd_soc_of_put_dai_link_codecs(struct snd_soc_dai_link *dai_link);
|
||||
|
||||
int snd_soc_add_dai_link(struct snd_soc_card *card,
|
||||
struct snd_soc_dai_link *dai_link);
|
||||
|
@ -21,8 +21,6 @@ obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-idma.o
|
||||
# S3C24XX Machine Support
|
||||
snd-soc-jive-wm8750-objs := jive_wm8750.o
|
||||
snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o
|
||||
snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o
|
||||
snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o
|
||||
snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o
|
||||
snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o
|
||||
snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
|
||||
@ -32,7 +30,6 @@ snd-soc-rx1950-uda1380-objs := rx1950_uda1380.o
|
||||
snd-soc-smdk-wm8580-objs := smdk_wm8580.o
|
||||
snd-soc-smdk-wm8994-objs := smdk_wm8994.o
|
||||
snd-soc-snow-objs := snow.o
|
||||
snd-soc-smdk-wm9713-objs := smdk_wm9713.o
|
||||
snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o
|
||||
snd-soc-smdk-spdif-objs := smdk_spdif.o
|
||||
snd-soc-smdk-wm8994pcm-objs := smdk_wm8994pcm.o
|
||||
|
@ -65,11 +65,12 @@
|
||||
#define CON_RXDMA_ACTIVE (1 << 1)
|
||||
#define CON_ACTIVE (1 << 0)
|
||||
|
||||
#define MOD_OPCLK_CDCLK_OUT (0 << 30)
|
||||
#define MOD_OPCLK_CDCLK_IN (1 << 30)
|
||||
#define MOD_OPCLK_BCLK_OUT (2 << 30)
|
||||
#define MOD_OPCLK_PCLK (3 << 30)
|
||||
#define MOD_OPCLK_MASK (3 << 30)
|
||||
#define MOD_OPCLK_SHIFT 30
|
||||
#define MOD_OPCLK_CDCLK_OUT (0 << MOD_OPCLK_SHIFT)
|
||||
#define MOD_OPCLK_CDCLK_IN (1 << MOD_OPCLK_SHIFT)
|
||||
#define MOD_OPCLK_BCLK_OUT (2 << MOD_OPCLK_SHIFT)
|
||||
#define MOD_OPCLK_PCLK (3 << MOD_OPCLK_SHIFT)
|
||||
#define MOD_OPCLK_MASK (3 << MOD_OPCLK_SHIFT)
|
||||
#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
|
||||
|
||||
#define MOD_BLCS_SHIFT 26
|
||||
|
@ -489,7 +489,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai,
|
||||
switch (clk_id) {
|
||||
case SAMSUNG_I2S_OPCLK:
|
||||
mask = MOD_OPCLK_MASK;
|
||||
val = dir;
|
||||
val = (dir << MOD_OPCLK_SHIFT) & MOD_OPCLK_MASK;
|
||||
break;
|
||||
case SAMSUNG_I2S_CDCLK:
|
||||
mask = 1 << i2s_regs->cdclkcon_off;
|
||||
@ -656,8 +656,12 @@ static int i2s_set_fmt(struct snd_soc_dai *dai,
|
||||
tmp |= mod_slave;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
/* Set default source clock in Master mode */
|
||||
if (i2s->rclk_srcrate == 0)
|
||||
/*
|
||||
* Set default source clock in Master mode, only when the
|
||||
* CLK_I2S_RCLK_SRC clock is not exposed so we ensure any
|
||||
* clock configuration assigned in DT is not overwritten.
|
||||
*/
|
||||
if (i2s->rclk_srcrate == 0 && i2s->clk_data.clks == NULL)
|
||||
i2s_set_sysclk(dai, SAMSUNG_I2S_RCLKSRC_0,
|
||||
0, SND_SOC_CLOCK_IN);
|
||||
break;
|
||||
@ -881,6 +885,11 @@ static int config_setup(struct i2s_dai *i2s)
|
||||
return 0;
|
||||
|
||||
if (!(i2s->quirks & QUIRK_NO_MUXPSR)) {
|
||||
struct clk *rclksrc = i2s->clk_table[CLK_I2S_RCLK_SRC];
|
||||
|
||||
if (rclksrc && !IS_ERR(rclksrc))
|
||||
i2s->rclk_srcrate = clk_get_rate(rclksrc);
|
||||
|
||||
psr = i2s->rclk_srcrate / i2s->frmclk / rfs;
|
||||
writel(((psr - 1) << 8) | PSR_PSREN, i2s->addr + I2SPSR);
|
||||
dev_dbg(&i2s->pdev->dev,
|
||||
@ -1184,11 +1193,13 @@ static void i2s_unregister_clock_provider(struct platform_device *pdev)
|
||||
|
||||
static int i2s_register_clock_provider(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct i2s_dai *i2s = dev_get_drvdata(dev);
|
||||
const char * const i2s_clk_desc[] = { "cdclk", "rclk_src", "prescaler" };
|
||||
const char *clk_name[2] = { "i2s_opclk0", "i2s_opclk1" };
|
||||
const char *p_names[2] = { NULL };
|
||||
struct device *dev = &pdev->dev;
|
||||
struct i2s_dai *i2s = dev_get_drvdata(dev);
|
||||
const struct samsung_i2s_variant_regs *reg_info = i2s->variant_regs;
|
||||
const char *i2s_clk_name[ARRAY_SIZE(i2s_clk_desc)];
|
||||
struct clk *rclksrc;
|
||||
int ret, i;
|
||||
|
||||
@ -1205,30 +1216,38 @@ static int i2s_register_clock_provider(struct platform_device *pdev)
|
||||
clk_put(rclksrc);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(i2s_clk_desc); i++) {
|
||||
i2s_clk_name[i] = devm_kasprintf(dev, GFP_KERNEL, "%s_%s",
|
||||
dev_name(dev), i2s_clk_desc[i]);
|
||||
if (!i2s_clk_name[i])
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (!(i2s->quirks & QUIRK_NO_MUXPSR)) {
|
||||
/* Activate the prescaler */
|
||||
u32 val = readl(i2s->addr + I2SPSR);
|
||||
writel(val | PSR_PSREN, i2s->addr + I2SPSR);
|
||||
|
||||
i2s->clk_table[CLK_I2S_RCLK_SRC] = clk_register_mux(dev,
|
||||
"i2s_rclksrc", p_names, ARRAY_SIZE(p_names),
|
||||
i2s_clk_name[CLK_I2S_RCLK_SRC], p_names,
|
||||
ARRAY_SIZE(p_names),
|
||||
CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
|
||||
i2s->addr + I2SMOD, reg_info->rclksrc_off,
|
||||
1, 0, i2s->lock);
|
||||
|
||||
i2s->clk_table[CLK_I2S_RCLK_PSR] = clk_register_divider(dev,
|
||||
"i2s_presc", "i2s_rclksrc",
|
||||
i2s_clk_name[CLK_I2S_RCLK_PSR],
|
||||
i2s_clk_name[CLK_I2S_RCLK_SRC],
|
||||
CLK_SET_RATE_PARENT,
|
||||
i2s->addr + I2SPSR, 8, 6, 0, i2s->lock);
|
||||
|
||||
p_names[0] = "i2s_presc";
|
||||
p_names[0] = i2s_clk_name[CLK_I2S_RCLK_PSR];
|
||||
i2s->clk_data.clk_num = 2;
|
||||
}
|
||||
of_property_read_string_index(dev->of_node,
|
||||
"clock-output-names", 0, &clk_name[0]);
|
||||
|
||||
i2s->clk_table[CLK_I2S_CDCLK] = clk_register_gate(dev, clk_name[0],
|
||||
p_names[0], CLK_SET_RATE_PARENT,
|
||||
i2s->clk_table[CLK_I2S_CDCLK] = clk_register_gate(dev,
|
||||
i2s_clk_name[CLK_I2S_CDCLK], p_names[0],
|
||||
CLK_SET_RATE_PARENT,
|
||||
i2s->addr + I2SMOD, reg_info->cdclkcon_off,
|
||||
CLK_GATE_SET_TO_DISABLE, i2s->lock);
|
||||
|
||||
@ -1385,9 +1404,14 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
ret = i2s_register_clock_provider(pdev);
|
||||
if (!ret)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
goto err_disable_pm;
|
||||
|
||||
pri_dai->op_clk = clk_get_parent(pri_dai->clk_table[CLK_I2S_RCLK_SRC]);
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_pm:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
err_disable_clk:
|
||||
clk_disable_unprepare(pri_dai->clk);
|
||||
|
@ -16,11 +16,16 @@
|
||||
#define SAMSUNG_I2S_DAI "samsung-i2s"
|
||||
#define SAMSUNG_I2S_DAI_SEC "samsung-i2s-sec"
|
||||
|
||||
#define SAMSUNG_I2S_DIV_BCLK 1
|
||||
#define SAMSUNG_I2S_DIV_BCLK 1
|
||||
|
||||
#define SAMSUNG_I2S_RCLKSRC_0 0
|
||||
#define SAMSUNG_I2S_RCLKSRC_1 1
|
||||
#define SAMSUNG_I2S_RCLKSRC_0 0
|
||||
#define SAMSUNG_I2S_RCLKSRC_1 1
|
||||
#define SAMSUNG_I2S_CDCLK 2
|
||||
/* Operation clock for IIS logic */
|
||||
#define SAMSUNG_I2S_OPCLK 3
|
||||
#define SAMSUNG_I2S_OPCLK_CDCLK_OUT 0 /* CODEC clock out */
|
||||
#define SAMSUNG_I2S_OPCLK_CDCLK_IN 1 /* CODEC clock in */
|
||||
#define SAMSUNG_I2S_OPCLK_BCLK_OUT 2 /* Bit clock out */
|
||||
#define SAMSUNG_I2S_OPCLK_PCLK 3 /* Audio bus clock */
|
||||
|
||||
#endif /* __SND_SOC_SAMSUNG_I2S_H */
|
||||
|
@ -36,23 +36,24 @@ static int odroid_card_hw_params(struct snd_pcm_substream *substream,
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
||||
unsigned int pll_freq, rclk_freq;
|
||||
unsigned int pll_freq, rclk_freq, rfs;
|
||||
int ret;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 32000:
|
||||
case 64000:
|
||||
pll_freq = 131072006U;
|
||||
pll_freq = 196608001U;
|
||||
rfs = 384;
|
||||
break;
|
||||
case 44100:
|
||||
case 88200:
|
||||
case 176400:
|
||||
pll_freq = 180633609U;
|
||||
rfs = 512;
|
||||
break;
|
||||
case 32000:
|
||||
case 48000:
|
||||
case 96000:
|
||||
case 192000:
|
||||
pll_freq = 196608001U;
|
||||
rfs = 512;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
@ -67,7 +68,7 @@ static int odroid_card_hw_params(struct snd_pcm_substream *substream,
|
||||
* frequency values due to the EPLL output frequency not being exact
|
||||
* multiple of the audio sampling rate.
|
||||
*/
|
||||
rclk_freq = params_rate(params) * 256 + 1;
|
||||
rclk_freq = params_rate(params) * rfs + 1;
|
||||
|
||||
ret = clk_set_rate(priv->sclk_i2s, rclk_freq);
|
||||
if (ret < 0)
|
||||
@ -90,18 +91,6 @@ static const struct snd_soc_ops odroid_card_ops = {
|
||||
.hw_params = odroid_card_hw_params,
|
||||
};
|
||||
|
||||
static void odroid_put_codec_of_nodes(struct snd_soc_dai_link *link)
|
||||
{
|
||||
struct snd_soc_dai_link_component *component = link->codecs;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < link->num_codecs; i++, component++) {
|
||||
if (!component->of_node)
|
||||
break;
|
||||
of_node_put(component->of_node);
|
||||
}
|
||||
}
|
||||
|
||||
static int odroid_audio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
@ -196,7 +185,7 @@ err_put_sclk:
|
||||
err_put_i2s_n:
|
||||
of_node_put(link->cpu_of_node);
|
||||
err_put_codec_n:
|
||||
odroid_put_codec_of_nodes(link);
|
||||
snd_soc_of_put_dai_link_codecs(link);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -205,7 +194,7 @@ static int odroid_audio_remove(struct platform_device *pdev)
|
||||
struct odroid_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
of_node_put(priv->dai_link.cpu_of_node);
|
||||
odroid_put_codec_of_nodes(&priv->dai_link);
|
||||
snd_soc_of_put_dai_link_codecs(&priv->dai_link);
|
||||
clk_put(priv->sclk_i2s);
|
||||
clk_put(priv->clk_i2s_bus);
|
||||
|
||||
@ -213,8 +202,10 @@ static int odroid_audio_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct of_device_id odroid_audio_of_match[] = {
|
||||
{ .compatible = "hardkernel,odroid-xu3-audio" },
|
||||
{ .compatible = "hardkernel,odroid-xu4-audio" },
|
||||
{ .compatible = "samsung,odroid-xu3-audio" },
|
||||
{ .compatible = "samsung,odroid-xu4-audio"},
|
||||
{ .compatible = "samsung,odroid-xu4-audio" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, odroid_audio_of_match);
|
||||
|
@ -11,97 +11,207 @@
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "i2s.h"
|
||||
|
||||
#define FIN_PLL_RATE 24000000
|
||||
|
||||
static struct snd_soc_dai_link snow_dai[] = {
|
||||
{
|
||||
.name = "Primary",
|
||||
.stream_name = "Primary",
|
||||
.codec_dai_name = "HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS,
|
||||
},
|
||||
struct snow_priv {
|
||||
struct snd_soc_dai_link dai_link;
|
||||
struct clk *clk_i2s_bus;
|
||||
};
|
||||
|
||||
static int snow_card_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
static const unsigned int pll_rate[] = {
|
||||
73728000U, 67737602U, 49152000U, 45158401U, 32768001U
|
||||
};
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snow_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
||||
int bfs, psr, rfs, bitwidth;
|
||||
unsigned long int rclk;
|
||||
long int freq = -EINVAL;
|
||||
int ret, i;
|
||||
|
||||
bitwidth = snd_pcm_format_width(params_format(params));
|
||||
if (bitwidth < 0) {
|
||||
dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth);
|
||||
return bitwidth;
|
||||
}
|
||||
|
||||
if (bitwidth != 16 && bitwidth != 24) {
|
||||
dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bfs = 2 * bitwidth;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 16000:
|
||||
case 22050:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
case 88200:
|
||||
case 96000:
|
||||
rfs = 8 * bfs;
|
||||
break;
|
||||
case 64000:
|
||||
rfs = 384;
|
||||
break;
|
||||
case 8000:
|
||||
case 11025:
|
||||
case 12000:
|
||||
rfs = 16 * bfs;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rclk = params_rate(params) * rfs;
|
||||
|
||||
for (psr = 8; psr > 0; psr /= 2) {
|
||||
for (i = 0; i < ARRAY_SIZE(pll_rate); i++) {
|
||||
if ((pll_rate[i] - rclk * psr) <= 2) {
|
||||
freq = pll_rate[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (freq < 0) {
|
||||
dev_err(rtd->card->dev, "Unsupported RCLK rate: %lu\n", rclk);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = clk_set_rate(priv->clk_i2s_bus, freq);
|
||||
if (ret < 0) {
|
||||
dev_err(rtd->card->dev, "I2S bus clock rate set failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_ops snow_card_ops = {
|
||||
.hw_params = snow_card_hw_params,
|
||||
};
|
||||
|
||||
static int snow_late_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd;
|
||||
struct snd_soc_dai *codec_dai;
|
||||
struct snd_soc_dai *cpu_dai;
|
||||
int ret;
|
||||
|
||||
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
|
||||
codec_dai = rtd->codec_dai;
|
||||
cpu_dai = rtd->cpu_dai;
|
||||
|
||||
/* In the multi-codec case codec_dais 0 is MAX98095 and 1 is HDMI. */
|
||||
if (rtd->num_codecs > 1)
|
||||
codec_dai = rtd->codec_dais[0];
|
||||
else
|
||||
codec_dai = rtd->codec_dai;
|
||||
|
||||
/* Set the MCLK rate for the codec */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
|
||||
FIN_PLL_RATE, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Select I2S Bus clock to set RCLK and BCLK */
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0,
|
||||
0, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
return snd_soc_dai_set_sysclk(codec_dai, 0,
|
||||
FIN_PLL_RATE, SND_SOC_CLOCK_IN);
|
||||
}
|
||||
|
||||
static struct snd_soc_card snow_snd = {
|
||||
.name = "Snow-I2S",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snow_dai,
|
||||
.num_links = ARRAY_SIZE(snow_dai),
|
||||
|
||||
.late_probe = snow_late_probe,
|
||||
};
|
||||
|
||||
static int snow_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct snd_soc_card *card = &snow_snd;
|
||||
struct device_node *i2s_node, *codec_node;
|
||||
int i, ret;
|
||||
struct device_node *cpu, *codec;
|
||||
struct snd_soc_dai_link *link;
|
||||
struct snow_priv *priv;
|
||||
int ret;
|
||||
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"samsung,i2s-controller", 0);
|
||||
if (!i2s_node) {
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'i2s-controller' missing or invalid\n");
|
||||
return -EINVAL;
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
link = &priv->dai_link;
|
||||
|
||||
link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
link->name = "Primary";
|
||||
link->stream_name = link->name;
|
||||
|
||||
card->dai_link = link;
|
||||
card->num_links = 1;
|
||||
card->dev = dev;
|
||||
|
||||
/* Try new DT bindings with HDMI support first. */
|
||||
cpu = of_get_child_by_name(dev->of_node, "cpu");
|
||||
|
||||
if (cpu) {
|
||||
link->ops = &snow_card_ops;
|
||||
|
||||
link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0);
|
||||
of_node_put(cpu);
|
||||
|
||||
if (!link->cpu_of_node) {
|
||||
dev_err(dev, "Failed parsing cpu/sound-dai property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec = of_get_child_by_name(dev->of_node, "codec");
|
||||
ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
|
||||
of_node_put(codec);
|
||||
|
||||
if (ret < 0) {
|
||||
of_node_put(link->cpu_of_node);
|
||||
dev_err(dev, "Failed parsing codec node\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
priv->clk_i2s_bus = of_clk_get_by_name(link->cpu_of_node,
|
||||
"i2s_opclk0");
|
||||
if (IS_ERR(priv->clk_i2s_bus)) {
|
||||
snd_soc_of_put_dai_link_codecs(link);
|
||||
of_node_put(link->cpu_of_node);
|
||||
return PTR_ERR(priv->clk_i2s_bus);
|
||||
}
|
||||
} else {
|
||||
link->codec_dai_name = "HiFi",
|
||||
|
||||
link->cpu_of_node = of_parse_phandle(dev->of_node,
|
||||
"samsung,i2s-controller", 0);
|
||||
if (!link->cpu_of_node) {
|
||||
dev_err(dev, "i2s-controller property parse error\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
link->codec_of_node = of_parse_phandle(dev->of_node,
|
||||
"samsung,audio-codec", 0);
|
||||
if (!link->codec_of_node) {
|
||||
of_node_put(link->cpu_of_node);
|
||||
dev_err(dev, "audio-codec property parse error\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
codec_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"samsung,audio-codec", 0);
|
||||
if (!codec_node) {
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'audio-codec' missing or invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snow_dai); i++) {
|
||||
snow_dai[i].codec_of_node = codec_node;
|
||||
snow_dai[i].cpu_of_node = i2s_node;
|
||||
snow_dai[i].platform_of_node = i2s_node;
|
||||
}
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
link->platform_of_node = link->cpu_of_node;
|
||||
|
||||
/* Update card-name if provided through DT, else use default name */
|
||||
snd_soc_of_parse_card_name(card, "samsung,model");
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, card);
|
||||
snd_soc_card_set_drvdata(card, priv);
|
||||
|
||||
ret = devm_snd_soc_register_card(dev, card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
return ret;
|
||||
@ -110,6 +220,20 @@ static int snow_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snow_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snow_priv *priv = platform_get_drvdata(pdev);
|
||||
struct snd_soc_dai_link *link = &priv->dai_link;
|
||||
|
||||
of_node_put(link->cpu_of_node);
|
||||
of_node_put(link->codec_of_node);
|
||||
snd_soc_of_put_dai_link_codecs(link);
|
||||
|
||||
clk_put(priv->clk_i2s_bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id snow_of_match[] = {
|
||||
{ .compatible = "google,snow-audio-max98090", },
|
||||
{ .compatible = "google,snow-audio-max98091", },
|
||||
@ -125,6 +249,7 @@ static struct platform_driver snow_driver = {
|
||||
.of_match_table = snow_of_match,
|
||||
},
|
||||
.probe = snow_probe,
|
||||
.remove = snow_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(snow_driver);
|
||||
|
@ -210,6 +210,59 @@ static struct snd_soc_ops tm2_aif2_ops = {
|
||||
.hw_free = tm2_aif2_hw_free,
|
||||
};
|
||||
|
||||
static int tm2_hdmi_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
unsigned int bfs;
|
||||
int bitwidth, ret;
|
||||
|
||||
bitwidth = snd_pcm_format_width(params_format(params));
|
||||
if (bitwidth < 0) {
|
||||
dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth);
|
||||
return bitwidth;
|
||||
}
|
||||
|
||||
switch (bitwidth) {
|
||||
case 48:
|
||||
bfs = 64;
|
||||
break;
|
||||
case 16:
|
||||
bfs = 32;
|
||||
break;
|
||||
default:
|
||||
dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 48000:
|
||||
case 96000:
|
||||
case 192000:
|
||||
break;
|
||||
default:
|
||||
dev_err(rtd->card->dev, "Unsupported sample rate: %d\n",
|
||||
params_rate(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
|
||||
0, SAMSUNG_I2S_OPCLK_PCLK);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops tm2_hdmi_ops = {
|
||||
.hw_params = tm2_hdmi_hw_params,
|
||||
};
|
||||
|
||||
static int tm2_mic_bias(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
@ -405,6 +458,12 @@ static struct snd_soc_dai_link tm2_dai_links[] = {
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM,
|
||||
.ignore_suspend = 1,
|
||||
}, {
|
||||
.name = "HDMI",
|
||||
.stream_name = "i2s1",
|
||||
.ops = &tm2_hdmi_ops,
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS,
|
||||
}
|
||||
};
|
||||
|
||||
@ -412,7 +471,6 @@ static struct snd_soc_card tm2_card = {
|
||||
.owner = THIS_MODULE,
|
||||
|
||||
.dai_link = tm2_dai_links,
|
||||
.num_links = ARRAY_SIZE(tm2_dai_links),
|
||||
.controls = tm2_controls,
|
||||
.num_controls = ARRAY_SIZE(tm2_controls),
|
||||
.dapm_widgets = tm2_dapm_widgets,
|
||||
@ -426,11 +484,14 @@ static struct snd_soc_card tm2_card = {
|
||||
|
||||
static int tm2_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_dai_node[2] = {};
|
||||
struct device_node *codec_dai_node[2] = {};
|
||||
const char *cells_name = NULL;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct snd_soc_card *card = &tm2_card;
|
||||
struct tm2_machine_priv *priv;
|
||||
struct device_node *cpu_dai_node, *codec_dai_node;
|
||||
int ret, i;
|
||||
struct of_phandle_args args;
|
||||
int num_codecs, ret, i;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
@ -464,47 +525,92 @@ static int tm2_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cpu_dai_node = of_parse_phandle(dev->of_node, "i2s-controller", 0);
|
||||
if (!cpu_dai_node) {
|
||||
dev_err(dev, "i2s-controllers property invalid or missing\n");
|
||||
ret = -EINVAL;
|
||||
goto amp_node_put;
|
||||
num_codecs = of_count_phandle_with_args(dev->of_node, "audio-codec",
|
||||
NULL);
|
||||
|
||||
/* Skip the HDMI link if not specified in DT */
|
||||
if (num_codecs > 1) {
|
||||
card->num_links = ARRAY_SIZE(tm2_dai_links);
|
||||
cells_name = "#sound-dai-cells";
|
||||
} else {
|
||||
card->num_links = ARRAY_SIZE(tm2_dai_links) - 1;
|
||||
}
|
||||
|
||||
codec_dai_node = of_parse_phandle(dev->of_node, "audio-codec", 0);
|
||||
if (!codec_dai_node) {
|
||||
dev_err(dev, "audio-codec property invalid or missing\n");
|
||||
ret = -EINVAL;
|
||||
goto cpu_dai_node_put;
|
||||
for (i = 0; i < num_codecs; i++) {
|
||||
struct of_phandle_args args;
|
||||
|
||||
ret = of_parse_phandle_with_args(dev->of_node, "i2s-controller",
|
||||
cells_name, i, &args);
|
||||
if (!args.np) {
|
||||
dev_err(dev, "i2s-controller property parse error: %d\n", i);
|
||||
ret = -EINVAL;
|
||||
goto dai_node_put;
|
||||
}
|
||||
cpu_dai_node[i] = args.np;
|
||||
|
||||
codec_dai_node[i] = of_parse_phandle(dev->of_node,
|
||||
"audio-codec", i);
|
||||
if (!codec_dai_node[i]) {
|
||||
dev_err(dev, "audio-codec property parse error\n");
|
||||
ret = -EINVAL;
|
||||
goto dai_node_put;
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize WM5110 - I2S and HDMI - I2S1 DAI links */
|
||||
for (i = 0; i < card->num_links; i++) {
|
||||
unsigned int dai_index = 0; /* WM5110 */
|
||||
|
||||
card->dai_link[i].cpu_name = NULL;
|
||||
card->dai_link[i].platform_name = NULL;
|
||||
card->dai_link[i].codec_of_node = codec_dai_node;
|
||||
card->dai_link[i].cpu_of_node = cpu_dai_node;
|
||||
card->dai_link[i].platform_of_node = cpu_dai_node;
|
||||
|
||||
if (num_codecs > 1 && i == card->num_links - 1)
|
||||
dai_index = 1; /* HDMI */
|
||||
|
||||
card->dai_link[i].codec_of_node = codec_dai_node[dai_index];
|
||||
card->dai_link[i].cpu_of_node = cpu_dai_node[dai_index];
|
||||
card->dai_link[i].platform_of_node = cpu_dai_node[dai_index];
|
||||
}
|
||||
|
||||
if (num_codecs > 1) {
|
||||
/* HDMI DAI link (I2S1) */
|
||||
i = card->num_links - 1;
|
||||
|
||||
ret = of_parse_phandle_with_fixed_args(dev->of_node,
|
||||
"audio-codec", 0, 1, &args);
|
||||
if (ret) {
|
||||
dev_err(dev, "audio-codec property parse error\n");
|
||||
goto dai_node_put;
|
||||
}
|
||||
|
||||
ret = snd_soc_get_dai_name(&args, &card->dai_link[i].codec_dai_name);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to get codec_dai_name\n");
|
||||
goto dai_node_put;
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_component(dev, &tm2_component,
|
||||
tm2_ext_dai, ARRAY_SIZE(tm2_ext_dai));
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to register component: %d\n", ret);
|
||||
goto codec_dai_node_put;
|
||||
goto dai_node_put;
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(dev, card);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to register card: %d\n", ret);
|
||||
goto codec_dai_node_put;
|
||||
goto dai_node_put;
|
||||
}
|
||||
|
||||
dai_node_put:
|
||||
for (i = 0; i < num_codecs; i++) {
|
||||
of_node_put(codec_dai_node[i]);
|
||||
of_node_put(cpu_dai_node[i]);
|
||||
}
|
||||
|
||||
codec_dai_node_put:
|
||||
of_node_put(codec_dai_node);
|
||||
cpu_dai_node_put:
|
||||
of_node_put(cpu_dai_node);
|
||||
amp_node_put:
|
||||
of_node_put(card->aux_dev[0].codec_of_node);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -4356,6 +4356,26 @@ int snd_soc_of_get_dai_name(struct device_node *of_node,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_name);
|
||||
|
||||
/*
|
||||
* snd_soc_of_put_dai_link_codecs - Dereference device nodes in the codecs array
|
||||
* @dai_link: DAI link
|
||||
*
|
||||
* Dereference device nodes acquired by snd_soc_of_get_dai_link_codecs().
|
||||
*/
|
||||
void snd_soc_of_put_dai_link_codecs(struct snd_soc_dai_link *dai_link)
|
||||
{
|
||||
struct snd_soc_dai_link_component *component = dai_link->codecs;
|
||||
int index;
|
||||
|
||||
for (index = 0; index < dai_link->num_codecs; index++, component++) {
|
||||
if (!component->of_node)
|
||||
break;
|
||||
of_node_put(component->of_node);
|
||||
component->of_node = NULL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_of_put_dai_link_codecs);
|
||||
|
||||
/*
|
||||
* snd_soc_of_get_dai_link_codecs - Parse a list of CODECs in the devicetree
|
||||
* @dev: Card device
|
||||
@ -4365,7 +4385,8 @@ EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_name);
|
||||
* Builds an array of CODEC DAI components from the DAI link property
|
||||
* 'sound-dai'.
|
||||
* The array is set in the DAI link and the number of DAIs is set accordingly.
|
||||
* The device nodes in the array (of_node) must be dereferenced by the caller.
|
||||
* The device nodes in the array (of_node) must be dereferenced by calling
|
||||
* snd_soc_of_put_dai_link_codecs() on @dai_link.
|
||||
*
|
||||
* Returns 0 for success
|
||||
*/
|
||||
@ -4413,14 +4434,7 @@ int snd_soc_of_get_dai_link_codecs(struct device *dev,
|
||||
}
|
||||
return 0;
|
||||
err:
|
||||
for (index = 0, component = dai_link->codecs;
|
||||
index < dai_link->num_codecs;
|
||||
index++, component++) {
|
||||
if (!component->of_node)
|
||||
break;
|
||||
of_node_put(component->of_node);
|
||||
component->of_node = NULL;
|
||||
}
|
||||
snd_soc_of_put_dai_link_codecs(dai_link);
|
||||
dai_link->codecs = NULL;
|
||||
dai_link->num_codecs = 0;
|
||||
return ret;
|
||||
|
Loading…
Reference in New Issue
Block a user