1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | // |
3 | // Copyright (c) 2018 BayLibre, SAS. |
4 | // Author: Jerome Brunet <jbrunet@baylibre.com> |
5 | |
6 | #include <linux/module.h> |
7 | #include <linux/of_platform.h> |
8 | #include <linux/regmap.h> |
9 | #include <sound/soc.h> |
10 | #include <sound/soc-dai.h> |
11 | |
12 | #include "axg-tdm-formatter.h" |
13 | |
14 | #define TDMIN_CTRL 0x00 |
15 | #define TDMIN_CTRL_ENABLE BIT(31) |
16 | #define TDMIN_CTRL_I2S_MODE BIT(30) |
17 | #define TDMIN_CTRL_RST_OUT BIT(29) |
18 | #define TDMIN_CTRL_RST_IN BIT(28) |
19 | #define TDMIN_CTRL_WS_INV BIT(25) |
20 | #define TDMIN_CTRL_SEL_SHIFT 20 |
21 | #define TDMIN_CTRL_IN_BIT_SKEW_MASK GENMASK(18, 16) |
22 | #define TDMIN_CTRL_IN_BIT_SKEW(x) ((x) << 16) |
23 | #define TDMIN_CTRL_LSB_FIRST BIT(5) |
24 | #define TDMIN_CTRL_BITNUM_MASK GENMASK(4, 0) |
25 | #define TDMIN_CTRL_BITNUM(x) ((x) << 0) |
26 | #define TDMIN_SWAP 0x04 |
27 | #define TDMIN_MASK0 0x08 |
28 | #define TDMIN_MASK1 0x0c |
29 | #define TDMIN_MASK2 0x10 |
30 | #define TDMIN_MASK3 0x14 |
31 | #define TDMIN_STAT 0x18 |
32 | #define TDMIN_MUTE_VAL 0x1c |
33 | #define TDMIN_MUTE0 0x20 |
34 | #define TDMIN_MUTE1 0x24 |
35 | #define TDMIN_MUTE2 0x28 |
36 | #define TDMIN_MUTE3 0x2c |
37 | |
38 | static const struct regmap_config axg_tdmin_regmap_cfg = { |
39 | .reg_bits = 32, |
40 | .val_bits = 32, |
41 | .reg_stride = 4, |
42 | .max_register = TDMIN_MUTE3, |
43 | }; |
44 | |
45 | static const char * const axg_tdmin_sel_texts[] = { |
46 | "IN 0" , "IN 1" , "IN 2" , "IN 3" , "IN 4" , "IN 5" , "IN 6" , "IN 7" , |
47 | "IN 8" , "IN 9" , "IN 10" , "IN 11" , "IN 12" , "IN 13" , "IN 14" , "IN 15" , |
48 | }; |
49 | |
50 | /* Change to special mux control to reset dapm */ |
51 | static SOC_ENUM_SINGLE_DECL(axg_tdmin_sel_enum, TDMIN_CTRL, |
52 | TDMIN_CTRL_SEL_SHIFT, axg_tdmin_sel_texts); |
53 | |
54 | static const struct snd_kcontrol_new axg_tdmin_in_mux = |
55 | SOC_DAPM_ENUM("Input Source" , axg_tdmin_sel_enum); |
56 | |
57 | static struct snd_soc_dai * |
58 | axg_tdmin_get_be(struct snd_soc_dapm_widget *w) |
59 | { |
60 | struct snd_soc_dapm_path *p; |
61 | struct snd_soc_dai *be; |
62 | |
63 | snd_soc_dapm_widget_for_each_source_path(w, p) { |
64 | if (!p->connect) |
65 | continue; |
66 | |
67 | if (p->source->id == snd_soc_dapm_dai_out) |
68 | return (struct snd_soc_dai *)p->source->priv; |
69 | |
70 | be = axg_tdmin_get_be(w: p->source); |
71 | if (be) |
72 | return be; |
73 | } |
74 | |
75 | return NULL; |
76 | } |
77 | |
78 | static struct axg_tdm_stream * |
79 | axg_tdmin_get_tdm_stream(struct snd_soc_dapm_widget *w) |
80 | { |
81 | struct snd_soc_dai *be = axg_tdmin_get_be(w); |
82 | |
83 | if (!be) |
84 | return NULL; |
85 | |
86 | return snd_soc_dai_dma_data_get_capture(be); |
87 | } |
88 | |
89 | static void axg_tdmin_enable(struct regmap *map) |
90 | { |
91 | /* Apply both reset */ |
92 | regmap_update_bits(map, TDMIN_CTRL, |
93 | TDMIN_CTRL_RST_OUT | TDMIN_CTRL_RST_IN, val: 0); |
94 | |
95 | /* Clear out reset before in reset */ |
96 | regmap_update_bits(map, TDMIN_CTRL, |
97 | TDMIN_CTRL_RST_OUT, TDMIN_CTRL_RST_OUT); |
98 | regmap_update_bits(map, TDMIN_CTRL, |
99 | TDMIN_CTRL_RST_IN, TDMIN_CTRL_RST_IN); |
100 | |
101 | /* Actually enable tdmin */ |
102 | regmap_update_bits(map, TDMIN_CTRL, |
103 | TDMIN_CTRL_ENABLE, TDMIN_CTRL_ENABLE); |
104 | } |
105 | |
106 | static void axg_tdmin_disable(struct regmap *map) |
107 | { |
108 | regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, val: 0); |
109 | } |
110 | |
111 | static int axg_tdmin_prepare(struct regmap *map, |
112 | const struct axg_tdm_formatter_hw *quirks, |
113 | struct axg_tdm_stream *ts) |
114 | { |
115 | unsigned int val, skew = quirks->skew_offset; |
116 | |
117 | /* Set stream skew */ |
118 | switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
119 | case SND_SOC_DAIFMT_I2S: |
120 | case SND_SOC_DAIFMT_DSP_A: |
121 | skew += 1; |
122 | break; |
123 | |
124 | case SND_SOC_DAIFMT_LEFT_J: |
125 | case SND_SOC_DAIFMT_DSP_B: |
126 | break; |
127 | |
128 | default: |
129 | pr_err("Unsupported format: %u\n" , |
130 | ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK); |
131 | return -EINVAL; |
132 | } |
133 | |
134 | val = TDMIN_CTRL_IN_BIT_SKEW(skew); |
135 | |
136 | /* Set stream format mode */ |
137 | switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
138 | case SND_SOC_DAIFMT_I2S: |
139 | case SND_SOC_DAIFMT_LEFT_J: |
140 | case SND_SOC_DAIFMT_RIGHT_J: |
141 | val |= TDMIN_CTRL_I2S_MODE; |
142 | break; |
143 | } |
144 | |
145 | /* If the sample clock is inverted, invert it back for the formatter */ |
146 | if (axg_tdm_lrclk_invert(fmt: ts->iface->fmt)) |
147 | val |= TDMIN_CTRL_WS_INV; |
148 | |
149 | /* Set the slot width */ |
150 | val |= TDMIN_CTRL_BITNUM(ts->iface->slot_width - 1); |
151 | |
152 | /* |
153 | * The following also reset LSB_FIRST which result in the formatter |
154 | * placing the first bit received at bit 31 |
155 | */ |
156 | regmap_update_bits(map, TDMIN_CTRL, |
157 | mask: (TDMIN_CTRL_IN_BIT_SKEW_MASK | TDMIN_CTRL_WS_INV | |
158 | TDMIN_CTRL_I2S_MODE | TDMIN_CTRL_LSB_FIRST | |
159 | TDMIN_CTRL_BITNUM_MASK), val); |
160 | |
161 | /* Set static swap mask configuration */ |
162 | regmap_write(map, TDMIN_SWAP, val: 0x76543210); |
163 | |
164 | return axg_tdm_formatter_set_channel_masks(map, ts, TDMIN_MASK0); |
165 | } |
166 | |
167 | static const struct snd_soc_dapm_widget axg_tdmin_dapm_widgets[] = { |
168 | SND_SOC_DAPM_AIF_IN("IN 0" , NULL, 0, SND_SOC_NOPM, 0, 0), |
169 | SND_SOC_DAPM_AIF_IN("IN 1" , NULL, 0, SND_SOC_NOPM, 0, 0), |
170 | SND_SOC_DAPM_AIF_IN("IN 2" , NULL, 0, SND_SOC_NOPM, 0, 0), |
171 | SND_SOC_DAPM_AIF_IN("IN 3" , NULL, 0, SND_SOC_NOPM, 0, 0), |
172 | SND_SOC_DAPM_AIF_IN("IN 4" , NULL, 0, SND_SOC_NOPM, 0, 0), |
173 | SND_SOC_DAPM_AIF_IN("IN 5" , NULL, 0, SND_SOC_NOPM, 0, 0), |
174 | SND_SOC_DAPM_AIF_IN("IN 6" , NULL, 0, SND_SOC_NOPM, 0, 0), |
175 | SND_SOC_DAPM_AIF_IN("IN 7" , NULL, 0, SND_SOC_NOPM, 0, 0), |
176 | SND_SOC_DAPM_AIF_IN("IN 8" , NULL, 0, SND_SOC_NOPM, 0, 0), |
177 | SND_SOC_DAPM_AIF_IN("IN 9" , NULL, 0, SND_SOC_NOPM, 0, 0), |
178 | SND_SOC_DAPM_AIF_IN("IN 10" , NULL, 0, SND_SOC_NOPM, 0, 0), |
179 | SND_SOC_DAPM_AIF_IN("IN 11" , NULL, 0, SND_SOC_NOPM, 0, 0), |
180 | SND_SOC_DAPM_AIF_IN("IN 12" , NULL, 0, SND_SOC_NOPM, 0, 0), |
181 | SND_SOC_DAPM_AIF_IN("IN 13" , NULL, 0, SND_SOC_NOPM, 0, 0), |
182 | SND_SOC_DAPM_AIF_IN("IN 14" , NULL, 0, SND_SOC_NOPM, 0, 0), |
183 | SND_SOC_DAPM_AIF_IN("IN 15" , NULL, 0, SND_SOC_NOPM, 0, 0), |
184 | SND_SOC_DAPM_MUX("SRC SEL" , SND_SOC_NOPM, 0, 0, &axg_tdmin_in_mux), |
185 | SND_SOC_DAPM_PGA_E("DEC" , SND_SOC_NOPM, 0, 0, NULL, 0, |
186 | axg_tdm_formatter_event, |
187 | (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), |
188 | SND_SOC_DAPM_AIF_OUT("OUT" , NULL, 0, SND_SOC_NOPM, 0, 0), |
189 | }; |
190 | |
191 | static const struct snd_soc_dapm_route axg_tdmin_dapm_routes[] = { |
192 | { "SRC SEL" , "IN 0" , "IN 0" }, |
193 | { "SRC SEL" , "IN 1" , "IN 1" }, |
194 | { "SRC SEL" , "IN 2" , "IN 2" }, |
195 | { "SRC SEL" , "IN 3" , "IN 3" }, |
196 | { "SRC SEL" , "IN 4" , "IN 4" }, |
197 | { "SRC SEL" , "IN 5" , "IN 5" }, |
198 | { "SRC SEL" , "IN 6" , "IN 6" }, |
199 | { "SRC SEL" , "IN 7" , "IN 7" }, |
200 | { "SRC SEL" , "IN 8" , "IN 8" }, |
201 | { "SRC SEL" , "IN 9" , "IN 9" }, |
202 | { "SRC SEL" , "IN 10" , "IN 10" }, |
203 | { "SRC SEL" , "IN 11" , "IN 11" }, |
204 | { "SRC SEL" , "IN 12" , "IN 12" }, |
205 | { "SRC SEL" , "IN 13" , "IN 13" }, |
206 | { "SRC SEL" , "IN 14" , "IN 14" }, |
207 | { "SRC SEL" , "IN 15" , "IN 15" }, |
208 | { "DEC" , NULL, "SRC SEL" }, |
209 | { "OUT" , NULL, "DEC" }, |
210 | }; |
211 | |
212 | static const struct snd_soc_component_driver axg_tdmin_component_drv = { |
213 | .dapm_widgets = axg_tdmin_dapm_widgets, |
214 | .num_dapm_widgets = ARRAY_SIZE(axg_tdmin_dapm_widgets), |
215 | .dapm_routes = axg_tdmin_dapm_routes, |
216 | .num_dapm_routes = ARRAY_SIZE(axg_tdmin_dapm_routes), |
217 | }; |
218 | |
219 | static const struct axg_tdm_formatter_ops axg_tdmin_ops = { |
220 | .get_stream = axg_tdmin_get_tdm_stream, |
221 | .prepare = axg_tdmin_prepare, |
222 | .enable = axg_tdmin_enable, |
223 | .disable = axg_tdmin_disable, |
224 | }; |
225 | |
226 | static const struct axg_tdm_formatter_driver axg_tdmin_drv = { |
227 | .component_drv = &axg_tdmin_component_drv, |
228 | .regmap_cfg = &axg_tdmin_regmap_cfg, |
229 | .ops = &axg_tdmin_ops, |
230 | .quirks = &(const struct axg_tdm_formatter_hw) { |
231 | .skew_offset = 3, |
232 | }, |
233 | }; |
234 | |
235 | static const struct of_device_id axg_tdmin_of_match[] = { |
236 | { |
237 | .compatible = "amlogic,axg-tdmin" , |
238 | .data = &axg_tdmin_drv, |
239 | }, { |
240 | .compatible = "amlogic,g12a-tdmin" , |
241 | .data = &axg_tdmin_drv, |
242 | }, { |
243 | .compatible = "amlogic,sm1-tdmin" , |
244 | .data = &axg_tdmin_drv, |
245 | }, {} |
246 | }; |
247 | MODULE_DEVICE_TABLE(of, axg_tdmin_of_match); |
248 | |
249 | static struct platform_driver axg_tdmin_pdrv = { |
250 | .probe = axg_tdm_formatter_probe, |
251 | .driver = { |
252 | .name = "axg-tdmin" , |
253 | .of_match_table = axg_tdmin_of_match, |
254 | }, |
255 | }; |
256 | module_platform_driver(axg_tdmin_pdrv); |
257 | |
258 | MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver" ); |
259 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>" ); |
260 | MODULE_LICENSE("GPL v2" ); |
261 | |