File Index
Files and directories
Sign in
FILE

loongson_i2s.c

File preview with syntax highlighting.

linux-6.19.14/sound/soc/loongson/loongson_i2s.c C
  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
 31static 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
 60static 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
116static 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
126static 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
143static 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
157static 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
212static 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
223static 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
231struct 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};
250EXPORT_SYMBOL_GPL(loongson_i2s_dai);
251
252static 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
261static 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
270const struct dev_pm_ops loongson_i2s_pm = {
271	SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
272};
273EXPORT_SYMBOL_GPL(loongson_i2s_pm);
274
275MODULE_LICENSE("GPL");
276MODULE_DESCRIPTION("Common functions for loongson I2S controller driver");
~/ Read-only file preview.