xref: /openbmc/linux/sound/soc/xilinx/xlnx_i2s.c (revision 335f70fa)
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