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 31 static 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 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_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 143 static 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 157 static 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 212 static 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 223 static 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 231 struct 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 }; 250 EXPORT_SYMBOL_GPL(loongson_i2s_dai); 251 252 static 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 261 static 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 270 const struct dev_pm_ops loongson_i2s_pm = { 271 SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) 272 }; 273 EXPORT_SYMBOL_GPL(loongson_i2s_pm); 274 275 MODULE_LICENSE("GPL"); 276 MODULE_DESCRIPTION("Common functions for loongson I2S controller driver"); 277