1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Au1000/Au1500/Au1100 I2S controller driver for ASoC |
4 | * |
5 | * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com> |
6 | * |
7 | * Note: clock supplied to the I2S controller must be 256x samplerate. |
8 | */ |
9 | |
10 | #include <linux/init.h> |
11 | #include <linux/module.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/suspend.h> |
14 | #include <sound/core.h> |
15 | #include <sound/pcm.h> |
16 | #include <sound/initval.h> |
17 | #include <sound/soc.h> |
18 | #include <asm/mach-au1x00/au1000.h> |
19 | |
20 | #include "psc.h" |
21 | |
22 | #define I2S_RXTX 0x00 |
23 | #define I2S_CFG 0x04 |
24 | #define I2S_ENABLE 0x08 |
25 | |
26 | #define CFG_XU (1 << 25) /* tx underflow */ |
27 | #define CFG_XO (1 << 24) |
28 | #define CFG_RU (1 << 23) |
29 | #define CFG_RO (1 << 22) |
30 | #define CFG_TR (1 << 21) |
31 | #define CFG_TE (1 << 20) |
32 | #define CFG_TF (1 << 19) |
33 | #define CFG_RR (1 << 18) |
34 | #define CFG_RF (1 << 17) |
35 | #define CFG_ICK (1 << 12) /* clock invert */ |
36 | #define CFG_PD (1 << 11) /* set to make I2SDIO INPUT */ |
37 | #define CFG_LB (1 << 10) /* loopback */ |
38 | #define CFG_IC (1 << 9) /* word select invert */ |
39 | #define CFG_FM_I2S (0 << 7) /* I2S format */ |
40 | #define CFG_FM_LJ (1 << 7) /* left-justified */ |
41 | #define CFG_FM_RJ (2 << 7) /* right-justified */ |
42 | #define CFG_FM_MASK (3 << 7) |
43 | #define CFG_TN (1 << 6) /* tx fifo en */ |
44 | #define CFG_RN (1 << 5) /* rx fifo en */ |
45 | #define CFG_SZ_8 (0x08) |
46 | #define CFG_SZ_16 (0x10) |
47 | #define CFG_SZ_18 (0x12) |
48 | #define CFG_SZ_20 (0x14) |
49 | #define CFG_SZ_24 (0x18) |
50 | #define CFG_SZ_MASK (0x1f) |
51 | #define EN_D (1 << 1) /* DISable */ |
52 | #define EN_CE (1 << 0) /* clock enable */ |
53 | |
54 | /* only limited by clock generator and board design */ |
55 | #define AU1XI2SC_RATES \ |
56 | SNDRV_PCM_RATE_CONTINUOUS |
57 | |
58 | #define AU1XI2SC_FMTS \ |
59 | (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ |
60 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ |
61 | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \ |
62 | SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \ |
63 | SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE | \ |
64 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ |
65 | SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE | \ |
66 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \ |
67 | SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE | \ |
68 | 0) |
69 | |
70 | static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg) |
71 | { |
72 | return __raw_readl(addr: ctx->mmio + reg); |
73 | } |
74 | |
75 | static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v) |
76 | { |
77 | __raw_writel(val: v, addr: ctx->mmio + reg); |
78 | wmb(); |
79 | } |
80 | |
81 | static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) |
82 | { |
83 | struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai: cpu_dai); |
84 | unsigned long c; |
85 | int ret; |
86 | |
87 | ret = -EINVAL; |
88 | c = ctx->cfg; |
89 | |
90 | c &= ~CFG_FM_MASK; |
91 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
92 | case SND_SOC_DAIFMT_I2S: |
93 | c |= CFG_FM_I2S; |
94 | break; |
95 | case SND_SOC_DAIFMT_MSB: |
96 | c |= CFG_FM_RJ; |
97 | break; |
98 | case SND_SOC_DAIFMT_LSB: |
99 | c |= CFG_FM_LJ; |
100 | break; |
101 | default: |
102 | goto out; |
103 | } |
104 | |
105 | c &= ~(CFG_IC | CFG_ICK); /* IB-IF */ |
106 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
107 | case SND_SOC_DAIFMT_NB_NF: |
108 | c |= CFG_IC | CFG_ICK; |
109 | break; |
110 | case SND_SOC_DAIFMT_NB_IF: |
111 | c |= CFG_IC; |
112 | break; |
113 | case SND_SOC_DAIFMT_IB_NF: |
114 | c |= CFG_ICK; |
115 | break; |
116 | case SND_SOC_DAIFMT_IB_IF: |
117 | break; |
118 | default: |
119 | goto out; |
120 | } |
121 | |
122 | /* I2S controller only supports provider */ |
123 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
124 | case SND_SOC_DAIFMT_BP_FP: /* CODEC consumer */ |
125 | break; |
126 | default: |
127 | goto out; |
128 | } |
129 | |
130 | ret = 0; |
131 | ctx->cfg = c; |
132 | out: |
133 | return ret; |
134 | } |
135 | |
136 | static int au1xi2s_trigger(struct snd_pcm_substream *substream, |
137 | int cmd, struct snd_soc_dai *dai) |
138 | { |
139 | struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); |
140 | int stype = SUBSTREAM_TYPE(substream); |
141 | |
142 | switch (cmd) { |
143 | case SNDRV_PCM_TRIGGER_START: |
144 | case SNDRV_PCM_TRIGGER_RESUME: |
145 | /* power up */ |
146 | WR(ctx, I2S_ENABLE, EN_D | EN_CE); |
147 | WR(ctx, I2S_ENABLE, EN_CE); |
148 | ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN; |
149 | WR(ctx, I2S_CFG, v: ctx->cfg); |
150 | break; |
151 | case SNDRV_PCM_TRIGGER_STOP: |
152 | case SNDRV_PCM_TRIGGER_SUSPEND: |
153 | ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN); |
154 | WR(ctx, I2S_CFG, v: ctx->cfg); |
155 | WR(ctx, I2S_ENABLE, EN_D); /* power off */ |
156 | break; |
157 | default: |
158 | return -EINVAL; |
159 | } |
160 | |
161 | return 0; |
162 | } |
163 | |
164 | static unsigned long msbits_to_reg(int msbits) |
165 | { |
166 | switch (msbits) { |
167 | case 8: |
168 | return CFG_SZ_8; |
169 | case 16: |
170 | return CFG_SZ_16; |
171 | case 18: |
172 | return CFG_SZ_18; |
173 | case 20: |
174 | return CFG_SZ_20; |
175 | case 24: |
176 | return CFG_SZ_24; |
177 | } |
178 | return 0; |
179 | } |
180 | |
181 | static int au1xi2s_hw_params(struct snd_pcm_substream *substream, |
182 | struct snd_pcm_hw_params *params, |
183 | struct snd_soc_dai *dai) |
184 | { |
185 | struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); |
186 | unsigned long v; |
187 | |
188 | v = msbits_to_reg(msbits: params->msbits); |
189 | if (!v) |
190 | return -EINVAL; |
191 | |
192 | ctx->cfg &= ~CFG_SZ_MASK; |
193 | ctx->cfg |= v; |
194 | return 0; |
195 | } |
196 | |
197 | static int au1xi2s_startup(struct snd_pcm_substream *substream, |
198 | struct snd_soc_dai *dai) |
199 | { |
200 | struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); |
201 | snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]); |
202 | return 0; |
203 | } |
204 | |
205 | static const struct snd_soc_dai_ops au1xi2s_dai_ops = { |
206 | .startup = au1xi2s_startup, |
207 | .trigger = au1xi2s_trigger, |
208 | .hw_params = au1xi2s_hw_params, |
209 | .set_fmt = au1xi2s_set_fmt, |
210 | }; |
211 | |
212 | static struct snd_soc_dai_driver au1xi2s_dai_driver = { |
213 | .symmetric_rate = 1, |
214 | .playback = { |
215 | .rates = AU1XI2SC_RATES, |
216 | .formats = AU1XI2SC_FMTS, |
217 | .channels_min = 2, |
218 | .channels_max = 2, |
219 | }, |
220 | .capture = { |
221 | .rates = AU1XI2SC_RATES, |
222 | .formats = AU1XI2SC_FMTS, |
223 | .channels_min = 2, |
224 | .channels_max = 2, |
225 | }, |
226 | .ops = &au1xi2s_dai_ops, |
227 | }; |
228 | |
229 | static const struct snd_soc_component_driver au1xi2s_component = { |
230 | .name = "au1xi2s" , |
231 | .legacy_dai_naming = 1, |
232 | }; |
233 | |
234 | static int au1xi2s_drvprobe(struct platform_device *pdev) |
235 | { |
236 | struct resource *iores, *dmares; |
237 | struct au1xpsc_audio_data *ctx; |
238 | |
239 | ctx = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ctx), GFP_KERNEL); |
240 | if (!ctx) |
241 | return -ENOMEM; |
242 | |
243 | iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
244 | if (!iores) |
245 | return -ENODEV; |
246 | |
247 | if (!devm_request_mem_region(&pdev->dev, iores->start, |
248 | resource_size(iores), |
249 | pdev->name)) |
250 | return -EBUSY; |
251 | |
252 | ctx->mmio = devm_ioremap(dev: &pdev->dev, offset: iores->start, |
253 | size: resource_size(res: iores)); |
254 | if (!ctx->mmio) |
255 | return -EBUSY; |
256 | |
257 | dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); |
258 | if (!dmares) |
259 | return -EBUSY; |
260 | ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start; |
261 | |
262 | dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1); |
263 | if (!dmares) |
264 | return -EBUSY; |
265 | ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start; |
266 | |
267 | platform_set_drvdata(pdev, data: ctx); |
268 | |
269 | return snd_soc_register_component(dev: &pdev->dev, component_driver: &au1xi2s_component, |
270 | dai_drv: &au1xi2s_dai_driver, num_dai: 1); |
271 | } |
272 | |
273 | static void au1xi2s_drvremove(struct platform_device *pdev) |
274 | { |
275 | struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev); |
276 | |
277 | snd_soc_unregister_component(dev: &pdev->dev); |
278 | |
279 | WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ |
280 | } |
281 | |
282 | #ifdef CONFIG_PM |
283 | static int au1xi2s_drvsuspend(struct device *dev) |
284 | { |
285 | struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); |
286 | |
287 | WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ |
288 | |
289 | return 0; |
290 | } |
291 | |
292 | static int au1xi2s_drvresume(struct device *dev) |
293 | { |
294 | return 0; |
295 | } |
296 | |
297 | static const struct dev_pm_ops au1xi2sc_pmops = { |
298 | .suspend = au1xi2s_drvsuspend, |
299 | .resume = au1xi2s_drvresume, |
300 | }; |
301 | |
302 | #define AU1XI2SC_PMOPS (&au1xi2sc_pmops) |
303 | |
304 | #else |
305 | |
306 | #define AU1XI2SC_PMOPS NULL |
307 | |
308 | #endif |
309 | |
310 | static struct platform_driver au1xi2s_driver = { |
311 | .driver = { |
312 | .name = "alchemy-i2sc" , |
313 | .pm = AU1XI2SC_PMOPS, |
314 | }, |
315 | .probe = au1xi2s_drvprobe, |
316 | .remove_new = au1xi2s_drvremove, |
317 | }; |
318 | |
319 | module_platform_driver(au1xi2s_driver); |
320 | |
321 | MODULE_LICENSE("GPL" ); |
322 | MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver" ); |
323 | MODULE_AUTHOR("Manuel Lauss" ); |
324 | |