1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Common functions for loongson I2S controller driver |
4 | // |
5 | // Copyright (C) 2023 Loongson Technology Corporation Limited. |
6 | // Author: Yingkun Meng <mengyingkun@loongson.cn> |
7 | // |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/pm_runtime.h> |
13 | #include <linux/dma-mapping.h> |
14 | #include <sound/soc.h> |
15 | #include <linux/regmap.h> |
16 | #include <sound/pcm_params.h> |
17 | #include "loongson_i2s.h" |
18 | |
19 | #define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ |
20 | SNDRV_PCM_FMTBIT_S16_LE | \ |
21 | SNDRV_PCM_FMTBIT_S20_3LE | \ |
22 | SNDRV_PCM_FMTBIT_S24_LE) |
23 | |
24 | static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd, |
25 | struct snd_soc_dai *dai) |
26 | { |
27 | struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
28 | int ret = 0; |
29 | |
30 | switch (cmd) { |
31 | case SNDRV_PCM_TRIGGER_START: |
32 | case SNDRV_PCM_TRIGGER_RESUME: |
33 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
34 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
35 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, |
36 | I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, |
37 | I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN); |
38 | else |
39 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, |
40 | I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, |
41 | I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN); |
42 | break; |
43 | case SNDRV_PCM_TRIGGER_STOP: |
44 | case SNDRV_PCM_TRIGGER_SUSPEND: |
45 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
46 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
47 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, |
48 | I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, val: 0); |
49 | else |
50 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, |
51 | I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, val: 0); |
52 | break; |
53 | default: |
54 | ret = -EINVAL; |
55 | } |
56 | |
57 | return ret; |
58 | } |
59 | |
60 | static int loongson_i2s_hw_params(struct snd_pcm_substream *substream, |
61 | struct snd_pcm_hw_params *params, |
62 | struct snd_soc_dai *dai) |
63 | { |
64 | struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
65 | u32 clk_rate = i2s->clk_rate; |
66 | u32 sysclk = i2s->sysclk; |
67 | u32 bits = params_width(p: params); |
68 | u32 chans = params_channels(p: params); |
69 | u32 fs = params_rate(p: params); |
70 | u32 bclk_ratio, mclk_ratio; |
71 | u32 mclk_ratio_frac; |
72 | u32 val = 0; |
73 | |
74 | switch (i2s->rev_id) { |
75 | case 0: |
76 | bclk_ratio = DIV_ROUND_CLOSEST(clk_rate, |
77 | (bits * chans * fs * 2)) - 1; |
78 | mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1; |
79 | |
80 | /* According to 2k1000LA user manual, set bits == depth */ |
81 | val |= (bits << 24); |
82 | val |= (bits << 16); |
83 | val |= (bclk_ratio << 8); |
84 | val |= mclk_ratio; |
85 | regmap_write(map: i2s->regmap, LS_I2S_CFG, val); |
86 | |
87 | break; |
88 | case 1: |
89 | bclk_ratio = DIV_ROUND_CLOSEST(sysclk, |
90 | (bits * chans * fs * 2)) - 1; |
91 | mclk_ratio = clk_rate / sysclk; |
92 | mclk_ratio_frac = DIV_ROUND_CLOSEST_ULL(((u64)clk_rate << 16), |
93 | sysclk) - (mclk_ratio << 16); |
94 | |
95 | regmap_read(map: i2s->regmap, LS_I2S_CFG, val: &val); |
96 | val |= (bits << 24); |
97 | val |= (bclk_ratio << 8); |
98 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
99 | val |= (bits << 16); |
100 | else |
101 | val |= bits; |
102 | regmap_write(map: i2s->regmap, LS_I2S_CFG, val); |
103 | |
104 | val = (mclk_ratio_frac << 16) | mclk_ratio; |
105 | regmap_write(map: i2s->regmap, LS_I2S_CFG1, val); |
106 | |
107 | break; |
108 | default: |
109 | dev_err(i2s->dev, "I2S revision invalid\n" ); |
110 | return -EINVAL; |
111 | } |
112 | |
113 | return 0; |
114 | } |
115 | |
116 | static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, |
117 | unsigned int freq, int dir) |
118 | { |
119 | struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
120 | |
121 | i2s->sysclk = freq; |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
127 | { |
128 | struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
129 | u32 val; |
130 | int ret; |
131 | |
132 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
133 | case SND_SOC_DAIFMT_I2S: |
134 | break; |
135 | case SND_SOC_DAIFMT_RIGHT_J: |
136 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB, |
137 | I2S_CTRL_MSB); |
138 | break; |
139 | default: |
140 | return -EINVAL; |
141 | } |
142 | |
143 | |
144 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
145 | case SND_SOC_DAIFMT_BC_FC: |
146 | break; |
147 | case SND_SOC_DAIFMT_BP_FC: |
148 | /* Enable master mode */ |
149 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, |
150 | I2S_CTRL_MASTER); |
151 | if (i2s->rev_id == 1) { |
152 | ret = regmap_read_poll_timeout_atomic(i2s->regmap, |
153 | LS_I2S_CTRL, val, |
154 | val & I2S_CTRL_CLK_READY, |
155 | 10, 500000); |
156 | if (ret < 0) |
157 | dev_warn(dai->dev, "wait BCLK ready timeout\n" ); |
158 | } |
159 | break; |
160 | case SND_SOC_DAIFMT_BC_FP: |
161 | /* Enable MCLK */ |
162 | if (i2s->rev_id == 1) { |
163 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, |
164 | I2S_CTRL_MCLK_EN, |
165 | I2S_CTRL_MCLK_EN); |
166 | ret = regmap_read_poll_timeout_atomic(i2s->regmap, |
167 | LS_I2S_CTRL, val, |
168 | val & I2S_CTRL_MCLK_READY, |
169 | 10, 500000); |
170 | if (ret < 0) |
171 | dev_warn(dai->dev, "wait MCLK ready timeout\n" ); |
172 | } |
173 | break; |
174 | case SND_SOC_DAIFMT_BP_FP: |
175 | /* Enable MCLK */ |
176 | if (i2s->rev_id == 1) { |
177 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, |
178 | I2S_CTRL_MCLK_EN, |
179 | I2S_CTRL_MCLK_EN); |
180 | ret = regmap_read_poll_timeout_atomic(i2s->regmap, |
181 | LS_I2S_CTRL, val, |
182 | val & I2S_CTRL_MCLK_READY, |
183 | 10, 500000); |
184 | if (ret < 0) |
185 | dev_warn(dai->dev, "wait MCLK ready timeout\n" ); |
186 | } |
187 | |
188 | /* Enable master mode */ |
189 | regmap_update_bits(map: i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, |
190 | I2S_CTRL_MASTER); |
191 | if (i2s->rev_id == 1) { |
192 | ret = regmap_read_poll_timeout_atomic(i2s->regmap, |
193 | LS_I2S_CTRL, val, |
194 | val & I2S_CTRL_CLK_READY, |
195 | 10, 500000); |
196 | if (ret < 0) |
197 | dev_warn(dai->dev, "wait BCLK ready timeout\n" ); |
198 | } |
199 | break; |
200 | default: |
201 | return -EINVAL; |
202 | } |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai) |
208 | { |
209 | struct loongson_i2s *i2s = dev_get_drvdata(dev: cpu_dai->dev); |
210 | |
211 | snd_soc_dai_init_dma_data(dai: cpu_dai, playback: &i2s->playback_dma_data, |
212 | capture: &i2s->capture_dma_data); |
213 | snd_soc_dai_set_drvdata(dai: cpu_dai, data: i2s); |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | static const struct snd_soc_dai_ops loongson_i2s_dai_ops = { |
219 | .probe = loongson_i2s_dai_probe, |
220 | .trigger = loongson_i2s_trigger, |
221 | .hw_params = loongson_i2s_hw_params, |
222 | .set_sysclk = loongson_i2s_set_dai_sysclk, |
223 | .set_fmt = loongson_i2s_set_fmt, |
224 | }; |
225 | |
226 | struct snd_soc_dai_driver loongson_i2s_dai = { |
227 | .name = "loongson-i2s" , |
228 | .playback = { |
229 | .stream_name = "CPU-Playback" , |
230 | .channels_min = 1, |
231 | .channels_max = 2, |
232 | .rates = SNDRV_PCM_RATE_8000_96000, |
233 | .formats = LOONGSON_I2S_FORMATS, |
234 | }, |
235 | .capture = { |
236 | .stream_name = "CPU-Capture" , |
237 | .channels_min = 1, |
238 | .channels_max = 2, |
239 | .rates = SNDRV_PCM_RATE_8000_96000, |
240 | .formats = LOONGSON_I2S_FORMATS, |
241 | }, |
242 | .ops = &loongson_i2s_dai_ops, |
243 | .symmetric_rate = 1, |
244 | }; |
245 | |
246 | static int i2s_suspend(struct device *dev) |
247 | { |
248 | struct loongson_i2s *i2s = dev_get_drvdata(dev); |
249 | |
250 | regcache_cache_only(map: i2s->regmap, enable: true); |
251 | |
252 | return 0; |
253 | } |
254 | |
255 | static int i2s_resume(struct device *dev) |
256 | { |
257 | struct loongson_i2s *i2s = dev_get_drvdata(dev); |
258 | int ret; |
259 | |
260 | regcache_cache_only(map: i2s->regmap, enable: false); |
261 | regcache_mark_dirty(map: i2s->regmap); |
262 | ret = regcache_sync(map: i2s->regmap); |
263 | |
264 | return ret; |
265 | } |
266 | |
267 | const struct dev_pm_ops loongson_i2s_pm = { |
268 | SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) |
269 | }; |
270 | |