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