1 // SPDX-License-Identifier: GPL-2.0
3 // Common functions for loongson I2S controller driver
5 // Copyright (C) 2023 Loongson Technology Corporation Limited.
6 // Author: Yingkun Meng <mengyingkun@loongson.cn>
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"
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)
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)
27 #define LOONGSON_I2S_DEF_DELAY 10
28 #define LOONGSON_I2S_DEF_TIMEOUT 500000
30 static int loongson_i2s_trigger(struct snd_pcm_substream
*substream
, int cmd
,
31 struct snd_soc_dai
*dai
)
33 struct loongson_i2s
*i2s
= snd_soc_dai_get_drvdata(dai
);
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
);
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);
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
)
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
;
73 switch (i2s
->rev_id
) {
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;
79 /* According to 2k1000LA user manual, set bits == depth */
82 val
|= (bclk_ratio
<< 8);
84 regmap_write(i2s
->regmap
, LS_I2S_CFG
, val
);
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);
94 regmap_read(i2s
->regmap
, LS_I2S_CFG
, &val
);
96 val
|= (bclk_ratio
<< 8);
97 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
101 regmap_write(i2s
->regmap
, LS_I2S_CFG
, val
);
103 val
= (mclk_ratio_frac
<< 16) | mclk_ratio
;
104 regmap_write(i2s
->regmap
, LS_I2S_CFG1
, val
);
108 dev_err(i2s
->dev
, "I2S revision invalid\n");
115 static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai
*dai
, int clk_id
,
116 unsigned int freq
, int dir
)
118 struct loongson_i2s
*i2s
= snd_soc_dai_get_drvdata(dai
);
125 static int loongson_i2s_enable_mclk(struct loongson_i2s
*i2s
)
129 if (i2s
->rev_id
== 0)
132 regmap_update_bits(i2s
->regmap
, LS_I2S_CTRL
,
133 I2S_CTRL_MCLK_EN
, I2S_CTRL_MCLK_EN
);
135 return regmap_read_poll_timeout_atomic(i2s
->regmap
,
137 val
& I2S_CTRL_MCLK_READY
,
138 LOONGSON_I2S_DEF_DELAY
,
139 LOONGSON_I2S_DEF_TIMEOUT
);
142 static int loongson_i2s_enable_bclk(struct loongson_i2s
*i2s
)
146 if (i2s
->rev_id
== 0)
149 return regmap_read_poll_timeout_atomic(i2s
->regmap
,
151 val
& I2S_CTRL_CLK_READY
,
152 LOONGSON_I2S_DEF_DELAY
,
153 LOONGSON_I2S_DEF_TIMEOUT
);
156 static int loongson_i2s_set_fmt(struct snd_soc_dai
*dai
, unsigned int fmt
)
158 struct loongson_i2s
*i2s
= snd_soc_dai_get_drvdata(dai
);
161 switch (fmt
& SND_SOC_DAIFMT_FORMAT_MASK
) {
162 case SND_SOC_DAIFMT_I2S
:
164 case SND_SOC_DAIFMT_RIGHT_J
:
165 regmap_update_bits(i2s
->regmap
, LS_I2S_CTRL
, I2S_CTRL_MSB
,
173 switch (fmt
& SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK
) {
174 case SND_SOC_DAIFMT_BC_FC
:
176 case SND_SOC_DAIFMT_BP_FC
:
177 /* Enable master mode */
178 regmap_update_bits(i2s
->regmap
, LS_I2S_CTRL
, I2S_CTRL_MASTER
,
180 ret
= loongson_i2s_enable_bclk(i2s
);
182 dev_warn(dai
->dev
, "wait BCLK ready timeout\n");
184 case SND_SOC_DAIFMT_BC_FP
:
186 ret
= loongson_i2s_enable_mclk(i2s
);
188 dev_warn(dai
->dev
, "wait MCLK ready timeout\n");
190 case SND_SOC_DAIFMT_BP_FP
:
192 ret
= loongson_i2s_enable_mclk(i2s
);
194 dev_warn(dai
->dev
, "wait MCLK ready timeout\n");
196 /* Enable master mode */
197 regmap_update_bits(i2s
->regmap
, LS_I2S_CTRL
, I2S_CTRL_MASTER
,
200 ret
= loongson_i2s_enable_bclk(i2s
);
202 dev_warn(dai
->dev
, "wait BCLK ready timeout\n");
211 static int loongson_i2s_dai_probe(struct snd_soc_dai
*cpu_dai
)
213 struct loongson_i2s
*i2s
= dev_get_drvdata(cpu_dai
->dev
);
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
);
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
,
230 struct snd_soc_dai_driver loongson_i2s_dai
= {
231 .name
= "loongson-i2s",
233 .stream_name
= "CPU-Playback",
236 .rates
= SNDRV_PCM_RATE_8000_96000
,
237 .formats
= LOONGSON_I2S_FORMATS
,
240 .stream_name
= "CPU-Capture",
243 .rates
= SNDRV_PCM_RATE_8000_96000
,
244 .formats
= LOONGSON_I2S_FORMATS
,
246 .ops
= &loongson_i2s_dai_ops
,
250 static int i2s_suspend(struct device
*dev
)
252 struct loongson_i2s
*i2s
= dev_get_drvdata(dev
);
254 regcache_cache_only(i2s
->regmap
, true);
259 static int i2s_resume(struct device
*dev
)
261 struct loongson_i2s
*i2s
= dev_get_drvdata(dev
);
263 regcache_cache_only(i2s
->regmap
, false);
264 regcache_mark_dirty(i2s
->regmap
);
265 return regcache_sync(i2s
->regmap
);
268 const struct dev_pm_ops loongson_i2s_pm
= {
269 SYSTEM_SLEEP_PM_OPS(i2s_suspend
, i2s_resume
)