s3c24xx-i2s.c 12.6 KB
Newer Older
1 2 3 4 5 6
/*
 * s3c24xx-i2s.c  --  ALSA Soc Audio Layer
 *
 * (c) 2006 Wolfson Microelectronics PLC.
 * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
 *
7
 * Copyright 2004-2005 Simtec Electronics
8 9 10 11 12 13 14 15 16 17 18
 *	http://armlinux.simtec.co.uk/
 *	Ben Dooks <ben@simtec.co.uk>
 *
 *  This program is free software; you can redistribute  it and/or modify it
 *  under  the terms of  the GNU General  Public License as published by the
 *  Free Software Foundation;  either version 2 of the  License, or (at your
 *  option) any later version.
 */

#include <linux/delay.h>
#include <linux/clk.h>
19
#include <linux/io.h>
20
#include <linux/gpio.h>
21
#include <linux/module.h>
22

23
#include <sound/soc.h>
24
#include <sound/pcm_params.h>
25

26 27
#include <mach/gpio-samsung.h>
#include <plat/gpio-cfg.h>
28
#include "regs-iis.h"
29

J
Jassi Brar 已提交
30
#include "dma.h"
31 32
#include "s3c24xx-i2s.h"

33 34
#include <linux/platform_data/asoc-s3c.h>

35 36 37
static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_out = {
	.chan_name	= "tx",
	.addr_width	= 2,
38 39
};

40 41 42
static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_in = {
	.chan_name	= "rx",
	.addr_width	= 2,
43 44 45 46 47
};

struct s3c24xx_i2s_info {
	void __iomem	*regs;
	struct clk	*iis_clk;
48 49 50 51
	u32		iiscon;
	u32		iismod;
	u32		iisfcon;
	u32		iispsr;
52 53 54 55 56 57 58 59 60
};
static struct s3c24xx_i2s_info s3c24xx_i2s;

static void s3c24xx_snd_txctrl(int on)
{
	u32 iisfcon;
	u32 iiscon;
	u32 iismod;

61
	pr_debug("Entered %s\n", __func__);
62 63 64 65 66

	iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
	iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
	iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);

67
	pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

	if (on) {
		iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
		iiscon  |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
		iiscon  &= ~S3C2410_IISCON_TXIDLE;
		iismod  |= S3C2410_IISMOD_TXMODE;

		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
	} else {
		/* note, we have to disable the FIFOs otherwise bad things
		 * seem to happen when the DMA stops. According to the
		 * Samsung supplied kernel, this should allow the DMA
		 * engine and FIFOs to reset. If this isn't allowed, the
		 * DMA engine will simply freeze randomly.
		 */

		iisfcon &= ~S3C2410_IISFCON_TXENABLE;
		iisfcon &= ~S3C2410_IISFCON_TXDMA;
		iiscon  |=  S3C2410_IISCON_TXIDLE;
		iiscon  &= ~S3C2410_IISCON_TXDMAEN;
		iismod  &= ~S3C2410_IISMOD_TXMODE;

		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
	}

97
	pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
98 99 100 101 102 103 104 105
}

static void s3c24xx_snd_rxctrl(int on)
{
	u32 iisfcon;
	u32 iiscon;
	u32 iismod;

106
	pr_debug("Entered %s\n", __func__);
107 108 109 110 111

	iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
	iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
	iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);

112
	pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

	if (on) {
		iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
		iiscon  |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
		iiscon  &= ~S3C2410_IISCON_RXIDLE;
		iismod  |= S3C2410_IISMOD_RXMODE;

		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
	} else {
		/* note, we have to disable the FIFOs otherwise bad things
		 * seem to happen when the DMA stops. According to the
		 * Samsung supplied kernel, this should allow the DMA
		 * engine and FIFOs to reset. If this isn't allowed, the
		 * DMA engine will simply freeze randomly.
		 */

131 132 133 134
		iisfcon &= ~S3C2410_IISFCON_RXENABLE;
		iisfcon &= ~S3C2410_IISFCON_RXDMA;
		iiscon  |= S3C2410_IISCON_RXIDLE;
		iiscon  &= ~S3C2410_IISCON_RXDMAEN;
135 136 137 138 139 140 141
		iismod  &= ~S3C2410_IISMOD_RXMODE;

		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
	}

142
	pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
143 144 145 146 147 148 149 150 151
}

/*
 * Wait for the LR signal to allow synchronisation to the L/R clock
 * from the codec. May only be needed for slave mode.
 */
static int s3c24xx_snd_lrsync(void)
{
	u32 iiscon;
152
	int timeout = 50; /* 5ms */
153

154
	pr_debug("Entered %s\n", __func__);
155 156 157 158 159 160

	while (1) {
		iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
		if (iiscon & S3C2410_IISCON_LRINDEX)
			break;

161
		if (!timeout--)
162
			return -ETIMEDOUT;
163
		udelay(100);
164 165 166 167 168 169 170 171 172 173
	}

	return 0;
}

/*
 * Check whether CPU is the master or slave
 */
static inline int s3c24xx_snd_is_clkmaster(void)
{
174
	pr_debug("Entered %s\n", __func__);
175 176 177 178 179 180 181

	return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
}

/*
 * Set S3C24xx I2S DAI format
 */
182
static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
183 184 185 186
		unsigned int fmt)
{
	u32 iismod;

187
	pr_debug("Entered %s\n", __func__);
188 189

	iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
190
	pr_debug("hw_params r: IISMOD: %x \n", iismod);
191 192 193 194 195 196

	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
	case SND_SOC_DAIFMT_CBM_CFM:
		iismod |= S3C2410_IISMOD_SLAVE;
		break;
	case SND_SOC_DAIFMT_CBS_CFS:
197
		iismod &= ~S3C2410_IISMOD_SLAVE;
198 199 200 201 202 203 204 205 206 207
		break;
	default:
		return -EINVAL;
	}

	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_LEFT_J:
		iismod |= S3C2410_IISMOD_MSB;
		break;
	case SND_SOC_DAIFMT_I2S:
208
		iismod &= ~S3C2410_IISMOD_MSB;
209 210 211 212 213 214
		break;
	default:
		return -EINVAL;
	}

	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
215
	pr_debug("hw_params w: IISMOD: %x \n", iismod);
216 217 218 219
	return 0;
}

static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
220 221
				 struct snd_pcm_hw_params *params,
				 struct snd_soc_dai *dai)
222
{
223
	struct snd_dmaengine_dai_dma_data *dma_data;
224 225
	u32 iismod;

226
	pr_debug("Entered %s\n", __func__);
227

228
	dma_data = snd_soc_dai_get_dma_data(dai, substream);
229 230 231

	/* Working copies of register */
	iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
232
	pr_debug("hw_params r: IISMOD: %x\n", iismod);
233

234 235
	switch (params_width(params)) {
	case 8:
236
		iismod &= ~S3C2410_IISMOD_16BIT;
237
		dma_data->addr_width = 1;
238
		break;
239
	case 16:
240
		iismod |= S3C2410_IISMOD_16BIT;
241
		dma_data->addr_width = 2;
242
		break;
243 244
	default:
		return -EINVAL;
245 246 247
	}

	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
248
	pr_debug("hw_params w: IISMOD: %x\n", iismod);
249 250 251
	return 0;
}

252 253
static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
			       struct snd_soc_dai *dai)
254 255 256
{
	int ret = 0;

257
	pr_debug("Entered %s\n", __func__);
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		if (!s3c24xx_snd_is_clkmaster()) {
			ret = s3c24xx_snd_lrsync();
			if (ret)
				goto exit_err;
		}

		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			s3c24xx_snd_rxctrl(1);
		else
			s3c24xx_snd_txctrl(1);
273

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
		break;
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			s3c24xx_snd_rxctrl(0);
		else
			s3c24xx_snd_txctrl(0);
		break;
	default:
		ret = -EINVAL;
		break;
	}

exit_err:
	return ret;
}

/*
 * Set S3C24xx Clock source
 */
295
static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
296 297 298 299
	int clk_id, unsigned int freq, int dir)
{
	u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);

300
	pr_debug("Entered %s\n", __func__);
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

	iismod &= ~S3C2440_IISMOD_MPLL;

	switch (clk_id) {
	case S3C24XX_CLKSRC_PCLK:
		break;
	case S3C24XX_CLKSRC_MPLL:
		iismod |= S3C2440_IISMOD_MPLL;
		break;
	default:
		return -EINVAL;
	}

	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
	return 0;
}

/*
 * Set S3C24xx Clock dividers
 */
321
static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
322 323 324 325
	int div_id, int div)
{
	u32 reg;

326
	pr_debug("Entered %s\n", __func__);
327 328

	switch (div_id) {
329
	case S3C24XX_DIV_BCLK:
330 331 332
		reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
		writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
		break;
333
	case S3C24XX_DIV_MCLK:
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
		reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
		writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
		break;
	case S3C24XX_DIV_PRESCALER:
		writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
		reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
		writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

/*
 * To avoid duplicating clock code, allow machine driver to
 * get the clockrate from here.
 */
u32 s3c24xx_i2s_get_clockrate(void)
{
	return clk_get_rate(s3c24xx_i2s.iis_clk);
}
EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);

359
static int s3c24xx_i2s_probe(struct snd_soc_dai *dai)
360
{
361
	pr_debug("Entered %s\n", __func__);
362

363 364
	snd_soc_dai_init_dma_data(dai, &s3c24xx_i2s_pcm_stereo_out,
					&s3c24xx_i2s_pcm_stereo_in);
365

366
	s3c24xx_i2s.iis_clk = devm_clk_get(dai->dev, "iis");
367
	if (IS_ERR(s3c24xx_i2s.iis_clk)) {
368
		pr_err("failed to get iis_clock\n");
369
		return PTR_ERR(s3c24xx_i2s.iis_clk);
370
	}
371
	clk_prepare_enable(s3c24xx_i2s.iis_clk);
372

373 374 375
	/* Configure the I2S pins (GPE0...GPE4) in correct mode */
	s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2),
			      S3C_GPIO_PULL_NONE);
376 377 378 379 380 381 382 383 384

	writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);

	s3c24xx_snd_txctrl(0);
	s3c24xx_snd_rxctrl(0);

	return 0;
}

385
#ifdef CONFIG_PM
386
static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai)
387
{
388
	pr_debug("Entered %s\n", __func__);
389

390 391 392 393 394
	s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
	s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
	s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
	s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);

395
	clk_disable_unprepare(s3c24xx_i2s.iis_clk);
396 397 398 399

	return 0;
}

400
static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai)
401
{
402
	pr_debug("Entered %s\n", __func__);
403
	clk_prepare_enable(s3c24xx_i2s.iis_clk);
404 405 406 407 408 409 410 411 412 413 414 415 416 417

	writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
	writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
	writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
	writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);

	return 0;
}
#else
#define s3c24xx_i2s_suspend NULL
#define s3c24xx_i2s_resume NULL
#endif


418 419 420 421 422
#define S3C24XX_I2S_RATES \
	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
	SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
	SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)

423
static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
424 425 426 427 428 429 430
	.trigger	= s3c24xx_i2s_trigger,
	.hw_params	= s3c24xx_i2s_hw_params,
	.set_fmt	= s3c24xx_i2s_set_fmt,
	.set_clkdiv	= s3c24xx_i2s_set_clkdiv,
	.set_sysclk	= s3c24xx_i2s_set_sysclk,
};

431
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
432
	.probe = s3c24xx_i2s_probe,
433 434
	.suspend = s3c24xx_i2s_suspend,
	.resume = s3c24xx_i2s_resume,
435 436 437 438 439 440 441 442 443 444
	.playback = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.capture = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
445
	.ops = &s3c24xx_i2s_dai_ops,
446
};
447

448 449 450 451
static const struct snd_soc_component_driver s3c24xx_i2s_component = {
	.name		= "s3c24xx-i2s",
};

452
static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
453
{
454
	int ret = 0;
455
	struct resource *res;
456 457 458 459 460 461
	struct s3c_audio_pdata *pdata = dev_get_platdata(&pdev->dev);

	if (!pdata) {
		dev_err(&pdev->dev, "missing platform data");
		return -ENXIO;
	}
462 463 464 465 466 467 468

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "Can't get IO resource.\n");
		return -ENOENT;
	}
	s3c24xx_i2s.regs = devm_ioremap_resource(&pdev->dev, res);
469 470
	if (IS_ERR(s3c24xx_i2s.regs))
		return PTR_ERR(s3c24xx_i2s.regs);
471

472 473 474 475
	s3c24xx_i2s_pcm_stereo_out.addr = res->start + S3C2410_IISFIFO;
	s3c24xx_i2s_pcm_stereo_out.filter_data = pdata->dma_playback;
	s3c24xx_i2s_pcm_stereo_in.addr = res->start + S3C2410_IISFIFO;
	s3c24xx_i2s_pcm_stereo_in.filter_data = pdata->dma_capture;
476

477 478 479
	ret = samsung_asoc_dma_platform_register(&pdev->dev,
						 pdata->dma_filter,
						 NULL, NULL);
480
	if (ret) {
481
		pr_err("failed to register the dma: %d\n", ret);
482 483 484
		return ret;
	}

485 486
	ret = devm_snd_soc_register_component(&pdev->dev,
			&s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);
487
	if (ret)
488
		pr_err("failed to register the dai\n");
489 490

	return ret;
491 492 493 494 495 496 497 498
}

static struct platform_driver s3c24xx_iis_driver = {
	.probe  = s3c24xx_iis_dev_probe,
	.driver = {
		.name = "s3c24xx-iis",
	},
};
499

500
module_platform_driver(s3c24xx_iis_driver);
M
Mark Brown 已提交
501

502 503 504 505
/* Module information */
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
MODULE_LICENSE("GPL");
506
MODULE_ALIAS("platform:s3c24xx-iis");