18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci//
38c2ecf20Sopenharmony_ci// ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
48c2ecf20Sopenharmony_ci//
58c2ecf20Sopenharmony_ci// Copyright (c) 2010 Samsung Electronics Co. Ltd
68c2ecf20Sopenharmony_ci//		http://www.samsung.com/
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/clk.h>
98c2ecf20Sopenharmony_ci#include <linux/io.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <sound/soc.h>
138c2ecf20Sopenharmony_ci#include <sound/pcm_params.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/platform_data/asoc-s3c.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include "dma.h"
188c2ecf20Sopenharmony_ci#include "spdif.h"
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/* Registers */
218c2ecf20Sopenharmony_ci#define CLKCON				0x00
228c2ecf20Sopenharmony_ci#define CON				0x04
238c2ecf20Sopenharmony_ci#define BSTAS				0x08
248c2ecf20Sopenharmony_ci#define CSTAS				0x0C
258c2ecf20Sopenharmony_ci#define DATA_OUTBUF			0x10
268c2ecf20Sopenharmony_ci#define DCNT				0x14
278c2ecf20Sopenharmony_ci#define BSTAS_S				0x18
288c2ecf20Sopenharmony_ci#define DCNT_S				0x1C
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define CLKCTL_MASK			0x7
318c2ecf20Sopenharmony_ci#define CLKCTL_MCLK_EXT			(0x1 << 2)
328c2ecf20Sopenharmony_ci#define CLKCTL_PWR_ON			(0x1 << 0)
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#define CON_MASK			0x3ffffff
358c2ecf20Sopenharmony_ci#define CON_FIFO_TH_SHIFT		19
368c2ecf20Sopenharmony_ci#define CON_FIFO_TH_MASK		(0x7 << 19)
378c2ecf20Sopenharmony_ci#define CON_USERDATA_23RDBIT		(0x1 << 12)
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#define CON_SW_RESET			(0x1 << 5)
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define CON_MCLKDIV_MASK		(0x3 << 3)
428c2ecf20Sopenharmony_ci#define CON_MCLKDIV_256FS		(0x0 << 3)
438c2ecf20Sopenharmony_ci#define CON_MCLKDIV_384FS		(0x1 << 3)
448c2ecf20Sopenharmony_ci#define CON_MCLKDIV_512FS		(0x2 << 3)
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci#define CON_PCM_MASK			(0x3 << 1)
478c2ecf20Sopenharmony_ci#define CON_PCM_16BIT			(0x0 << 1)
488c2ecf20Sopenharmony_ci#define CON_PCM_20BIT			(0x1 << 1)
498c2ecf20Sopenharmony_ci#define CON_PCM_24BIT			(0x2 << 1)
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci#define CON_PCM_DATA			(0x1 << 0)
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci#define CSTAS_MASK			0x3fffffff
548c2ecf20Sopenharmony_ci#define CSTAS_SAMP_FREQ_MASK		(0xF << 24)
558c2ecf20Sopenharmony_ci#define CSTAS_SAMP_FREQ_44		(0x0 << 24)
568c2ecf20Sopenharmony_ci#define CSTAS_SAMP_FREQ_48		(0x2 << 24)
578c2ecf20Sopenharmony_ci#define CSTAS_SAMP_FREQ_32		(0x3 << 24)
588c2ecf20Sopenharmony_ci#define CSTAS_SAMP_FREQ_96		(0xA << 24)
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci#define CSTAS_CATEGORY_MASK		(0xFF << 8)
618c2ecf20Sopenharmony_ci#define CSTAS_CATEGORY_CODE_CDP		(0x01 << 8)
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci#define CSTAS_NO_COPYRIGHT		(0x1 << 2)
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci/**
668c2ecf20Sopenharmony_ci * struct samsung_spdif_info - Samsung S/PDIF Controller information
678c2ecf20Sopenharmony_ci * @lock: Spin lock for S/PDIF.
688c2ecf20Sopenharmony_ci * @dev: The parent device passed to use from the probe.
698c2ecf20Sopenharmony_ci * @regs: The pointer to the device register block.
708c2ecf20Sopenharmony_ci * @clk_rate: Current clock rate for calcurate ratio.
718c2ecf20Sopenharmony_ci * @pclk: The peri-clock pointer for spdif master operation.
728c2ecf20Sopenharmony_ci * @sclk: The source clock pointer for making sync signals.
738c2ecf20Sopenharmony_ci * @saved_clkcon: Backup clkcon reg. in suspend.
748c2ecf20Sopenharmony_ci * @saved_con: Backup con reg. in suspend.
758c2ecf20Sopenharmony_ci * @saved_cstas: Backup cstas reg. in suspend.
768c2ecf20Sopenharmony_ci * @dma_playback: DMA information for playback channel.
778c2ecf20Sopenharmony_ci */
788c2ecf20Sopenharmony_cistruct samsung_spdif_info {
798c2ecf20Sopenharmony_ci	spinlock_t	lock;
808c2ecf20Sopenharmony_ci	struct device	*dev;
818c2ecf20Sopenharmony_ci	void __iomem	*regs;
828c2ecf20Sopenharmony_ci	unsigned long	clk_rate;
838c2ecf20Sopenharmony_ci	struct clk	*pclk;
848c2ecf20Sopenharmony_ci	struct clk	*sclk;
858c2ecf20Sopenharmony_ci	u32		saved_clkcon;
868c2ecf20Sopenharmony_ci	u32		saved_con;
878c2ecf20Sopenharmony_ci	u32		saved_cstas;
888c2ecf20Sopenharmony_ci	struct snd_dmaengine_dai_dma_data *dma_playback;
898c2ecf20Sopenharmony_ci};
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic struct snd_dmaengine_dai_dma_data spdif_stereo_out;
928c2ecf20Sopenharmony_cistatic struct samsung_spdif_info spdif_info;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic inline struct samsung_spdif_info
958c2ecf20Sopenharmony_ci*component_to_info(struct snd_soc_component *component)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	return snd_soc_component_get_drvdata(component);
988c2ecf20Sopenharmony_ci}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	return snd_soc_dai_get_drvdata(cpu_dai);
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on)
1068c2ecf20Sopenharmony_ci{
1078c2ecf20Sopenharmony_ci	void __iomem *regs = spdif->regs;
1088c2ecf20Sopenharmony_ci	u32 clkcon;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	dev_dbg(spdif->dev, "Entered %s\n", __func__);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
1138c2ecf20Sopenharmony_ci	if (on)
1148c2ecf20Sopenharmony_ci		writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON);
1158c2ecf20Sopenharmony_ci	else
1168c2ecf20Sopenharmony_ci		writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic int spdif_set_sysclk(struct snd_soc_dai *cpu_dai,
1208c2ecf20Sopenharmony_ci				int clk_id, unsigned int freq, int dir)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct samsung_spdif_info *spdif = to_info(cpu_dai);
1238c2ecf20Sopenharmony_ci	u32 clkcon;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	dev_dbg(spdif->dev, "Entered %s\n", __func__);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	clkcon = readl(spdif->regs + CLKCON);
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	if (clk_id == SND_SOC_SPDIF_INT_MCLK)
1308c2ecf20Sopenharmony_ci		clkcon &= ~CLKCTL_MCLK_EXT;
1318c2ecf20Sopenharmony_ci	else
1328c2ecf20Sopenharmony_ci		clkcon |= CLKCTL_MCLK_EXT;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	writel(clkcon, spdif->regs + CLKCON);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	spdif->clk_rate = freq;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	return 0;
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic int spdif_trigger(struct snd_pcm_substream *substream, int cmd,
1428c2ecf20Sopenharmony_ci				struct snd_soc_dai *dai)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
1458c2ecf20Sopenharmony_ci	struct samsung_spdif_info *spdif = to_info(asoc_rtd_to_cpu(rtd, 0));
1468c2ecf20Sopenharmony_ci	unsigned long flags;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	dev_dbg(spdif->dev, "Entered %s\n", __func__);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	switch (cmd) {
1518c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
1528c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_RESUME:
1538c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
1548c2ecf20Sopenharmony_ci		spin_lock_irqsave(&spdif->lock, flags);
1558c2ecf20Sopenharmony_ci		spdif_snd_txctrl(spdif, 1);
1568c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&spdif->lock, flags);
1578c2ecf20Sopenharmony_ci		break;
1588c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
1598c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_SUSPEND:
1608c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
1618c2ecf20Sopenharmony_ci		spin_lock_irqsave(&spdif->lock, flags);
1628c2ecf20Sopenharmony_ci		spdif_snd_txctrl(spdif, 0);
1638c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&spdif->lock, flags);
1648c2ecf20Sopenharmony_ci		break;
1658c2ecf20Sopenharmony_ci	default:
1668c2ecf20Sopenharmony_ci		return -EINVAL;
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	return 0;
1708c2ecf20Sopenharmony_ci}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_cistatic int spdif_sysclk_ratios[] = {
1738c2ecf20Sopenharmony_ci	512, 384, 256,
1748c2ecf20Sopenharmony_ci};
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_cistatic int spdif_hw_params(struct snd_pcm_substream *substream,
1778c2ecf20Sopenharmony_ci				struct snd_pcm_hw_params *params,
1788c2ecf20Sopenharmony_ci				struct snd_soc_dai *socdai)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
1818c2ecf20Sopenharmony_ci	struct samsung_spdif_info *spdif = to_info(asoc_rtd_to_cpu(rtd, 0));
1828c2ecf20Sopenharmony_ci	void __iomem *regs = spdif->regs;
1838c2ecf20Sopenharmony_ci	struct snd_dmaengine_dai_dma_data *dma_data;
1848c2ecf20Sopenharmony_ci	u32 con, clkcon, cstas;
1858c2ecf20Sopenharmony_ci	unsigned long flags;
1868c2ecf20Sopenharmony_ci	int i, ratio;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	dev_dbg(spdif->dev, "Entered %s\n", __func__);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
1918c2ecf20Sopenharmony_ci		dma_data = spdif->dma_playback;
1928c2ecf20Sopenharmony_ci	else {
1938c2ecf20Sopenharmony_ci		dev_err(spdif->dev, "Capture is not supported\n");
1948c2ecf20Sopenharmony_ci		return -EINVAL;
1958c2ecf20Sopenharmony_ci	}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	snd_soc_dai_set_dma_data(asoc_rtd_to_cpu(rtd, 0), substream, dma_data);
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	spin_lock_irqsave(&spdif->lock, flags);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	con = readl(regs + CON) & CON_MASK;
2028c2ecf20Sopenharmony_ci	cstas = readl(regs + CSTAS) & CSTAS_MASK;
2038c2ecf20Sopenharmony_ci	clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	con &= ~CON_FIFO_TH_MASK;
2068c2ecf20Sopenharmony_ci	con |= (0x7 << CON_FIFO_TH_SHIFT);
2078c2ecf20Sopenharmony_ci	con |= CON_USERDATA_23RDBIT;
2088c2ecf20Sopenharmony_ci	con |= CON_PCM_DATA;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	con &= ~CON_PCM_MASK;
2118c2ecf20Sopenharmony_ci	switch (params_width(params)) {
2128c2ecf20Sopenharmony_ci	case 16:
2138c2ecf20Sopenharmony_ci		con |= CON_PCM_16BIT;
2148c2ecf20Sopenharmony_ci		break;
2158c2ecf20Sopenharmony_ci	default:
2168c2ecf20Sopenharmony_ci		dev_err(spdif->dev, "Unsupported data size.\n");
2178c2ecf20Sopenharmony_ci		goto err;
2188c2ecf20Sopenharmony_ci	}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	ratio = spdif->clk_rate / params_rate(params);
2218c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++)
2228c2ecf20Sopenharmony_ci		if (ratio == spdif_sysclk_ratios[i])
2238c2ecf20Sopenharmony_ci			break;
2248c2ecf20Sopenharmony_ci	if (i == ARRAY_SIZE(spdif_sysclk_ratios)) {
2258c2ecf20Sopenharmony_ci		dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n",
2268c2ecf20Sopenharmony_ci				spdif->clk_rate, params_rate(params));
2278c2ecf20Sopenharmony_ci		goto err;
2288c2ecf20Sopenharmony_ci	}
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	con &= ~CON_MCLKDIV_MASK;
2318c2ecf20Sopenharmony_ci	switch (ratio) {
2328c2ecf20Sopenharmony_ci	case 256:
2338c2ecf20Sopenharmony_ci		con |= CON_MCLKDIV_256FS;
2348c2ecf20Sopenharmony_ci		break;
2358c2ecf20Sopenharmony_ci	case 384:
2368c2ecf20Sopenharmony_ci		con |= CON_MCLKDIV_384FS;
2378c2ecf20Sopenharmony_ci		break;
2388c2ecf20Sopenharmony_ci	case 512:
2398c2ecf20Sopenharmony_ci		con |= CON_MCLKDIV_512FS;
2408c2ecf20Sopenharmony_ci		break;
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	cstas &= ~CSTAS_SAMP_FREQ_MASK;
2448c2ecf20Sopenharmony_ci	switch (params_rate(params)) {
2458c2ecf20Sopenharmony_ci	case 44100:
2468c2ecf20Sopenharmony_ci		cstas |= CSTAS_SAMP_FREQ_44;
2478c2ecf20Sopenharmony_ci		break;
2488c2ecf20Sopenharmony_ci	case 48000:
2498c2ecf20Sopenharmony_ci		cstas |= CSTAS_SAMP_FREQ_48;
2508c2ecf20Sopenharmony_ci		break;
2518c2ecf20Sopenharmony_ci	case 32000:
2528c2ecf20Sopenharmony_ci		cstas |= CSTAS_SAMP_FREQ_32;
2538c2ecf20Sopenharmony_ci		break;
2548c2ecf20Sopenharmony_ci	case 96000:
2558c2ecf20Sopenharmony_ci		cstas |= CSTAS_SAMP_FREQ_96;
2568c2ecf20Sopenharmony_ci		break;
2578c2ecf20Sopenharmony_ci	default:
2588c2ecf20Sopenharmony_ci		dev_err(spdif->dev, "Invalid sampling rate %d\n",
2598c2ecf20Sopenharmony_ci				params_rate(params));
2608c2ecf20Sopenharmony_ci		goto err;
2618c2ecf20Sopenharmony_ci	}
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	cstas &= ~CSTAS_CATEGORY_MASK;
2648c2ecf20Sopenharmony_ci	cstas |= CSTAS_CATEGORY_CODE_CDP;
2658c2ecf20Sopenharmony_ci	cstas |= CSTAS_NO_COPYRIGHT;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	writel(con, regs + CON);
2688c2ecf20Sopenharmony_ci	writel(cstas, regs + CSTAS);
2698c2ecf20Sopenharmony_ci	writel(clkcon, regs + CLKCON);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&spdif->lock, flags);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return 0;
2748c2ecf20Sopenharmony_cierr:
2758c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&spdif->lock, flags);
2768c2ecf20Sopenharmony_ci	return -EINVAL;
2778c2ecf20Sopenharmony_ci}
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_cistatic void spdif_shutdown(struct snd_pcm_substream *substream,
2808c2ecf20Sopenharmony_ci				struct snd_soc_dai *dai)
2818c2ecf20Sopenharmony_ci{
2828c2ecf20Sopenharmony_ci	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
2838c2ecf20Sopenharmony_ci	struct samsung_spdif_info *spdif = to_info(asoc_rtd_to_cpu(rtd, 0));
2848c2ecf20Sopenharmony_ci	void __iomem *regs = spdif->regs;
2858c2ecf20Sopenharmony_ci	u32 con, clkcon;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	dev_dbg(spdif->dev, "Entered %s\n", __func__);
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	con = readl(regs + CON) & CON_MASK;
2908c2ecf20Sopenharmony_ci	clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	writel(con | CON_SW_RESET, regs + CON);
2938c2ecf20Sopenharmony_ci	cpu_relax();
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
2968c2ecf20Sopenharmony_ci}
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
2998c2ecf20Sopenharmony_cistatic int spdif_suspend(struct snd_soc_component *component)
3008c2ecf20Sopenharmony_ci{
3018c2ecf20Sopenharmony_ci	struct samsung_spdif_info *spdif = component_to_info(component);
3028c2ecf20Sopenharmony_ci	u32 con = spdif->saved_con;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	dev_dbg(spdif->dev, "Entered %s\n", __func__);
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	spdif->saved_clkcon = readl(spdif->regs	+ CLKCON) & CLKCTL_MASK;
3078c2ecf20Sopenharmony_ci	spdif->saved_con = readl(spdif->regs + CON) & CON_MASK;
3088c2ecf20Sopenharmony_ci	spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK;
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci	writel(con | CON_SW_RESET, spdif->regs + CON);
3118c2ecf20Sopenharmony_ci	cpu_relax();
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	return 0;
3148c2ecf20Sopenharmony_ci}
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_cistatic int spdif_resume(struct snd_soc_component *component)
3178c2ecf20Sopenharmony_ci{
3188c2ecf20Sopenharmony_ci	struct samsung_spdif_info *spdif = component_to_info(component);
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	dev_dbg(spdif->dev, "Entered %s\n", __func__);
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci	writel(spdif->saved_clkcon, spdif->regs	+ CLKCON);
3238c2ecf20Sopenharmony_ci	writel(spdif->saved_con, spdif->regs + CON);
3248c2ecf20Sopenharmony_ci	writel(spdif->saved_cstas, spdif->regs + CSTAS);
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	return 0;
3278c2ecf20Sopenharmony_ci}
3288c2ecf20Sopenharmony_ci#else
3298c2ecf20Sopenharmony_ci#define spdif_suspend NULL
3308c2ecf20Sopenharmony_ci#define spdif_resume NULL
3318c2ecf20Sopenharmony_ci#endif
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops spdif_dai_ops = {
3348c2ecf20Sopenharmony_ci	.set_sysclk	= spdif_set_sysclk,
3358c2ecf20Sopenharmony_ci	.trigger	= spdif_trigger,
3368c2ecf20Sopenharmony_ci	.hw_params	= spdif_hw_params,
3378c2ecf20Sopenharmony_ci	.shutdown	= spdif_shutdown,
3388c2ecf20Sopenharmony_ci};
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver samsung_spdif_dai = {
3418c2ecf20Sopenharmony_ci	.name = "samsung-spdif",
3428c2ecf20Sopenharmony_ci	.playback = {
3438c2ecf20Sopenharmony_ci		.stream_name = "S/PDIF Playback",
3448c2ecf20Sopenharmony_ci		.channels_min = 2,
3458c2ecf20Sopenharmony_ci		.channels_max = 2,
3468c2ecf20Sopenharmony_ci		.rates = (SNDRV_PCM_RATE_32000 |
3478c2ecf20Sopenharmony_ci				SNDRV_PCM_RATE_44100 |
3488c2ecf20Sopenharmony_ci				SNDRV_PCM_RATE_48000 |
3498c2ecf20Sopenharmony_ci				SNDRV_PCM_RATE_96000),
3508c2ecf20Sopenharmony_ci		.formats = SNDRV_PCM_FMTBIT_S16_LE, },
3518c2ecf20Sopenharmony_ci	.ops = &spdif_dai_ops,
3528c2ecf20Sopenharmony_ci};
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver samsung_spdif_component = {
3558c2ecf20Sopenharmony_ci	.name		= "samsung-spdif",
3568c2ecf20Sopenharmony_ci	.suspend	= spdif_suspend,
3578c2ecf20Sopenharmony_ci	.resume		= spdif_resume,
3588c2ecf20Sopenharmony_ci};
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_cistatic int spdif_probe(struct platform_device *pdev)
3618c2ecf20Sopenharmony_ci{
3628c2ecf20Sopenharmony_ci	struct s3c_audio_pdata *spdif_pdata;
3638c2ecf20Sopenharmony_ci	struct resource *mem_res;
3648c2ecf20Sopenharmony_ci	struct samsung_spdif_info *spdif;
3658c2ecf20Sopenharmony_ci	dma_filter_fn filter;
3668c2ecf20Sopenharmony_ci	int ret;
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	spdif_pdata = pdev->dev.platform_data;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	dev_dbg(&pdev->dev, "Entered %s\n", __func__);
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
3738c2ecf20Sopenharmony_ci	if (!mem_res) {
3748c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Unable to get register resource.\n");
3758c2ecf20Sopenharmony_ci		return -ENXIO;
3768c2ecf20Sopenharmony_ci	}
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	if (spdif_pdata && spdif_pdata->cfg_gpio
3798c2ecf20Sopenharmony_ci			&& spdif_pdata->cfg_gpio(pdev)) {
3808c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Unable to configure GPIO pins\n");
3818c2ecf20Sopenharmony_ci		return -EINVAL;
3828c2ecf20Sopenharmony_ci	}
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci	spdif = &spdif_info;
3858c2ecf20Sopenharmony_ci	spdif->dev = &pdev->dev;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	spin_lock_init(&spdif->lock);
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci	spdif->pclk = devm_clk_get(&pdev->dev, "spdif");
3908c2ecf20Sopenharmony_ci	if (IS_ERR(spdif->pclk)) {
3918c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to get peri-clock\n");
3928c2ecf20Sopenharmony_ci		ret = -ENOENT;
3938c2ecf20Sopenharmony_ci		goto err0;
3948c2ecf20Sopenharmony_ci	}
3958c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(spdif->pclk);
3968c2ecf20Sopenharmony_ci	if (ret)
3978c2ecf20Sopenharmony_ci		goto err0;
3988c2ecf20Sopenharmony_ci
3998c2ecf20Sopenharmony_ci	spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif");
4008c2ecf20Sopenharmony_ci	if (IS_ERR(spdif->sclk)) {
4018c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to get internal source clock\n");
4028c2ecf20Sopenharmony_ci		ret = -ENOENT;
4038c2ecf20Sopenharmony_ci		goto err1;
4048c2ecf20Sopenharmony_ci	}
4058c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(spdif->sclk);
4068c2ecf20Sopenharmony_ci	if (ret)
4078c2ecf20Sopenharmony_ci		goto err1;
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	/* Request S/PDIF Register's memory region */
4108c2ecf20Sopenharmony_ci	if (!request_mem_region(mem_res->start,
4118c2ecf20Sopenharmony_ci				resource_size(mem_res), "samsung-spdif")) {
4128c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Unable to request register region\n");
4138c2ecf20Sopenharmony_ci		ret = -EBUSY;
4148c2ecf20Sopenharmony_ci		goto err2;
4158c2ecf20Sopenharmony_ci	}
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	spdif->regs = ioremap(mem_res->start, 0x100);
4188c2ecf20Sopenharmony_ci	if (spdif->regs == NULL) {
4198c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Cannot ioremap registers\n");
4208c2ecf20Sopenharmony_ci		ret = -ENXIO;
4218c2ecf20Sopenharmony_ci		goto err3;
4228c2ecf20Sopenharmony_ci	}
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	spdif_stereo_out.addr_width = 2;
4258c2ecf20Sopenharmony_ci	spdif_stereo_out.addr = mem_res->start + DATA_OUTBUF;
4268c2ecf20Sopenharmony_ci	filter = NULL;
4278c2ecf20Sopenharmony_ci	if (spdif_pdata) {
4288c2ecf20Sopenharmony_ci		spdif_stereo_out.filter_data = spdif_pdata->dma_playback;
4298c2ecf20Sopenharmony_ci		filter = spdif_pdata->dma_filter;
4308c2ecf20Sopenharmony_ci	}
4318c2ecf20Sopenharmony_ci	spdif->dma_playback = &spdif_stereo_out;
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_ci	ret = samsung_asoc_dma_platform_register(&pdev->dev, filter,
4348c2ecf20Sopenharmony_ci						 NULL, NULL, NULL);
4358c2ecf20Sopenharmony_ci	if (ret) {
4368c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to register DMA: %d\n", ret);
4378c2ecf20Sopenharmony_ci		goto err4;
4388c2ecf20Sopenharmony_ci	}
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_ci	dev_set_drvdata(&pdev->dev, spdif);
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	ret = devm_snd_soc_register_component(&pdev->dev,
4438c2ecf20Sopenharmony_ci			&samsung_spdif_component, &samsung_spdif_dai, 1);
4448c2ecf20Sopenharmony_ci	if (ret != 0) {
4458c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "fail to register dai\n");
4468c2ecf20Sopenharmony_ci		goto err4;
4478c2ecf20Sopenharmony_ci	}
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci	return 0;
4508c2ecf20Sopenharmony_cierr4:
4518c2ecf20Sopenharmony_ci	iounmap(spdif->regs);
4528c2ecf20Sopenharmony_cierr3:
4538c2ecf20Sopenharmony_ci	release_mem_region(mem_res->start, resource_size(mem_res));
4548c2ecf20Sopenharmony_cierr2:
4558c2ecf20Sopenharmony_ci	clk_disable_unprepare(spdif->sclk);
4568c2ecf20Sopenharmony_cierr1:
4578c2ecf20Sopenharmony_ci	clk_disable_unprepare(spdif->pclk);
4588c2ecf20Sopenharmony_cierr0:
4598c2ecf20Sopenharmony_ci	return ret;
4608c2ecf20Sopenharmony_ci}
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_cistatic int spdif_remove(struct platform_device *pdev)
4638c2ecf20Sopenharmony_ci{
4648c2ecf20Sopenharmony_ci	struct samsung_spdif_info *spdif = &spdif_info;
4658c2ecf20Sopenharmony_ci	struct resource *mem_res;
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	iounmap(spdif->regs);
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ci	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
4708c2ecf20Sopenharmony_ci	if (mem_res)
4718c2ecf20Sopenharmony_ci		release_mem_region(mem_res->start, resource_size(mem_res));
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	clk_disable_unprepare(spdif->sclk);
4748c2ecf20Sopenharmony_ci	clk_disable_unprepare(spdif->pclk);
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	return 0;
4778c2ecf20Sopenharmony_ci}
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_cistatic struct platform_driver samsung_spdif_driver = {
4808c2ecf20Sopenharmony_ci	.probe	= spdif_probe,
4818c2ecf20Sopenharmony_ci	.remove	= spdif_remove,
4828c2ecf20Sopenharmony_ci	.driver	= {
4838c2ecf20Sopenharmony_ci		.name	= "samsung-spdif",
4848c2ecf20Sopenharmony_ci	},
4858c2ecf20Sopenharmony_ci};
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_cimodule_platform_driver(samsung_spdif_driver);
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ciMODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
4908c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Samsung S/PDIF Controller Driver");
4918c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
4928c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:samsung-spdif");
493