1 // SPDX-License-Identifier: GPL-2.0+
3 // Copyright (c) 2014, Insignal Co., Ltd.
5 // Author: Claude <claude@insginal.co.kr>
7 #include <linux/module.h>
8 #include <linux/of_device.h>
9 #include <linux/platform_device.h>
10 #include <linux/clk.h>
12 #include <sound/soc.h>
13 #include <sound/soc-dapm.h>
14 #include <sound/pcm.h>
15 #include <sound/pcm_params.h>
17 #include "../codecs/wm8994.h"
20 static int arndale_rt5631_hw_params(struct snd_pcm_substream
*substream
,
21 struct snd_pcm_hw_params
*params
)
23 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
24 struct snd_soc_dai
*cpu_dai
= rtd
->cpu_dai
;
25 struct snd_soc_dai
*codec_dai
= rtd
->codec_dai
;
31 rclk
= params_rate(params
) * rfs
;
33 ret
= snd_soc_dai_set_sysclk(cpu_dai
, SAMSUNG_I2S_CDCLK
,
34 0, SND_SOC_CLOCK_OUT
);
38 ret
= snd_soc_dai_set_sysclk(cpu_dai
, SAMSUNG_I2S_RCLKSRC_0
,
39 0, SND_SOC_CLOCK_OUT
);
44 ret
= snd_soc_dai_set_sysclk(codec_dai
, 0, rclk
, SND_SOC_CLOCK_OUT
);
51 static struct snd_soc_ops arndale_rt5631_ops
= {
52 .hw_params
= arndale_rt5631_hw_params
,
55 static int arndale_wm1811_hw_params(struct snd_pcm_substream
*substream
,
56 struct snd_pcm_hw_params
*params
)
58 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
59 struct snd_soc_dai
*codec_dai
= rtd
->codec_dai
;
60 unsigned int rfs
, rclk
;
62 /* Ensure AIF1CLK is >= 3 MHz for optimal performance */
63 if (params_width(params
) == 24)
65 else if (params_rate(params
) == 8000 || params_rate(params
) == 11025)
70 rclk
= params_rate(params
) * rfs
;
73 * We add 1 to the frequency value to ensure proper EPLL setting
74 * for each audio sampling rate (see epll_24mhz_tbl in drivers/clk/
75 * samsung/clk-exynos5250.c for list of available EPLL rates).
76 * The CODEC uses clk API and the value will be rounded hence the MCLK1
77 * clock's frequency will still be exact multiple of the sample rate.
79 return snd_soc_dai_set_sysclk(codec_dai
, WM8994_SYSCLK_MCLK1
,
80 rclk
+ 1, SND_SOC_CLOCK_IN
);
83 static struct snd_soc_ops arndale_wm1811_ops
= {
84 .hw_params
= arndale_wm1811_hw_params
,
87 SND_SOC_DAILINK_DEFS(rt5631_hifi
,
88 DAILINK_COMP_ARRAY(COMP_EMPTY()),
89 DAILINK_COMP_ARRAY(COMP_CODEC(NULL
, "rt5631-aif1")),
90 DAILINK_COMP_ARRAY(COMP_EMPTY()));
92 static struct snd_soc_dai_link arndale_rt5631_dai
[] = {
94 .name
= "RT5631 HiFi",
95 .stream_name
= "Primary",
96 .dai_fmt
= SND_SOC_DAIFMT_I2S
97 | SND_SOC_DAIFMT_NB_NF
98 | SND_SOC_DAIFMT_CBS_CFS
,
99 .ops
= &arndale_rt5631_ops
,
100 SND_SOC_DAILINK_REG(rt5631_hifi
),
104 SND_SOC_DAILINK_DEFS(wm1811_hifi
,
105 DAILINK_COMP_ARRAY(COMP_EMPTY()),
106 DAILINK_COMP_ARRAY(COMP_CODEC(NULL
, "wm8994-aif1")),
107 DAILINK_COMP_ARRAY(COMP_EMPTY()));
109 static struct snd_soc_dai_link arndale_wm1811_dai
[] = {
111 .name
= "WM1811 HiFi",
112 .stream_name
= "Primary",
113 .dai_fmt
= SND_SOC_DAIFMT_I2S
114 | SND_SOC_DAIFMT_NB_NF
115 | SND_SOC_DAIFMT_CBM_CFM
,
116 .ops
= &arndale_wm1811_ops
,
117 SND_SOC_DAILINK_REG(wm1811_hifi
),
121 static struct snd_soc_card arndale_rt5631
= {
122 .name
= "Arndale RT5631",
123 .owner
= THIS_MODULE
,
124 .dai_link
= arndale_rt5631_dai
,
125 .num_links
= ARRAY_SIZE(arndale_rt5631_dai
),
128 static struct snd_soc_card arndale_wm1811
= {
129 .name
= "Arndale WM1811",
130 .owner
= THIS_MODULE
,
131 .dai_link
= arndale_wm1811_dai
,
132 .num_links
= ARRAY_SIZE(arndale_wm1811_dai
),
135 static void arndale_put_of_nodes(struct snd_soc_card
*card
)
137 struct snd_soc_dai_link
*dai_link
;
140 for_each_card_prelinks(card
, i
, dai_link
) {
141 of_node_put(dai_link
->cpus
->of_node
);
142 of_node_put(dai_link
->codecs
->of_node
);
146 static int arndale_audio_probe(struct platform_device
*pdev
)
148 struct device_node
*np
= pdev
->dev
.of_node
;
149 struct snd_soc_card
*card
;
150 struct snd_soc_dai_link
*dai_link
;
153 card
= (struct snd_soc_card
*)of_device_get_match_data(&pdev
->dev
);
154 card
->dev
= &pdev
->dev
;
155 dai_link
= card
->dai_link
;
157 dai_link
->cpus
->of_node
= of_parse_phandle(np
, "samsung,audio-cpu", 0);
158 if (!dai_link
->cpus
->of_node
) {
160 "Property 'samsung,audio-cpu' missing or invalid\n");
164 if (!dai_link
->platforms
->name
)
165 dai_link
->platforms
->of_node
= dai_link
->cpus
->of_node
;
167 dai_link
->codecs
->of_node
= of_parse_phandle(np
, "samsung,audio-codec", 0);
168 if (!dai_link
->codecs
->of_node
) {
170 "Property 'samsung,audio-codec' missing or invalid\n");
172 goto err_put_of_nodes
;
175 ret
= devm_snd_soc_register_card(card
->dev
, card
);
177 dev_err(&pdev
->dev
, "snd_soc_register_card() failed: %d\n", ret
);
178 goto err_put_of_nodes
;
183 arndale_put_of_nodes(card
);
187 static int arndale_audio_remove(struct platform_device
*pdev
)
189 struct snd_soc_card
*card
= platform_get_drvdata(pdev
);
191 arndale_put_of_nodes(card
);
195 static const struct of_device_id arndale_audio_of_match
[] = {
196 { .compatible
= "samsung,arndale-rt5631", .data
= &arndale_rt5631
},
197 { .compatible
= "samsung,arndale-alc5631", .data
= &arndale_rt5631
},
198 { .compatible
= "samsung,arndale-wm1811", .data
= &arndale_wm1811
},
201 MODULE_DEVICE_TABLE(of
, arndale_audio_of_match
);
203 static struct platform_driver arndale_audio_driver
= {
205 .name
= "arndale-audio",
206 .pm
= &snd_soc_pm_ops
,
207 .of_match_table
= arndale_audio_of_match
,
209 .probe
= arndale_audio_probe
,
210 .remove
= arndale_audio_remove
,
213 module_platform_driver(arndale_audio_driver
);
215 MODULE_AUTHOR("Claude <claude@insignal.co.kr>");
216 MODULE_DESCRIPTION("ALSA SoC Driver for Arndale Board");
217 MODULE_LICENSE("GPL");