[alsa-devel] [PATCH 6/6] ASoC: add Renesas R-Car SSI feature

Kuninori Morimoto kuninori.morimoto.gx at renesas.com
Mon Jul 1 08:54:37 CEST 2013


Renesas R-Car series sound circuit consists of SSI and its peripheral.
But this peripheral circuit is different between
R-Car Generation1 (E1/M1/H1) and Generation2 (E2/M2/H2)
(Actually, there are many difference in Generation1 chips)

As 1st protype, this patch adds SSI feature on this driver.
But, it is PIO sound playback/capture support only at this point.
The DMA transfer will be supported in the future

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx at renesas.com>
---
 include/sound/rcar_snd.h   |   23 +-
 sound/soc/sh/rcar/Makefile |    2 +-
 sound/soc/sh/rcar/core.c   |    5 +
 sound/soc/sh/rcar/gen.c    |   24 +-
 sound/soc/sh/rcar/rsnd.h   |   22 ++
 sound/soc/sh/rcar/ssi.c    |  581 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 653 insertions(+), 4 deletions(-)
 create mode 100644 sound/soc/sh/rcar/ssi.c

diff --git a/include/sound/rcar_snd.h b/include/sound/rcar_snd.h
index 6babd6f..99d8dd0 100644
--- a/include/sound/rcar_snd.h
+++ b/include/sound/rcar_snd.h
@@ -16,11 +16,30 @@
 
 #define RSND_GEN1_SRU	0
 #define RSND_GEN1_ADG	1
+#define RSND_GEN1_SSI	2
 
 #define RSND_GEN2_SRU	0
 #define RSND_GEN2_ADG	1
+#define RSND_GEN2_SSIU	2
+#define RSND_GEN2_SSI	3
 
-#define RSND_BASE_MAX	2
+#define RSND_BASE_MAX	4
+
+/*
+ * flags
+ *
+ * 0xA0000000
+ *
+ * A : clock sharing settings
+ */
+#define RSND_SSI_CLK_PIN_SHARE		(1 << 31)
+#define RSND_SSI_CLK_FROM_ADG		(1 << 30) /* clock parent is master */
+#define RSND_SSI_SYNC			(1 << 29) /* SSI34_sync etc */
+
+struct rsnd_ssi_platform_info {
+	int pio_irq;
+	u32 flags;
+};
 
 struct rsnd_scu_platform_info {
 	u32 flags;
@@ -43,6 +62,8 @@ struct rsnd_dai_platform_info {
 
 struct rcar_snd_info {
 	u32 flags;
+	struct rsnd_ssi_platform_info *ssi_info;
+	int ssi_info_nr;
 	struct rsnd_scu_platform_info *scu_info;
 	int scu_info_nr;
 	struct rsnd_dai_platform_info *dai_info;
diff --git a/sound/soc/sh/rcar/Makefile b/sound/soc/sh/rcar/Makefile
index c11280c..0ff492d 100644
--- a/sound/soc/sh/rcar/Makefile
+++ b/sound/soc/sh/rcar/Makefile
@@ -1,2 +1,2 @@
-snd-soc-rcar-objs	:= core.o gen.o scu.o adg.o
+snd-soc-rcar-objs	:= core.o gen.o scu.o adg.o ssi.o
 obj-$(CONFIG_SND_SOC_RCAR)	+= snd-soc-rcar.o
\ No newline at end of file
diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c
index 4ca7b72..ad5e4c8 100644
--- a/sound/soc/sh/rcar/core.c
+++ b/sound/soc/sh/rcar/core.c
@@ -633,6 +633,10 @@ static int rsnd_probe(struct platform_device *pdev)
 	if (ret < 0)
 		return ret;
 
+	ret = rsnd_ssi_probe(pdev, info, priv);
+	if (ret < 0)
+		return ret;
+
 	/*
 	 *	asoc register
 	 */
@@ -671,6 +675,7 @@ static int rsnd_remove(struct platform_device *pdev)
 	/*
 	 *	remove each module
 	 */
+	rsnd_ssi_remove(pdev, priv);
 	rsnd_adg_remove(pdev, priv);
 	rsnd_scu_remove(pdev, priv);
 	rsnd_dai_remove(pdev, priv);
diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c
index f111d0e..a21c716 100644
--- a/sound/soc/sh/rcar/gen.c
+++ b/sound/soc/sh/rcar/gen.c
@@ -92,6 +92,12 @@ static int rsnd_gen1_path_init(struct rsnd_priv *priv,
 	/* SCU */
 	mod = rsnd_scu_mod_get(priv, id);
 	ret = rsnd_dai_connect(rdai, mod, io);
+	if (ret < 0)
+		return ret;
+
+	/* SSI */
+	mod = rsnd_ssi_mod_get(priv, id);
+	ret = rsnd_dai_connect(rdai, mod, io);
 
 	return ret;
 }
@@ -137,6 +143,12 @@ static void rsnd_gen1_reg_map_init(struct rsnd_gen *gen)
 	RSND_GEN1_REG_MAP(gen, ADG,	AUDIO_CLK_SEL3,	0x0,	0x18);
 	RSND_GEN1_REG_MAP(gen, ADG,	AUDIO_CLK_SEL4,	0x0,	0x1c);
 	RSND_GEN1_REG_MAP(gen, ADG,	AUDIO_CLK_SEL5,	0x0,	0x20);
+
+	RSND_GEN1_REG_MAP(gen, SSI,	SSICR,		0x40,	0x00);
+	RSND_GEN1_REG_MAP(gen, SSI,	SSISR,		0x40,	0x04);
+	RSND_GEN1_REG_MAP(gen, SSI,	SSITDR,		0x40,	0x08);
+	RSND_GEN1_REG_MAP(gen, SSI,	SSIRDR,		0x40,	0x0c);
+	RSND_GEN1_REG_MAP(gen, SSI,	SSIWSR,		0x40,	0x20);
 }
 
 static int rsnd_gen1_probe(struct platform_device *pdev,
@@ -147,14 +159,17 @@ static int rsnd_gen1_probe(struct platform_device *pdev,
 	struct rsnd_gen *gen = rsnd_priv_to_gen(priv);
 	struct resource *sru_res;
 	struct resource *adg_res;
+	struct resource *ssi_res;
 
 	/*
 	 * map address
 	 */
 	sru_res	= platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN1_SRU);
 	adg_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN1_ADG);
+	ssi_res	= platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN1_SSI);
 	if (!sru_res ||
-	    !adg_res) {
+	    !adg_res ||
+	    !ssi_res) {
 		dev_err(dev, "Not enough SRU/SSI/ADG platform resources.\n");
 		return -ENODEV;
 	}
@@ -163,8 +178,10 @@ static int rsnd_gen1_probe(struct platform_device *pdev,
 
 	gen->base[RSND_GEN1_SRU] = devm_ioremap_resource(dev, sru_res);
 	gen->base[RSND_GEN1_ADG] = devm_ioremap_resource(dev, adg_res);
+	gen->base[RSND_GEN1_SSI] = devm_ioremap_resource(dev, ssi_res);
 	if (!gen->base[RSND_GEN1_SRU] ||
-	    !gen->base[RSND_GEN1_ADG]) {
+	    !gen->base[RSND_GEN1_ADG] ||
+	    !gen->base[RSND_GEN1_SSI]) {
 		dev_err(dev, "SRU/SSI/ADG ioremap failed\n");
 		return -ENODEV;
 	}
@@ -176,8 +193,11 @@ static int rsnd_gen1_probe(struct platform_device *pdev,
 						gen->base[RSND_GEN1_SRU]);
 	dev_dbg(dev, "ADG : %08x => %p\n",	adg_res->start,
 						gen->base[RSND_GEN1_ADG]);
+	dev_dbg(dev, "SSI : %08x => %p\n",	ssi_res->start,
+						gen->base[RSND_GEN1_SSI]);
 
 	return 0;
+
 }
 
 static void rsnd_gen1_remove(struct platform_device *pdev,
diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h
index e326a7a..f425424 100644
--- a/sound/soc/sh/rcar/rsnd.h
+++ b/sound/soc/sh/rcar/rsnd.h
@@ -43,6 +43,13 @@ enum rsnd_reg {
 	RSND_REG_AUDIO_CLK_SEL4,
 	RSND_REG_AUDIO_CLK_SEL5,
 
+	/* SSI */
+	RSND_REG_SSICR,
+	RSND_REG_SSISR,
+	RSND_REG_SSITDR,
+	RSND_REG_SSIRDR,
+	RSND_REG_SSIWSR,
+
 	RSND_REG_MAX,
 };
 
@@ -206,6 +213,11 @@ struct rsnd_priv {
 	void *adg;
 
 	/*
+	 * below value will be filled on rsnd_ssi_probe()
+	 */
+	void *ssiu;
+
+	/*
 	 * below value will be filled on rsnd_dai_probe()
 	 */
 	struct snd_soc_dai_driver *daidrv;
@@ -228,4 +240,14 @@ void rsnd_scu_remove(struct platform_device *pdev,
 struct rsnd_mod* rsnd_scu_mod_get(struct rsnd_priv *priv, int id);
 #define rsnd_scu_nr(priv) (priv)->scu_nr
 
+/*
+ *	R-Car SSI
+ */
+int rsnd_ssi_probe(struct platform_device *pdev,
+		   struct rcar_snd_info *info,
+		   struct rsnd_priv *priv);
+void rsnd_ssi_remove(struct platform_device *pdev,
+		   struct rsnd_priv *priv);
+struct rsnd_mod* rsnd_ssi_mod_get(struct rsnd_priv *priv, int id);
+
 #endif
diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c
new file mode 100644
index 0000000..f7984a2
--- /dev/null
+++ b/sound/soc/sh/rcar/ssi.c
@@ -0,0 +1,581 @@
+/*
+ * Renesas R-Car SSIU/SSI support
+ *
+ * Copyright (C) 2013 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx at renesas.com>
+ *
+ * Based on fsi.c
+ * Kuninori Morimoto <morimoto.kuninori at renesas.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/delay.h>
+#include "rsnd.h"
+#define RSND_SSI_NAME_SIZE 16
+
+/*
+ * SSICR
+ */
+#define	FORCE		(1 << 31)	/* Fixed */
+#define	UIEN		(1 << 27)	/* Underflow Interrupt Enable */
+#define	OIEN		(1 << 26)	/* Overflow Interrupt Enable */
+#define	IIEN		(1 << 25)	/* Idle Mode Interrupt Enable */
+#define	DIEN		(1 << 24)	/* Data Interrupt Enable */
+
+#define	DWL_8		(0 << 19)	/* Data Word Length */
+#define	DWL_16		(1 << 19)	/* Data Word Length */
+#define	DWL_18		(2 << 19)	/* Data Word Length */
+#define	DWL_20		(3 << 19)	/* Data Word Length */
+#define	DWL_22		(4 << 19)	/* Data Word Length */
+#define	DWL_24		(5 << 19)	/* Data Word Length */
+#define	DWL_32		(6 << 19)	/* Data Word Length */
+
+#define	SWL_32		(3 << 16)	/* R/W System Word Length */
+#define	SCKD		(1 << 15)	/* Serial Bit Clock Direction */
+#define	SWSD		(1 << 14)	/* Serial WS Direction */
+#define	SCKP		(1 << 13)	/* Serial Bit Clock Polarity */
+#define	SWSP		(1 << 12)	/* Serial WS Polarity */
+#define	SDTA		(1 << 10)	/* Serial Data Alignment */
+#define	DEL		(1 <<  8)	/* Serial Data Delay */
+#define	CKDV(v)		(v <<  4)	/* Serial Clock Division Ratio */
+#define	TRMD		(1 <<  1)	/* Transmit/Receive Mode Select */
+#define	EN		(1 <<  0)	/* SSI Module Enable */
+
+/*
+ * SSISR
+ */
+#define	UIRQ		(1 << 27)	/* Underflow Error Interrupt Status */
+#define	OIRQ		(1 << 26)	/* Overflow Error Interrupt Status */
+#define	IIRQ		(1 << 25)	/* Idle Mode Interrupt Status */
+#define	DIRQ		(1 << 24)	/* Data Interrupt Status Flag */
+
+struct rsnd_ssi {
+	struct clk *clk;
+	struct rsnd_ssi_platform_info *info; /* rcar_snd.h */
+	struct rsnd_ssi *parent;
+	struct rsnd_mod mod;
+
+	struct rsnd_dai *rdai;
+	struct rsnd_dai_stream *io;
+	u32 cr_own;
+	u32 cr_clk;
+	u32 cr_etc;
+	int err;
+	unsigned int usrcnt;
+	unsigned int rate;
+};
+
+struct rsnd_ssiu {
+	u32 ssi_mode0;
+	u32 ssi_mode1;
+
+	int ssi_nr;
+	struct rsnd_ssi *ssi;
+};
+
+#define for_each_rsnd_ssi(pos, priv, i)					\
+	for (i = 0, (pos) = ((struct rsnd_ssiu *)((priv)->ssiu))->ssi;	\
+	     i < rsnd_ssi_nr(priv);					\
+	     i++, (pos) = ((struct rsnd_ssiu *)((priv)->ssiu))->ssi + i)
+
+#define rsnd_ssi_nr(priv) ((struct rsnd_ssiu *)((priv)->ssiu))->ssi_nr
+#define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod)
+#define rsnd_ssi_is_pio(ssi) (ssi->info->pio_irq > 0)
+#define rsnd_ssi_clk_from_parent(ssi) (ssi->parent)
+#define rsnd_rdai_is_clk_master(rdai) (rdai->clk_master)
+#define rsnd_io_to_runtime(io) io->substream->runtime
+#define rsnd_ssi_mode_flags(p) p->info->flags
+#define rsnd_ssi_to_ssiu(ssi) (((struct rsnd_ssiu *)(ssi - rsnd_mod_id(&ssi->mod))) - 1)
+
+static void rsnd_ssi_mode_init(struct rsnd_priv *priv,
+			       struct rsnd_ssiu *ssiu)
+{
+	struct rsnd_ssi *ssi;
+	u32 flags;
+	u32 val;
+	int i;
+
+	/*
+	 * SSI_MODE0
+	 */
+	ssiu->ssi_mode0 = 0;
+	for_each_rsnd_ssi(ssi, priv, i)
+		ssiu->ssi_mode0 |= (1 << i);
+
+	/*
+	 * SSI_MODE1
+	 */
+#define ssi_parent_set(p, sync, adg, ext)	\
+	ssi->parent = ssiu->ssi + p;		\
+	if (flags & RSND_SSI_CLK_FROM_ADG)	\
+		val = adg;			\
+	else					\
+		val = ext;			\
+	if (flags & RSND_SSI_SYNC)		\
+		val |= sync
+
+	ssiu->ssi_mode1 = 0;
+	for_each_rsnd_ssi(ssi, priv, i) {
+		flags = rsnd_ssi_mode_flags(ssi);
+
+		if (!(flags & RSND_SSI_CLK_PIN_SHARE))
+			continue;
+
+		val = 0;
+		switch (i) {
+		case 1:
+			ssi_parent_set(0, (1 << 4), (0x2 << 0), (0x1 << 0));
+			break;
+		case 2:
+			ssi_parent_set(0, (1 << 4), (0x2 << 2), (0x1 << 2));
+			break;
+		case 4:
+			ssi_parent_set(3, (1 << 20), (0x2 << 16), (0x1 << 16));
+			break;
+		case 8:
+			ssi_parent_set(7, 0, 0, 0);
+			break;
+		}
+
+		ssiu->ssi_mode1 |= val;
+	}
+}
+
+static void rsnd_ssi_mode_set(struct rsnd_ssi *ssi)
+{
+	struct rsnd_ssiu *ssiu = rsnd_ssi_to_ssiu(ssi);
+
+	rsnd_mod_write(&ssi->mod, SSI_MODE0, ssiu->ssi_mode0);
+	rsnd_mod_write(&ssi->mod, SSI_MODE1, ssiu->ssi_mode1);
+}
+
+static void rsnd_ssi_status_check(struct rsnd_mod *mod,
+				  u32 bit)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	u32 status;
+	int i;
+
+	for (i = 0; i < 1024; i++) {
+		status = rsnd_mod_read(mod, SSISR);
+		if (status & bit)
+			return;
+
+		udelay(50);
+	}
+
+	dev_warn(dev, "status check failed\n");
+}
+
+static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi,
+				     unsigned int rate)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(&ssi->mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	int i, j, ret;
+	int adg_clk_div_table[] = {
+		1, 6, /* see adg.c */
+	};
+	int ssi_clk_mul_table[] = {
+		1, 2, 4, 8, 16, 6, 12,
+	};
+	unsigned int master_rate;
+
+	/*
+	 * Find best clock, and try to start ADG
+	 */
+	for (i = 0; i < ARRAY_SIZE(adg_clk_div_table); i++) {
+		for (j = 0; j < ARRAY_SIZE(ssi_clk_mul_table); j++) {
+
+			/*
+			 * this driver is assuming that
+			 * system word is 64fs (= 2 x 32bit)
+			 * see rsnd_ssi_start()
+			 */
+			master_rate = rate / adg_clk_div_table[i]
+				* 32 * 2 * ssi_clk_mul_table[j];
+
+			ret = rsnd_adg_ssi_clk_try_start(&ssi->mod, master_rate);
+			if (0 == ret) {
+				ssi->rate	= rate;
+				ssi->cr_clk	= FORCE | SWL_32 | SCKD | SWSD | CKDV(j);
+
+				dev_dbg(dev, "ssi%d outputs %u Hz\n", rsnd_mod_id(&ssi->mod), rate);
+
+				return 0;
+			}
+		}
+	}
+
+	dev_err(dev, "unsupported clock rate\n");
+	return -EIO;
+}
+
+static void rsnd_ssi_master_clk_stop(struct rsnd_ssi *ssi)
+{
+	ssi->rate = 0;
+	ssi->cr_clk = 0;
+	rsnd_adg_ssi_clk_stop(&ssi->mod);
+}
+
+static void rsnd_ssi_hw_start(struct rsnd_ssi *ssi,
+			      struct rsnd_dai *rdai,
+			      struct rsnd_dai_stream *io)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(&ssi->mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	u32 cr;
+
+	if (0 == ssi->usrcnt) {
+		clk_enable(ssi->clk);
+
+		if (rsnd_rdai_is_clk_master(rdai)) {
+			struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+
+			if (rsnd_ssi_clk_from_parent(ssi))
+				rsnd_ssi_hw_start(ssi->parent, rdai, io);
+			else
+				rsnd_ssi_master_clk_start(ssi, runtime->rate);
+		}
+	}
+
+	cr  =	ssi->cr_own	|
+		ssi->cr_clk	|
+		ssi->cr_etc	|
+		EN;
+
+	rsnd_mod_write(&ssi->mod, SSICR, cr);
+
+	ssi->usrcnt++;
+
+	dev_dbg(dev, "ssi%d hw started\n", rsnd_mod_id(&ssi->mod));
+}
+
+static void rsnd_ssi_hw_stop(struct rsnd_ssi *ssi,
+			     struct rsnd_dai *rdai)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(&ssi->mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	u32 cr;
+
+	if (0 == ssi->usrcnt) /* stop might be called without start */
+		return;
+
+	ssi->usrcnt--;
+
+	if (0 == ssi->usrcnt) {
+		/*
+		 * disable all IRQ,
+		 * and, wait all data was sent
+		 */
+		cr  =	ssi->cr_own	|
+			ssi->cr_clk;
+
+		rsnd_mod_write(&ssi->mod, SSICR, cr | EN);
+		rsnd_ssi_status_check(&ssi->mod, DIRQ);
+
+		/*
+		 * disable SSI,
+		 * and, wait idle state
+		 */
+		rsnd_mod_write(&ssi->mod, SSICR, cr);	/* disabled all */
+		rsnd_ssi_status_check(&ssi->mod, IIRQ);
+
+		if (rsnd_rdai_is_clk_master(rdai)) {
+			if (rsnd_ssi_clk_from_parent(ssi))
+				rsnd_ssi_hw_stop(ssi->parent, rdai);
+			else
+				rsnd_ssi_master_clk_stop(ssi);
+		}
+
+		clk_disable(ssi->clk);
+	}
+
+	dev_dbg(dev, "ssi%d hw stopped\n", rsnd_mod_id(&ssi->mod));
+}
+
+/*
+ *	SSI mod common functions
+ */
+static int rsnd_ssi_init(struct rsnd_mod *mod,
+			 struct rsnd_dai *rdai,
+			 struct rsnd_dai_stream *io)
+{
+	struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+	u32 cr;
+
+	cr = FORCE;
+
+	/*
+	 * always use 32bit system word for easy clock calculation.
+	 * see also rsnd_ssi_master_clk_enable()
+	 */
+	cr |= SWL_32;
+
+	/*
+	 * init clock settings for SSICR
+	 */
+	switch (runtime->sample_bits) {
+	case 16:
+		cr |= DWL_16;
+		break;
+	case 32:
+		cr |= DWL_24;
+		break;
+	default:
+		return -EIO;
+	}
+
+	if (rdai->bit_clk_inv)
+		cr |= SCKP;
+	if (rdai->frm_clk_inv)
+		cr |= SWSP;
+	if (rdai->data_alignment)
+		cr |= SDTA;
+	if (rdai->sys_delay)
+		cr |= DEL;
+	if (rsnd_dai_is_play(rdai, io))
+		cr |= TRMD;
+
+	/*
+	 * set ssi parameter
+	 */
+	ssi->rdai	= rdai;
+	ssi->io		= io;
+	ssi->cr_own	= cr;
+	ssi->err	= -1; /* ignore 1st error */
+
+	rsnd_ssi_mode_set(ssi);
+
+	dev_dbg(dev, "%s.%d init\n", rsnd_mod_name(mod), rsnd_mod_id(mod));
+
+	return 0;
+}
+
+static int rsnd_ssi_quit(struct rsnd_mod *mod,
+			 struct rsnd_dai *rdai,
+			 struct rsnd_dai_stream *io)
+{
+	struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+
+	dev_dbg(dev, "%s.%d quit\n", rsnd_mod_name(mod), rsnd_mod_id(mod));
+
+	if (ssi->err > 0)
+		dev_warn(dev, "ssi under/over flow err = %d\n", ssi->err);
+
+	ssi->rdai	= NULL;
+	ssi->io		= NULL;
+	ssi->cr_own	= 0;
+	ssi->err	= 0;
+
+	return 0;
+}
+
+/*
+ *		SSI PIO
+ */
+static irqreturn_t ssi_pio_interrupt(int irq, void *data)
+{
+	struct rsnd_ssi *ssi = data;
+	struct rsnd_dai_stream *io = ssi->io;
+	u32 status = rsnd_mod_read(&ssi->mod, SSISR);
+	irqreturn_t ret = IRQ_NONE;
+
+	if (io && (status & DIRQ)) {
+		struct rsnd_dai *rdai = ssi->rdai;
+		struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+		u32 *buf = (u32 *)(runtime->dma_area +
+				   rsnd_dai_pointer_offset(io, 0));
+
+		/*
+		 * under/over flow error
+		 */
+		if (status & (UIRQ | OIRQ)) {
+			ssi->err++;
+
+			/* clear error status */
+			rsnd_mod_write(&ssi->mod, SSISR, 0);
+		}
+
+		/*
+		 * transfer next data
+		 */
+
+		/*
+		 * 8/16/32 data can be assesse to TDR/RDR register
+		 * directly as 32bit data
+		 * see rsnd_ssi_init()
+		 */
+		if (rsnd_dai_is_play(rdai, io))
+			rsnd_mod_write(&ssi->mod, SSITDR, *buf);
+		else
+			*buf = rsnd_mod_read(&ssi->mod, SSIRDR);
+
+		rsnd_dai_pointer_update(io, sizeof(*buf));
+
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+static int rsnd_ssi_pio_start(struct rsnd_mod *mod,
+			      struct rsnd_dai *rdai,
+			      struct rsnd_dai_stream *io)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+
+	/* enable PIO IRQ */
+	ssi->cr_etc = UIEN | OIEN | DIEN;
+
+	rsnd_ssi_hw_start(ssi, rdai, io);
+
+	dev_dbg(dev, "%s.%d start\n", rsnd_mod_name(mod), rsnd_mod_id(mod));
+
+	return 0;
+}
+
+static int rsnd_ssi_pio_stop(struct rsnd_mod *mod,
+			     struct rsnd_dai *rdai,
+			     struct rsnd_dai_stream *io)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+
+	dev_dbg(dev, "%s.%d stop\n", rsnd_mod_name(mod), rsnd_mod_id(mod));
+
+	ssi->cr_etc = 0;
+
+	rsnd_ssi_hw_stop(ssi, rdai);
+
+	return 0;
+}
+
+static struct rsnd_mod_ops rsnd_ssi_pio_ops = {
+	.name	= "ssi (pio)",
+	.init	= rsnd_ssi_init,
+	.quit	= rsnd_ssi_quit,
+	.start	= rsnd_ssi_pio_start,
+	.stop	= rsnd_ssi_pio_stop,
+};
+
+/*
+ *		Non SSI
+ */
+static int rsnd_ssi_non(struct rsnd_mod *mod,
+			struct rsnd_dai *rdai,
+			struct rsnd_dai_stream *io)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return 0;
+}
+
+static struct rsnd_mod_ops rsnd_ssi_non_ops = {
+	.name	= "ssi (non)",
+	.init	= rsnd_ssi_non,
+	.quit	= rsnd_ssi_non,
+	.start	= rsnd_ssi_non,
+	.stop	= rsnd_ssi_non,
+};
+
+/*
+ *		ssi mod function
+ */
+struct rsnd_mod* rsnd_ssi_mod_get(struct rsnd_priv *priv, int id)
+{
+	BUG_ON (id < 0 || id >= rsnd_ssi_nr(priv));
+
+	return &(((struct rsnd_ssiu *)(priv->ssiu))->ssi + id)->mod;
+}
+
+int rsnd_ssi_probe(struct platform_device *pdev,
+		   struct rcar_snd_info *info,
+		   struct rsnd_priv *priv)
+{
+	struct rsnd_ssi_platform_info *pinfo;
+	struct device *dev = rsnd_priv_to_dev(priv);
+	struct rsnd_mod_ops *ops;
+	struct clk *clk;
+	struct rsnd_ssiu *ssiu;
+	struct rsnd_ssi *ssi;
+	char name[RSND_SSI_NAME_SIZE];
+	int i, nr, ret;
+
+	/*
+	 *	init SSI
+	 */
+	nr	= info->ssi_info_nr;
+	ssiu	= devm_kzalloc(dev, sizeof(*ssiu) + (sizeof(*ssi) * nr),
+			       GFP_KERNEL);
+	if (!ssiu) {
+		dev_err(dev, "SSI allocate failed\n");
+		return -ENOMEM;
+	}
+
+	priv->ssiu	= ssiu;
+	ssiu->ssi	= (struct rsnd_ssi *)(ssiu + 1);
+	ssiu->ssi_nr	= nr;
+
+	for_each_rsnd_ssi(ssi, priv, i) {
+		pinfo = &info->ssi_info[i];
+
+		snprintf(name, RSND_SSI_NAME_SIZE, "ssi.%d", i);
+
+		clk = clk_get(dev, name);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+
+		ssi->info	= pinfo;
+		ssi->clk	= clk;
+
+		ops = &rsnd_ssi_non_ops;
+
+		/*
+		 * SSI PIO case
+		 */
+		if (rsnd_ssi_is_pio(ssi)) {
+			ret = devm_request_irq(dev, pinfo->pio_irq,
+					       &ssi_pio_interrupt, IRQF_SHARED,
+					       dev_name(dev), ssi);
+			if (ret) {
+				dev_err(dev, "SSI request interrupt failed\n");
+				return ret;
+			}
+
+			ops	= &rsnd_ssi_pio_ops;
+		}
+
+		rsnd_mod_init(priv, &ssi->mod, ops, i);
+	}
+
+	rsnd_ssi_mode_init(priv, ssiu);
+
+	dev_dbg(dev, "ssi probed\n");
+
+	return 0;
+}
+
+void rsnd_ssi_remove(struct platform_device *pdev,
+		   struct rsnd_priv *priv)
+{
+	struct rsnd_ssi *ssi;
+	int i;
+
+	for_each_rsnd_ssi(ssi, priv, i)
+		clk_put(ssi->clk);
+}
-- 
1.7.9.5



More information about the Alsa-devel mailing list