|
@@ -10,9 +10,11 @@
|
|
|
* published by the Free Software Foundation.
|
|
|
*/
|
|
|
|
|
|
+#include <dt-bindings/sound/samsung-i2s.h>
|
|
|
#include <linux/delay.h>
|
|
|
#include <linux/slab.h>
|
|
|
#include <linux/clk.h>
|
|
|
+#include <linux/clk-provider.h>
|
|
|
#include <linux/io.h>
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/of.h>
|
|
@@ -59,10 +61,8 @@ struct samsung_i2s_dai_data {
|
|
|
struct i2s_dai {
|
|
|
/* Platform device for this DAI */
|
|
|
struct platform_device *pdev;
|
|
|
- /* IOREMAP'd SFRs */
|
|
|
+ /* Memory mapped SFR region */
|
|
|
void __iomem *addr;
|
|
|
- /* Physical base address of SFRs */
|
|
|
- u32 base;
|
|
|
/* Rate of RCLK source clock */
|
|
|
unsigned long rclk_srcrate;
|
|
|
/* Frame Clock */
|
|
@@ -83,8 +83,6 @@ struct i2s_dai {
|
|
|
#define DAI_OPENED (1 << 0) /* Dai is opened */
|
|
|
#define DAI_MANAGER (1 << 1) /* Dai is the manager */
|
|
|
unsigned mode;
|
|
|
- /* CDCLK pin direction: 0 - input, 1 - output */
|
|
|
- unsigned int cdclk_out:1;
|
|
|
/* Driver for this DAI */
|
|
|
struct snd_soc_dai_driver i2s_dai_drv;
|
|
|
/* DMA parameters */
|
|
@@ -95,8 +93,15 @@ struct i2s_dai {
|
|
|
u32 suspend_i2smod;
|
|
|
u32 suspend_i2scon;
|
|
|
u32 suspend_i2spsr;
|
|
|
- unsigned long gpios[7]; /* i2s gpio line numbers */
|
|
|
const struct samsung_i2s_variant_regs *variant_regs;
|
|
|
+
|
|
|
+ /* Spinlock protecting access to the device's registers */
|
|
|
+ spinlock_t spinlock;
|
|
|
+ spinlock_t *lock;
|
|
|
+
|
|
|
+ /* Below fields are only valid if this is the primary FIFO */
|
|
|
+ struct clk *clk_table[3];
|
|
|
+ struct clk_onecell_data clk_data;
|
|
|
};
|
|
|
|
|
|
/* Lock for cross i/f checks */
|
|
@@ -133,10 +138,16 @@ static inline bool tx_active(struct i2s_dai *i2s)
|
|
|
return active ? true : false;
|
|
|
}
|
|
|
|
|
|
+/* Return pointer to the other DAI */
|
|
|
+static inline struct i2s_dai *get_other_dai(struct i2s_dai *i2s)
|
|
|
+{
|
|
|
+ return i2s->pri_dai ? : i2s->sec_dai;
|
|
|
+}
|
|
|
+
|
|
|
/* If the other interface of the controller is transmitting data */
|
|
|
static inline bool other_tx_active(struct i2s_dai *i2s)
|
|
|
{
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
+ struct i2s_dai *other = get_other_dai(i2s);
|
|
|
|
|
|
return tx_active(other);
|
|
|
}
|
|
@@ -163,7 +174,7 @@ static inline bool rx_active(struct i2s_dai *i2s)
|
|
|
/* If the other interface of the controller is receiving data */
|
|
|
static inline bool other_rx_active(struct i2s_dai *i2s)
|
|
|
{
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
+ struct i2s_dai *other = get_other_dai(i2s);
|
|
|
|
|
|
return rx_active(other);
|
|
|
}
|
|
@@ -464,18 +475,23 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai,
|
|
|
int clk_id, unsigned int rfs, int dir)
|
|
|
{
|
|
|
struct i2s_dai *i2s = to_info(dai);
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
- u32 mod = readl(i2s->addr + I2SMOD);
|
|
|
+ struct i2s_dai *other = get_other_dai(i2s);
|
|
|
const struct samsung_i2s_variant_regs *i2s_regs = i2s->variant_regs;
|
|
|
unsigned int cdcon_mask = 1 << i2s_regs->cdclkcon_off;
|
|
|
unsigned int rsrc_mask = 1 << i2s_regs->rclksrc_off;
|
|
|
+ u32 mod, mask, val = 0;
|
|
|
+
|
|
|
+ spin_lock(i2s->lock);
|
|
|
+ mod = readl(i2s->addr + I2SMOD);
|
|
|
+ spin_unlock(i2s->lock);
|
|
|
|
|
|
switch (clk_id) {
|
|
|
case SAMSUNG_I2S_OPCLK:
|
|
|
- mod &= ~MOD_OPCLK_MASK;
|
|
|
- mod |= dir;
|
|
|
+ mask = MOD_OPCLK_MASK;
|
|
|
+ val = dir;
|
|
|
break;
|
|
|
case SAMSUNG_I2S_CDCLK:
|
|
|
+ mask = 1 << i2s_regs->cdclkcon_off;
|
|
|
/* Shouldn't matter in GATING(CLOCK_IN) mode */
|
|
|
if (dir == SND_SOC_CLOCK_IN)
|
|
|
rfs = 0;
|
|
@@ -492,15 +508,15 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai,
|
|
|
}
|
|
|
|
|
|
if (dir == SND_SOC_CLOCK_IN)
|
|
|
- mod |= 1 << i2s_regs->cdclkcon_off;
|
|
|
- else
|
|
|
- mod &= ~(1 << i2s_regs->cdclkcon_off);
|
|
|
+ val = 1 << i2s_regs->cdclkcon_off;
|
|
|
|
|
|
i2s->rfs = rfs;
|
|
|
break;
|
|
|
|
|
|
case SAMSUNG_I2S_RCLKSRC_0: /* clock corrsponding to IISMOD[10] := 0 */
|
|
|
case SAMSUNG_I2S_RCLKSRC_1: /* clock corrsponding to IISMOD[10] := 1 */
|
|
|
+ mask = 1 << i2s_regs->rclksrc_off;
|
|
|
+
|
|
|
if ((i2s->quirks & QUIRK_NO_MUXPSR)
|
|
|
|| (clk_id == SAMSUNG_I2S_RCLKSRC_0))
|
|
|
clk_id = 0;
|
|
@@ -550,18 +566,19 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- if (clk_id == 0)
|
|
|
- mod &= ~(1 << i2s_regs->rclksrc_off);
|
|
|
- else
|
|
|
- mod |= 1 << i2s_regs->rclksrc_off;
|
|
|
-
|
|
|
+ if (clk_id == 1)
|
|
|
+ val = 1 << i2s_regs->rclksrc_off;
|
|
|
break;
|
|
|
default:
|
|
|
dev_err(&i2s->pdev->dev, "We don't serve that!\n");
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
+ spin_lock(i2s->lock);
|
|
|
+ mod = readl(i2s->addr + I2SMOD);
|
|
|
+ mod = (mod & ~mask) | val;
|
|
|
writel(mod, i2s->addr + I2SMOD);
|
|
|
+ spin_unlock(i2s->lock);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -570,9 +587,8 @@ static int i2s_set_fmt(struct snd_soc_dai *dai,
|
|
|
unsigned int fmt)
|
|
|
{
|
|
|
struct i2s_dai *i2s = to_info(dai);
|
|
|
- u32 mod = readl(i2s->addr + I2SMOD);
|
|
|
int lrp_shift, sdf_shift, sdf_mask, lrp_rlow, mod_slave;
|
|
|
- u32 tmp = 0;
|
|
|
+ u32 mod, tmp = 0;
|
|
|
|
|
|
lrp_shift = i2s->variant_regs->lrp_off;
|
|
|
sdf_shift = i2s->variant_regs->sdf_off;
|
|
@@ -632,12 +648,15 @@ static int i2s_set_fmt(struct snd_soc_dai *dai,
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
+ spin_lock(i2s->lock);
|
|
|
+ mod = readl(i2s->addr + I2SMOD);
|
|
|
/*
|
|
|
* Don't change the I2S mode if any controller is active on this
|
|
|
* channel.
|
|
|
*/
|
|
|
if (any_active(i2s) &&
|
|
|
((mod & (sdf_mask | lrp_rlow | mod_slave)) != tmp)) {
|
|
|
+ spin_unlock(i2s->lock);
|
|
|
dev_err(&i2s->pdev->dev,
|
|
|
"%s:%d Other DAI busy\n", __func__, __LINE__);
|
|
|
return -EAGAIN;
|
|
@@ -646,6 +665,7 @@ static int i2s_set_fmt(struct snd_soc_dai *dai,
|
|
|
mod &= ~(sdf_mask | lrp_rlow | mod_slave);
|
|
|
mod |= tmp;
|
|
|
writel(mod, i2s->addr + I2SMOD);
|
|
|
+ spin_unlock(i2s->lock);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -654,16 +674,16 @@ static int i2s_hw_params(struct snd_pcm_substream *substream,
|
|
|
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
|
|
|
{
|
|
|
struct i2s_dai *i2s = to_info(dai);
|
|
|
- u32 mod = readl(i2s->addr + I2SMOD);
|
|
|
+ u32 mod, mask = 0, val = 0;
|
|
|
|
|
|
if (!is_secondary(i2s))
|
|
|
- mod &= ~(MOD_DC2_EN | MOD_DC1_EN);
|
|
|
+ mask |= (MOD_DC2_EN | MOD_DC1_EN);
|
|
|
|
|
|
switch (params_channels(params)) {
|
|
|
case 6:
|
|
|
- mod |= MOD_DC2_EN;
|
|
|
+ val |= MOD_DC2_EN;
|
|
|
case 4:
|
|
|
- mod |= MOD_DC1_EN;
|
|
|
+ val |= MOD_DC1_EN;
|
|
|
break;
|
|
|
case 2:
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
@@ -685,44 +705,49 @@ static int i2s_hw_params(struct snd_pcm_substream *substream,
|
|
|
}
|
|
|
|
|
|
if (is_secondary(i2s))
|
|
|
- mod &= ~MOD_BLCS_MASK;
|
|
|
+ mask |= MOD_BLCS_MASK;
|
|
|
else
|
|
|
- mod &= ~MOD_BLCP_MASK;
|
|
|
+ mask |= MOD_BLCP_MASK;
|
|
|
|
|
|
if (is_manager(i2s))
|
|
|
- mod &= ~MOD_BLC_MASK;
|
|
|
+ mask |= MOD_BLC_MASK;
|
|
|
|
|
|
switch (params_width(params)) {
|
|
|
case 8:
|
|
|
if (is_secondary(i2s))
|
|
|
- mod |= MOD_BLCS_8BIT;
|
|
|
+ val |= MOD_BLCS_8BIT;
|
|
|
else
|
|
|
- mod |= MOD_BLCP_8BIT;
|
|
|
+ val |= MOD_BLCP_8BIT;
|
|
|
if (is_manager(i2s))
|
|
|
- mod |= MOD_BLC_8BIT;
|
|
|
+ val |= MOD_BLC_8BIT;
|
|
|
break;
|
|
|
case 16:
|
|
|
if (is_secondary(i2s))
|
|
|
- mod |= MOD_BLCS_16BIT;
|
|
|
+ val |= MOD_BLCS_16BIT;
|
|
|
else
|
|
|
- mod |= MOD_BLCP_16BIT;
|
|
|
+ val |= MOD_BLCP_16BIT;
|
|
|
if (is_manager(i2s))
|
|
|
- mod |= MOD_BLC_16BIT;
|
|
|
+ val |= MOD_BLC_16BIT;
|
|
|
break;
|
|
|
case 24:
|
|
|
if (is_secondary(i2s))
|
|
|
- mod |= MOD_BLCS_24BIT;
|
|
|
+ val |= MOD_BLCS_24BIT;
|
|
|
else
|
|
|
- mod |= MOD_BLCP_24BIT;
|
|
|
+ val |= MOD_BLCP_24BIT;
|
|
|
if (is_manager(i2s))
|
|
|
- mod |= MOD_BLC_24BIT;
|
|
|
+ val |= MOD_BLC_24BIT;
|
|
|
break;
|
|
|
default:
|
|
|
dev_err(&i2s->pdev->dev, "Format(%d) not supported\n",
|
|
|
params_format(params));
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
+
|
|
|
+ spin_lock(i2s->lock);
|
|
|
+ mod = readl(i2s->addr + I2SMOD);
|
|
|
+ mod = (mod & ~mask) | val;
|
|
|
writel(mod, i2s->addr + I2SMOD);
|
|
|
+ spin_unlock(i2s->lock);
|
|
|
|
|
|
samsung_asoc_init_dma_data(dai, &i2s->dma_playback, &i2s->dma_capture);
|
|
|
|
|
@@ -736,7 +761,7 @@ static int i2s_startup(struct snd_pcm_substream *substream,
|
|
|
struct snd_soc_dai *dai)
|
|
|
{
|
|
|
struct i2s_dai *i2s = to_info(dai);
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
+ struct i2s_dai *other = get_other_dai(i2s);
|
|
|
unsigned long flags;
|
|
|
|
|
|
spin_lock_irqsave(&lock, flags);
|
|
@@ -753,9 +778,6 @@ static int i2s_startup(struct snd_pcm_substream *substream,
|
|
|
|
|
|
spin_unlock_irqrestore(&lock, flags);
|
|
|
|
|
|
- if (!is_opened(other) && i2s->cdclk_out)
|
|
|
- i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK,
|
|
|
- 0, SND_SOC_CLOCK_OUT);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -763,38 +785,27 @@ static void i2s_shutdown(struct snd_pcm_substream *substream,
|
|
|
struct snd_soc_dai *dai)
|
|
|
{
|
|
|
struct i2s_dai *i2s = to_info(dai);
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
+ struct i2s_dai *other = get_other_dai(i2s);
|
|
|
unsigned long flags;
|
|
|
- const struct samsung_i2s_variant_regs *i2s_regs = i2s->variant_regs;
|
|
|
|
|
|
spin_lock_irqsave(&lock, flags);
|
|
|
|
|
|
i2s->mode &= ~DAI_OPENED;
|
|
|
i2s->mode &= ~DAI_MANAGER;
|
|
|
|
|
|
- if (is_opened(other)) {
|
|
|
+ if (is_opened(other))
|
|
|
other->mode |= DAI_MANAGER;
|
|
|
- } else {
|
|
|
- u32 mod = readl(i2s->addr + I2SMOD);
|
|
|
- i2s->cdclk_out = !(mod & (1 << i2s_regs->cdclkcon_off));
|
|
|
- if (other)
|
|
|
- other->cdclk_out = i2s->cdclk_out;
|
|
|
- }
|
|
|
+
|
|
|
/* Reset any constraint on RFS and BFS */
|
|
|
i2s->rfs = 0;
|
|
|
i2s->bfs = 0;
|
|
|
|
|
|
spin_unlock_irqrestore(&lock, flags);
|
|
|
-
|
|
|
- /* Gate CDCLK by default */
|
|
|
- if (!is_opened(other))
|
|
|
- i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK,
|
|
|
- 0, SND_SOC_CLOCK_IN);
|
|
|
}
|
|
|
|
|
|
static int config_setup(struct i2s_dai *i2s)
|
|
|
{
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
+ struct i2s_dai *other = get_other_dai(i2s);
|
|
|
unsigned rfs, bfs, blc;
|
|
|
u32 psr;
|
|
|
|
|
@@ -864,10 +875,10 @@ static int i2s_trigger(struct snd_pcm_substream *substream,
|
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
|
- local_irq_save(flags);
|
|
|
+ spin_lock_irqsave(i2s->lock, flags);
|
|
|
|
|
|
if (config_setup(i2s)) {
|
|
|
- local_irq_restore(flags);
|
|
|
+ spin_unlock_irqrestore(i2s->lock, flags);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -876,12 +887,12 @@ static int i2s_trigger(struct snd_pcm_substream *substream,
|
|
|
else
|
|
|
i2s_txctrl(i2s, 1);
|
|
|
|
|
|
- local_irq_restore(flags);
|
|
|
+ spin_unlock_irqrestore(i2s->lock, flags);
|
|
|
break;
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
|
- local_irq_save(flags);
|
|
|
+ spin_lock_irqsave(i2s->lock, flags);
|
|
|
|
|
|
if (capture) {
|
|
|
i2s_rxctrl(i2s, 0);
|
|
@@ -891,7 +902,7 @@ static int i2s_trigger(struct snd_pcm_substream *substream,
|
|
|
i2s_fifo(i2s, FIC_TXFLUSH);
|
|
|
}
|
|
|
|
|
|
- local_irq_restore(flags);
|
|
|
+ spin_unlock_irqrestore(i2s->lock, flags);
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -902,7 +913,7 @@ static int i2s_set_clkdiv(struct snd_soc_dai *dai,
|
|
|
int div_id, int div)
|
|
|
{
|
|
|
struct i2s_dai *i2s = to_info(dai);
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
+ struct i2s_dai *other = get_other_dai(i2s);
|
|
|
|
|
|
switch (div_id) {
|
|
|
case SAMSUNG_I2S_DIV_BCLK:
|
|
@@ -971,58 +982,36 @@ static int i2s_resume(struct snd_soc_dai *dai)
|
|
|
static int samsung_i2s_dai_probe(struct snd_soc_dai *dai)
|
|
|
{
|
|
|
struct i2s_dai *i2s = to_info(dai);
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
- int ret;
|
|
|
+ struct i2s_dai *other = get_other_dai(i2s);
|
|
|
+ unsigned long flags;
|
|
|
|
|
|
- if (other && other->clk) { /* If this is probe on secondary */
|
|
|
+ if (is_secondary(i2s)) { /* If this is probe on the secondary DAI */
|
|
|
samsung_asoc_init_dma_data(dai, &other->sec_dai->dma_playback,
|
|
|
NULL);
|
|
|
- goto probe_exit;
|
|
|
- }
|
|
|
-
|
|
|
- i2s->addr = ioremap(i2s->base, 0x100);
|
|
|
- if (i2s->addr == NULL) {
|
|
|
- dev_err(&i2s->pdev->dev, "cannot ioremap registers\n");
|
|
|
- return -ENXIO;
|
|
|
- }
|
|
|
-
|
|
|
- i2s->clk = clk_get(&i2s->pdev->dev, "iis");
|
|
|
- if (IS_ERR(i2s->clk)) {
|
|
|
- dev_err(&i2s->pdev->dev, "failed to get i2s_clock\n");
|
|
|
- iounmap(i2s->addr);
|
|
|
- return PTR_ERR(i2s->clk);
|
|
|
- }
|
|
|
-
|
|
|
- ret = clk_prepare_enable(i2s->clk);
|
|
|
- if (ret != 0) {
|
|
|
- dev_err(&i2s->pdev->dev, "failed to enable clock: %d\n", ret);
|
|
|
- return ret;
|
|
|
- }
|
|
|
-
|
|
|
- samsung_asoc_init_dma_data(dai, &i2s->dma_playback, &i2s->dma_capture);
|
|
|
-
|
|
|
- if (other) {
|
|
|
- other->addr = i2s->addr;
|
|
|
- other->clk = i2s->clk;
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ samsung_asoc_init_dma_data(dai, &i2s->dma_playback,
|
|
|
+ &i2s->dma_capture);
|
|
|
|
|
|
- if (i2s->quirks & QUIRK_NEED_RSTCLR)
|
|
|
- writel(CON_RSTCLR, i2s->addr + I2SCON);
|
|
|
+ if (i2s->quirks & QUIRK_NEED_RSTCLR)
|
|
|
+ writel(CON_RSTCLR, i2s->addr + I2SCON);
|
|
|
|
|
|
- if (i2s->quirks & QUIRK_SUPPORTS_IDMA)
|
|
|
- idma_reg_addr_init(i2s->addr,
|
|
|
+ if (i2s->quirks & QUIRK_SUPPORTS_IDMA)
|
|
|
+ idma_reg_addr_init(i2s->addr,
|
|
|
i2s->sec_dai->idma_playback.dma_addr);
|
|
|
+ }
|
|
|
|
|
|
-probe_exit:
|
|
|
/* Reset any constraint on RFS and BFS */
|
|
|
i2s->rfs = 0;
|
|
|
i2s->bfs = 0;
|
|
|
i2s->rclk_srcrate = 0;
|
|
|
+
|
|
|
+ spin_lock_irqsave(i2s->lock, flags);
|
|
|
i2s_txctrl(i2s, 0);
|
|
|
i2s_rxctrl(i2s, 0);
|
|
|
i2s_fifo(i2s, FIC_TXFLUSH);
|
|
|
i2s_fifo(other, FIC_TXFLUSH);
|
|
|
i2s_fifo(i2s, FIC_RXFLUSH);
|
|
|
+ spin_unlock_irqrestore(i2s->lock, flags);
|
|
|
|
|
|
/* Gate CDCLK by default */
|
|
|
if (!is_opened(other))
|
|
@@ -1035,21 +1024,15 @@ probe_exit:
|
|
|
static int samsung_i2s_dai_remove(struct snd_soc_dai *dai)
|
|
|
{
|
|
|
struct i2s_dai *i2s = snd_soc_dai_get_drvdata(dai);
|
|
|
- struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
-
|
|
|
- if (!other || !other->clk) {
|
|
|
|
|
|
- if (i2s->quirks & QUIRK_NEED_RSTCLR)
|
|
|
+ if (!is_secondary(i2s)) {
|
|
|
+ if (i2s->quirks & QUIRK_NEED_RSTCLR) {
|
|
|
+ spin_lock(i2s->lock);
|
|
|
writel(0, i2s->addr + I2SCON);
|
|
|
-
|
|
|
- clk_disable_unprepare(i2s->clk);
|
|
|
- clk_put(i2s->clk);
|
|
|
-
|
|
|
- iounmap(i2s->addr);
|
|
|
+ spin_unlock(i2s->lock);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- i2s->clk = NULL;
|
|
|
-
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1124,15 +1107,14 @@ static const struct of_device_id exynos_i2s_match[];
|
|
|
static inline const struct samsung_i2s_dai_data *samsung_i2s_get_driver_data(
|
|
|
struct platform_device *pdev)
|
|
|
{
|
|
|
-#ifdef CONFIG_OF
|
|
|
- if (pdev->dev.of_node) {
|
|
|
+ if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) {
|
|
|
const struct of_device_id *match;
|
|
|
match = of_match_node(exynos_i2s_match, pdev->dev.of_node);
|
|
|
- return match->data;
|
|
|
- } else
|
|
|
-#endif
|
|
|
+ return match ? match->data : NULL;
|
|
|
+ } else {
|
|
|
return (struct samsung_i2s_dai_data *)
|
|
|
platform_get_device_id(pdev)->driver_data;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
@@ -1155,6 +1137,87 @@ static int i2s_runtime_resume(struct device *dev)
|
|
|
}
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
|
+static void i2s_unregister_clocks(struct i2s_dai *i2s)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < i2s->clk_data.clk_num; i++) {
|
|
|
+ if (!IS_ERR(i2s->clk_table[i]))
|
|
|
+ clk_unregister(i2s->clk_table[i]);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void i2s_unregister_clock_provider(struct platform_device *pdev)
|
|
|
+{
|
|
|
+ struct i2s_dai *i2s = dev_get_drvdata(&pdev->dev);
|
|
|
+
|
|
|
+ of_clk_del_provider(pdev->dev.of_node);
|
|
|
+ i2s_unregister_clocks(i2s);
|
|
|
+}
|
|
|
+
|
|
|
+static int i2s_register_clock_provider(struct platform_device *pdev)
|
|
|
+{
|
|
|
+ struct device *dev = &pdev->dev;
|
|
|
+ struct i2s_dai *i2s = dev_get_drvdata(dev);
|
|
|
+ const char *clk_name[2] = { "i2s_opclk0", "i2s_opclk1" };
|
|
|
+ const char *p_names[2] = { NULL };
|
|
|
+ const struct samsung_i2s_variant_regs *reg_info = i2s->variant_regs;
|
|
|
+ struct clk *rclksrc;
|
|
|
+ int ret, i;
|
|
|
+
|
|
|
+ /* Register the clock provider only if it's expected in the DTB */
|
|
|
+ if (!of_find_property(dev->of_node, "#clock-cells", NULL))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /* Get the RCLKSRC mux clock parent clock names */
|
|
|
+ for (i = 0; i < ARRAY_SIZE(p_names); i++) {
|
|
|
+ rclksrc = clk_get(dev, clk_name[i]);
|
|
|
+ if (IS_ERR(rclksrc))
|
|
|
+ continue;
|
|
|
+ p_names[i] = __clk_get_name(rclksrc);
|
|
|
+ clk_put(rclksrc);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!(i2s->quirks & QUIRK_NO_MUXPSR)) {
|
|
|
+ /* Activate the prescaler */
|
|
|
+ u32 val = readl(i2s->addr + I2SPSR);
|
|
|
+ writel(val | PSR_PSREN, i2s->addr + I2SPSR);
|
|
|
+
|
|
|
+ i2s->clk_table[CLK_I2S_RCLK_SRC] = clk_register_mux(NULL,
|
|
|
+ "i2s_rclksrc", p_names, ARRAY_SIZE(p_names),
|
|
|
+ CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
|
|
|
+ i2s->addr + I2SMOD, reg_info->rclksrc_off,
|
|
|
+ 1, 0, i2s->lock);
|
|
|
+
|
|
|
+ i2s->clk_table[CLK_I2S_RCLK_PSR] = clk_register_divider(NULL,
|
|
|
+ "i2s_presc", "i2s_rclksrc",
|
|
|
+ CLK_SET_RATE_PARENT,
|
|
|
+ i2s->addr + I2SPSR, 8, 6, 0, i2s->lock);
|
|
|
+
|
|
|
+ p_names[0] = "i2s_presc";
|
|
|
+ i2s->clk_data.clk_num = 2;
|
|
|
+ }
|
|
|
+ of_property_read_string_index(dev->of_node,
|
|
|
+ "clock-output-names", 0, &clk_name[0]);
|
|
|
+
|
|
|
+ i2s->clk_table[CLK_I2S_CDCLK] = clk_register_gate(NULL, clk_name[0],
|
|
|
+ p_names[0], CLK_SET_RATE_PARENT,
|
|
|
+ i2s->addr + I2SMOD, reg_info->cdclkcon_off,
|
|
|
+ CLK_GATE_SET_TO_DISABLE, i2s->lock);
|
|
|
+
|
|
|
+ i2s->clk_data.clk_num += 1;
|
|
|
+ i2s->clk_data.clks = i2s->clk_table;
|
|
|
+
|
|
|
+ ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get,
|
|
|
+ &i2s->clk_data);
|
|
|
+ if (ret < 0) {
|
|
|
+ dev_err(dev, "failed to add clock provider: %d\n", ret);
|
|
|
+ i2s_unregister_clocks(i2s);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
{
|
|
|
struct i2s_dai *pri_dai, *sec_dai = NULL;
|
|
@@ -1164,7 +1227,7 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
u32 regs_base, quirks = 0, idma_addr = 0;
|
|
|
struct device_node *np = pdev->dev.of_node;
|
|
|
const struct samsung_i2s_dai_data *i2s_dai_data;
|
|
|
- int ret = 0;
|
|
|
+ int ret;
|
|
|
|
|
|
/* Call during Seconday interface registration */
|
|
|
i2s_dai_data = samsung_i2s_get_driver_data(pdev);
|
|
@@ -1175,11 +1238,13 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
dev_err(&pdev->dev, "Unable to get drvdata\n");
|
|
|
return -EFAULT;
|
|
|
}
|
|
|
- devm_snd_soc_register_component(&sec_dai->pdev->dev,
|
|
|
+ ret = devm_snd_soc_register_component(&sec_dai->pdev->dev,
|
|
|
&samsung_i2s_component,
|
|
|
&sec_dai->i2s_dai_drv, 1);
|
|
|
- samsung_asoc_dma_platform_register(&pdev->dev);
|
|
|
- return 0;
|
|
|
+ if (ret != 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ return samsung_asoc_dma_platform_register(&pdev->dev);
|
|
|
}
|
|
|
|
|
|
pri_dai = i2s_alloc_dai(pdev, false);
|
|
@@ -1188,6 +1253,9 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
return -ENOMEM;
|
|
|
}
|
|
|
|
|
|
+ spin_lock_init(&pri_dai->spinlock);
|
|
|
+ pri_dai->lock = &pri_dai->spinlock;
|
|
|
+
|
|
|
if (!np) {
|
|
|
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
|
|
if (!res) {
|
|
@@ -1229,25 +1297,29 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
}
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
- if (!res) {
|
|
|
- dev_err(&pdev->dev, "Unable to get I2S SFR address\n");
|
|
|
- return -ENXIO;
|
|
|
- }
|
|
|
+ pri_dai->addr = devm_ioremap_resource(&pdev->dev, res);
|
|
|
+ if (IS_ERR(pri_dai->addr))
|
|
|
+ return PTR_ERR(pri_dai->addr);
|
|
|
|
|
|
- if (!request_mem_region(res->start, resource_size(res),
|
|
|
- "samsung-i2s")) {
|
|
|
- dev_err(&pdev->dev, "Unable to request SFR region\n");
|
|
|
- return -EBUSY;
|
|
|
- }
|
|
|
regs_base = res->start;
|
|
|
|
|
|
+ pri_dai->clk = devm_clk_get(&pdev->dev, "iis");
|
|
|
+ if (IS_ERR(pri_dai->clk)) {
|
|
|
+ dev_err(&pdev->dev, "Failed to get iis clock\n");
|
|
|
+ return PTR_ERR(pri_dai->clk);
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = clk_prepare_enable(pri_dai->clk);
|
|
|
+ if (ret != 0) {
|
|
|
+ dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
pri_dai->dma_playback.dma_addr = regs_base + I2STXD;
|
|
|
pri_dai->dma_capture.dma_addr = regs_base + I2SRXD;
|
|
|
pri_dai->dma_playback.ch_name = "tx";
|
|
|
pri_dai->dma_capture.ch_name = "rx";
|
|
|
pri_dai->dma_playback.dma_size = 4;
|
|
|
pri_dai->dma_capture.dma_size = 4;
|
|
|
- pri_dai->base = regs_base;
|
|
|
pri_dai->quirks = quirks;
|
|
|
pri_dai->variant_regs = i2s_dai_data->i2s_variant_regs;
|
|
|
|
|
@@ -1258,10 +1330,10 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
sec_dai = i2s_alloc_dai(pdev, true);
|
|
|
if (!sec_dai) {
|
|
|
dev_err(&pdev->dev, "Unable to alloc I2S_sec\n");
|
|
|
- ret = -ENOMEM;
|
|
|
- goto err;
|
|
|
+ return -ENOMEM;
|
|
|
}
|
|
|
|
|
|
+ sec_dai->lock = &pri_dai->spinlock;
|
|
|
sec_dai->variant_regs = pri_dai->variant_regs;
|
|
|
sec_dai->dma_playback.dma_addr = regs_base + I2STXDS;
|
|
|
sec_dai->dma_playback.ch_name = "tx-sec";
|
|
@@ -1273,7 +1345,8 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
}
|
|
|
|
|
|
sec_dai->dma_playback.dma_size = 4;
|
|
|
- sec_dai->base = regs_base;
|
|
|
+ sec_dai->addr = pri_dai->addr;
|
|
|
+ sec_dai->clk = pri_dai->clk;
|
|
|
sec_dai->quirks = quirks;
|
|
|
sec_dai->idma_playback.dma_addr = idma_addr;
|
|
|
sec_dai->pri_dai = pri_dai;
|
|
@@ -1282,8 +1355,7 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
|
|
|
if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) {
|
|
|
dev_err(&pdev->dev, "Unable to configure gpio\n");
|
|
|
- ret = -EINVAL;
|
|
|
- goto err;
|
|
|
+ return -EINVAL;
|
|
|
}
|
|
|
|
|
|
devm_snd_soc_register_component(&pri_dai->pdev->dev,
|
|
@@ -1292,32 +1364,30 @@ static int samsung_i2s_probe(struct platform_device *pdev)
|
|
|
|
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
|
|
- samsung_asoc_dma_platform_register(&pdev->dev);
|
|
|
-
|
|
|
- return 0;
|
|
|
-err:
|
|
|
- if (res)
|
|
|
- release_mem_region(regs_base, resource_size(res));
|
|
|
+ ret = samsung_asoc_dma_platform_register(&pdev->dev);
|
|
|
+ if (ret != 0)
|
|
|
+ return ret;
|
|
|
|
|
|
- return ret;
|
|
|
+ return i2s_register_clock_provider(pdev);
|
|
|
}
|
|
|
|
|
|
static int samsung_i2s_remove(struct platform_device *pdev)
|
|
|
{
|
|
|
struct i2s_dai *i2s, *other;
|
|
|
- struct resource *res;
|
|
|
|
|
|
i2s = dev_get_drvdata(&pdev->dev);
|
|
|
- other = i2s->pri_dai ? : i2s->sec_dai;
|
|
|
+ other = get_other_dai(i2s);
|
|
|
|
|
|
if (other) {
|
|
|
other->pri_dai = NULL;
|
|
|
other->sec_dai = NULL;
|
|
|
} else {
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
- if (res)
|
|
|
- release_mem_region(res->start, resource_size(res));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!is_secondary(i2s)) {
|
|
|
+ i2s_unregister_clock_provider(pdev);
|
|
|
+ clk_disable_unprepare(i2s->clk);
|
|
|
}
|
|
|
|
|
|
i2s->pri_dai = NULL;
|