xref: /linux/sound/soc/loongson/loongson_i2s.c (revision 299f489f5bad3554531f67335d1762225448ff39)
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