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 #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 10 28 #define LOONGSON_I2S_DEF_TIMEOUT 500000 29 30 static 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 59 static 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 else 100 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 115 static 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 125 static 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 142 static 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 156 static 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 211 static 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 222 static 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 230 struct 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 250 static 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 259 static 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 268 const struct dev_pm_ops loongson_i2s_pm = { 269 SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) 270 }; 271