Skip to content

Commit 6ae9ca9

Browse files
jbrun3tbroonie
authored andcommitted
ASoC: meson: aiu: add i2s and spdif support
Add support for the i2s and spdif audio outputs (AIU) found in the amlogic Gx SoC family Signed-off-by: Jerome Brunet <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mark Brown <[email protected]>
1 parent 06b7282 commit 6ae9ca9

10 files changed

Lines changed: 1589 additions & 0 deletions

File tree

sound/soc/meson/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
menu "ASoC support for Amlogic platforms"
33
depends on ARCH_MESON || COMPILE_TEST
44

5+
config SND_MESON_AIU
6+
tristate "Amlogic AIU"
7+
select SND_PCM_IEC958
8+
help
9+
Select Y or M to add support for the Audio output subsystem found
10+
in the Amlogic GX SoC family
11+
512
config SND_MESON_AXG_FIFO
613
tristate
714
select REGMAP_MMIO

sound/soc/meson/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# SPDX-License-Identifier: (GPL-2.0 OR MIT)
22

3+
snd-soc-meson-aiu-objs := aiu.o
4+
snd-soc-meson-aiu-objs += aiu-encoder-i2s.o
5+
snd-soc-meson-aiu-objs += aiu-encoder-spdif.o
6+
snd-soc-meson-aiu-objs += aiu-fifo.o
7+
snd-soc-meson-aiu-objs += aiu-fifo-i2s.o
8+
snd-soc-meson-aiu-objs += aiu-fifo-spdif.o
39
snd-soc-meson-axg-fifo-objs := axg-fifo.o
410
snd-soc-meson-axg-frddr-objs := axg-frddr.o
511
snd-soc-meson-axg-toddr-objs := axg-toddr.o
@@ -14,6 +20,7 @@ snd-soc-meson-axg-pdm-objs := axg-pdm.o
1420
snd-soc-meson-codec-glue-objs := meson-codec-glue.o
1521
snd-soc-meson-g12a-tohdmitx-objs := g12a-tohdmitx.o
1622

23+
obj-$(CONFIG_SND_MESON_AIU) += snd-soc-meson-aiu.o
1724
obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
1825
obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
1926
obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o

sound/soc/meson/aiu-encoder-i2s.c

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
//
3+
// Copyright (c) 2020 BayLibre, SAS.
4+
// Author: Jerome Brunet <[email protected]>
5+
6+
#include <linux/bitfield.h>
7+
#include <linux/clk.h>
8+
#include <sound/pcm_params.h>
9+
#include <sound/soc.h>
10+
#include <sound/soc-dai.h>
11+
12+
#include "aiu.h"
13+
14+
#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)
15+
#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)
16+
#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9)
17+
#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11)
18+
#define AIU_RST_SOFT_I2S_FAST BIT(0)
19+
20+
#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2)
21+
#define AIU_I2S_MISC_HOLD_EN BIT(2)
22+
#define AIU_CLK_CTRL_I2S_DIV_EN BIT(0)
23+
#define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2)
24+
#define AIU_CLK_CTRL_AOCLK_INVERT BIT(6)
25+
#define AIU_CLK_CTRL_LRCLK_INVERT BIT(7)
26+
#define AIU_CLK_CTRL_LRCLK_SKEW GENMASK(9, 8)
27+
#define AIU_CLK_CTRL_MORE_HDMI_AMCLK BIT(6)
28+
#define AIU_CLK_CTRL_MORE_I2S_DIV GENMASK(5, 0)
29+
#define AIU_CODEC_DAC_LRCLK_CTRL_DIV GENMASK(11, 0)
30+
31+
struct aiu_encoder_i2s {
32+
struct clk *aoclk;
33+
struct clk *mclk;
34+
struct clk *mixer;
35+
struct clk *pclk;
36+
};
37+
38+
static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component,
39+
bool enable)
40+
{
41+
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
42+
AIU_CLK_CTRL_I2S_DIV_EN,
43+
enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0);
44+
}
45+
46+
static void aiu_encoder_i2s_hold(struct snd_soc_component *component,
47+
bool enable)
48+
{
49+
snd_soc_component_update_bits(component, AIU_I2S_MISC,
50+
AIU_I2S_MISC_HOLD_EN,
51+
enable ? AIU_I2S_MISC_HOLD_EN : 0);
52+
}
53+
54+
static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
55+
struct snd_soc_dai *dai)
56+
{
57+
struct snd_soc_component *component = dai->component;
58+
59+
switch (cmd) {
60+
case SNDRV_PCM_TRIGGER_START:
61+
case SNDRV_PCM_TRIGGER_RESUME:
62+
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
63+
aiu_encoder_i2s_hold(component, false);
64+
return 0;
65+
66+
case SNDRV_PCM_TRIGGER_STOP:
67+
case SNDRV_PCM_TRIGGER_SUSPEND:
68+
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
69+
aiu_encoder_i2s_hold(component, true);
70+
return 0;
71+
72+
default:
73+
return -EINVAL;
74+
}
75+
}
76+
77+
static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,
78+
struct snd_pcm_hw_params *params)
79+
{
80+
/* Always operate in split (classic interleaved) mode */
81+
unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;
82+
unsigned int val;
83+
84+
/* Reset required to update the pipeline */
85+
snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);
86+
snd_soc_component_read(component, AIU_I2S_SYNC, &val);
87+
88+
switch (params_physical_width(params)) {
89+
case 16: /* Nothing to do */
90+
break;
91+
92+
case 32:
93+
desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |
94+
AIU_I2S_SOURCE_DESC_MODE_32BIT);
95+
break;
96+
97+
default:
98+
return -EINVAL;
99+
}
100+
101+
switch (params_channels(params)) {
102+
case 2: /* Nothing to do */
103+
break;
104+
case 8:
105+
desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;
106+
break;
107+
default:
108+
return -EINVAL;
109+
}
110+
111+
snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC,
112+
AIU_I2S_SOURCE_DESC_MODE_8CH |
113+
AIU_I2S_SOURCE_DESC_MODE_24BIT |
114+
AIU_I2S_SOURCE_DESC_MODE_32BIT |
115+
AIU_I2S_SOURCE_DESC_MODE_SPLIT,
116+
desc);
117+
118+
return 0;
119+
}
120+
121+
static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,
122+
struct snd_pcm_hw_params *params)
123+
{
124+
struct aiu *aiu = snd_soc_component_get_drvdata(component);
125+
unsigned int srate = params_rate(params);
126+
unsigned int fs, bs;
127+
128+
/* Get the oversampling factor */
129+
fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate);
130+
131+
if (fs % 64)
132+
return -EINVAL;
133+
134+
/* Send data MSB first */
135+
snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG,
136+
AIU_I2S_DAC_CFG_MSB_FIRST,
137+
AIU_I2S_DAC_CFG_MSB_FIRST);
138+
139+
/* Set bclk to lrlck ratio */
140+
snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL,
141+
AIU_CODEC_DAC_LRCLK_CTRL_DIV,
142+
FIELD_PREP(AIU_CODEC_DAC_LRCLK_CTRL_DIV,
143+
64 - 1));
144+
145+
/* Use CLK_MORE for mclk to bclk divider */
146+
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
147+
AIU_CLK_CTRL_I2S_DIV, 0);
148+
149+
/*
150+
* NOTE: this HW is odd.
151+
* In most configuration, the i2s divider is 'mclk / blck'.
152+
* However, in 16 bits - 8ch mode, this factor needs to be
153+
* increased by 50% to get the correct output rate.
154+
* No idea why !
155+
*/
156+
bs = fs / 64;
157+
if (params_width(params) == 16 && params_channels(params) == 8) {
158+
if (bs % 2) {
159+
dev_err(component->dev,
160+
"Cannot increase i2s divider by 50%%\n");
161+
return -EINVAL;
162+
}
163+
bs += bs / 2;
164+
}
165+
166+
snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
167+
AIU_CLK_CTRL_MORE_I2S_DIV,
168+
FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,
169+
bs - 1));
170+
171+
/* Make sure amclk is used for HDMI i2s as well */
172+
snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
173+
AIU_CLK_CTRL_MORE_HDMI_AMCLK,
174+
AIU_CLK_CTRL_MORE_HDMI_AMCLK);
175+
176+
return 0;
177+
}
178+
179+
static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
180+
struct snd_pcm_hw_params *params,
181+
struct snd_soc_dai *dai)
182+
{
183+
struct snd_soc_component *component = dai->component;
184+
int ret;
185+
186+
/* Disable the clock while changing the settings */
187+
aiu_encoder_i2s_divider_enable(component, false);
188+
189+
ret = aiu_encoder_i2s_setup_desc(component, params);
190+
if (ret) {
191+
dev_err(dai->dev, "setting i2s desc failed\n");
192+
return ret;
193+
}
194+
195+
ret = aiu_encoder_i2s_set_clocks(component, params);
196+
if (ret) {
197+
dev_err(dai->dev, "setting i2s clocks failed\n");
198+
return ret;
199+
}
200+
201+
aiu_encoder_i2s_divider_enable(component, true);
202+
203+
return 0;
204+
}
205+
206+
static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,
207+
struct snd_soc_dai *dai)
208+
{
209+
struct snd_soc_component *component = dai->component;
210+
211+
aiu_encoder_i2s_divider_enable(component, false);
212+
213+
return 0;
214+
}
215+
216+
static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
217+
{
218+
struct snd_soc_component *component = dai->component;
219+
unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;
220+
unsigned int val = 0;
221+
unsigned int skew;
222+
223+
/* Only CPU Master / Codec Slave supported ATM */
224+
if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
225+
return -EINVAL;
226+
227+
if (inv == SND_SOC_DAIFMT_NB_IF ||
228+
inv == SND_SOC_DAIFMT_IB_IF)
229+
val |= AIU_CLK_CTRL_LRCLK_INVERT;
230+
231+
if (inv == SND_SOC_DAIFMT_IB_NF ||
232+
inv == SND_SOC_DAIFMT_IB_IF)
233+
val |= AIU_CLK_CTRL_AOCLK_INVERT;
234+
235+
/* Signal skew */
236+
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
237+
case SND_SOC_DAIFMT_I2S:
238+
/* Invert sample clock for i2s */
239+
val ^= AIU_CLK_CTRL_LRCLK_INVERT;
240+
skew = 1;
241+
break;
242+
case SND_SOC_DAIFMT_LEFT_J:
243+
skew = 0;
244+
break;
245+
default:
246+
return -EINVAL;
247+
}
248+
249+
val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew);
250+
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
251+
AIU_CLK_CTRL_LRCLK_INVERT |
252+
AIU_CLK_CTRL_AOCLK_INVERT |
253+
AIU_CLK_CTRL_LRCLK_SKEW,
254+
val);
255+
256+
return 0;
257+
}
258+
259+
static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
260+
unsigned int freq, int dir)
261+
{
262+
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
263+
int ret;
264+
265+
if (WARN_ON(clk_id != 0))
266+
return -EINVAL;
267+
268+
if (dir == SND_SOC_CLOCK_IN)
269+
return 0;
270+
271+
ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq);
272+
if (ret)
273+
dev_err(dai->dev, "Failed to set sysclk to %uHz", freq);
274+
275+
return ret;
276+
}
277+
278+
static const unsigned int hw_channels[] = {2, 8};
279+
static const struct snd_pcm_hw_constraint_list hw_channel_constraints = {
280+
.list = hw_channels,
281+
.count = ARRAY_SIZE(hw_channels),
282+
.mask = 0,
283+
};
284+
285+
static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,
286+
struct snd_soc_dai *dai)
287+
{
288+
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
289+
int ret;
290+
291+
/* Make sure the encoder gets either 2 or 8 channels */
292+
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
293+
SNDRV_PCM_HW_PARAM_CHANNELS,
294+
&hw_channel_constraints);
295+
if (ret) {
296+
dev_err(dai->dev, "adding channels constraints failed\n");
297+
return ret;
298+
}
299+
300+
ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks);
301+
if (ret)
302+
dev_err(dai->dev, "failed to enable i2s clocks\n");
303+
304+
return ret;
305+
}
306+
307+
static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,
308+
struct snd_soc_dai *dai)
309+
{
310+
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
311+
312+
clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks);
313+
}
314+
315+
const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = {
316+
.trigger = aiu_encoder_i2s_trigger,
317+
.hw_params = aiu_encoder_i2s_hw_params,
318+
.hw_free = aiu_encoder_i2s_hw_free,
319+
.set_fmt = aiu_encoder_i2s_set_fmt,
320+
.set_sysclk = aiu_encoder_i2s_set_sysclk,
321+
.startup = aiu_encoder_i2s_startup,
322+
.shutdown = aiu_encoder_i2s_shutdown,
323+
};
324+

0 commit comments

Comments
 (0)