xref: /openbmc/linux/sound/soc/xilinx/xlnx_i2s.c (revision bd486b07)
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 
xlnx_i2s_set_sclkout_div(struct snd_soc_dai * cpu_dai,int div_id,int div)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 
xlnx_i2s_set_sysclk(struct snd_soc_dai * dai,int clk_id,unsigned int freq,int dir)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 
xlnx_i2s_startup(struct snd_pcm_substream * substream,struct snd_soc_dai * dai)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 
xlnx_i2s_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params,struct snd_soc_dai * i2s_dai)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 
xlnx_i2s_trigger(struct snd_pcm_substream * substream,int cmd,struct snd_soc_dai * i2s_dai)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 	.legacy_dai_naming = 1,
162 };
163 
164 static 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 };
169 MODULE_DEVICE_TABLE(of, xlnx_i2s_of_match);
170 
xlnx_i2s_probe(struct platform_device * pdev)171 static 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 
246 static 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 
254 module_platform_driver(xlnx_i2s_aud_driver);
255 
256 MODULE_LICENSE("GPL v2");
257 MODULE_AUTHOR("Praveen Vuppala  <praveenv@xilinx.com>");
258 MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");
259