18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * ALSA SoC SPDIF In Audio Layer for spear processors
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2012 ST Microelectronics
58c2ecf20Sopenharmony_ci * Vipin Kumar <vipin.kumar@st.com>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public
88c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any
98c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied.
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/clk.h>
138c2ecf20Sopenharmony_ci#include <linux/delay.h>
148c2ecf20Sopenharmony_ci#include <linux/device.h>
158c2ecf20Sopenharmony_ci#include <linux/kernel.h>
168c2ecf20Sopenharmony_ci#include <linux/init.h>
178c2ecf20Sopenharmony_ci#include <linux/io.h>
188c2ecf20Sopenharmony_ci#include <linux/ioport.h>
198c2ecf20Sopenharmony_ci#include <linux/module.h>
208c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
218c2ecf20Sopenharmony_ci#include <sound/dmaengine_pcm.h>
228c2ecf20Sopenharmony_ci#include <sound/pcm.h>
238c2ecf20Sopenharmony_ci#include <sound/pcm_params.h>
248c2ecf20Sopenharmony_ci#include <sound/soc.h>
258c2ecf20Sopenharmony_ci#include <sound/spear_dma.h>
268c2ecf20Sopenharmony_ci#include <sound/spear_spdif.h>
278c2ecf20Sopenharmony_ci#include "spdif_in_regs.h"
288c2ecf20Sopenharmony_ci#include "spear_pcm.h"
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistruct spdif_in_params {
318c2ecf20Sopenharmony_ci	u32 format;
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistruct spdif_in_dev {
358c2ecf20Sopenharmony_ci	struct clk *clk;
368c2ecf20Sopenharmony_ci	struct spear_dma_data dma_params;
378c2ecf20Sopenharmony_ci	struct spdif_in_params saved_params;
388c2ecf20Sopenharmony_ci	void *io_base;
398c2ecf20Sopenharmony_ci	struct device *dev;
408c2ecf20Sopenharmony_ci	void (*reset_perip)(void);
418c2ecf20Sopenharmony_ci	int irq;
428c2ecf20Sopenharmony_ci	struct snd_dmaengine_dai_dma_data dma_params_rx;
438c2ecf20Sopenharmony_ci	struct snd_dmaengine_pcm_config config;
448c2ecf20Sopenharmony_ci};
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic void spdif_in_configure(struct spdif_in_dev *host)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	u32 ctrl = SPDIF_IN_PRTYEN | SPDIF_IN_STATEN | SPDIF_IN_USREN |
498c2ecf20Sopenharmony_ci		SPDIF_IN_VALEN | SPDIF_IN_BLKEN;
508c2ecf20Sopenharmony_ci	ctrl |= SPDIF_MODE_16BIT | SPDIF_FIFO_THRES_16;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	writel(ctrl, host->io_base + SPDIF_IN_CTRL);
538c2ecf20Sopenharmony_ci	writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK);
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic int spdif_in_dai_probe(struct snd_soc_dai *dai)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai);
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	host->dma_params_rx.filter_data = &host->dma_params;
618c2ecf20Sopenharmony_ci	dai->capture_dma_data = &host->dma_params_rx;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	return 0;
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic void spdif_in_shutdown(struct snd_pcm_substream *substream,
678c2ecf20Sopenharmony_ci		struct snd_soc_dai *dai)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
728c2ecf20Sopenharmony_ci		return;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK);
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic void spdif_in_format(struct spdif_in_dev *host, u32 format)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	u32 ctrl = readl(host->io_base + SPDIF_IN_CTRL);
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	switch (format) {
828c2ecf20Sopenharmony_ci	case SNDRV_PCM_FORMAT_S16_LE:
838c2ecf20Sopenharmony_ci		ctrl |= SPDIF_XTRACT_16BIT;
848c2ecf20Sopenharmony_ci		break;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
878c2ecf20Sopenharmony_ci		ctrl &= ~SPDIF_XTRACT_16BIT;
888c2ecf20Sopenharmony_ci		break;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	writel(ctrl, host->io_base + SPDIF_IN_CTRL);
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic int spdif_in_hw_params(struct snd_pcm_substream *substream,
958c2ecf20Sopenharmony_ci		struct snd_pcm_hw_params *params,
968c2ecf20Sopenharmony_ci		struct snd_soc_dai *dai)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci	struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai);
998c2ecf20Sopenharmony_ci	u32 format;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
1028c2ecf20Sopenharmony_ci		return -EINVAL;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	format = params_format(params);
1058c2ecf20Sopenharmony_ci	host->saved_params.format = format;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	return 0;
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd,
1118c2ecf20Sopenharmony_ci		struct snd_soc_dai *dai)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai);
1148c2ecf20Sopenharmony_ci	u32 ctrl;
1158c2ecf20Sopenharmony_ci	int ret = 0;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
1188c2ecf20Sopenharmony_ci		return -EINVAL;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	switch (cmd) {
1218c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
1228c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_RESUME:
1238c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
1248c2ecf20Sopenharmony_ci		clk_enable(host->clk);
1258c2ecf20Sopenharmony_ci		spdif_in_configure(host);
1268c2ecf20Sopenharmony_ci		spdif_in_format(host, host->saved_params.format);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci		ctrl = readl(host->io_base + SPDIF_IN_CTRL);
1298c2ecf20Sopenharmony_ci		ctrl |= SPDIF_IN_SAMPLE | SPDIF_IN_ENB;
1308c2ecf20Sopenharmony_ci		writel(ctrl, host->io_base + SPDIF_IN_CTRL);
1318c2ecf20Sopenharmony_ci		writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK);
1328c2ecf20Sopenharmony_ci		break;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
1358c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_SUSPEND:
1368c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
1378c2ecf20Sopenharmony_ci		ctrl = readl(host->io_base + SPDIF_IN_CTRL);
1388c2ecf20Sopenharmony_ci		ctrl &= ~(SPDIF_IN_SAMPLE | SPDIF_IN_ENB);
1398c2ecf20Sopenharmony_ci		writel(ctrl, host->io_base + SPDIF_IN_CTRL);
1408c2ecf20Sopenharmony_ci		writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci		if (host->reset_perip)
1438c2ecf20Sopenharmony_ci			host->reset_perip();
1448c2ecf20Sopenharmony_ci		clk_disable(host->clk);
1458c2ecf20Sopenharmony_ci		break;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	default:
1488c2ecf20Sopenharmony_ci		ret = -EINVAL;
1498c2ecf20Sopenharmony_ci		break;
1508c2ecf20Sopenharmony_ci	}
1518c2ecf20Sopenharmony_ci	return ret;
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops spdif_in_dai_ops = {
1558c2ecf20Sopenharmony_ci	.shutdown	= spdif_in_shutdown,
1568c2ecf20Sopenharmony_ci	.trigger	= spdif_in_trigger,
1578c2ecf20Sopenharmony_ci	.hw_params	= spdif_in_hw_params,
1588c2ecf20Sopenharmony_ci};
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver spdif_in_dai = {
1618c2ecf20Sopenharmony_ci	.probe = spdif_in_dai_probe,
1628c2ecf20Sopenharmony_ci	.capture = {
1638c2ecf20Sopenharmony_ci		.channels_min = 2,
1648c2ecf20Sopenharmony_ci		.channels_max = 2,
1658c2ecf20Sopenharmony_ci		.rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
1668c2ecf20Sopenharmony_ci				 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \
1678c2ecf20Sopenharmony_ci				 SNDRV_PCM_RATE_192000),
1688c2ecf20Sopenharmony_ci		.formats = SNDRV_PCM_FMTBIT_S16_LE | \
1698c2ecf20Sopenharmony_ci			   SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
1708c2ecf20Sopenharmony_ci	},
1718c2ecf20Sopenharmony_ci	.ops = &spdif_in_dai_ops,
1728c2ecf20Sopenharmony_ci};
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver spdif_in_component = {
1758c2ecf20Sopenharmony_ci	.name		= "spdif-in",
1768c2ecf20Sopenharmony_ci};
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic irqreturn_t spdif_in_irq(int irq, void *arg)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	struct spdif_in_dev *host = (struct spdif_in_dev *)arg;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	u32 irq_status = readl(host->io_base + SPDIF_IN_IRQ);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	if (!irq_status)
1858c2ecf20Sopenharmony_ci		return IRQ_NONE;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	if (irq_status & SPDIF_IRQ_FIFOWRITE)
1888c2ecf20Sopenharmony_ci		dev_err(host->dev, "spdif in: fifo write error");
1898c2ecf20Sopenharmony_ci	if (irq_status & SPDIF_IRQ_EMPTYFIFOREAD)
1908c2ecf20Sopenharmony_ci		dev_err(host->dev, "spdif in: empty fifo read error");
1918c2ecf20Sopenharmony_ci	if (irq_status & SPDIF_IRQ_FIFOFULL)
1928c2ecf20Sopenharmony_ci		dev_err(host->dev, "spdif in: fifo full error");
1938c2ecf20Sopenharmony_ci	if (irq_status & SPDIF_IRQ_OUTOFRANGE)
1948c2ecf20Sopenharmony_ci		dev_err(host->dev, "spdif in: out of range error");
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	writel(0, host->io_base + SPDIF_IN_IRQ);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_cistatic int spdif_in_probe(struct platform_device *pdev)
2028c2ecf20Sopenharmony_ci{
2038c2ecf20Sopenharmony_ci	struct spdif_in_dev *host;
2048c2ecf20Sopenharmony_ci	struct spear_spdif_platform_data *pdata;
2058c2ecf20Sopenharmony_ci	struct resource *res_fifo;
2068c2ecf20Sopenharmony_ci	void __iomem *io_base;
2078c2ecf20Sopenharmony_ci	int ret;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	io_base = devm_platform_ioremap_resource(pdev, 0);
2108c2ecf20Sopenharmony_ci	if (IS_ERR(io_base))
2118c2ecf20Sopenharmony_ci		return PTR_ERR(io_base);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	res_fifo = platform_get_resource(pdev, IORESOURCE_IO, 0);
2148c2ecf20Sopenharmony_ci	if (!res_fifo)
2158c2ecf20Sopenharmony_ci		return -EINVAL;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
2188c2ecf20Sopenharmony_ci	if (!host)
2198c2ecf20Sopenharmony_ci		return -ENOMEM;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	host->io_base = io_base;
2228c2ecf20Sopenharmony_ci	host->irq = platform_get_irq(pdev, 0);
2238c2ecf20Sopenharmony_ci	if (host->irq < 0) {
2248c2ecf20Sopenharmony_ci		dev_warn(&pdev->dev, "failed to get IRQ: %d\n", host->irq);
2258c2ecf20Sopenharmony_ci		return host->irq;
2268c2ecf20Sopenharmony_ci	}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	host->clk = devm_clk_get(&pdev->dev, NULL);
2298c2ecf20Sopenharmony_ci	if (IS_ERR(host->clk))
2308c2ecf20Sopenharmony_ci		return PTR_ERR(host->clk);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	pdata = dev_get_platdata(&pdev->dev);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	if (!pdata)
2358c2ecf20Sopenharmony_ci		return -EINVAL;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	host->dma_params.data = pdata->dma_params;
2388c2ecf20Sopenharmony_ci	host->dma_params.addr = res_fifo->start;
2398c2ecf20Sopenharmony_ci	host->dma_params.max_burst = 16;
2408c2ecf20Sopenharmony_ci	host->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
2418c2ecf20Sopenharmony_ci	host->reset_perip = pdata->reset_perip;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	host->dev = &pdev->dev;
2448c2ecf20Sopenharmony_ci	dev_set_drvdata(&pdev->dev, host);
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0,
2478c2ecf20Sopenharmony_ci			"spdif-in", host);
2488c2ecf20Sopenharmony_ci	if (ret) {
2498c2ecf20Sopenharmony_ci		dev_warn(&pdev->dev, "request_irq failed\n");
2508c2ecf20Sopenharmony_ci		return ret;
2518c2ecf20Sopenharmony_ci	}
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component,
2548c2ecf20Sopenharmony_ci					      &spdif_in_dai, 1);
2558c2ecf20Sopenharmony_ci	if (ret)
2568c2ecf20Sopenharmony_ci		return ret;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	return devm_spear_pcm_platform_register(&pdev->dev, &host->config,
2598c2ecf20Sopenharmony_ci						pdata->filter);
2608c2ecf20Sopenharmony_ci}
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_cistatic struct platform_driver spdif_in_driver = {
2638c2ecf20Sopenharmony_ci	.probe		= spdif_in_probe,
2648c2ecf20Sopenharmony_ci	.driver		= {
2658c2ecf20Sopenharmony_ci		.name	= "spdif-in",
2668c2ecf20Sopenharmony_ci	},
2678c2ecf20Sopenharmony_ci};
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_cimodule_platform_driver(spdif_in_driver);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vipin Kumar <vipin.kumar@st.com>");
2728c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SPEAr SPDIF IN SoC Interface");
2738c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2748c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:spdif_in");
275