sound: ASoC: Blackfin: I2S CPU DAI driver
[linux-2.6.git] / sound / soc / blackfin / bf5xx-i2s.c
1 /*
2  * File:         sound/soc/blackfin/bf5xx-i2s.c
3  * Author:       Cliff Cai <Cliff.Cai@analog.com>
4  *
5  * Created:      Tue June 06 2008
6  * Description:  Blackfin I2S CPU DAI driver
7  *
8  * Modified:
9  *               Copyright 2008 Analog Devices Inc.
10  *
11  * Bugs:         Enter bugs at http://blackfin.uclinux.org/
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, see the file COPYING, or write
25  * to the Free Software Foundation, Inc.,
26  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
27  */
28
29 #include <linux/init.h>
30 #include <linux/module.h>
31 #include <linux/device.h>
32 #include <linux/delay.h>
33 #include <sound/core.h>
34 #include <sound/pcm.h>
35 #include <sound/pcm_params.h>
36 #include <sound/initval.h>
37 #include <sound/soc.h>
38
39 #include <asm/irq.h>
40 #include <asm/portmux.h>
41 #include <linux/mutex.h>
42 #include <linux/gpio.h>
43
44 #include "bf5xx-sport.h"
45 #include "bf5xx-i2s.h"
46
47 struct bf5xx_i2s_port {
48         u16 tcr1;
49         u16 rcr1;
50         u16 tcr2;
51         u16 rcr2;
52         int counter;
53 };
54
55 static struct bf5xx_i2s_port bf5xx_i2s;
56 static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
57
58 static struct sport_param sport_params[2] = {
59         {
60                 .dma_rx_chan    = CH_SPORT0_RX,
61                 .dma_tx_chan    = CH_SPORT0_TX,
62                 .err_irq        = IRQ_SPORT0_ERROR,
63                 .regs           = (struct sport_register *)SPORT0_TCR1,
64         },
65         {
66                 .dma_rx_chan    = CH_SPORT1_RX,
67                 .dma_tx_chan    = CH_SPORT1_TX,
68                 .err_irq        = IRQ_SPORT1_ERROR,
69                 .regs           = (struct sport_register *)SPORT1_TCR1,
70         }
71 };
72
73 static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
74                 unsigned int fmt)
75 {
76         int ret = 0;
77
78         /* interface format:support I2S,slave mode */
79         switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
80         case SND_SOC_DAIFMT_I2S:
81                 break;
82         case SND_SOC_DAIFMT_LEFT_J:
83                 ret = -EINVAL;
84                 break;
85         default:
86                 ret = -EINVAL;
87                 break;
88         }
89
90         switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
91         case SND_SOC_DAIFMT_CBS_CFS:
92                 ret = -EINVAL;
93                 break;
94         case SND_SOC_DAIFMT_CBM_CFS:
95                 ret = -EINVAL;
96                 break;
97         case SND_SOC_DAIFMT_CBM_CFM:
98                 break;
99         case SND_SOC_DAIFMT_CBS_CFM:
100                 ret = -EINVAL;
101                 break;
102         default:
103                 ret = -EINVAL;
104                 break;
105         }
106
107         return ret;
108 }
109
110 static int bf5xx_i2s_startup(struct snd_pcm_substream *substream)
111 {
112         pr_debug("%s enter\n", __func__);
113
114         /*this counter is used for counting how many pcm streams are opened*/
115         bf5xx_i2s.counter++;
116         return 0;
117 }
118
119 static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream,
120                                 struct snd_pcm_hw_params *params)
121 {
122         int ret = 0;
123
124         bf5xx_i2s.tcr2 &= ~0x1f;
125         bf5xx_i2s.rcr2 &= ~0x1f;
126         switch (params_format(params)) {
127         case SNDRV_PCM_FORMAT_S16_LE:
128                 bf5xx_i2s.tcr2 |= 15;
129                 bf5xx_i2s.rcr2 |= 15;
130                 break;
131         case SNDRV_PCM_FORMAT_S24_LE:
132                 bf5xx_i2s.tcr2 |= 23;
133                 bf5xx_i2s.rcr2 |= 23;
134                 break;
135         case SNDRV_PCM_FORMAT_S32_LE:
136                 bf5xx_i2s.tcr2 |= 31;
137                 bf5xx_i2s.rcr2 |= 31;
138                 break;
139         }
140
141         if (bf5xx_i2s.counter == 1) {
142                 /*
143                  * TX and RX are not independent,they are enabled at the
144                  * same time, even if only one side is running. So, we
145                  * need to configure both of them at the time when the first
146                  * stream is opened.
147                  *
148                  * CPU DAI format:I2S, slave mode.
149                  */
150                 ret = sport_config_rx(sport_handle, RFSR | RCKFE,
151                                       RSFSE|bf5xx_i2s.rcr2, 0, 0);
152                 if (ret) {
153                         pr_err("SPORT is busy!\n");
154                         return -EBUSY;
155                 }
156
157                 ret = sport_config_tx(sport_handle, TFSR | TCKFE,
158                                       TSFSE|bf5xx_i2s.tcr2, 0, 0);
159                 if (ret) {
160                         pr_err("SPORT is busy!\n");
161                         return -EBUSY;
162                 }
163         }
164
165         return 0;
166 }
167
168 static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream)
169 {
170         pr_debug("%s enter\n", __func__);
171         bf5xx_i2s.counter--;
172 }
173
174 static int bf5xx_i2s_probe(struct platform_device *pdev,
175                            struct snd_soc_dai *dai)
176 {
177         u16 sport_req[][7] = {
178                 { P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS,
179                   P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0},
180                 { P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS,
181                   P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0},
182         };
183
184         pr_debug("%s enter\n", __func__);
185         if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
186                 pr_err("Requesting Peripherals failed\n");
187                 return -EFAULT;
188         }
189
190         /* request DMA for SPORT */
191         sport_handle = sport_init(&sport_params[sport_num], 4, \
192                         2 * sizeof(u32), NULL);
193         if (!sport_handle) {
194                 peripheral_free_list(&sport_req[sport_num][0]);
195                 return -ENODEV;
196         }
197
198         return 0;
199 }
200
201 #ifdef CONFIG_PM
202 static int bf5xx_i2s_suspend(struct platform_device *dev,
203                              struct snd_soc_dai *dai)
204 {
205         struct sport_device *sport =
206                 (struct sport_device *)dai->private_data;
207
208         pr_debug("%s : sport %d\n", __func__, dai->id);
209         if (!dai->active)
210                 return 0;
211         if (dai->capture.active)
212                 sport_rx_stop(sport);
213         if (dai->playback.active)
214                 sport_tx_stop(sport);
215         return 0;
216 }
217
218 static int bf5xx_i2s_resume(struct platform_device *pdev,
219                             struct snd_soc_dai *dai)
220 {
221         int ret;
222         struct sport_device *sport =
223                 (struct sport_device *)dai->private_data;
224
225         pr_debug("%s : sport %d\n", __func__, dai->id);
226         if (!dai->active)
227                 return 0;
228
229         ret = sport_config_rx(sport_handle, RFSR | RCKFE, RSFSE|0x1f, 0, 0);
230         if (ret) {
231                 pr_err("SPORT is busy!\n");
232                 return -EBUSY;
233         }
234
235         ret = sport_config_tx(sport_handle, TFSR | TCKFE, TSFSE|0x1f, 0, 0);
236         if (ret) {
237                 pr_err("SPORT is busy!\n");
238                 return -EBUSY;
239         }
240
241         if (dai->capture.active)
242                 sport_rx_start(sport);
243         if (dai->playback.active)
244                 sport_tx_start(sport);
245         return 0;
246 }
247
248 #else
249 #define bf5xx_i2s_suspend       NULL
250 #define bf5xx_i2s_resume        NULL
251 #endif
252
253 #define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
254                 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
255                 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
256                 SNDRV_PCM_RATE_96000)
257
258 #define BF5XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
259         SNDRV_PCM_FMTBIT_S32_LE)
260
261 struct snd_soc_dai bf5xx_i2s_dai = {
262         .name = "bf5xx-i2s",
263         .id = 0,
264         .type = SND_SOC_DAI_I2S,
265         .probe = bf5xx_i2s_probe,
266         .suspend = bf5xx_i2s_suspend,
267         .resume = bf5xx_i2s_resume,
268         .playback = {
269                 .channels_min = 2,
270                 .channels_max = 2,
271                 .rates = BF5XX_I2S_RATES,
272                 .formats = BF5XX_I2S_FORMATS,},
273         .capture = {
274                 .channels_min = 2,
275                 .channels_max = 2,
276                 .rates = BF5XX_I2S_RATES,
277                 .formats = BF5XX_I2S_FORMATS,},
278         .ops = {
279                 .startup   = bf5xx_i2s_startup,
280                 .shutdown  = bf5xx_i2s_shutdown,
281                 .hw_params = bf5xx_i2s_hw_params,},
282         .dai_ops = {
283                 .set_fmt = bf5xx_i2s_set_dai_fmt,
284         },
285 };
286 EXPORT_SYMBOL_GPL(bf5xx_i2s_dai);
287
288 /* Module information */
289 MODULE_AUTHOR("Cliff Cai");
290 MODULE_DESCRIPTION("I2S driver for ADI Blackfin");
291 MODULE_LICENSE("GPL");
292