260 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// Xilinx ASoC I2S audio support4//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 0x0821#define I2S_CORE_CTRL_32BIT_LRCLK BIT(3)22#define I2S_CORE_CTRL_ENABLE BIT(0)23#define I2S_I2STIM_OFFSET 0x2024#define I2S_CH0_OFFSET 0x3025#define I2S_I2STIM_VALID_MASK GENMASK(7, 0)26 27struct 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 38static 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 53static 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 else65 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 77static 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 90static 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 else103 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 128static 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 151static 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_params157};158 159static const struct snd_soc_component_driver xlnx_i2s_component = {160 .name = DRV_NAME,161 .legacy_dai_naming = 1,162};163 164static const struct of_device_id xlnx_i2s_of_match[] = {165 { .compatible = "xlnx,i2s-transmitter-1.0", },166 { .compatible = "xlnx,i2s-receiver-1.0", },167 {},168};169MODULE_DEVICE_TABLE(of, xlnx_i2s_of_match);170 171static int xlnx_i2s_probe(struct platform_device *pdev)172{173 struct xlnx_i2s_drv_data *drv_data;174 int ret;175 u32 format;176 struct device *dev = &pdev->dev;177 struct device_node *node = dev->of_node;178 179 drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL);180 if (!drv_data)181 return -ENOMEM;182 183 drv_data->base = devm_platform_ioremap_resource(pdev, 0);184 if (IS_ERR(drv_data->base))185 return PTR_ERR(drv_data->base);186 187 ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels);188 if (ret < 0) {189 dev_err(dev, "cannot get supported channels\n");190 return ret;191 }192 drv_data->channels *= 2;193 194 ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width);195 if (ret < 0) {196 dev_err(dev, "cannot get data width\n");197 return ret;198 }199 switch (drv_data->data_width) {200 case 16:201 format = SNDRV_PCM_FMTBIT_S16_LE;202 break;203 case 24:204 format = SNDRV_PCM_FMTBIT_S24_LE;205 break;206 default:207 return -EINVAL;208 }209 210 if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) {211 drv_data->dai_drv.name = "xlnx_i2s_playback";212 drv_data->dai_drv.playback.stream_name = "Playback";213 drv_data->dai_drv.playback.formats = format;214 drv_data->dai_drv.playback.channels_min = drv_data->channels;215 drv_data->dai_drv.playback.channels_max = drv_data->channels;216 drv_data->dai_drv.playback.rates = SNDRV_PCM_RATE_8000_192000;217 drv_data->dai_drv.ops = &xlnx_i2s_dai_ops;218 } else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) {219 drv_data->dai_drv.name = "xlnx_i2s_capture";220 drv_data->dai_drv.capture.stream_name = "Capture";221 drv_data->dai_drv.capture.formats = format;222 drv_data->dai_drv.capture.channels_min = drv_data->channels;223 drv_data->dai_drv.capture.channels_max = drv_data->channels;224 drv_data->dai_drv.capture.rates = SNDRV_PCM_RATE_8000_192000;225 drv_data->dai_drv.ops = &xlnx_i2s_dai_ops;226 } else {227 return -ENODEV;228 }229 drv_data->is_32bit_lrclk = readl(drv_data->base + I2S_CORE_CTRL_OFFSET) &230 I2S_CORE_CTRL_32BIT_LRCLK;231 232 dev_set_drvdata(&pdev->dev, drv_data);233 234 ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component,235 &drv_data->dai_drv, 1);236 if (ret) {237 dev_err(&pdev->dev, "i2s component registration failed\n");238 return ret;239 }240 241 dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name);242 243 return ret;244}245 246static struct platform_driver xlnx_i2s_aud_driver = {247 .driver = {248 .name = DRV_NAME,249 .of_match_table = xlnx_i2s_of_match,250 },251 .probe = xlnx_i2s_probe,252};253 254module_platform_driver(xlnx_i2s_aud_driver);255 256MODULE_DESCRIPTION("ASoC driver for Xilinx I2S audio");257MODULE_LICENSE("GPL v2");258MODULE_AUTHOR("Praveen Vuppala <praveenv@xilinx.com>");259MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");260