diff mbox

[093/237] ASoC: add Renesas R-Car SSI feature

Message ID 1387000406-29111-94-git-send-email-horms+renesas@verge.net.au (mailing list archive)
State New, archived
Headers show

Commit Message

Simon Horman Dec. 14, 2013, 5:51 a.m. UTC
From: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>

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 support only at this point.
The DMA transfer, and capture feature will be supported in the future

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
(cherry picked from commit ae5c322303fff50b93d60e34c6563f1264a5941b)
Signed-off-by: Simon Horman <horms+renesas@verge.net.au>
---
 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   |  23 ++
 sound/soc/sh/rcar/ssi.c    | 588 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 661 insertions(+), 4 deletions(-)
 create mode 100644 sound/soc/sh/rcar/ssi.c
diff mbox

Patch

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 e588d8a..9a5469d 100644
--- a/sound/soc/sh/rcar/core.c
+++ b/sound/soc/sh/rcar/core.c
@@ -639,6 +639,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
 	 */
@@ -677,6 +681,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 ed21a13..5e4ae0d 100644
--- a/sound/soc/sh/rcar/gen.c
+++ b/sound/soc/sh/rcar/gen.c
@@ -72,6 +72,12 @@  static int rsnd_gen1_path_init(struct rsnd_priv *priv,
 	else
 		id = info->ssi_id_capture;
 
+	/* SSI */
+	mod = rsnd_ssi_mod_get(priv, id);
+	ret = rsnd_dai_connect(rdai, mod, io);
+	if (ret < 0)
+		return ret;
+
 	/* SCU */
 	mod = rsnd_scu_mod_get(priv, id);
 	ret = rsnd_dai_connect(rdai, mod, io);
@@ -120,6 +126,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,
@@ -130,14 +142,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;
 	}
@@ -146,8 +161,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;
 	}
@@ -159,8 +176,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 344fd59..0e7727c 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,
 };
 
@@ -151,6 +158,7 @@  int rsnd_dai_connect(struct rsnd_dai *rdai, struct rsnd_mod *mod,
 		     struct rsnd_dai_stream *io);
 int rsnd_dai_is_play(struct rsnd_dai *rdai, struct rsnd_dai_stream *io);
 #define rsnd_dai_get_platform_info(rdai) ((rdai)->info)
+#define rsnd_io_to_runtime(io) ((io)->substream->runtime)
 
 void rsnd_dai_pointer_update(struct rsnd_dai_stream *io, int cnt);
 int rsnd_dai_pointer_offset(struct rsnd_dai_stream *io, int additional);
@@ -210,6 +218,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;
@@ -232,4 +245,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..061ac7e
--- /dev/null
+++ b/sound/soc/sh/rcar/ssi.c
@@ -0,0 +1,588 @@ 
+/*
+ * Renesas R-Car SSIU/SSI support
+ *
+ * Copyright (C) 2013 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * Based on fsi.c
+ * Kuninori Morimoto <morimoto.kuninori@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;							\
+	     (i < rsnd_ssi_nr(priv)) &&					\
+		((pos) = ((struct rsnd_ssiu *)((priv)->ssiu))->ssi + i); \
+	     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_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)		\
+	do {						\
+		ssi->parent = ssiu->ssi + p;		\
+		if (flags & RSND_SSI_CLK_FROM_ADG)	\
+			val = adg;			\
+		else					\
+			val = ext;			\
+		if (flags & RSND_SSI_SYNC)		\
+			val |= sync;			\
+	} while (0)
+
+	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 main_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()
+			 */
+			main_rate = rate / adg_clk_div_table[i]
+				* 32 * 2 * ssi_clk_mul_table[j];
+
+			ret = rsnd_adg_ssi_clk_try_start(&ssi->mod, main_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;
+
+			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;
+}
+
+static void rsnd_ssi_record_error(struct rsnd_ssi *ssi, u32 status)
+{
+	/* under/over flow error */
+	if (status & (UIRQ | OIRQ)) {
+		ssi->err++;
+
+		/* clear error status */
+		rsnd_mod_write(&ssi->mod, SSISR, 0);
+	}
+}
+
+/*
+ *		SSI PIO
+ */
+static irqreturn_t rsnd_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));
+
+		rsnd_ssi_record_error(ssi, status);
+
+		/*
+		 * 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,
+					       &rsnd_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);
+}