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/export.h>
13 #include <linux/pm_runtime.h>
14 #include <linux/dma-mapping.h>
15 #include <sound/soc.h>
16 #include <linux/regmap.h>
17 #include <sound/pcm_params.h>
18 #include "loongson_i2s.h"
19
20 #define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
21 SNDRV_PCM_FMTBIT_S16_LE | \
22 SNDRV_PCM_FMTBIT_S20_3LE | \
23 SNDRV_PCM_FMTBIT_S24_LE)
24
25 #define LOONGSON_I2S_TX_ENABLE (I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN)
26 #define LOONGSON_I2S_RX_ENABLE (I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN)
27
28 #define LOONGSON_I2S_DEF_DELAY 10
29 #define LOONGSON_I2S_DEF_TIMEOUT 500000
30
loongson_i2s_trigger(struct snd_pcm_substream * substream,int cmd,struct snd_soc_dai * dai)31 static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
32 struct snd_soc_dai *dai)
33 {
34 struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
35 unsigned int mask;
36 int ret = 0;
37
38 switch (cmd) {
39 case SNDRV_PCM_TRIGGER_START:
40 case SNDRV_PCM_TRIGGER_RESUME:
41 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
42 mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
43 LOONGSON_I2S_TX_ENABLE : LOONGSON_I2S_RX_ENABLE;
44 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, mask, mask);
45 break;
46 case SNDRV_PCM_TRIGGER_STOP:
47 case SNDRV_PCM_TRIGGER_SUSPEND:
48 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
49 mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
50 LOONGSON_I2S_TX_ENABLE : LOONGSON_I2S_RX_ENABLE;
51 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, mask, 0);
52 break;
53 default:
54 ret = -EINVAL;
55 }
56
57 return ret;
58 }
59
loongson_i2s_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params,struct snd_soc_dai * dai)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(params);
68 u32 chans = params_channels(params);
69 u32 fs = params_rate(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(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(i2s->regmap, LS_I2S_CFG, &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(i2s->regmap, LS_I2S_CFG, val);
103
104 val = (mclk_ratio_frac << 16) | mclk_ratio;
105 regmap_write(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
loongson_i2s_set_dai_sysclk(struct snd_soc_dai * dai,int clk_id,unsigned int freq,int dir)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
loongson_i2s_enable_mclk(struct loongson_i2s * i2s)126 static int loongson_i2s_enable_mclk(struct loongson_i2s *i2s)
127 {
128 u32 val;
129
130 if (i2s->rev_id == 0)
131 return 0;
132
133 regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
134 I2S_CTRL_MCLK_EN, I2S_CTRL_MCLK_EN);
135
136 return regmap_read_poll_timeout_atomic(i2s->regmap,
137 LS_I2S_CTRL, val,
138 val & I2S_CTRL_MCLK_READY,
139 LOONGSON_I2S_DEF_DELAY,
140 LOONGSON_I2S_DEF_TIMEOUT);
141 }
142
loongson_i2s_enable_bclk(struct loongson_i2s * i2s)143 static int loongson_i2s_enable_bclk(struct loongson_i2s *i2s)
144 {
145 u32 val;
146
147 if (i2s->rev_id == 0)
148 return 0;
149
150 return regmap_read_poll_timeout_atomic(i2s->regmap,
151 LS_I2S_CTRL, val,
152 val & I2S_CTRL_CLK_READY,
153 LOONGSON_I2S_DEF_DELAY,
154 LOONGSON_I2S_DEF_TIMEOUT);
155 }
156
loongson_i2s_set_fmt(struct snd_soc_dai * dai,unsigned int fmt)157 static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
158 {
159 struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
160 int ret;
161
162 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
163 case SND_SOC_DAIFMT_I2S:
164 break;
165 case SND_SOC_DAIFMT_RIGHT_J:
166 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB,
167 I2S_CTRL_MSB);
168 break;
169 default:
170 return -EINVAL;
171 }
172
173
174 switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
175 case SND_SOC_DAIFMT_BC_FC:
176 break;
177 case SND_SOC_DAIFMT_BP_FC:
178 /* Enable master mode */
179 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
180 I2S_CTRL_MASTER);
181 ret = loongson_i2s_enable_bclk(i2s);
182 if (ret < 0)
183 dev_warn(dai->dev, "wait BCLK ready timeout\n");
184 break;
185 case SND_SOC_DAIFMT_BC_FP:
186 /* Enable MCLK */
187 ret = loongson_i2s_enable_mclk(i2s);
188 if (ret < 0)
189 dev_warn(dai->dev, "wait MCLK ready timeout\n");
190 break;
191 case SND_SOC_DAIFMT_BP_FP:
192 /* Enable MCLK */
193 ret = loongson_i2s_enable_mclk(i2s);
194 if (ret < 0)
195 dev_warn(dai->dev, "wait MCLK ready timeout\n");
196
197 /* Enable master mode */
198 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
199 I2S_CTRL_MASTER);
200
201 ret = loongson_i2s_enable_bclk(i2s);
202 if (ret < 0)
203 dev_warn(dai->dev, "wait BCLK ready timeout\n");
204 break;
205 default:
206 return -EINVAL;
207 }
208
209 return 0;
210 }
211
loongson_i2s_dai_probe(struct snd_soc_dai * cpu_dai)212 static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
213 {
214 struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev);
215
216 snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data,
217 &i2s->capture_dma_data);
218 snd_soc_dai_set_drvdata(cpu_dai, i2s);
219
220 return 0;
221 }
222
223 static const struct snd_soc_dai_ops loongson_i2s_dai_ops = {
224 .probe = loongson_i2s_dai_probe,
225 .trigger = loongson_i2s_trigger,
226 .hw_params = loongson_i2s_hw_params,
227 .set_sysclk = loongson_i2s_set_dai_sysclk,
228 .set_fmt = loongson_i2s_set_fmt,
229 };
230
231 struct snd_soc_dai_driver loongson_i2s_dai = {
232 .name = "loongson-i2s",
233 .playback = {
234 .stream_name = "CPU-Playback",
235 .channels_min = 1,
236 .channels_max = 2,
237 .rates = SNDRV_PCM_RATE_8000_96000,
238 .formats = LOONGSON_I2S_FORMATS,
239 },
240 .capture = {
241 .stream_name = "CPU-Capture",
242 .channels_min = 1,
243 .channels_max = 2,
244 .rates = SNDRV_PCM_RATE_8000_96000,
245 .formats = LOONGSON_I2S_FORMATS,
246 },
247 .ops = &loongson_i2s_dai_ops,
248 .symmetric_rate = 1,
249 };
250 EXPORT_SYMBOL_GPL(loongson_i2s_dai);
251
i2s_suspend(struct device * dev)252 static int i2s_suspend(struct device *dev)
253 {
254 struct loongson_i2s *i2s = dev_get_drvdata(dev);
255
256 regcache_cache_only(i2s->regmap, true);
257
258 return 0;
259 }
260
i2s_resume(struct device * dev)261 static int i2s_resume(struct device *dev)
262 {
263 struct loongson_i2s *i2s = dev_get_drvdata(dev);
264
265 regcache_cache_only(i2s->regmap, false);
266 regcache_mark_dirty(i2s->regmap);
267 return regcache_sync(i2s->regmap);
268 }
269
270 const struct dev_pm_ops loongson_i2s_pm = {
271 SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
272 };
273 EXPORT_SYMBOL_GPL(loongson_i2s_pm);
274
275 MODULE_LICENSE("GPL");
276 MODULE_DESCRIPTION("Common functions for loongson I2S controller driver");
277