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