]>
Commit | Line | Data |
---|---|---|
6ae9ca9c JB |
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/pcm_iec958.h> | |
10 | #include <sound/soc.h> | |
11 | #include <sound/soc-dai.h> | |
12 | ||
13 | #include "aiu.h" | |
14 | ||
15 | #define AIU_958_MISC_NON_PCM BIT(0) | |
16 | #define AIU_958_MISC_MODE_16BITS BIT(1) | |
17 | #define AIU_958_MISC_16BITS_ALIGN GENMASK(6, 5) | |
18 | #define AIU_958_MISC_MODE_32BITS BIT(7) | |
19 | #define AIU_958_MISC_U_FROM_STREAM BIT(12) | |
20 | #define AIU_958_MISC_FORCE_LR BIT(13) | |
21 | #define AIU_958_CTRL_HOLD_EN BIT(0) | |
22 | #define AIU_CLK_CTRL_958_DIV_EN BIT(1) | |
23 | #define AIU_CLK_CTRL_958_DIV GENMASK(5, 4) | |
24 | #define AIU_CLK_CTRL_958_DIV_MORE BIT(12) | |
25 | ||
26 | #define AIU_CS_WORD_LEN 4 | |
27 | #define AIU_958_INTERNAL_DIV 2 | |
28 | ||
29 | static void | |
30 | aiu_encoder_spdif_divider_enable(struct snd_soc_component *component, | |
31 | bool enable) | |
32 | { | |
33 | snd_soc_component_update_bits(component, AIU_CLK_CTRL, | |
34 | AIU_CLK_CTRL_958_DIV_EN, | |
35 | enable ? AIU_CLK_CTRL_958_DIV_EN : 0); | |
36 | } | |
37 | ||
38 | static void aiu_encoder_spdif_hold(struct snd_soc_component *component, | |
39 | bool enable) | |
40 | { | |
41 | snd_soc_component_update_bits(component, AIU_958_CTRL, | |
42 | AIU_958_CTRL_HOLD_EN, | |
43 | enable ? AIU_958_CTRL_HOLD_EN : 0); | |
44 | } | |
45 | ||
46 | static int | |
47 | aiu_encoder_spdif_trigger(struct snd_pcm_substream *substream, int cmd, | |
48 | struct snd_soc_dai *dai) | |
49 | { | |
50 | struct snd_soc_component *component = dai->component; | |
51 | ||
52 | switch (cmd) { | |
53 | case SNDRV_PCM_TRIGGER_START: | |
54 | case SNDRV_PCM_TRIGGER_RESUME: | |
55 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
56 | aiu_encoder_spdif_hold(component, false); | |
57 | return 0; | |
58 | ||
59 | case SNDRV_PCM_TRIGGER_STOP: | |
60 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
61 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
62 | aiu_encoder_spdif_hold(component, true); | |
63 | return 0; | |
64 | ||
65 | default: | |
66 | return -EINVAL; | |
67 | } | |
68 | } | |
69 | ||
70 | static int aiu_encoder_spdif_setup_cs_word(struct snd_soc_component *component, | |
71 | struct snd_pcm_hw_params *params) | |
72 | { | |
73 | u8 cs[AIU_CS_WORD_LEN]; | |
74 | unsigned int val; | |
75 | int ret; | |
76 | ||
77 | ret = snd_pcm_create_iec958_consumer_hw_params(params, cs, | |
78 | AIU_CS_WORD_LEN); | |
79 | if (ret < 0) | |
80 | return ret; | |
81 | ||
82 | /* Write the 1st half word */ | |
83 | val = cs[1] | cs[0] << 8; | |
84 | snd_soc_component_write(component, AIU_958_CHSTAT_L0, val); | |
85 | snd_soc_component_write(component, AIU_958_CHSTAT_R0, val); | |
86 | ||
87 | /* Write the 2nd half word */ | |
88 | val = cs[3] | cs[2] << 8; | |
89 | snd_soc_component_write(component, AIU_958_CHSTAT_L1, val); | |
90 | snd_soc_component_write(component, AIU_958_CHSTAT_R1, val); | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
95 | static int aiu_encoder_spdif_hw_params(struct snd_pcm_substream *substream, | |
96 | struct snd_pcm_hw_params *params, | |
97 | struct snd_soc_dai *dai) | |
98 | { | |
99 | struct snd_soc_component *component = dai->component; | |
100 | struct aiu *aiu = snd_soc_component_get_drvdata(component); | |
101 | unsigned int val = 0, mrate; | |
102 | int ret; | |
103 | ||
104 | /* Disable the clock while changing the settings */ | |
105 | aiu_encoder_spdif_divider_enable(component, false); | |
106 | ||
107 | switch (params_physical_width(params)) { | |
108 | case 16: | |
109 | val |= AIU_958_MISC_MODE_16BITS; | |
110 | val |= FIELD_PREP(AIU_958_MISC_16BITS_ALIGN, 2); | |
111 | break; | |
112 | case 32: | |
113 | val |= AIU_958_MISC_MODE_32BITS; | |
114 | break; | |
115 | default: | |
116 | dev_err(dai->dev, "Unsupport physical width\n"); | |
117 | return -EINVAL; | |
118 | } | |
119 | ||
120 | snd_soc_component_update_bits(component, AIU_958_MISC, | |
121 | AIU_958_MISC_NON_PCM | | |
122 | AIU_958_MISC_MODE_16BITS | | |
123 | AIU_958_MISC_16BITS_ALIGN | | |
124 | AIU_958_MISC_MODE_32BITS | | |
125 | AIU_958_MISC_FORCE_LR | | |
126 | AIU_958_MISC_U_FROM_STREAM, | |
127 | val); | |
128 | ||
129 | /* Set the stream channel status word */ | |
130 | ret = aiu_encoder_spdif_setup_cs_word(component, params); | |
131 | if (ret) { | |
132 | dev_err(dai->dev, "failed to set channel status word\n"); | |
133 | return ret; | |
134 | } | |
135 | ||
136 | snd_soc_component_update_bits(component, AIU_CLK_CTRL, | |
137 | AIU_CLK_CTRL_958_DIV | | |
138 | AIU_CLK_CTRL_958_DIV_MORE, | |
139 | FIELD_PREP(AIU_CLK_CTRL_958_DIV, | |
140 | __ffs(AIU_958_INTERNAL_DIV))); | |
141 | ||
142 | /* 2 * 32bits per subframe * 2 channels = 128 */ | |
143 | mrate = params_rate(params) * 128 * AIU_958_INTERNAL_DIV; | |
144 | ret = clk_set_rate(aiu->spdif.clks[MCLK].clk, mrate); | |
145 | if (ret) { | |
146 | dev_err(dai->dev, "failed to set mclk rate\n"); | |
147 | return ret; | |
148 | } | |
149 | ||
150 | aiu_encoder_spdif_divider_enable(component, true); | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
155 | static int aiu_encoder_spdif_hw_free(struct snd_pcm_substream *substream, | |
156 | struct snd_soc_dai *dai) | |
157 | { | |
158 | struct snd_soc_component *component = dai->component; | |
159 | ||
160 | aiu_encoder_spdif_divider_enable(component, false); | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
165 | static int aiu_encoder_spdif_startup(struct snd_pcm_substream *substream, | |
166 | struct snd_soc_dai *dai) | |
167 | { | |
168 | struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); | |
169 | int ret; | |
170 | ||
171 | /* | |
172 | * NOTE: Make sure the spdif block is on its own divider. | |
173 | * | |
174 | * The spdif can be clocked by the i2s master clock or its own | |
175 | * clock. We should (in theory) change the source depending on the | |
176 | * origin of the data. | |
177 | * | |
178 | * However, considering the clocking scheme used on these platforms, | |
179 | * the master clocks will pick the same PLL source when they are | |
180 | * playing from the same FIFO. The clock should be in sync so, it | |
181 | * should not be necessary to reparent the spdif master clock. | |
182 | */ | |
183 | ret = clk_set_parent(aiu->spdif.clks[MCLK].clk, | |
184 | aiu->spdif_mclk); | |
185 | if (ret) | |
186 | return ret; | |
187 | ||
188 | ret = clk_bulk_prepare_enable(aiu->spdif.clk_num, aiu->spdif.clks); | |
189 | if (ret) | |
190 | dev_err(dai->dev, "failed to enable spdif clocks\n"); | |
191 | ||
192 | return ret; | |
193 | } | |
194 | ||
195 | static void aiu_encoder_spdif_shutdown(struct snd_pcm_substream *substream, | |
196 | struct snd_soc_dai *dai) | |
197 | { | |
198 | struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); | |
199 | ||
200 | clk_bulk_disable_unprepare(aiu->spdif.clk_num, aiu->spdif.clks); | |
201 | } | |
202 | ||
203 | const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops = { | |
204 | .trigger = aiu_encoder_spdif_trigger, | |
205 | .hw_params = aiu_encoder_spdif_hw_params, | |
206 | .hw_free = aiu_encoder_spdif_hw_free, | |
207 | .startup = aiu_encoder_spdif_startup, | |
208 | .shutdown = aiu_encoder_spdif_shutdown, | |
209 | }; |