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