blob: 973d4c02ef8db02de0f0a8f211cdcc254b04f573 [file] [log] [blame]
Jerome Brunet13a22e62018-07-17 17:43:01 +02001// 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
38static 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
45static const char * const axg_tdmin_sel_texts[] = {
Jerome Brunetcde9f122019-09-05 14:01:17 +020046 "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",
Jerome Brunet13a22e62018-07-17 17:43:01 +020048};
49
50/* Change to special mux control to reset dapm */
51static SOC_ENUM_SINGLE_DECL(axg_tdmin_sel_enum, TDMIN_CTRL,
52 TDMIN_CTRL_SEL_SHIFT, axg_tdmin_sel_texts);
53
54static const struct snd_kcontrol_new axg_tdmin_in_mux =
55 SOC_DAPM_ENUM("Input Source", axg_tdmin_sel_enum);
56
57static struct snd_soc_dai *
58axg_tdmin_get_be(struct snd_soc_dapm_widget *w)
59{
60 struct snd_soc_dapm_path *p = NULL;
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(p->source);
71 if (be)
72 return be;
73 }
74
75 return NULL;
76}
77
78static struct axg_tdm_stream *
79axg_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 be->capture_dma_data;
87}
88
89static 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, 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
106static void axg_tdmin_disable(struct regmap *map)
107{
108 regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, 0);
109}
110
Jerome Brunetf01bc672019-04-04 13:17:32 +0200111static int axg_tdmin_prepare(struct regmap *map,
112 const struct axg_tdm_formatter_hw *quirks,
113 struct axg_tdm_stream *ts)
Jerome Brunet13a22e62018-07-17 17:43:01 +0200114{
Jerome Brunetf01bc672019-04-04 13:17:32 +0200115 unsigned int val, skew = quirks->skew_offset;
Jerome Brunet13a22e62018-07-17 17:43:01 +0200116
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:
Jerome Brunetf01bc672019-04-04 13:17:32 +0200121 skew += 1;
Jerome Brunet13a22e62018-07-17 17:43:01 +0200122 break;
123
124 case SND_SOC_DAIFMT_LEFT_J:
Jerome Brunet13a22e62018-07-17 17:43:01 +0200125 case SND_SOC_DAIFMT_DSP_B:
Jerome Brunet13a22e62018-07-17 17:43:01 +0200126 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
Jerome Brunetf01bc672019-04-04 13:17:32 +0200134 val = TDMIN_CTRL_IN_BIT_SKEW(skew);
135
Jerome Brunet13a22e62018-07-17 17:43:01 +0200136 /* 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(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 (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, 0x76543210);
163
164 return axg_tdm_formatter_set_channel_masks(map, ts, TDMIN_MASK0);
165}
166
167static const struct snd_soc_dapm_widget axg_tdmin_dapm_widgets[] = {
Jerome Brunetcde9f122019-09-05 14:01:17 +0200168 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),
Jerome Brunet13a22e62018-07-17 17:43:01 +0200184 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
191static const struct snd_soc_dapm_route axg_tdmin_dapm_routes[] = {
Jerome Brunetcde9f122019-09-05 14:01:17 +0200192 { "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" },
Jerome Brunet13a22e62018-07-17 17:43:01 +0200208 { "DEC", NULL, "SRC SEL" },
209 { "OUT", NULL, "DEC" },
210};
211
212static 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
219static 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
226static 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,
Jerome Brunetf01bc672019-04-04 13:17:32 +0200230 .quirks = &(const struct axg_tdm_formatter_hw) {
231 .invert_sclk = false,
232 .skew_offset = 2,
233 },
Jerome Brunet13a22e62018-07-17 17:43:01 +0200234};
235
236static const struct of_device_id axg_tdmin_of_match[] = {
237 {
238 .compatible = "amlogic,axg-tdmin",
239 .data = &axg_tdmin_drv,
240 }, {}
241};
242MODULE_DEVICE_TABLE(of, axg_tdmin_of_match);
243
244static struct platform_driver axg_tdmin_pdrv = {
245 .probe = axg_tdm_formatter_probe,
246 .driver = {
247 .name = "axg-tdmin",
248 .of_match_table = axg_tdmin_of_match,
249 },
250};
251module_platform_driver(axg_tdmin_pdrv);
252
253MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver");
254MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
255MODULE_LICENSE("GPL v2");