1/*
2 * pxa2xx-i2s.c  --  ALSA Soc Audio Layer
3 *
4 * Copyright 2005 Wolfson Microelectronics PLC.
5 * Author: Liam Girdwood
6 *         liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
7 *
8 *  This program is free software; you can redistribute  it and/or modify it
9 *  under  the terms of  the GNU General  Public License as published by the
10 *  Free Software Foundation;  either version 2 of the  License, or (at your
11 *  option) any later version.
12 *
13 *  Revision history
14 *    12th Aug 2005   Initial version.
15 */
16
17#include <linux/init.h>
18#include <linux/module.h>
19#include <linux/device.h>
20#include <linux/delay.h>
21#include <sound/driver.h>
22#include <sound/core.h>
23#include <sound/pcm.h>
24#include <sound/initval.h>
25#include <sound/soc.h>
26
27#include <asm/hardware.h>
28#include <asm/arch/pxa-regs.h>
29#include <asm/arch/audio.h>
30
31#include "pxa2xx-pcm.h"
32#include "pxa2xx-i2s.h"
33
34struct pxa_i2s_port {
35	u32 sadiv;
36	u32 sacr0;
37	u32 sacr1;
38	u32 saimr;
39	int master;
40	u32 fmt;
41};
42static struct pxa_i2s_port pxa_i2s;
43
44static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_out = {
45	.name			= "I2S PCM Stereo out",
46	.dev_addr		= __PREG(SADR),
47	.drcmr			= &DRCMRTXSADR,
48	.dcmd			= DCMD_INCSRCADDR | DCMD_FLOWTRG |
49				  DCMD_BURST32 | DCMD_WIDTH4,
50};
51
52static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_in = {
53	.name			= "I2S PCM Stereo in",
54	.dev_addr		= __PREG(SADR),
55	.drcmr			= &DRCMRRXSADR,
56	.dcmd			= DCMD_INCTRGADDR | DCMD_FLOWSRC |
57				  DCMD_BURST32 | DCMD_WIDTH4,
58};
59
60static struct pxa2xx_gpio gpio_bus[] = {
61	{ /* I2S SoC Slave */
62		.rx = GPIO29_SDATA_IN_I2S_MD,
63		.tx = GPIO30_SDATA_OUT_I2S_MD,
64		.clk = GPIO28_BITCLK_IN_I2S_MD,
65		.frm = GPIO31_SYNC_I2S_MD,
66	},
67	{ /* I2S SoC Master */
68#ifdef CONFIG_PXA27x
69		.sys = GPIO113_I2S_SYSCLK_MD,
70#else
71		.sys = GPIO32_SYSCLK_I2S_MD,
72#endif
73		.rx = GPIO29_SDATA_IN_I2S_MD,
74		.tx = GPIO30_SDATA_OUT_I2S_MD,
75		.clk = GPIO28_BITCLK_OUT_I2S_MD,
76		.frm = GPIO31_SYNC_I2S_MD,
77	},
78};
79
80static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream)
81{
82	struct snd_soc_pcm_runtime *rtd = substream->private_data;
83	struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
84
85	if (!cpu_dai->active) {
86		SACR0 |= SACR0_RST;
87		SACR0 = 0;
88	}
89
90	return 0;
91}
92
93/* wait for I2S controller to be ready */
94static int pxa_i2s_wait(void)
95{
96	int i;
97
98	/* flush the Rx FIFO */
99	for(i = 0; i < 16; i++)
100		SADR;
101	return 0;
102}
103
104static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai,
105		unsigned int fmt)
106{
107	/* interface format */
108	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
109	case SND_SOC_DAIFMT_I2S:
110		pxa_i2s.fmt = 0;
111		break;
112	case SND_SOC_DAIFMT_LEFT_J:
113		pxa_i2s.fmt = SACR1_AMSL;
114		break;
115	}
116
117	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
118	case SND_SOC_DAIFMT_CBS_CFS:
119		pxa_i2s.master = 1;
120		break;
121	case SND_SOC_DAIFMT_CBM_CFS:
122		pxa_i2s.master = 0;
123		break;
124	default:
125		break;
126	}
127	return 0;
128}
129
130static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai,
131		int clk_id, unsigned int freq, int dir)
132{
133	if (clk_id != PXA2XX_I2S_SYSCLK)
134		return -ENODEV;
135
136	if (pxa_i2s.master && dir == SND_SOC_CLOCK_OUT)
137		pxa_gpio_mode(gpio_bus[pxa_i2s.master].sys);
138
139	return 0;
140}
141
142static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream,
143				struct snd_pcm_hw_params *params)
144{
145	struct snd_soc_pcm_runtime *rtd = substream->private_data;
146	struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
147
148	pxa_gpio_mode(gpio_bus[pxa_i2s.master].rx);
149	pxa_gpio_mode(gpio_bus[pxa_i2s.master].tx);
150	pxa_gpio_mode(gpio_bus[pxa_i2s.master].frm);
151	pxa_gpio_mode(gpio_bus[pxa_i2s.master].clk);
152	pxa_set_cken(CKEN_I2S, 1);
153	pxa_i2s_wait();
154
155	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
156		cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_out;
157	else
158		cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_in;
159
160	/* is port used by another stream */
161	if (!(SACR0 & SACR0_ENB)) {
162
163		SACR0 = 0;
164		SACR1 = 0;
165		if (pxa_i2s.master)
166			SACR0 |= SACR0_BCKD;
167
168		SACR0 |= SACR0_RFTH(14) | SACR0_TFTH(1);
169		SACR1 |= pxa_i2s.fmt;
170	}
171	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
172		SAIMR |= SAIMR_TFS;
173	else
174		SAIMR |= SAIMR_RFS;
175
176	switch (params_rate(params)) {
177	case 8000:
178		SADIV = 0x48;
179		break;
180	case 11025:
181		SADIV = 0x34;
182		break;
183	case 16000:
184		SADIV = 0x24;
185		break;
186	case 22050:
187		SADIV = 0x1a;
188		break;
189	case 44100:
190		SADIV = 0xd;
191		break;
192	case 48000:
193		SADIV = 0xc;
194		break;
195	case 96000: /* not in manual and possibly slightly inaccurate */
196		SADIV = 0x6;
197		break;
198	}
199
200	return 0;
201}
202
203static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
204{
205	int ret = 0;
206
207	switch (cmd) {
208	case SNDRV_PCM_TRIGGER_START:
209		SACR0 |= SACR0_ENB;
210		break;
211	case SNDRV_PCM_TRIGGER_RESUME:
212	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
213	case SNDRV_PCM_TRIGGER_STOP:
214	case SNDRV_PCM_TRIGGER_SUSPEND:
215	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
216		break;
217	default:
218		ret = -EINVAL;
219	}
220
221	return ret;
222}
223
224static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream)
225{
226	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
227		SACR1 |= SACR1_DRPL;
228		SAIMR &= ~SAIMR_TFS;
229	} else {
230		SACR1 |= SACR1_DREC;
231		SAIMR &= ~SAIMR_RFS;
232	}
233
234	if (SACR1 & (SACR1_DREC | SACR1_DRPL)) {
235		SACR0 &= ~SACR0_ENB;
236		pxa_i2s_wait();
237		pxa_set_cken(CKEN_I2S, 0);
238	}
239}
240
241#ifdef CONFIG_PM
242static int pxa2xx_i2s_suspend(struct platform_device *dev,
243	struct snd_soc_cpu_dai *dai)
244{
245	if (!dai->active)
246		return 0;
247
248	/* store registers */
249	pxa_i2s.sacr0 = SACR0;
250	pxa_i2s.sacr1 = SACR1;
251	pxa_i2s.saimr = SAIMR;
252	pxa_i2s.sadiv = SADIV;
253
254	/* deactivate link */
255	SACR0 &= ~SACR0_ENB;
256	pxa_i2s_wait();
257	return 0;
258}
259
260static int pxa2xx_i2s_resume(struct platform_device *pdev,
261	struct snd_soc_cpu_dai *dai)
262{
263	if (!dai->active)
264		return 0;
265
266	pxa_i2s_wait();
267
268	SACR0 = pxa_i2s.sacr0 &= ~SACR0_ENB;
269	SACR1 = pxa_i2s.sacr1;
270	SAIMR = pxa_i2s.saimr;
271	SADIV = pxa_i2s.sadiv;
272	SACR0 |= SACR0_ENB;
273
274	return 0;
275}
276
277#else
278#define pxa2xx_i2s_suspend	NULL
279#define pxa2xx_i2s_resume	NULL
280#endif
281
282#define PXA2XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
283		SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
284		SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
285
286struct snd_soc_cpu_dai pxa_i2s_dai = {
287	.name = "pxa2xx-i2s",
288	.id = 0,
289	.type = SND_SOC_DAI_I2S,
290	.suspend = pxa2xx_i2s_suspend,
291	.resume = pxa2xx_i2s_resume,
292	.playback = {
293		.channels_min = 2,
294		.channels_max = 2,
295		.rates = PXA2XX_I2S_RATES,
296		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
297	.capture = {
298		.channels_min = 2,
299		.channels_max = 2,
300		.rates = PXA2XX_I2S_RATES,
301		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
302	.ops = {
303		.startup = pxa2xx_i2s_startup,
304		.shutdown = pxa2xx_i2s_shutdown,
305		.trigger = pxa2xx_i2s_trigger,
306		.hw_params = pxa2xx_i2s_hw_params,},
307	.dai_ops = {
308		.set_fmt = pxa2xx_i2s_set_dai_fmt,
309		.set_sysclk = pxa2xx_i2s_set_dai_sysclk,
310	},
311};
312
313EXPORT_SYMBOL_GPL(pxa_i2s_dai);
314
315/* Module information */
316MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com");
317MODULE_DESCRIPTION("pxa2xx I2S SoC Interface");
318MODULE_LICENSE("GPL");
319