tegra20_i2s.c 12.2 KB
Newer Older
1
/*
2
 * tegra20_i2s.c - Tegra20 I2S driver
3 4
 *
 * Author: Stephen Warren <swarren@nvidia.com>
5
 * Copyright (C) 2010,2012 - NVIDIA, Inc.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 *
 * Based on code copyright/by:
 *
 * Copyright (c) 2009-2010, NVIDIA Corporation.
 * Scott Peterson <speterson@nvidia.com>
 *
 * Copyright (C) 2010 Google, Inc.
 * Iliyan Malchev <malchev@google.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <linux/clk.h>
#include <linux/device.h>
33 34 35
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
36
#include <linux/platform_device.h>
37
#include <linux/pm_runtime.h>
38
#include <linux/regmap.h>
39 40 41 42 43 44
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>

45
#include "tegra20_i2s.h"
46

47
#define DRV_NAME "tegra20-i2s"
48

49
static inline void tegra20_i2s_write(struct tegra20_i2s *i2s, u32 reg, u32 val)
50
{
51
	regmap_write(i2s->regmap, reg, val);
52 53
}

54
static inline u32 tegra20_i2s_read(struct tegra20_i2s *i2s, u32 reg)
55
{
56 57 58
	u32 val;
	regmap_read(i2s->regmap, reg, &val);
	return val;
59 60
}

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
static int tegra20_i2s_runtime_suspend(struct device *dev)
{
	struct tegra20_i2s *i2s = dev_get_drvdata(dev);

	clk_disable(i2s->clk_i2s);

	return 0;
}

static int tegra20_i2s_runtime_resume(struct device *dev)
{
	struct tegra20_i2s *i2s = dev_get_drvdata(dev);
	int ret;

	ret = clk_enable(i2s->clk_i2s);
	if (ret) {
		dev_err(dev, "clk_enable failed: %d\n", ret);
		return ret;
	}

	return 0;
}

84
static int tegra20_i2s_set_fmt(struct snd_soc_dai *dai,
85 86
				unsigned int fmt)
{
87
	struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
88 89 90 91 92 93 94 95

	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
	case SND_SOC_DAIFMT_NB_NF:
		break;
	default:
		return -EINVAL;
	}

96
	i2s->reg_ctrl &= ~TEGRA20_I2S_CTRL_MASTER_ENABLE;
97 98
	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
	case SND_SOC_DAIFMT_CBS_CFS:
99
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_MASTER_ENABLE;
100 101 102 103 104 105 106
		break;
	case SND_SOC_DAIFMT_CBM_CFM:
		break;
	default:
		return -EINVAL;
	}

107 108
	i2s->reg_ctrl &= ~(TEGRA20_I2S_CTRL_BIT_FORMAT_MASK |
			   TEGRA20_I2S_CTRL_LRCK_MASK);
109 110
	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_DSP_A:
111 112
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP;
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
113 114
		break;
	case SND_SOC_DAIFMT_DSP_B:
115 116
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP;
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_R_LOW;
117 118
		break;
	case SND_SOC_DAIFMT_I2S:
119 120
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_I2S;
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
121 122
		break;
	case SND_SOC_DAIFMT_RIGHT_J:
123 124
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_RJM;
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
125 126
		break;
	case SND_SOC_DAIFMT_LEFT_J:
127 128
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_FORMAT_LJM;
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
129 130 131 132 133 134 135 136
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

137 138 139
static int tegra20_i2s_hw_params(struct snd_pcm_substream *substream,
				 struct snd_pcm_hw_params *params,
				 struct snd_soc_dai *dai)
140
{
141
	struct device *dev = substream->pcm->card->dev;
142
	struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
143 144 145
	u32 reg;
	int ret, sample_size, srate, i2sclock, bitcnt;

146
	i2s->reg_ctrl &= ~TEGRA20_I2S_CTRL_BIT_SIZE_MASK;
147 148
	switch (params_format(params)) {
	case SNDRV_PCM_FORMAT_S16_LE:
149
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_SIZE_16;
150 151 152
		sample_size = 16;
		break;
	case SNDRV_PCM_FORMAT_S24_LE:
153
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_SIZE_24;
154 155 156
		sample_size = 24;
		break;
	case SNDRV_PCM_FORMAT_S32_LE:
157
		i2s->reg_ctrl |= TEGRA20_I2S_CTRL_BIT_SIZE_32;
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
		sample_size = 32;
		break;
	default:
		return -EINVAL;
	}

	srate = params_rate(params);

	/* Final "* 2" required by Tegra hardware */
	i2sclock = srate * params_channels(params) * sample_size * 2;

	ret = clk_set_rate(i2s->clk_i2s, i2sclock);
	if (ret) {
		dev_err(dev, "Can't set I2S clock rate: %d\n", ret);
		return ret;
	}

	bitcnt = (i2sclock / (2 * srate)) - 1;
176
	if (bitcnt < 0 || bitcnt > TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)
177
		return -EINVAL;
178
	reg = bitcnt << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT;
179 180

	if (i2sclock % (2 * srate))
181
		reg |= TEGRA20_I2S_TIMING_NON_SYM_ENABLE;
182

183
	tegra20_i2s_write(i2s, TEGRA20_I2S_TIMING, reg);
184

185 186 187
	tegra20_i2s_write(i2s, TEGRA20_I2S_FIFO_SCR,
		TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS |
		TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS);
188 189 190 191

	return 0;
}

192
static void tegra20_i2s_start_playback(struct tegra20_i2s *i2s)
193
{
194 195
	i2s->reg_ctrl |= TEGRA20_I2S_CTRL_FIFO1_ENABLE;
	tegra20_i2s_write(i2s, TEGRA20_I2S_CTRL, i2s->reg_ctrl);
196 197
}

198
static void tegra20_i2s_stop_playback(struct tegra20_i2s *i2s)
199
{
200 201
	i2s->reg_ctrl &= ~TEGRA20_I2S_CTRL_FIFO1_ENABLE;
	tegra20_i2s_write(i2s, TEGRA20_I2S_CTRL, i2s->reg_ctrl);
202 203
}

204
static void tegra20_i2s_start_capture(struct tegra20_i2s *i2s)
205
{
206 207
	i2s->reg_ctrl |= TEGRA20_I2S_CTRL_FIFO2_ENABLE;
	tegra20_i2s_write(i2s, TEGRA20_I2S_CTRL, i2s->reg_ctrl);
208 209
}

210
static void tegra20_i2s_stop_capture(struct tegra20_i2s *i2s)
211
{
212 213
	i2s->reg_ctrl &= ~TEGRA20_I2S_CTRL_FIFO2_ENABLE;
	tegra20_i2s_write(i2s, TEGRA20_I2S_CTRL, i2s->reg_ctrl);
214 215
}

216 217
static int tegra20_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
			       struct snd_soc_dai *dai)
218
{
219
	struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
220 221 222 223 224 225

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
	case SNDRV_PCM_TRIGGER_RESUME:
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
226
			tegra20_i2s_start_playback(i2s);
227
		else
228
			tegra20_i2s_start_capture(i2s);
229 230 231 232 233
		break;
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
	case SNDRV_PCM_TRIGGER_SUSPEND:
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
234
			tegra20_i2s_stop_playback(i2s);
235
		else
236
			tegra20_i2s_stop_capture(i2s);
237 238 239 240 241 242 243 244
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

245
static int tegra20_i2s_probe(struct snd_soc_dai *dai)
246
{
247
	struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
248 249 250 251 252 253 254

	dai->capture_dma_data = &i2s->capture_dma_data;
	dai->playback_dma_data = &i2s->playback_dma_data;

	return 0;
}

255 256 257 258
static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = {
	.set_fmt	= tegra20_i2s_set_fmt,
	.hw_params	= tegra20_i2s_hw_params,
	.trigger	= tegra20_i2s_trigger,
259 260
};

261 262
static const struct snd_soc_dai_driver tegra20_i2s_dai_template = {
	.probe = tegra20_i2s_probe,
263 264 265 266 267
	.playback = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = SNDRV_PCM_RATE_8000_96000,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
268
	},
269 270 271 272 273
	.capture = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = SNDRV_PCM_RATE_8000_96000,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
274
	},
275
	.ops = &tegra20_i2s_dai_ops,
276
	.symmetric_rates = 1,
277 278
};

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
static bool tegra20_i2s_wr_rd_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case TEGRA20_I2S_CTRL:
	case TEGRA20_I2S_STATUS:
	case TEGRA20_I2S_TIMING:
	case TEGRA20_I2S_FIFO_SCR:
	case TEGRA20_I2S_PCM_CTRL:
	case TEGRA20_I2S_NW_CTRL:
	case TEGRA20_I2S_TDM_CTRL:
	case TEGRA20_I2S_TDM_TX_RX_CTRL:
	case TEGRA20_I2S_FIFO1:
	case TEGRA20_I2S_FIFO2:
		return true;
	default:
		return false;
	};
}

static bool tegra20_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case TEGRA20_I2S_STATUS:
	case TEGRA20_I2S_FIFO_SCR:
	case TEGRA20_I2S_FIFO1:
	case TEGRA20_I2S_FIFO2:
		return true;
	default:
		return false;
	};
}

static bool tegra20_i2s_precious_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case TEGRA20_I2S_FIFO1:
	case TEGRA20_I2S_FIFO2:
		return true;
	default:
		return false;
	};
}

static const struct regmap_config tegra20_i2s_regmap_config = {
	.reg_bits = 32,
	.reg_stride = 4,
	.val_bits = 32,
	.max_register = TEGRA20_I2S_FIFO2,
	.writeable_reg = tegra20_i2s_wr_rd_reg,
	.readable_reg = tegra20_i2s_wr_rd_reg,
	.volatile_reg = tegra20_i2s_volatile_reg,
	.precious_reg = tegra20_i2s_precious_reg,
	.cache_type = REGCACHE_RBTREE,
};

334
static __devinit int tegra20_i2s_platform_probe(struct platform_device *pdev)
335
{
336
	struct tegra20_i2s *i2s;
337
	struct resource *mem, *memregion, *dmareq;
338 339
	u32 of_dma[2];
	u32 dma_ch;
340
	void __iomem *regs;
341 342
	int ret;

343
	i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL);
344
	if (!i2s) {
345
		dev_err(&pdev->dev, "Can't allocate tegra20_i2s\n");
346
		ret = -ENOMEM;
347
		goto err;
348 349 350
	}
	dev_set_drvdata(&pdev->dev, i2s);

351
	i2s->dai = tegra20_i2s_dai_template;
352 353
	i2s->dai.name = dev_name(&pdev->dev);

354
	i2s->clk_i2s = clk_get(&pdev->dev, NULL);
355
	if (IS_ERR(i2s->clk_i2s)) {
356
		dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
357
		ret = PTR_ERR(i2s->clk_i2s);
358
		goto err;
359 360 361 362 363 364 365 366 367 368 369
	}

	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!mem) {
		dev_err(&pdev->dev, "No memory resource\n");
		ret = -ENODEV;
		goto err_clk_put;
	}

	dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0);
	if (!dmareq) {
370 371 372 373 374 375 376 377 378 379
		if (of_property_read_u32_array(pdev->dev.of_node,
					"nvidia,dma-request-selector",
					of_dma, 2) < 0) {
			dev_err(&pdev->dev, "No DMA resource\n");
			ret = -ENODEV;
			goto err_clk_put;
		}
		dma_ch = of_dma[1];
	} else {
		dma_ch = dmareq->start;
380 381
	}

382 383
	memregion = devm_request_mem_region(&pdev->dev, mem->start,
					    resource_size(mem), DRV_NAME);
384 385 386 387 388 389
	if (!memregion) {
		dev_err(&pdev->dev, "Memory region already claimed\n");
		ret = -EBUSY;
		goto err_clk_put;
	}

390 391
	regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
	if (!regs) {
392 393
		dev_err(&pdev->dev, "ioremap failed\n");
		ret = -ENOMEM;
394
		goto err_clk_put;
395 396
	}

397 398 399 400 401 402 403 404
	i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
					    &tegra20_i2s_regmap_config);
	if (IS_ERR(i2s->regmap)) {
		dev_err(&pdev->dev, "regmap init failed\n");
		ret = PTR_ERR(i2s->regmap);
		goto err_clk_put;
	}

405
	i2s->capture_dma_data.addr = mem->start + TEGRA20_I2S_FIFO2;
406 407
	i2s->capture_dma_data.wrap = 4;
	i2s->capture_dma_data.width = 32;
408
	i2s->capture_dma_data.req_sel = dma_ch;
409

410
	i2s->playback_dma_data.addr = mem->start + TEGRA20_I2S_FIFO1;
411 412
	i2s->playback_dma_data.wrap = 4;
	i2s->playback_dma_data.width = 32;
413
	i2s->playback_dma_data.req_sel = dma_ch;
414

415
	i2s->reg_ctrl = TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED;
416

417 418 419 420 421 422 423
	pm_runtime_enable(&pdev->dev);
	if (!pm_runtime_enabled(&pdev->dev)) {
		ret = tegra20_i2s_runtime_resume(&pdev->dev);
		if (ret)
			goto err_pm_disable;
	}

424
	ret = snd_soc_register_dai(&pdev->dev, &i2s->dai);
425 426 427
	if (ret) {
		dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
		ret = -ENOMEM;
428
		goto err_suspend;
429 430
	}

431 432 433 434 435 436
	ret = tegra_pcm_platform_register(&pdev->dev);
	if (ret) {
		dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
		goto err_unregister_dai;
	}

437 438
	return 0;

439 440
err_unregister_dai:
	snd_soc_unregister_dai(&pdev->dev);
441 442 443 444 445
err_suspend:
	if (!pm_runtime_status_suspended(&pdev->dev))
		tegra20_i2s_runtime_suspend(&pdev->dev);
err_pm_disable:
	pm_runtime_disable(&pdev->dev);
446 447
err_clk_put:
	clk_put(i2s->clk_i2s);
448
err:
449 450 451
	return ret;
}

452
static int __devexit tegra20_i2s_platform_remove(struct platform_device *pdev)
453
{
454
	struct tegra20_i2s *i2s = dev_get_drvdata(&pdev->dev);
455

456 457 458 459
	pm_runtime_disable(&pdev->dev);
	if (!pm_runtime_status_suspended(&pdev->dev))
		tegra20_i2s_runtime_suspend(&pdev->dev);

460
	tegra_pcm_platform_unregister(&pdev->dev);
461 462 463 464 465 466 467
	snd_soc_unregister_dai(&pdev->dev);

	clk_put(i2s->clk_i2s);

	return 0;
}

468
static const struct of_device_id tegra20_i2s_of_match[] __devinitconst = {
469 470 471 472
	{ .compatible = "nvidia,tegra20-i2s", },
	{},
};

473 474 475 476 477
static const struct dev_pm_ops tegra20_i2s_pm_ops __devinitconst = {
	SET_RUNTIME_PM_OPS(tegra20_i2s_runtime_suspend,
			   tegra20_i2s_runtime_resume, NULL)
};

478
static struct platform_driver tegra20_i2s_driver = {
479 480 481
	.driver = {
		.name = DRV_NAME,
		.owner = THIS_MODULE,
482
		.of_match_table = tegra20_i2s_of_match,
483
		.pm = &tegra20_i2s_pm_ops,
484
	},
485 486
	.probe = tegra20_i2s_platform_probe,
	.remove = __devexit_p(tegra20_i2s_platform_remove),
487
};
488
module_platform_driver(tegra20_i2s_driver);
489 490

MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
491
MODULE_DESCRIPTION("Tegra20 I2S ASoC driver");
492
MODULE_LICENSE("GPL");
S
Stephen Warren 已提交
493
MODULE_ALIAS("platform:" DRV_NAME);
494
MODULE_DEVICE_TABLE(of, tegra20_i2s_of_match);