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/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 static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd, 25 struct snd_soc_dai *dai) 26 { 27 struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); 28 int ret = 0; 29 30 switch (cmd) { 31 case SNDRV_PCM_TRIGGER_START: 32 case SNDRV_PCM_TRIGGER_RESUME: 33 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 34 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 35 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 36 I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 37 I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN); 38 else 39 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 40 I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 41 I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN); 42 break; 43 case SNDRV_PCM_TRIGGER_STOP: 44 case SNDRV_PCM_TRIGGER_SUSPEND: 45 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 46 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 47 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 48 I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0); 49 else 50 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 51 I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 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_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 127 { 128 struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); 129 u32 val; 130 int ret; 131 132 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 133 case SND_SOC_DAIFMT_I2S: 134 break; 135 case SND_SOC_DAIFMT_RIGHT_J: 136 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB, 137 I2S_CTRL_MSB); 138 break; 139 default: 140 return -EINVAL; 141 } 142 143 144 switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { 145 case SND_SOC_DAIFMT_BC_FC: 146 break; 147 case SND_SOC_DAIFMT_BP_FC: 148 /* Enable master mode */ 149 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, 150 I2S_CTRL_MASTER); 151 if (i2s->rev_id == 1) { 152 ret = regmap_read_poll_timeout_atomic(i2s->regmap, 153 LS_I2S_CTRL, val, 154 val & I2S_CTRL_CLK_READY, 155 10, 500000); 156 if (ret < 0) 157 dev_warn(dai->dev, "wait BCLK ready timeout\n"); 158 } 159 break; 160 case SND_SOC_DAIFMT_BC_FP: 161 /* Enable MCLK */ 162 if (i2s->rev_id == 1) { 163 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 164 I2S_CTRL_MCLK_EN, 165 I2S_CTRL_MCLK_EN); 166 ret = regmap_read_poll_timeout_atomic(i2s->regmap, 167 LS_I2S_CTRL, val, 168 val & I2S_CTRL_MCLK_READY, 169 10, 500000); 170 if (ret < 0) 171 dev_warn(dai->dev, "wait MCLK ready timeout\n"); 172 } 173 break; 174 case SND_SOC_DAIFMT_BP_FP: 175 /* Enable MCLK */ 176 if (i2s->rev_id == 1) { 177 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 178 I2S_CTRL_MCLK_EN, 179 I2S_CTRL_MCLK_EN); 180 ret = regmap_read_poll_timeout_atomic(i2s->regmap, 181 LS_I2S_CTRL, val, 182 val & I2S_CTRL_MCLK_READY, 183 10, 500000); 184 if (ret < 0) 185 dev_warn(dai->dev, "wait MCLK ready timeout\n"); 186 } 187 188 /* Enable master mode */ 189 regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, 190 I2S_CTRL_MASTER); 191 if (i2s->rev_id == 1) { 192 ret = regmap_read_poll_timeout_atomic(i2s->regmap, 193 LS_I2S_CTRL, val, 194 val & I2S_CTRL_CLK_READY, 195 10, 500000); 196 if (ret < 0) 197 dev_warn(dai->dev, "wait BCLK ready timeout\n"); 198 } 199 break; 200 default: 201 return -EINVAL; 202 } 203 204 return 0; 205 } 206 207 static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai) 208 { 209 struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev); 210 211 snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data, 212 &i2s->capture_dma_data); 213 snd_soc_dai_set_drvdata(cpu_dai, i2s); 214 215 return 0; 216 } 217 218 static const struct snd_soc_dai_ops loongson_i2s_dai_ops = { 219 .probe = loongson_i2s_dai_probe, 220 .trigger = loongson_i2s_trigger, 221 .hw_params = loongson_i2s_hw_params, 222 .set_sysclk = loongson_i2s_set_dai_sysclk, 223 .set_fmt = loongson_i2s_set_fmt, 224 }; 225 226 struct snd_soc_dai_driver loongson_i2s_dai = { 227 .name = "loongson-i2s", 228 .playback = { 229 .stream_name = "CPU-Playback", 230 .channels_min = 1, 231 .channels_max = 2, 232 .rates = SNDRV_PCM_RATE_8000_96000, 233 .formats = LOONGSON_I2S_FORMATS, 234 }, 235 .capture = { 236 .stream_name = "CPU-Capture", 237 .channels_min = 1, 238 .channels_max = 2, 239 .rates = SNDRV_PCM_RATE_8000_96000, 240 .formats = LOONGSON_I2S_FORMATS, 241 }, 242 .ops = &loongson_i2s_dai_ops, 243 .symmetric_rate = 1, 244 }; 245 246 static int i2s_suspend(struct device *dev) 247 { 248 struct loongson_i2s *i2s = dev_get_drvdata(dev); 249 250 regcache_cache_only(i2s->regmap, true); 251 252 return 0; 253 } 254 255 static int i2s_resume(struct device *dev) 256 { 257 struct loongson_i2s *i2s = dev_get_drvdata(dev); 258 int ret; 259 260 regcache_cache_only(i2s->regmap, false); 261 regcache_mark_dirty(i2s->regmap); 262 ret = regcache_sync(i2s->regmap); 263 264 return ret; 265 } 266 267 const struct dev_pm_ops loongson_i2s_pm = { 268 SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) 269 }; 270
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.