brintos

brintos / linux-shallow public Read only

0
0
Text · 6.7 KiB · 40bbf32 Raw
271 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// Common functions for loongson I2S controller driver4//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		1028#define LOONGSON_I2S_DEF_TIMEOUT	50000029 30static 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 59static 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		else100			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 115static 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 125static 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 142static 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 156static 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 211static 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 222static 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 230struct 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 250static 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 259static 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 268const struct dev_pm_ops loongson_i2s_pm = {269	SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)270};271