1/*
2 * Copyright (C) STMicroelectronics SA 2015
3 * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com>
4 *          for STMicroelectronics.
5 * License terms:  GNU General Public License (GPL), version 2
6 */
7
8#include <linux/module.h>
9#include <linux/pinctrl/consumer.h>
10
11#include "uniperif.h"
12
13/*
14 * sti_uniperiph_dai_create_ctrl
15 * This function is used to create Ctrl associated to DAI but also pcm device.
16 * Request is done by front end to associate ctrl with pcm device id
17 */
18static int sti_uniperiph_dai_create_ctrl(struct snd_soc_dai *dai)
19{
20	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
21	struct uniperif *uni = priv->dai_data.uni;
22	struct snd_kcontrol_new *ctrl;
23	int i;
24
25	if (!uni->num_ctrls)
26		return 0;
27
28	for (i = 0; i < uni->num_ctrls; i++) {
29		/*
30		 * Several Control can have same name. Controls are indexed on
31		 * Uniperipheral instance ID
32		 */
33		ctrl = &uni->snd_ctrls[i];
34		ctrl->index = uni->info->id;
35		ctrl->device = uni->info->id;
36	}
37
38	return snd_soc_add_dai_controls(dai, uni->snd_ctrls, uni->num_ctrls);
39}
40
41/*
42 * DAI
43 */
44int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream,
45				struct snd_pcm_hw_params *params,
46				struct snd_soc_dai *dai)
47{
48	struct snd_dmaengine_dai_dma_data *dma_data;
49	int transfer_size;
50
51	transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES;
52
53	dma_data = snd_soc_dai_get_dma_data(dai, substream);
54	dma_data->maxburst = transfer_size;
55
56	return 0;
57}
58
59int sti_uniperiph_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
60{
61	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
62
63	priv->dai_data.uni->daifmt = fmt;
64
65	return 0;
66}
67
68static int sti_uniperiph_dai_suspend(struct snd_soc_dai *dai)
69{
70	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
71	struct uniperif *uni = priv->dai_data.uni;
72	int ret;
73
74	/* The uniperipheral should be in stopped state */
75	if (uni->state != UNIPERIF_STATE_STOPPED) {
76		dev_err(uni->dev, "%s: invalid uni state( %d)",
77			__func__, (int)uni->state);
78		return -EBUSY;
79	}
80
81	/* Pinctrl: switch pinstate to sleep */
82	ret = pinctrl_pm_select_sleep_state(uni->dev);
83	if (ret)
84		dev_err(uni->dev, "%s: failed to select pinctrl state",
85			__func__);
86
87	return ret;
88}
89
90static int sti_uniperiph_dai_resume(struct snd_soc_dai *dai)
91{
92	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
93	struct uniperif *uni = priv->dai_data.uni;
94	int ret;
95
96	if (of_device_is_compatible(dai->dev->of_node, "st,sti-uni-player")) {
97		ret = uni_player_resume(uni);
98		if (ret)
99			return ret;
100	}
101
102	/* pinctrl: switch pinstate to default */
103	ret = pinctrl_pm_select_default_state(uni->dev);
104	if (ret)
105		dev_err(uni->dev, "%s: failed to select pinctrl state",
106			__func__);
107
108	return ret;
109}
110
111static int sti_uniperiph_dai_probe(struct snd_soc_dai *dai)
112{
113	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
114	struct sti_uniperiph_dai *dai_data = &priv->dai_data;
115
116	/* DMA settings*/
117	if (of_device_is_compatible(dai->dev->of_node, "st,sti-uni-player"))
118		snd_soc_dai_init_dma_data(dai, &dai_data->dma_data, NULL);
119	else
120		snd_soc_dai_init_dma_data(dai, NULL, &dai_data->dma_data);
121
122	dai_data->dma_data.addr = dai_data->uni->fifo_phys_address;
123	dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
124
125	return sti_uniperiph_dai_create_ctrl(dai);
126}
127
128static const struct snd_soc_dai_driver sti_uniperiph_dai_template = {
129	.probe = sti_uniperiph_dai_probe,
130	.suspend = sti_uniperiph_dai_suspend,
131	.resume = sti_uniperiph_dai_resume
132};
133
134static const struct snd_soc_component_driver sti_uniperiph_dai_component = {
135	.name = "sti_cpu_dai",
136};
137
138static int sti_uniperiph_cpu_dai_of(struct device_node *node,
139				    struct sti_uniperiph_data *priv)
140{
141	const char *str;
142	int ret;
143	struct device *dev = &priv->pdev->dev;
144	struct sti_uniperiph_dai *dai_data = &priv->dai_data;
145	struct snd_soc_dai_driver *dai = priv->dai;
146	struct snd_soc_pcm_stream *stream;
147	struct uniperif *uni;
148
149	uni = devm_kzalloc(dev, sizeof(*uni), GFP_KERNEL);
150	if (!uni)
151		return -ENOMEM;
152
153	*dai = sti_uniperiph_dai_template;
154	ret = of_property_read_string(node, "dai-name", &str);
155	if (ret < 0) {
156		dev_err(dev, "%s: dai name missing.\n", __func__);
157		return -EINVAL;
158	}
159	dai->name = str;
160
161	/* Get resources */
162	uni->mem_region = platform_get_resource(priv->pdev, IORESOURCE_MEM, 0);
163
164	if (!uni->mem_region) {
165		dev_err(dev, "Failed to get memory resource");
166		return -ENODEV;
167	}
168
169	uni->base = devm_ioremap_resource(dev, uni->mem_region);
170
171	if (IS_ERR(uni->base))
172		return PTR_ERR(uni->base);
173
174	uni->fifo_phys_address = uni->mem_region->start +
175				     UNIPERIF_FIFO_DATA_OFFSET(uni);
176
177	uni->irq = platform_get_irq(priv->pdev, 0);
178	if (uni->irq < 0) {
179		dev_err(dev, "Failed to get IRQ resource");
180		return -ENXIO;
181	}
182
183	dai_data->uni = uni;
184
185	if (of_device_is_compatible(node, "st,sti-uni-player")) {
186		uni_player_init(priv->pdev, uni);
187		stream = &dai->playback;
188	} else {
189		uni_reader_init(priv->pdev, uni);
190		stream = &dai->capture;
191	}
192	dai->ops = uni->dai_ops;
193
194	stream->stream_name = dai->name;
195	stream->channels_min = uni->hw->channels_min;
196	stream->channels_max = uni->hw->channels_max;
197	stream->rates = uni->hw->rates;
198	stream->formats = uni->hw->formats;
199
200	return 0;
201}
202
203static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = {
204	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
205};
206
207static int sti_uniperiph_probe(struct platform_device *pdev)
208{
209	struct sti_uniperiph_data *priv;
210	struct device_node *node = pdev->dev.of_node;
211	int ret;
212
213	/* Allocate the private data and the CPU_DAI array */
214	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
215	if (!priv)
216		return -ENOMEM;
217	priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai), GFP_KERNEL);
218	if (!priv->dai)
219		return -ENOMEM;
220
221	priv->pdev = pdev;
222
223	ret = sti_uniperiph_cpu_dai_of(node, priv);
224
225	dev_set_drvdata(&pdev->dev, priv);
226
227	ret = devm_snd_soc_register_component(&pdev->dev,
228					      &sti_uniperiph_dai_component,
229					      priv->dai, 1);
230	if (ret < 0)
231		return ret;
232
233	return devm_snd_dmaengine_pcm_register(&pdev->dev,
234					       &dmaengine_pcm_config, 0);
235}
236
237static const struct of_device_id snd_soc_sti_match[] = {
238	{ .compatible = "st,sti-uni-player", },
239	{ .compatible = "st,sti-uni-reader", },
240	{},
241};
242
243static struct platform_driver sti_uniperiph_driver = {
244	.driver = {
245		.name = "sti-uniperiph-dai",
246		.of_match_table = snd_soc_sti_match,
247	},
248	.probe = sti_uniperiph_probe,
249};
250module_platform_driver(sti_uniperiph_driver);
251
252MODULE_DESCRIPTION("uniperipheral DAI driver");
253MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
254MODULE_LICENSE("GPL v2");
255