1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // Xilinx ASoC I2S audio support 4 // 5 // Copyright (C) 2018 Xilinx, Inc. 6 // 7 // Author: Praveen Vuppala <praveenv@xilinx.com> 8 // Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com> 9 10 #include <linux/io.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 #include <linux/of_platform.h> 14 #include <linux/platform_device.h> 15 #include <sound/pcm_params.h> 16 #include <sound/soc.h> 17 18 #define DRV_NAME "xlnx_i2s" 19 20 #define I2S_CORE_CTRL_OFFSET 0x08 21 #define I2S_CORE_CTRL_32BIT_LRCLK BIT(3) 22 #define I2S_CORE_CTRL_ENABLE BIT(0) 23 #define I2S_I2STIM_OFFSET 0x20 24 #define I2S_CH0_OFFSET 0x30 25 #define I2S_I2STIM_VALID_MASK GENMASK(7, 0) 26 27 struct xlnx_i2s_drv_data { 28 struct snd_soc_dai_driver dai_drv; 29 void __iomem *base; 30 unsigned int sysclk; 31 u32 data_width; 32 u32 channels; 33 bool is_32bit_lrclk; 34 struct snd_ratnum ratnum; 35 struct snd_pcm_hw_constraint_ratnums rate_constraints; 36 }; 37 38 static int xlnx_i2s_set_sclkout_div(struct snd_soc_dai *cpu_dai, 39 int div_id, int div) 40 { 41 struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(cpu_dai); 42 43 if (!div || (div & ~I2S_I2STIM_VALID_MASK)) 44 return -EINVAL; 45 46 drv_data->sysclk = 0; 47 48 writel(div, drv_data->base + I2S_I2STIM_OFFSET); 49 50 return 0; 51 } 52 53 static int xlnx_i2s_set_sysclk(struct snd_soc_dai *dai, 54 int clk_id, unsigned int freq, int dir) 55 { 56 struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(dai); 57 58 drv_data->sysclk = freq; 59 if (freq) { 60 unsigned int bits_per_sample; 61 62 if (drv_data->is_32bit_lrclk) 63 bits_per_sample = 32; 64 else 65 bits_per_sample = drv_data->data_width; 66 67 drv_data->ratnum.num = freq / (bits_per_sample * drv_data->channels) / 2; 68 drv_data->ratnum.den_step = 1; 69 drv_data->ratnum.den_min = 1; 70 drv_data->ratnum.den_max = 255; 71 drv_data->rate_constraints.rats = &drv_data->ratnum; 72 drv_data->rate_constraints.nrats = 1; 73 } 74 return 0; 75 } 76 77 static int xlnx_i2s_startup(struct snd_pcm_substream *substream, 78 struct snd_soc_dai *dai) 79 { 80 struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(dai); 81 82 if (drv_data->sysclk) 83 return snd_pcm_hw_constraint_ratnums(substream->runtime, 0, 84 SNDRV_PCM_HW_PARAM_RATE, 85 &drv_data->rate_constraints); 86 87 return 0; 88 } 89 90 static int xlnx_i2s_hw_params(struct snd_pcm_substream *substream, 91 struct snd_pcm_hw_params *params, 92 struct snd_soc_dai *i2s_dai) 93 { 94 u32 reg_off, chan_id; 95 struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(i2s_dai); 96 97 if (drv_data->sysclk) { 98 unsigned int bits_per_sample, sclk, sclk_div; 99 100 if (drv_data->is_32bit_lrclk) 101 bits_per_sample = 32; 102 else 103 bits_per_sample = drv_data->data_width; 104 105 sclk = params_rate(params) * bits_per_sample * params_channels(params); 106 sclk_div = drv_data->sysclk / sclk / 2; 107 108 if ((drv_data->sysclk % sclk != 0) || 109 !sclk_div || (sclk_div & ~I2S_I2STIM_VALID_MASK)) { 110 dev_warn(i2s_dai->dev, "invalid SCLK divisor for sysclk %u and sclk %u\n", 111 drv_data->sysclk, sclk); 112 return -EINVAL; 113 } 114 writel(sclk_div, drv_data->base + I2S_I2STIM_OFFSET); 115 } 116 117 chan_id = params_channels(params) / 2; 118 119 while (chan_id > 0) { 120 reg_off = I2S_CH0_OFFSET + ((chan_id - 1) * 4); 121 writel(chan_id, drv_data->base + reg_off); 122 chan_id--; 123 } 124 125 return 0; 126 } 127 128 static int xlnx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, 129 struct snd_soc_dai *i2s_dai) 130 { 131 struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(i2s_dai); 132 133 switch (cmd) { 134 case SNDRV_PCM_TRIGGER_START: 135 case SNDRV_PCM_TRIGGER_RESUME: 136 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 137 writel(I2S_CORE_CTRL_ENABLE, drv_data->base + I2S_CORE_CTRL_OFFSET); 138 break; 139 case SNDRV_PCM_TRIGGER_STOP: 140 case SNDRV_PCM_TRIGGER_SUSPEND: 141 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 142 writel(0, drv_data->base + I2S_CORE_CTRL_OFFSET); 143 break; 144 default: 145 return -EINVAL; 146 } 147 148 return 0; 149 } 150 151 static const struct snd_soc_dai_ops xlnx_i2s_dai_ops = { 152 .trigger = xlnx_i2s_trigger, 153 .set_sysclk = xlnx_i2s_set_sysclk, 154 .set_clkdiv = xlnx_i2s_set_sclkout_div, 155 .startup = xlnx_i2s_startup, 156 .hw_params = xlnx_i2s_hw_params 157 }; 158 159 static const struct snd_soc_component_driver xlnx_i2s_component = { 160 .name = DRV_NAME, 161 }; 162 163 static const struct of_device_id xlnx_i2s_of_match[] = { 164 { .compatible = "xlnx,i2s-transmitter-1.0", }, 165 { .compatible = "xlnx,i2s-receiver-1.0", }, 166 {}, 167 }; 168 MODULE_DEVICE_TABLE(of, xlnx_i2s_of_match); 169 170 static int xlnx_i2s_probe(struct platform_device *pdev) 171 { 172 struct xlnx_i2s_drv_data *drv_data; 173 int ret; 174 u32 format; 175 struct device *dev = &pdev->dev; 176 struct device_node *node = dev->of_node; 177 178 drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL); 179 if (!drv_data) 180 return -ENOMEM; 181 182 drv_data->base = devm_platform_ioremap_resource(pdev, 0); 183 if (IS_ERR(drv_data->base)) 184 return PTR_ERR(drv_data->base); 185 186 ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels); 187 if (ret < 0) { 188 dev_err(dev, "cannot get supported channels\n"); 189 return ret; 190 } 191 drv_data->channels *= 2; 192 193 ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width); 194 if (ret < 0) { 195 dev_err(dev, "cannot get data width\n"); 196 return ret; 197 } 198 switch (drv_data->data_width) { 199 case 16: 200 format = SNDRV_PCM_FMTBIT_S16_LE; 201 break; 202 case 24: 203 format = SNDRV_PCM_FMTBIT_S24_LE; 204 break; 205 default: 206 return -EINVAL; 207 } 208 209 if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) { 210 drv_data->dai_drv.name = "xlnx_i2s_playback"; 211 drv_data->dai_drv.playback.stream_name = "Playback"; 212 drv_data->dai_drv.playback.formats = format; 213 drv_data->dai_drv.playback.channels_min = drv_data->channels; 214 drv_data->dai_drv.playback.channels_max = drv_data->channels; 215 drv_data->dai_drv.playback.rates = SNDRV_PCM_RATE_8000_192000; 216 drv_data->dai_drv.ops = &xlnx_i2s_dai_ops; 217 } else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) { 218 drv_data->dai_drv.name = "xlnx_i2s_capture"; 219 drv_data->dai_drv.capture.stream_name = "Capture"; 220 drv_data->dai_drv.capture.formats = format; 221 drv_data->dai_drv.capture.channels_min = drv_data->channels; 222 drv_data->dai_drv.capture.channels_max = drv_data->channels; 223 drv_data->dai_drv.capture.rates = SNDRV_PCM_RATE_8000_192000; 224 drv_data->dai_drv.ops = &xlnx_i2s_dai_ops; 225 } else { 226 return -ENODEV; 227 } 228 drv_data->is_32bit_lrclk = readl(drv_data->base + I2S_CORE_CTRL_OFFSET) & 229 I2S_CORE_CTRL_32BIT_LRCLK; 230 231 dev_set_drvdata(&pdev->dev, drv_data); 232 233 ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component, 234 &drv_data->dai_drv, 1); 235 if (ret) { 236 dev_err(&pdev->dev, "i2s component registration failed\n"); 237 return ret; 238 } 239 240 dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name); 241 242 return ret; 243 } 244 245 static struct platform_driver xlnx_i2s_aud_driver = { 246 .driver = { 247 .name = DRV_NAME, 248 .of_match_table = xlnx_i2s_of_match, 249 }, 250 .probe = xlnx_i2s_probe, 251 }; 252 253 module_platform_driver(xlnx_i2s_aud_driver); 254 255 MODULE_LICENSE("GPL v2"); 256 MODULE_AUTHOR("Praveen Vuppala <praveenv@xilinx.com>"); 257 MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>"); 258