Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/soc/loongson/loongson_i2s.c
26437 views
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 <[email protected]>
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
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
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
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_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
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
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
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
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
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
278