From: Sugar Zhang Date: Thu, 2 Jul 2015 12:02:31 +0000 (+0800) Subject: ASoC: rockchip: rename rk30_i2s.c to rk_i2s.c X-Git-Tag: firefly_0821_release~3951 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=e890a32bcb002cb29ad31365e921a6a19bc44be2;p=firefly-linux-kernel-4.4.55.git ASoC: rockchip: rename rk30_i2s.c to rk_i2s.c Signed-off-by: Sugar Zhang --- diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile index eff5607a4347..cea9f66cf05d 100755 --- a/sound/soc/rockchip/Makefile +++ b/sound/soc/rockchip/Makefile @@ -3,7 +3,7 @@ snd-soc-rockchip-objs := rk_pcm.o card_info.o ifdef CONFIG_ARCH_RK29 snd-soc-rockchip-i2s-objs := rk29_i2s.o else -snd-soc-rockchip-i2s-objs := rk30_i2s.o +snd-soc-rockchip-i2s-objs := rk_i2s.o endif snd-soc-rockchip-spdif-objs := rk_spdif.o diff --git a/sound/soc/rockchip/rk30_i2s.c b/sound/soc/rockchip/rk30_i2s.c deleted file mode 100644 index d551860c4acb..000000000000 --- a/sound/soc/rockchip/rk30_i2s.c +++ /dev/null @@ -1,785 +0,0 @@ -/* - * Rockchip I2S ALSA SoC Digital Audio Interface(DAI) driver - * - * Copyright (C) 2015 Fuzhou Rockchip Electronics Co., Ltd - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "rk_pcm.h" -#include "rk_i2s.h" - -#define CLK_SET_LATER -#define I2S_DEFAULT_FREQ (11289600) -#define I2S_DMA_BURST_SIZE (16) /* size * width: 16*4 = 64 bytes */ -static DEFINE_SPINLOCK(lock); - -#if defined(CONFIG_RK_HDMI) && defined(CONFIG_SND_RK_SOC_HDMI_I2S) -extern int snd_config_hdmi_audio(struct snd_pcm_hw_params *params); -#endif - -struct rk_i2s_dev { - struct device *dev; - struct clk *clk; /* bclk */ - struct clk *mclk; /*mclk output only */ - struct clk *hclk; /*ahb clk */ - struct snd_dmaengine_dai_dma_data capture_dma_data; - struct snd_dmaengine_dai_dma_data playback_dma_data; - struct regmap *regmap; - bool tx_start; - bool rx_start; -#ifdef CLK_SET_LATER - struct delayed_work clk_delayed_work; -#endif -}; - -static inline struct rk_i2s_dev *to_info(struct snd_soc_dai *dai) -{ - return snd_soc_dai_get_drvdata(dai); -} - -static void rockchip_snd_txctrl(struct rk_i2s_dev *i2s, int on) -{ - unsigned long flags; - unsigned int val = 0; - int retry = 10; - - spin_lock_irqsave(&lock, flags); - - dev_dbg(i2s->dev, "%s: %d: on: %d\n", __func__, __LINE__, on); - - if (on) { - regmap_update_bits(i2s->regmap, I2S_DMACR, - I2S_DMACR_TDE_MASK, I2S_DMACR_TDE_ENABLE); - - regmap_update_bits(i2s->regmap, I2S_XFER, - I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK, - I2S_XFER_TXS_START | I2S_XFER_RXS_START); - - i2s->tx_start = true; - } else { - i2s->tx_start = false; - - regmap_update_bits(i2s->regmap, I2S_DMACR, - I2S_DMACR_TDE_MASK, I2S_DMACR_TDE_DISABLE); - - - if (!i2s->rx_start) { - regmap_update_bits(i2s->regmap, I2S_XFER, - I2S_XFER_TXS_MASK | - I2S_XFER_RXS_MASK, - I2S_XFER_TXS_STOP | - I2S_XFER_RXS_STOP); - - regmap_update_bits(i2s->regmap, I2S_CLR, - I2S_CLR_TXC_MASK | I2S_CLR_RXC_MASK, - I2S_CLR_TXC | I2S_CLR_RXC); - - regmap_read(i2s->regmap, I2S_CLR, &val); - - /* Should wait for clear operation to finish */ - while (val) { - regmap_read(i2s->regmap, I2S_CLR, &val); - retry--; - if (!retry) { - dev_warn(i2s->dev, "fail to clear\n"); - break; - } - } - dev_dbg(i2s->dev, "%s: %d: stop xfer\n", - __func__, __LINE__); - } - } - - spin_unlock_irqrestore(&lock, flags); -} - -static void rockchip_snd_rxctrl(struct rk_i2s_dev *i2s, int on) -{ - unsigned long flags; - unsigned int val = 0; - int retry = 10; - - spin_lock_irqsave(&lock, flags); - - dev_dbg(i2s->dev, "%s: %d: on: %d\n", __func__, __LINE__, on); - - if (on) { - regmap_update_bits(i2s->regmap, I2S_DMACR, - I2S_DMACR_RDE_MASK, I2S_DMACR_RDE_ENABLE); - - regmap_update_bits(i2s->regmap, I2S_XFER, - I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK, - I2S_XFER_TXS_START | I2S_XFER_RXS_START); - - i2s->rx_start = true; - } else { - i2s->rx_start = false; - - regmap_update_bits(i2s->regmap, I2S_DMACR, - I2S_DMACR_RDE_MASK, I2S_DMACR_RDE_DISABLE); - - if (!i2s->tx_start) { - regmap_update_bits(i2s->regmap, I2S_XFER, - I2S_XFER_TXS_MASK | - I2S_XFER_RXS_MASK, - I2S_XFER_TXS_STOP | - I2S_XFER_RXS_STOP); - - regmap_update_bits(i2s->regmap, I2S_CLR, - I2S_CLR_TXC_MASK | I2S_CLR_RXC_MASK, - I2S_CLR_TXC | I2S_CLR_RXC); - - regmap_read(i2s->regmap, I2S_CLR, &val); - - /* Should wait for clear operation to finish */ - while (val) { - regmap_read(i2s->regmap, I2S_CLR, &val); - retry--; - if (!retry) { - dev_warn(i2s->dev, "fail to clear\n"); - break; - } - } - dev_dbg(i2s->dev, "%s: %d: stop xfer\n", - __func__, __LINE__); - } - } - - spin_unlock_irqrestore(&lock, flags); -} - -static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, - unsigned int fmt) -{ - struct rk_i2s_dev *i2s = to_info(cpu_dai); - unsigned int mask = 0, val = 0; - int ret = 0; - unsigned long flags; - - spin_lock_irqsave(&lock, flags); - - mask = I2S_CKR_MSS_MASK; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - /* Codec is slave, so set cpu master */ - val = I2S_CKR_MSS_MASTER; - break; - case SND_SOC_DAIFMT_CBM_CFM: - /* Codec is master, so set cpu slave */ - val = I2S_CKR_MSS_SLAVE; - break; - default: - ret = -EINVAL; - goto err_fmt; - } - - regmap_update_bits(i2s->regmap, I2S_CKR, mask, val); - - mask = I2S_TXCR_IBM_MASK; - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_RIGHT_J: - val = I2S_TXCR_IBM_RSJM; - break; - case SND_SOC_DAIFMT_LEFT_J: - val = I2S_TXCR_IBM_LSJM; - break; - case SND_SOC_DAIFMT_I2S: - val = I2S_TXCR_IBM_NORMAL; - break; - default: - ret = -EINVAL; - goto err_fmt; - } - - regmap_update_bits(i2s->regmap, I2S_TXCR, mask, val); - - mask = I2S_RXCR_IBM_MASK; - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_RIGHT_J: - val = I2S_RXCR_IBM_RSJM; - break; - case SND_SOC_DAIFMT_LEFT_J: - val = I2S_RXCR_IBM_LSJM; - break; - case SND_SOC_DAIFMT_I2S: - val = I2S_RXCR_IBM_NORMAL; - break; - default: - ret = -EINVAL; - goto err_fmt; - } - - regmap_update_bits(i2s->regmap, I2S_RXCR, mask, val); - -err_fmt: - - spin_unlock_irqrestore(&lock, flags); - return ret; -} - -static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - struct rk_i2s_dev *i2s = to_info(dai); - unsigned int val = 0; - unsigned long flags; - - spin_lock_irqsave(&lock, flags); - - dev_dbg(i2s->dev, "%s: %d\n", __func__, __LINE__); - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S8: - val |= I2S_TXCR_VDW(8); - break; - case SNDRV_PCM_FORMAT_S16_LE: - val |= I2S_TXCR_VDW(16); - break; - case SNDRV_PCM_FORMAT_S20_3LE: - val |= I2S_TXCR_VDW(20); - break; - case SNDRV_PCM_FORMAT_S24_LE: - case SNDRV_PCM_FORMAT_S24_3LE: - val |= I2S_TXCR_VDW(24); - break; - case SNDRV_PCM_FORMAT_S32_LE: - val |= I2S_TXCR_VDW(32); - break; - default: - dev_err(i2s->dev, "invalid fmt: %d\n", params_format(params)); - spin_unlock_irqrestore(&lock, flags); - return -EINVAL; - } - - switch (params_channels(params)) { - case I2S_CHANNEL_8: - val |= I2S_TXCR_CHN_8; - break; - case I2S_CHANNEL_6: - val |= I2S_TXCR_CHN_6; - break; - case I2S_CHANNEL_4: - val |= I2S_TXCR_CHN_4; - break; - case I2S_CHANNEL_2: - val |= I2S_TXCR_CHN_2; - break; - default: - dev_err(i2s->dev, "invalid channel: %d\n", - params_channels(params)); - spin_unlock_irqrestore(&lock, flags); - return -EINVAL; - } - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - regmap_update_bits(i2s->regmap, I2S_TXCR, - I2S_TXCR_VDW_MASK | - I2S_TXCR_CSR_MASK, - val); - } else { - regmap_update_bits(i2s->regmap, I2S_RXCR, - I2S_RXCR_VDW_MASK, val); - } - - regmap_update_bits(i2s->regmap, I2S_DMACR, - I2S_DMACR_TDL_MASK | I2S_DMACR_RDL_MASK, - I2S_DMACR_TDL(16) | I2S_DMACR_RDL(16)); - -#if defined(CONFIG_RK_HDMI) && defined(CONFIG_SND_RK_SOC_HDMI_I2S) - snd_config_hdmi_audio(params); -#endif - spin_unlock_irqrestore(&lock, flags); - - return 0; -} - -static int rockchip_i2s_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - struct rk_i2s_dev *i2s = to_info(dai); - int ret = 0; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) - rockchip_snd_rxctrl(i2s, 1); - else - rockchip_snd_txctrl(i2s, 1); - break; - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) - rockchip_snd_rxctrl(i2s, 0); - else - rockchip_snd_txctrl(i2s, 0); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static int rockchip_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, - int clk_id, unsigned int freq, int dir) -{ - struct rk_i2s_dev *i2s = to_info(cpu_dai); - int ret; - - ret = clk_set_rate(i2s->clk, freq); - if (ret) - dev_err(i2s->dev, "fail set clk: freq: %d\n", freq); - - return ret; -} - -static int rockchip_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, - int div_id, int div) -{ - struct rk_i2s_dev *i2s = to_info(cpu_dai); - unsigned int val = 0; - unsigned long flags; - - spin_lock_irqsave(&lock, flags); - - dev_dbg(i2s->dev, "%s: div_id=%d, div=%d\n", __func__, div_id, div); - - switch (div_id) { - case ROCKCHIP_DIV_BCLK: - val |= I2S_CKR_TSD(div); - val |= I2S_CKR_RSD(div); - regmap_update_bits(i2s->regmap, I2S_CKR, - I2S_CKR_TSD_MASK | I2S_CKR_RSD_MASK, - val); - break; - case ROCKCHIP_DIV_MCLK: - val |= I2S_CKR_MDIV(div); - regmap_update_bits(i2s->regmap, I2S_CKR, - I2S_CKR_MDIV_MASK, val); - break; - default: - spin_unlock_irqrestore(&lock, flags); - return -EINVAL; - } - - spin_unlock_irqrestore(&lock, flags); - - return 0; -} - -static int rockchip_i2s_dai_probe(struct snd_soc_dai *dai) -{ - struct rk_i2s_dev *i2s = to_info(dai); - - dai->capture_dma_data = &i2s->capture_dma_data; - dai->playback_dma_data = &i2s->playback_dma_data; - - return 0; -} - -static struct snd_soc_dai_ops rockchip_i2s_dai_ops = { - .trigger = rockchip_i2s_trigger, - .hw_params = rockchip_i2s_hw_params, - .set_fmt = rockchip_i2s_set_fmt, - .set_clkdiv = rockchip_i2s_set_clkdiv, - .set_sysclk = rockchip_i2s_set_sysclk, -}; - -#define ROCKCHIP_I2S_RATES SNDRV_PCM_RATE_8000_192000 -#define ROCKCHIP_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S20_3LE | \ - SNDRV_PCM_FMTBIT_S24_LE | \ - SNDRV_PCM_FORMAT_S32_LE) - -struct snd_soc_dai_driver rockchip_i2s_dai[] = { - { - .probe = rockchip_i2s_dai_probe, - .name = "rockchip-i2s.0", - .id = 0, - .playback = { - .channels_min = 2, - .channels_max = 8, - .rates = ROCKCHIP_I2S_RATES, - .formats = ROCKCHIP_I2S_FORMATS, - }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = ROCKCHIP_I2S_RATES, - .formats = ROCKCHIP_I2S_FORMATS, - }, - .ops = &rockchip_i2s_dai_ops, - .symmetric_rates = 1, - }, - { - .probe = rockchip_i2s_dai_probe, - .name = "rockchip-i2s.1", - .id = 1, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = ROCKCHIP_I2S_RATES, - .formats = ROCKCHIP_I2S_FORMATS, - }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = ROCKCHIP_I2S_RATES, - .formats = ROCKCHIP_I2S_FORMATS, - }, - .ops = &rockchip_i2s_dai_ops, - .symmetric_rates = 1, - }, -}; - -static const struct snd_soc_component_driver rockchip_i2s_component = { - .name = "rockchip-i2s", -}; - -#ifdef CONFIG_PM -static int i2s_runtime_suspend(struct device *dev) -{ - struct rk_i2s_dev *i2s = dev_get_drvdata(dev); - - dev_dbg(i2s->dev, "%s\n", __func__); - return pinctrl_pm_select_sleep_state(dev); -} - -static int i2s_runtime_resume(struct device *dev) -{ - struct rk_i2s_dev *i2s = dev_get_drvdata(dev); - - dev_dbg(i2s->dev, "%s\n", __func__); - return pinctrl_pm_select_default_state(dev); -} -#else -#define i2s_runtime_suspend NULL -#define i2s_runtime_resume NULL -#endif - -#ifdef CLK_SET_LATER -static void set_clk_later_work(struct work_struct *work) -{ - struct rk_i2s_dev *i2s = container_of(work, struct rk_i2s_dev, - clk_delayed_work.work); - - clk_set_rate(i2s->clk, I2S_DEFAULT_FREQ); - if (!IS_ERR(i2s->mclk)) - clk_set_rate(i2s->mclk, I2S_DEFAULT_FREQ); -} -#endif - -static bool rockchip_i2s_wr_reg(struct device *dev, unsigned int reg) -{ - switch (reg) { - case I2S_TXCR: - case I2S_RXCR: - case I2S_CKR: - case I2S_DMACR: - case I2S_INTCR: - case I2S_XFER: - case I2S_CLR: - case I2S_TXDR: - return true; - default: - return false; - } -} - -static bool rockchip_i2s_rd_reg(struct device *dev, unsigned int reg) -{ - switch (reg) { - case I2S_TXCR: - case I2S_RXCR: - case I2S_CKR: - case I2S_DMACR: - case I2S_INTCR: - case I2S_XFER: - case I2S_CLR: - case I2S_RXDR: - case I2S_FIFOLR: - case I2S_INTSR: - return true; - default: - return false; - } -} - -static bool rockchip_i2s_volatile_reg(struct device *dev, unsigned int reg) -{ - switch (reg) { - case I2S_INTSR: - case I2S_CLR: - return true; - default: - return false; - } -} - -static bool rockchip_i2s_precious_reg(struct device *dev, unsigned int reg) -{ - switch (reg) { - default: - return false; - } -} - -static const struct regmap_config rockchip_i2s_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = I2S_RXDR, - .writeable_reg = rockchip_i2s_wr_reg, - .readable_reg = rockchip_i2s_rd_reg, - .volatile_reg = rockchip_i2s_volatile_reg, - .precious_reg = rockchip_i2s_precious_reg, - .cache_type = REGCACHE_FLAT, -}; - -static int rockchip_i2s_probe(struct platform_device *pdev) -{ - struct device_node *node = pdev->dev.of_node; - struct rk_i2s_dev *i2s; - struct resource *res; - void __iomem *regs; - int ret; - - ret = of_property_read_u32(node, "i2s-id", &pdev->id); - if (ret < 0) { - dev_err(&pdev->dev, "Property 'i2s-id' missing or invalid\n"); - ret = -EINVAL; - goto err; - } - - if (soc_is_rk3126b()) { - int sdi_src = 0; - - /* rk3126b has no i2s1 controller(i2s_8ch) */ - if (1 == pdev->id) { - pr_info("rk3126b has no i2s1 controller\n"); - ret = -ENODEV; - goto err; - } - - ret = of_property_read_u32(node, "sdi_source", - &sdi_src); - if (ret < 0) - sdi_src = 0; - - if (1 == sdi_src) { - int val; - - /*GRF_SOC_CON*/ - val = readl_relaxed(RK_GRF_VIRT + 0x0140); - val = val | 0x04000400; - writel_relaxed(val, RK_GRF_VIRT + 0x0140); - } - } - - i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); - if (!i2s) { - dev_err(&pdev->dev, "Can't allocate rk_i2s_dev\n"); - ret = -ENOMEM; - goto err; - } - - i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk"); - if (IS_ERR(i2s->hclk)) { - dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); - ret = PTR_ERR(i2s->hclk); - goto err; - } else { - clk_prepare_enable(i2s->hclk); - } - - i2s->clk = devm_clk_get(&pdev->dev, "i2s_clk"); - if (IS_ERR(i2s->clk)) { - dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); - ret = PTR_ERR(i2s->clk); - goto err; - } -#ifdef CLK_SET_LATER - INIT_DELAYED_WORK(&i2s->clk_delayed_work, set_clk_later_work); - schedule_delayed_work(&i2s->clk_delayed_work, msecs_to_jiffies(10)); -#else - clk_set_rate(i2s->clk, I2S_DEFAULT_FREQ); -#endif - clk_prepare_enable(i2s->clk); - - i2s->mclk = devm_clk_get(&pdev->dev, "i2s_mclk"); - if (IS_ERR(i2s->mclk)) { - dev_info(&pdev->dev, "i2s%d has no mclk\n", pdev->id); - } else { - #ifndef CLK_SET_LATER - clk_set_rate(i2s->mclk, I2S_DEFAULT_FREQ); - #endif - clk_prepare_enable(i2s->mclk); - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(regs)) { - ret = PTR_ERR(regs); - goto err; - } - - i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, - &rockchip_i2s_regmap_config); - if (IS_ERR(i2s->regmap)) { - dev_err(&pdev->dev, - "Failed to initialise managed register map\n"); - ret = PTR_ERR(i2s->regmap); - goto err; - } - - i2s->playback_dma_data.addr = res->start + I2S_TXDR; - i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - i2s->playback_dma_data.maxburst = I2S_DMA_BURST_SIZE; - - i2s->capture_dma_data.addr = res->start + I2S_RXDR; - i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - i2s->capture_dma_data.maxburst = I2S_DMA_BURST_SIZE; - - i2s->tx_start = false; - i2s->rx_start = false; - - i2s->dev = &pdev->dev; - dev_set_drvdata(&pdev->dev, i2s); - - pm_runtime_enable(&pdev->dev); - if (!pm_runtime_enabled(&pdev->dev)) { - ret = i2s_runtime_resume(&pdev->dev); - if (ret) - goto err_pm_disable; - } - - ret = snd_soc_register_component(&pdev->dev, &rockchip_i2s_component, - &rockchip_i2s_dai[pdev->id], 1); - - if (ret) { - dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); - ret = -ENOMEM; - goto err_suspend; - } - - ret = rockchip_pcm_platform_register(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); - goto err_unregister_component; - } - - rockchip_snd_txctrl(i2s, 0); - rockchip_snd_rxctrl(i2s, 0); - - return 0; - -err_unregister_component: - snd_soc_unregister_component(&pdev->dev); -err_suspend: - if (!pm_runtime_status_suspended(&pdev->dev)) - i2s_runtime_suspend(&pdev->dev); -err_pm_disable: - pm_runtime_disable(&pdev->dev); -err: - return ret; -} - -static int rockchip_i2s_remove(struct platform_device *pdev) -{ - struct rk_i2s_dev *i2s = dev_get_drvdata(&pdev->dev); - - pm_runtime_disable(&pdev->dev); - if (!pm_runtime_status_suspended(&pdev->dev)) - i2s_runtime_suspend(&pdev->dev); - - if (!IS_ERR(i2s->mclk)) - clk_disable_unprepare(i2s->mclk); - - clk_disable_unprepare(i2s->clk); - clk_disable_unprepare(i2s->hclk); - rockchip_pcm_platform_unregister(&pdev->dev); - snd_soc_unregister_component(&pdev->dev); - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id rockchip_i2s_match[] = { - { .compatible = "rockchip-i2s", }, - {}, -}; -MODULE_DEVICE_TABLE(of, rockchip_i2s_match); -#endif - -static const struct dev_pm_ops rockchip_i2s_pm_ops = { - SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume, - NULL) -}; - -static struct platform_driver rockchip_i2s_driver = { - .probe = rockchip_i2s_probe, - .remove = rockchip_i2s_remove, - .driver = { - .name = "rockchip-i2s", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(rockchip_i2s_match), - .pm = &rockchip_i2s_pm_ops, - }, -}; - -static int __init rockchip_i2s_init(void) -{ - return platform_driver_register(&rockchip_i2s_driver); -} -subsys_initcall_sync(rockchip_i2s_init); - -static void __exit rockchip_i2s_exit(void) -{ - platform_driver_unregister(&rockchip_i2s_driver); -} -module_exit(rockchip_i2s_exit); - -MODULE_AUTHOR("Sugar "); -MODULE_DESCRIPTION("Rockchip I2S Controller Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/rockchip/rk_i2s.c b/sound/soc/rockchip/rk_i2s.c new file mode 100644 index 000000000000..d551860c4acb --- /dev/null +++ b/sound/soc/rockchip/rk_i2s.c @@ -0,0 +1,785 @@ +/* + * Rockchip I2S ALSA SoC Digital Audio Interface(DAI) driver + * + * Copyright (C) 2015 Fuzhou Rockchip Electronics Co., Ltd + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rk_pcm.h" +#include "rk_i2s.h" + +#define CLK_SET_LATER +#define I2S_DEFAULT_FREQ (11289600) +#define I2S_DMA_BURST_SIZE (16) /* size * width: 16*4 = 64 bytes */ +static DEFINE_SPINLOCK(lock); + +#if defined(CONFIG_RK_HDMI) && defined(CONFIG_SND_RK_SOC_HDMI_I2S) +extern int snd_config_hdmi_audio(struct snd_pcm_hw_params *params); +#endif + +struct rk_i2s_dev { + struct device *dev; + struct clk *clk; /* bclk */ + struct clk *mclk; /*mclk output only */ + struct clk *hclk; /*ahb clk */ + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; + bool tx_start; + bool rx_start; +#ifdef CLK_SET_LATER + struct delayed_work clk_delayed_work; +#endif +}; + +static inline struct rk_i2s_dev *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +static void rockchip_snd_txctrl(struct rk_i2s_dev *i2s, int on) +{ + unsigned long flags; + unsigned int val = 0; + int retry = 10; + + spin_lock_irqsave(&lock, flags); + + dev_dbg(i2s->dev, "%s: %d: on: %d\n", __func__, __LINE__, on); + + if (on) { + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_TDE_MASK, I2S_DMACR_TDE_ENABLE); + + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK, + I2S_XFER_TXS_START | I2S_XFER_RXS_START); + + i2s->tx_start = true; + } else { + i2s->tx_start = false; + + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_TDE_MASK, I2S_DMACR_TDE_DISABLE); + + + if (!i2s->rx_start) { + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_MASK | + I2S_XFER_RXS_MASK, + I2S_XFER_TXS_STOP | + I2S_XFER_RXS_STOP); + + regmap_update_bits(i2s->regmap, I2S_CLR, + I2S_CLR_TXC_MASK | I2S_CLR_RXC_MASK, + I2S_CLR_TXC | I2S_CLR_RXC); + + regmap_read(i2s->regmap, I2S_CLR, &val); + + /* Should wait for clear operation to finish */ + while (val) { + regmap_read(i2s->regmap, I2S_CLR, &val); + retry--; + if (!retry) { + dev_warn(i2s->dev, "fail to clear\n"); + break; + } + } + dev_dbg(i2s->dev, "%s: %d: stop xfer\n", + __func__, __LINE__); + } + } + + spin_unlock_irqrestore(&lock, flags); +} + +static void rockchip_snd_rxctrl(struct rk_i2s_dev *i2s, int on) +{ + unsigned long flags; + unsigned int val = 0; + int retry = 10; + + spin_lock_irqsave(&lock, flags); + + dev_dbg(i2s->dev, "%s: %d: on: %d\n", __func__, __LINE__, on); + + if (on) { + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_RDE_MASK, I2S_DMACR_RDE_ENABLE); + + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK, + I2S_XFER_TXS_START | I2S_XFER_RXS_START); + + i2s->rx_start = true; + } else { + i2s->rx_start = false; + + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_RDE_MASK, I2S_DMACR_RDE_DISABLE); + + if (!i2s->tx_start) { + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_MASK | + I2S_XFER_RXS_MASK, + I2S_XFER_TXS_STOP | + I2S_XFER_RXS_STOP); + + regmap_update_bits(i2s->regmap, I2S_CLR, + I2S_CLR_TXC_MASK | I2S_CLR_RXC_MASK, + I2S_CLR_TXC | I2S_CLR_RXC); + + regmap_read(i2s->regmap, I2S_CLR, &val); + + /* Should wait for clear operation to finish */ + while (val) { + regmap_read(i2s->regmap, I2S_CLR, &val); + retry--; + if (!retry) { + dev_warn(i2s->dev, "fail to clear\n"); + break; + } + } + dev_dbg(i2s->dev, "%s: %d: stop xfer\n", + __func__, __LINE__); + } + } + + spin_unlock_irqrestore(&lock, flags); +} + +static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct rk_i2s_dev *i2s = to_info(cpu_dai); + unsigned int mask = 0, val = 0; + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&lock, flags); + + mask = I2S_CKR_MSS_MASK; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Codec is slave, so set cpu master */ + val = I2S_CKR_MSS_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Codec is master, so set cpu slave */ + val = I2S_CKR_MSS_SLAVE; + break; + default: + ret = -EINVAL; + goto err_fmt; + } + + regmap_update_bits(i2s->regmap, I2S_CKR, mask, val); + + mask = I2S_TXCR_IBM_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S_TXCR_IBM_RSJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S_TXCR_IBM_LSJM; + break; + case SND_SOC_DAIFMT_I2S: + val = I2S_TXCR_IBM_NORMAL; + break; + default: + ret = -EINVAL; + goto err_fmt; + } + + regmap_update_bits(i2s->regmap, I2S_TXCR, mask, val); + + mask = I2S_RXCR_IBM_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S_RXCR_IBM_RSJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S_RXCR_IBM_LSJM; + break; + case SND_SOC_DAIFMT_I2S: + val = I2S_RXCR_IBM_NORMAL; + break; + default: + ret = -EINVAL; + goto err_fmt; + } + + regmap_update_bits(i2s->regmap, I2S_RXCR, mask, val); + +err_fmt: + + spin_unlock_irqrestore(&lock, flags); + return ret; +} + +static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rk_i2s_dev *i2s = to_info(dai); + unsigned int val = 0; + unsigned long flags; + + spin_lock_irqsave(&lock, flags); + + dev_dbg(i2s->dev, "%s: %d\n", __func__, __LINE__); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + val |= I2S_TXCR_VDW(8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + val |= I2S_TXCR_VDW(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val |= I2S_TXCR_VDW(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_3LE: + val |= I2S_TXCR_VDW(24); + break; + case SNDRV_PCM_FORMAT_S32_LE: + val |= I2S_TXCR_VDW(32); + break; + default: + dev_err(i2s->dev, "invalid fmt: %d\n", params_format(params)); + spin_unlock_irqrestore(&lock, flags); + return -EINVAL; + } + + switch (params_channels(params)) { + case I2S_CHANNEL_8: + val |= I2S_TXCR_CHN_8; + break; + case I2S_CHANNEL_6: + val |= I2S_TXCR_CHN_6; + break; + case I2S_CHANNEL_4: + val |= I2S_TXCR_CHN_4; + break; + case I2S_CHANNEL_2: + val |= I2S_TXCR_CHN_2; + break; + default: + dev_err(i2s->dev, "invalid channel: %d\n", + params_channels(params)); + spin_unlock_irqrestore(&lock, flags); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(i2s->regmap, I2S_TXCR, + I2S_TXCR_VDW_MASK | + I2S_TXCR_CSR_MASK, + val); + } else { + regmap_update_bits(i2s->regmap, I2S_RXCR, + I2S_RXCR_VDW_MASK, val); + } + + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_TDL_MASK | I2S_DMACR_RDL_MASK, + I2S_DMACR_TDL(16) | I2S_DMACR_RDL(16)); + +#if defined(CONFIG_RK_HDMI) && defined(CONFIG_SND_RK_SOC_HDMI_I2S) + snd_config_hdmi_audio(params); +#endif + spin_unlock_irqrestore(&lock, flags); + + return 0; +} + +static int rockchip_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct rk_i2s_dev *i2s = to_info(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s, 1); + else + rockchip_snd_txctrl(i2s, 1); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s, 0); + else + rockchip_snd_txctrl(i2s, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rockchip_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct rk_i2s_dev *i2s = to_info(cpu_dai); + int ret; + + ret = clk_set_rate(i2s->clk, freq); + if (ret) + dev_err(i2s->dev, "fail set clk: freq: %d\n", freq); + + return ret; +} + +static int rockchip_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct rk_i2s_dev *i2s = to_info(cpu_dai); + unsigned int val = 0; + unsigned long flags; + + spin_lock_irqsave(&lock, flags); + + dev_dbg(i2s->dev, "%s: div_id=%d, div=%d\n", __func__, div_id, div); + + switch (div_id) { + case ROCKCHIP_DIV_BCLK: + val |= I2S_CKR_TSD(div); + val |= I2S_CKR_RSD(div); + regmap_update_bits(i2s->regmap, I2S_CKR, + I2S_CKR_TSD_MASK | I2S_CKR_RSD_MASK, + val); + break; + case ROCKCHIP_DIV_MCLK: + val |= I2S_CKR_MDIV(div); + regmap_update_bits(i2s->regmap, I2S_CKR, + I2S_CKR_MDIV_MASK, val); + break; + default: + spin_unlock_irqrestore(&lock, flags); + return -EINVAL; + } + + spin_unlock_irqrestore(&lock, flags); + + return 0; +} + +static int rockchip_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct rk_i2s_dev *i2s = to_info(dai); + + dai->capture_dma_data = &i2s->capture_dma_data; + dai->playback_dma_data = &i2s->playback_dma_data; + + return 0; +} + +static struct snd_soc_dai_ops rockchip_i2s_dai_ops = { + .trigger = rockchip_i2s_trigger, + .hw_params = rockchip_i2s_hw_params, + .set_fmt = rockchip_i2s_set_fmt, + .set_clkdiv = rockchip_i2s_set_clkdiv, + .set_sysclk = rockchip_i2s_set_sysclk, +}; + +#define ROCKCHIP_I2S_RATES SNDRV_PCM_RATE_8000_192000 +#define ROCKCHIP_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FORMAT_S32_LE) + +struct snd_soc_dai_driver rockchip_i2s_dai[] = { + { + .probe = rockchip_i2s_dai_probe, + .name = "rockchip-i2s.0", + .id = 0, + .playback = { + .channels_min = 2, + .channels_max = 8, + .rates = ROCKCHIP_I2S_RATES, + .formats = ROCKCHIP_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = ROCKCHIP_I2S_RATES, + .formats = ROCKCHIP_I2S_FORMATS, + }, + .ops = &rockchip_i2s_dai_ops, + .symmetric_rates = 1, + }, + { + .probe = rockchip_i2s_dai_probe, + .name = "rockchip-i2s.1", + .id = 1, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = ROCKCHIP_I2S_RATES, + .formats = ROCKCHIP_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = ROCKCHIP_I2S_RATES, + .formats = ROCKCHIP_I2S_FORMATS, + }, + .ops = &rockchip_i2s_dai_ops, + .symmetric_rates = 1, + }, +}; + +static const struct snd_soc_component_driver rockchip_i2s_component = { + .name = "rockchip-i2s", +}; + +#ifdef CONFIG_PM +static int i2s_runtime_suspend(struct device *dev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(dev); + + dev_dbg(i2s->dev, "%s\n", __func__); + return pinctrl_pm_select_sleep_state(dev); +} + +static int i2s_runtime_resume(struct device *dev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(dev); + + dev_dbg(i2s->dev, "%s\n", __func__); + return pinctrl_pm_select_default_state(dev); +} +#else +#define i2s_runtime_suspend NULL +#define i2s_runtime_resume NULL +#endif + +#ifdef CLK_SET_LATER +static void set_clk_later_work(struct work_struct *work) +{ + struct rk_i2s_dev *i2s = container_of(work, struct rk_i2s_dev, + clk_delayed_work.work); + + clk_set_rate(i2s->clk, I2S_DEFAULT_FREQ); + if (!IS_ERR(i2s->mclk)) + clk_set_rate(i2s->mclk, I2S_DEFAULT_FREQ); +} +#endif + +static bool rockchip_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_TXDR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_RXDR: + case I2S_FIFOLR: + case I2S_INTSR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_INTSR: + case I2S_CLR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + default: + return false; + } +} + +static const struct regmap_config rockchip_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = I2S_RXDR, + .writeable_reg = rockchip_i2s_wr_reg, + .readable_reg = rockchip_i2s_rd_reg, + .volatile_reg = rockchip_i2s_volatile_reg, + .precious_reg = rockchip_i2s_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int rockchip_i2s_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct rk_i2s_dev *i2s; + struct resource *res; + void __iomem *regs; + int ret; + + ret = of_property_read_u32(node, "i2s-id", &pdev->id); + if (ret < 0) { + dev_err(&pdev->dev, "Property 'i2s-id' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + if (soc_is_rk3126b()) { + int sdi_src = 0; + + /* rk3126b has no i2s1 controller(i2s_8ch) */ + if (1 == pdev->id) { + pr_info("rk3126b has no i2s1 controller\n"); + ret = -ENODEV; + goto err; + } + + ret = of_property_read_u32(node, "sdi_source", + &sdi_src); + if (ret < 0) + sdi_src = 0; + + if (1 == sdi_src) { + int val; + + /*GRF_SOC_CON*/ + val = readl_relaxed(RK_GRF_VIRT + 0x0140); + val = val | 0x04000400; + writel_relaxed(val, RK_GRF_VIRT + 0x0140); + } + } + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) { + dev_err(&pdev->dev, "Can't allocate rk_i2s_dev\n"); + ret = -ENOMEM; + goto err; + } + + i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk"); + if (IS_ERR(i2s->hclk)) { + dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); + ret = PTR_ERR(i2s->hclk); + goto err; + } else { + clk_prepare_enable(i2s->hclk); + } + + i2s->clk = devm_clk_get(&pdev->dev, "i2s_clk"); + if (IS_ERR(i2s->clk)) { + dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); + ret = PTR_ERR(i2s->clk); + goto err; + } +#ifdef CLK_SET_LATER + INIT_DELAYED_WORK(&i2s->clk_delayed_work, set_clk_later_work); + schedule_delayed_work(&i2s->clk_delayed_work, msecs_to_jiffies(10)); +#else + clk_set_rate(i2s->clk, I2S_DEFAULT_FREQ); +#endif + clk_prepare_enable(i2s->clk); + + i2s->mclk = devm_clk_get(&pdev->dev, "i2s_mclk"); + if (IS_ERR(i2s->mclk)) { + dev_info(&pdev->dev, "i2s%d has no mclk\n", pdev->id); + } else { + #ifndef CLK_SET_LATER + clk_set_rate(i2s->mclk, I2S_DEFAULT_FREQ); + #endif + clk_prepare_enable(i2s->mclk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &rockchip_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, + "Failed to initialise managed register map\n"); + ret = PTR_ERR(i2s->regmap); + goto err; + } + + i2s->playback_dma_data.addr = res->start + I2S_TXDR; + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->playback_dma_data.maxburst = I2S_DMA_BURST_SIZE; + + i2s->capture_dma_data.addr = res->start + I2S_RXDR; + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->capture_dma_data.maxburst = I2S_DMA_BURST_SIZE; + + i2s->tx_start = false; + i2s->rx_start = false; + + i2s->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, i2s); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_component(&pdev->dev, &rockchip_i2s_component, + &rockchip_i2s_dai[pdev->id], 1); + + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_suspend; + } + + ret = rockchip_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_component; + } + + rockchip_snd_txctrl(i2s, 0); + rockchip_snd_rxctrl(i2s, 0); + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err: + return ret; +} + +static int rockchip_i2s_remove(struct platform_device *pdev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_runtime_suspend(&pdev->dev); + + if (!IS_ERR(i2s->mclk)) + clk_disable_unprepare(i2s->mclk); + + clk_disable_unprepare(i2s->clk); + clk_disable_unprepare(i2s->hclk); + rockchip_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id rockchip_i2s_match[] = { + { .compatible = "rockchip-i2s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rockchip_i2s_match); +#endif + +static const struct dev_pm_ops rockchip_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume, + NULL) +}; + +static struct platform_driver rockchip_i2s_driver = { + .probe = rockchip_i2s_probe, + .remove = rockchip_i2s_remove, + .driver = { + .name = "rockchip-i2s", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rockchip_i2s_match), + .pm = &rockchip_i2s_pm_ops, + }, +}; + +static int __init rockchip_i2s_init(void) +{ + return platform_driver_register(&rockchip_i2s_driver); +} +subsys_initcall_sync(rockchip_i2s_init); + +static void __exit rockchip_i2s_exit(void) +{ + platform_driver_unregister(&rockchip_i2s_driver); +} +module_exit(rockchip_i2s_exit); + +MODULE_AUTHOR("Sugar "); +MODULE_DESCRIPTION("Rockchip I2S Controller Driver"); +MODULE_LICENSE("GPL v2");