162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * ALSA SoC SPDIF In Audio Layer for spear processors
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2012 ST Microelectronics
562306a36Sopenharmony_ci * Vipin Kumar <vipin.kumar@st.com>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * This file is licensed under the terms of the GNU General Public
862306a36Sopenharmony_ci * License version 2. This program is licensed "as is" without any
962306a36Sopenharmony_ci * warranty of any kind, whether express or implied.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/clk.h>
1362306a36Sopenharmony_ci#include <linux/delay.h>
1462306a36Sopenharmony_ci#include <linux/device.h>
1562306a36Sopenharmony_ci#include <linux/kernel.h>
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/io.h>
1862306a36Sopenharmony_ci#include <linux/ioport.h>
1962306a36Sopenharmony_ci#include <linux/module.h>
2062306a36Sopenharmony_ci#include <linux/platform_device.h>
2162306a36Sopenharmony_ci#include <sound/dmaengine_pcm.h>
2262306a36Sopenharmony_ci#include <sound/pcm.h>
2362306a36Sopenharmony_ci#include <sound/pcm_params.h>
2462306a36Sopenharmony_ci#include <sound/soc.h>
2562306a36Sopenharmony_ci#include <sound/spear_dma.h>
2662306a36Sopenharmony_ci#include <sound/spear_spdif.h>
2762306a36Sopenharmony_ci#include "spdif_in_regs.h"
2862306a36Sopenharmony_ci#include "spear_pcm.h"
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct spdif_in_params {
3162306a36Sopenharmony_ci	u32 format;
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistruct spdif_in_dev {
3562306a36Sopenharmony_ci	struct clk *clk;
3662306a36Sopenharmony_ci	struct spear_dma_data dma_params;
3762306a36Sopenharmony_ci	struct spdif_in_params saved_params;
3862306a36Sopenharmony_ci	void *io_base;
3962306a36Sopenharmony_ci	struct device *dev;
4062306a36Sopenharmony_ci	void (*reset_perip)(void);
4162306a36Sopenharmony_ci	int irq;
4262306a36Sopenharmony_ci	struct snd_dmaengine_dai_dma_data dma_params_rx;
4362306a36Sopenharmony_ci	struct snd_dmaengine_pcm_config config;
4462306a36Sopenharmony_ci};
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic void spdif_in_configure(struct spdif_in_dev *host)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	u32 ctrl = SPDIF_IN_PRTYEN | SPDIF_IN_STATEN | SPDIF_IN_USREN |
4962306a36Sopenharmony_ci		SPDIF_IN_VALEN | SPDIF_IN_BLKEN;
5062306a36Sopenharmony_ci	ctrl |= SPDIF_MODE_16BIT | SPDIF_FIFO_THRES_16;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	writel(ctrl, host->io_base + SPDIF_IN_CTRL);
5362306a36Sopenharmony_ci	writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK);
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int spdif_in_dai_probe(struct snd_soc_dai *dai)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	host->dma_params_rx.filter_data = &host->dma_params;
6162306a36Sopenharmony_ci	dai->capture_dma_data = &host->dma_params_rx;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	return 0;
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic void spdif_in_shutdown(struct snd_pcm_substream *substream,
6762306a36Sopenharmony_ci		struct snd_soc_dai *dai)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
7262306a36Sopenharmony_ci		return;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK);
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic void spdif_in_format(struct spdif_in_dev *host, u32 format)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	u32 ctrl = readl(host->io_base + SPDIF_IN_CTRL);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	switch (format) {
8262306a36Sopenharmony_ci	case SNDRV_PCM_FORMAT_S16_LE:
8362306a36Sopenharmony_ci		ctrl |= SPDIF_XTRACT_16BIT;
8462306a36Sopenharmony_ci		break;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
8762306a36Sopenharmony_ci		ctrl &= ~SPDIF_XTRACT_16BIT;
8862306a36Sopenharmony_ci		break;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	writel(ctrl, host->io_base + SPDIF_IN_CTRL);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic int spdif_in_hw_params(struct snd_pcm_substream *substream,
9562306a36Sopenharmony_ci		struct snd_pcm_hw_params *params,
9662306a36Sopenharmony_ci		struct snd_soc_dai *dai)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai);
9962306a36Sopenharmony_ci	u32 format;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
10262306a36Sopenharmony_ci		return -EINVAL;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	format = params_format(params);
10562306a36Sopenharmony_ci	host->saved_params.format = format;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	return 0;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd,
11162306a36Sopenharmony_ci		struct snd_soc_dai *dai)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai);
11462306a36Sopenharmony_ci	u32 ctrl;
11562306a36Sopenharmony_ci	int ret = 0;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
11862306a36Sopenharmony_ci		return -EINVAL;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	switch (cmd) {
12162306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
12262306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_RESUME:
12362306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
12462306a36Sopenharmony_ci		clk_enable(host->clk);
12562306a36Sopenharmony_ci		spdif_in_configure(host);
12662306a36Sopenharmony_ci		spdif_in_format(host, host->saved_params.format);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci		ctrl = readl(host->io_base + SPDIF_IN_CTRL);
12962306a36Sopenharmony_ci		ctrl |= SPDIF_IN_SAMPLE | SPDIF_IN_ENB;
13062306a36Sopenharmony_ci		writel(ctrl, host->io_base + SPDIF_IN_CTRL);
13162306a36Sopenharmony_ci		writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK);
13262306a36Sopenharmony_ci		break;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
13562306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_SUSPEND:
13662306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
13762306a36Sopenharmony_ci		ctrl = readl(host->io_base + SPDIF_IN_CTRL);
13862306a36Sopenharmony_ci		ctrl &= ~(SPDIF_IN_SAMPLE | SPDIF_IN_ENB);
13962306a36Sopenharmony_ci		writel(ctrl, host->io_base + SPDIF_IN_CTRL);
14062306a36Sopenharmony_ci		writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		if (host->reset_perip)
14362306a36Sopenharmony_ci			host->reset_perip();
14462306a36Sopenharmony_ci		clk_disable(host->clk);
14562306a36Sopenharmony_ci		break;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	default:
14862306a36Sopenharmony_ci		ret = -EINVAL;
14962306a36Sopenharmony_ci		break;
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci	return ret;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic const struct snd_soc_dai_ops spdif_in_dai_ops = {
15562306a36Sopenharmony_ci	.shutdown	= spdif_in_shutdown,
15662306a36Sopenharmony_ci	.trigger	= spdif_in_trigger,
15762306a36Sopenharmony_ci	.hw_params	= spdif_in_hw_params,
15862306a36Sopenharmony_ci};
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic struct snd_soc_dai_driver spdif_in_dai = {
16162306a36Sopenharmony_ci	.probe = spdif_in_dai_probe,
16262306a36Sopenharmony_ci	.capture = {
16362306a36Sopenharmony_ci		.channels_min = 2,
16462306a36Sopenharmony_ci		.channels_max = 2,
16562306a36Sopenharmony_ci		.rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
16662306a36Sopenharmony_ci				 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \
16762306a36Sopenharmony_ci				 SNDRV_PCM_RATE_192000),
16862306a36Sopenharmony_ci		.formats = SNDRV_PCM_FMTBIT_S16_LE | \
16962306a36Sopenharmony_ci			   SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
17062306a36Sopenharmony_ci	},
17162306a36Sopenharmony_ci	.ops = &spdif_in_dai_ops,
17262306a36Sopenharmony_ci};
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic const struct snd_soc_component_driver spdif_in_component = {
17562306a36Sopenharmony_ci	.name			= "spdif-in",
17662306a36Sopenharmony_ci	.legacy_dai_naming	= 1,
17762306a36Sopenharmony_ci};
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic irqreturn_t spdif_in_irq(int irq, void *arg)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	struct spdif_in_dev *host = (struct spdif_in_dev *)arg;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	u32 irq_status = readl(host->io_base + SPDIF_IN_IRQ);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	if (!irq_status)
18662306a36Sopenharmony_ci		return IRQ_NONE;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	if (irq_status & SPDIF_IRQ_FIFOWRITE)
18962306a36Sopenharmony_ci		dev_err(host->dev, "spdif in: fifo write error");
19062306a36Sopenharmony_ci	if (irq_status & SPDIF_IRQ_EMPTYFIFOREAD)
19162306a36Sopenharmony_ci		dev_err(host->dev, "spdif in: empty fifo read error");
19262306a36Sopenharmony_ci	if (irq_status & SPDIF_IRQ_FIFOFULL)
19362306a36Sopenharmony_ci		dev_err(host->dev, "spdif in: fifo full error");
19462306a36Sopenharmony_ci	if (irq_status & SPDIF_IRQ_OUTOFRANGE)
19562306a36Sopenharmony_ci		dev_err(host->dev, "spdif in: out of range error");
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	writel(0, host->io_base + SPDIF_IN_IRQ);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	return IRQ_HANDLED;
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic int spdif_in_probe(struct platform_device *pdev)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	struct spdif_in_dev *host;
20562306a36Sopenharmony_ci	struct spear_spdif_platform_data *pdata;
20662306a36Sopenharmony_ci	struct resource *res_fifo;
20762306a36Sopenharmony_ci	void __iomem *io_base;
20862306a36Sopenharmony_ci	int ret;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	io_base = devm_platform_ioremap_resource(pdev, 0);
21162306a36Sopenharmony_ci	if (IS_ERR(io_base))
21262306a36Sopenharmony_ci		return PTR_ERR(io_base);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	res_fifo = platform_get_resource(pdev, IORESOURCE_IO, 0);
21562306a36Sopenharmony_ci	if (!res_fifo)
21662306a36Sopenharmony_ci		return -EINVAL;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
21962306a36Sopenharmony_ci	if (!host)
22062306a36Sopenharmony_ci		return -ENOMEM;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	host->io_base = io_base;
22362306a36Sopenharmony_ci	host->irq = platform_get_irq(pdev, 0);
22462306a36Sopenharmony_ci	if (host->irq < 0) {
22562306a36Sopenharmony_ci		dev_warn(&pdev->dev, "failed to get IRQ: %d\n", host->irq);
22662306a36Sopenharmony_ci		return host->irq;
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	host->clk = devm_clk_get(&pdev->dev, NULL);
23062306a36Sopenharmony_ci	if (IS_ERR(host->clk))
23162306a36Sopenharmony_ci		return PTR_ERR(host->clk);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	pdata = dev_get_platdata(&pdev->dev);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	if (!pdata)
23662306a36Sopenharmony_ci		return -EINVAL;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	host->dma_params.data = pdata->dma_params;
23962306a36Sopenharmony_ci	host->dma_params.addr = res_fifo->start;
24062306a36Sopenharmony_ci	host->dma_params.max_burst = 16;
24162306a36Sopenharmony_ci	host->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
24262306a36Sopenharmony_ci	host->reset_perip = pdata->reset_perip;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	host->dev = &pdev->dev;
24562306a36Sopenharmony_ci	dev_set_drvdata(&pdev->dev, host);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0,
24862306a36Sopenharmony_ci			"spdif-in", host);
24962306a36Sopenharmony_ci	if (ret) {
25062306a36Sopenharmony_ci		dev_warn(&pdev->dev, "request_irq failed\n");
25162306a36Sopenharmony_ci		return ret;
25262306a36Sopenharmony_ci	}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component,
25562306a36Sopenharmony_ci					      &spdif_in_dai, 1);
25662306a36Sopenharmony_ci	if (ret)
25762306a36Sopenharmony_ci		return ret;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	return devm_spear_pcm_platform_register(&pdev->dev, &host->config,
26062306a36Sopenharmony_ci						pdata->filter);
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic struct platform_driver spdif_in_driver = {
26462306a36Sopenharmony_ci	.probe		= spdif_in_probe,
26562306a36Sopenharmony_ci	.driver		= {
26662306a36Sopenharmony_ci		.name	= "spdif-in",
26762306a36Sopenharmony_ci	},
26862306a36Sopenharmony_ci};
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cimodule_platform_driver(spdif_in_driver);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ciMODULE_AUTHOR("Vipin Kumar <vipin.kumar@st.com>");
27362306a36Sopenharmony_ciMODULE_DESCRIPTION("SPEAr SPDIF IN SoC Interface");
27462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
27562306a36Sopenharmony_ciMODULE_ALIAS("platform:spdif_in");
276