Browse Source

Merge remote-tracking branch 'asoc/topic/rcar' into asoc-next

Mark Brown 12 years ago
parent
commit
b008387ab5

+ 84 - 0
include/sound/rcar_snd.h

@@ -0,0 +1,84 @@
+/*
+ * Renesas R-Car SRU/SCU/SSIU/SSI support
+ *
+ * Copyright (C) 2013 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@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.
+ */
+
+#ifndef RCAR_SND_H
+#define RCAR_SND_H
+
+#include <linux/sh_clk.h>
+
+#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	4
+
+/*
+ * flags
+ *
+ * 0xAB000000
+ *
+ * A : clock sharing settings
+ * B : SSI direction
+ */
+#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 */
+#define RSND_SSI_DEPENDENT		(1 << 28) /* SSI needs SRU/SCU */
+
+#define RSND_SSI_PLAY			(1 << 24)
+
+#define RSND_SSI_SET(_dai_id, _dma_id, _pio_irq, _flags)	\
+{ .dai_id = _dai_id, .dma_id = _dma_id, .pio_irq = _pio_irq, .flags = _flags }
+#define RSND_SSI_UNUSED \
+{ .dai_id = -1, .dma_id = -1, .pio_irq = -1, .flags = 0 }
+
+struct rsnd_ssi_platform_info {
+	int dai_id;
+	int dma_id;
+	int pio_irq;
+	u32 flags;
+};
+
+/*
+ * flags
+ */
+#define RSND_SCU_USB_HPBIF		(1 << 31) /* it needs RSND_SSI_DEPENDENT */
+
+struct rsnd_scu_platform_info {
+	u32 flags;
+};
+
+/*
+ * flags
+ *
+ * 0x0000000A
+ *
+ * A : generation
+ */
+#define RSND_GEN1	(1 << 0) /* fixme */
+#define RSND_GEN2	(2 << 0) /* fixme */
+
+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;
+	int (*start)(int id);
+	int (*stop)(int id);
+};
+
+#endif

+ 7 - 0
sound/soc/sh/Kconfig

@@ -34,6 +34,13 @@ config SND_SOC_SH4_SIU
 	select SH_DMAE
 	select FW_LOADER
 
+config SND_SOC_RCAR
+	tristate "R-Car series SRU/SCU/SSIU/SSI support"
+	select SND_SIMPLE_CARD
+	select RCAR_CLK_ADG
+	help
+	  This option enables R-Car SUR/SCU/SSIU/SSI sound support
+
 ##
 ## Boards
 ##

+ 3 - 0
sound/soc/sh/Makefile

@@ -12,6 +12,9 @@ obj-$(CONFIG_SND_SOC_SH4_SSI)	+= snd-soc-ssi.o
 obj-$(CONFIG_SND_SOC_SH4_FSI)	+= snd-soc-fsi.o
 obj-$(CONFIG_SND_SOC_SH4_SIU)	+= snd-soc-siu.o
 
+## audio units for R-Car
+obj-$(CONFIG_SND_SOC_RCAR)	+= rcar/
+
 ## boards
 snd-soc-sh7760-ac97-objs	:= sh7760-ac97.o
 snd-soc-migor-objs		:= migor.o

+ 2 - 0
sound/soc/sh/rcar/Makefile

@@ -0,0 +1,2 @@
+snd-soc-rcar-objs	:= core.o gen.o scu.o adg.o ssi.o
+obj-$(CONFIG_SND_SOC_RCAR)	+= snd-soc-rcar.o

+ 234 - 0
sound/soc/sh/rcar/adg.c

@@ -0,0 +1,234 @@
+/*
+ * Helper routines for R-Car sound ADG.
+ *
+ *  Copyright (C) 2013  Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+#include <linux/sh_clk.h>
+#include <mach/clock.h>
+#include "rsnd.h"
+
+#define CLKA	0
+#define CLKB	1
+#define CLKC	2
+#define CLKI	3
+#define CLKMAX	4
+
+struct rsnd_adg {
+	struct clk *clk[CLKMAX];
+
+	int rate_of_441khz_div_6;
+	int rate_of_48khz_div_6;
+};
+
+#define for_each_rsnd_clk(pos, adg, i)		\
+	for (i = 0, (pos) = adg->clk[i];	\
+	     i < CLKMAX;			\
+	     i++, (pos) = adg->clk[i])
+#define rsnd_priv_to_adg(priv) ((struct rsnd_adg *)(priv)->adg)
+
+static enum rsnd_reg rsnd_adg_ssi_reg_get(int id)
+{
+	enum rsnd_reg reg;
+
+	/*
+	 * SSI 8 is not connected to ADG.
+	 * it works with SSI 7
+	 */
+	if (id == 8)
+		return RSND_REG_MAX;
+
+	if (0 <= id && id <= 3)
+		reg = RSND_REG_AUDIO_CLK_SEL0;
+	else if (4 <= id && id <= 7)
+		reg = RSND_REG_AUDIO_CLK_SEL1;
+	else
+		reg = RSND_REG_AUDIO_CLK_SEL2;
+
+	return reg;
+}
+
+int rsnd_adg_ssi_clk_stop(struct rsnd_mod *mod)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	enum rsnd_reg reg;
+	int id;
+
+	/*
+	 * "mod" = "ssi" here.
+	 * we can get "ssi id" from mod
+	 */
+	id  = rsnd_mod_id(mod);
+	reg = rsnd_adg_ssi_reg_get(id);
+
+	rsnd_write(priv, mod, reg, 0);
+
+	return 0;
+}
+
+int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *mod, unsigned int rate)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct rsnd_adg *adg = rsnd_priv_to_adg(priv);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	struct clk *clk;
+	enum rsnd_reg reg;
+	int id, shift, i;
+	u32 data;
+	int sel_table[] = {
+		[CLKA] = 0x1,
+		[CLKB] = 0x2,
+		[CLKC] = 0x3,
+		[CLKI] = 0x0,
+	};
+
+	dev_dbg(dev, "request clock = %d\n", rate);
+
+	/*
+	 * find suitable clock from
+	 * AUDIO_CLKA/AUDIO_CLKB/AUDIO_CLKC/AUDIO_CLKI.
+	 */
+	data = 0;
+	for_each_rsnd_clk(clk, adg, i) {
+		if (rate == clk_get_rate(clk)) {
+			data = sel_table[i];
+			goto found_clock;
+		}
+	}
+
+	/*
+	 * find 1/6 clock from BRGA/BRGB
+	 */
+	if (rate == adg->rate_of_441khz_div_6) {
+		data = 0x10;
+		goto found_clock;
+	}
+
+	if (rate == adg->rate_of_48khz_div_6) {
+		data = 0x20;
+		goto found_clock;
+	}
+
+	return -EIO;
+
+found_clock:
+
+	/*
+	 * This "mod" = "ssi" here.
+	 * we can get "ssi id" from mod
+	 */
+	id  = rsnd_mod_id(mod);
+	reg = rsnd_adg_ssi_reg_get(id);
+
+	dev_dbg(dev, "ADG: ssi%d selects clk%d = %d", id, i, rate);
+
+	/*
+	 * Enable SSIx clock
+	 */
+	shift = (id % 4) * 8;
+
+	rsnd_bset(priv, mod, reg,
+		   0xFF << shift,
+		   data << shift);
+
+	return 0;
+}
+
+static void rsnd_adg_ssi_clk_init(struct rsnd_priv *priv, struct rsnd_adg *adg)
+{
+	struct clk *clk;
+	unsigned long rate;
+	u32 ckr;
+	int i;
+	int brg_table[] = {
+		[CLKA] = 0x0,
+		[CLKB] = 0x1,
+		[CLKC] = 0x4,
+		[CLKI] = 0x2,
+	};
+
+	/*
+	 * This driver is assuming that AUDIO_CLKA/AUDIO_CLKB/AUDIO_CLKC
+	 * have 44.1kHz or 48kHz base clocks for now.
+	 *
+	 * SSI itself can divide parent clock by 1/1 - 1/16
+	 * So,  BRGA outputs 44.1kHz base parent clock 1/32,
+	 * and, BRGB outputs 48.0kHz base parent clock 1/32 here.
+	 * see
+	 *	rsnd_adg_ssi_clk_try_start()
+	 */
+	ckr = 0;
+	adg->rate_of_441khz_div_6 = 0;
+	adg->rate_of_48khz_div_6  = 0;
+	for_each_rsnd_clk(clk, adg, i) {
+		rate = clk_get_rate(clk);
+
+		if (0 == rate) /* not used */
+			continue;
+
+		/* RBGA */
+		if (!adg->rate_of_441khz_div_6 && (0 == rate % 44100)) {
+			adg->rate_of_441khz_div_6 = rate / 6;
+			ckr |= brg_table[i] << 20;
+		}
+
+		/* RBGB */
+		if (!adg->rate_of_48khz_div_6 && (0 == rate % 48000)) {
+			adg->rate_of_48khz_div_6 = rate / 6;
+			ckr |= brg_table[i] << 16;
+		}
+	}
+
+	rsnd_priv_bset(priv, SSICKR, 0x00FF0000, ckr);
+	rsnd_priv_write(priv, BRRA,  0x00000002); /* 1/6 */
+	rsnd_priv_write(priv, BRRB,  0x00000002); /* 1/6 */
+}
+
+int rsnd_adg_probe(struct platform_device *pdev,
+		   struct rcar_snd_info *info,
+		   struct rsnd_priv *priv)
+{
+	struct rsnd_adg *adg;
+	struct device *dev = rsnd_priv_to_dev(priv);
+	struct clk *clk;
+	int i;
+
+	adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL);
+	if (!adg) {
+		dev_err(dev, "ADG allocate failed\n");
+		return -ENOMEM;
+	}
+
+	adg->clk[CLKA] = clk_get(NULL, "audio_clk_a");
+	adg->clk[CLKB] = clk_get(NULL, "audio_clk_b");
+	adg->clk[CLKC] = clk_get(NULL, "audio_clk_c");
+	adg->clk[CLKI] = clk_get(NULL, "audio_clk_internal");
+	for_each_rsnd_clk(clk, adg, i) {
+		if (IS_ERR(clk)) {
+			dev_err(dev, "Audio clock failed\n");
+			return -EIO;
+		}
+	}
+
+	rsnd_adg_ssi_clk_init(priv, adg);
+
+	priv->adg = adg;
+
+	dev_dbg(dev, "adg probed\n");
+
+	return 0;
+}
+
+void rsnd_adg_remove(struct platform_device *pdev,
+		     struct rsnd_priv *priv)
+{
+	struct rsnd_adg *adg = priv->adg;
+	struct clk *clk;
+	int i;
+
+	for_each_rsnd_clk(clk, adg, i)
+		clk_put(clk);
+}

+ 861 - 0
sound/soc/sh/rcar/core.c

@@ -0,0 +1,861 @@
+/*
+ * Renesas R-Car SRU/SCU/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.
+ */
+
+/*
+ * Renesas R-Car sound device structure
+ *
+ * Gen1
+ *
+ * SRU		: Sound Routing Unit
+ *  - SRC	: Sampling Rate Converter
+ *  - CMD
+ *    - CTU	: Channel Count Conversion Unit
+ *    - MIX	: Mixer
+ *    - DVC	: Digital Volume and Mute Function
+ *  - SSI	: Serial Sound Interface
+ *
+ * Gen2
+ *
+ * SCU		: Sampling Rate Converter Unit
+ *  - SRC	: Sampling Rate Converter
+ *  - CMD
+ *   - CTU	: Channel Count Conversion Unit
+ *   - MIX	: Mixer
+ *   - DVC	: Digital Volume and Mute Function
+ * SSIU		: Serial Sound Interface Unit
+ *  - SSI	: Serial Sound Interface
+ */
+
+/*
+ *	driver data Image
+ *
+ * rsnd_priv
+ *   |
+ *   | ** this depends on Gen1/Gen2
+ *   |
+ *   +- gen
+ *   |
+ *   | ** these depend on data path
+ *   | ** gen and platform data control it
+ *   |
+ *   +- rdai[0]
+ *   |   |		 sru     ssiu      ssi
+ *   |   +- playback -> [mod] -> [mod] -> [mod] -> ...
+ *   |   |
+ *   |   |		 sru     ssiu      ssi
+ *   |   +- capture  -> [mod] -> [mod] -> [mod] -> ...
+ *   |
+ *   +- rdai[1]
+ *   |   |		 sru     ssiu      ssi
+ *   |   +- playback -> [mod] -> [mod] -> [mod] -> ...
+ *   |   |
+ *   |   |		 sru     ssiu      ssi
+ *   |   +- capture  -> [mod] -> [mod] -> [mod] -> ...
+ *   ...
+ *   |
+ *   | ** these control ssi
+ *   |
+ *   +- ssi
+ *   |  |
+ *   |  +- ssi[0]
+ *   |  +- ssi[1]
+ *   |  +- ssi[2]
+ *   |  ...
+ *   |
+ *   | ** these control scu
+ *   |
+ *   +- scu
+ *      |
+ *      +- scu[0]
+ *      +- scu[1]
+ *      +- scu[2]
+ *      ...
+ *
+ *
+ * for_each_rsnd_dai(xx, priv, xx)
+ *  rdai[0] => rdai[1] => rdai[2] => ...
+ *
+ * for_each_rsnd_mod(xx, rdai, xx)
+ *  [mod] => [mod] => [mod] => ...
+ *
+ * rsnd_dai_call(xxx, fn )
+ *  [mod]->fn() -> [mod]->fn() -> [mod]->fn()...
+ *
+ */
+#include <linux/pm_runtime.h>
+#include "rsnd.h"
+
+#define RSND_RATES SNDRV_PCM_RATE_8000_96000
+#define RSND_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
+
+/*
+ *	rsnd_platform functions
+ */
+#define rsnd_platform_call(priv, dai, func, param...)	\
+	(!(priv->info->func) ? -ENODEV :		\
+	 priv->info->func(param))
+
+
+/*
+ *	basic function
+ */
+u32 rsnd_read(struct rsnd_priv *priv,
+	      struct rsnd_mod *mod, enum rsnd_reg reg)
+{
+	void __iomem *base = rsnd_gen_reg_get(priv, mod, reg);
+
+	BUG_ON(!base);
+
+	return ioread32(base);
+}
+
+void rsnd_write(struct rsnd_priv *priv,
+		struct rsnd_mod *mod,
+		enum rsnd_reg reg, u32 data)
+{
+	void __iomem *base = rsnd_gen_reg_get(priv, mod, reg);
+	struct device *dev = rsnd_priv_to_dev(priv);
+
+	BUG_ON(!base);
+
+	dev_dbg(dev, "w %p : %08x\n", base, data);
+
+	iowrite32(data, base);
+}
+
+void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod,
+	       enum rsnd_reg reg, u32 mask, u32 data)
+{
+	void __iomem *base = rsnd_gen_reg_get(priv, mod, reg);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	u32 val;
+
+	BUG_ON(!base);
+
+	val = ioread32(base);
+	val &= ~mask;
+	val |= data & mask;
+	iowrite32(val, base);
+
+	dev_dbg(dev, "s %p : %08x\n", base, val);
+}
+
+/*
+ *	rsnd_mod functions
+ */
+char *rsnd_mod_name(struct rsnd_mod *mod)
+{
+	if (!mod || !mod->ops)
+		return "unknown";
+
+	return mod->ops->name;
+}
+
+void rsnd_mod_init(struct rsnd_priv *priv,
+		   struct rsnd_mod *mod,
+		   struct rsnd_mod_ops *ops,
+		   int id)
+{
+	mod->priv	= priv;
+	mod->id		= id;
+	mod->ops	= ops;
+	INIT_LIST_HEAD(&mod->list);
+}
+
+/*
+ *	rsnd_dma functions
+ */
+static void rsnd_dma_continue(struct rsnd_dma *dma)
+{
+	/* push next A or B plane */
+	dma->submit_loop = 1;
+	schedule_work(&dma->work);
+}
+
+void rsnd_dma_start(struct rsnd_dma *dma)
+{
+	/* push both A and B plane*/
+	dma->submit_loop = 2;
+	schedule_work(&dma->work);
+}
+
+void rsnd_dma_stop(struct rsnd_dma *dma)
+{
+	dma->submit_loop = 0;
+	cancel_work_sync(&dma->work);
+	dmaengine_terminate_all(dma->chan);
+}
+
+static void rsnd_dma_complete(void *data)
+{
+	struct rsnd_dma *dma = (struct rsnd_dma *)data;
+	struct rsnd_priv *priv = dma->priv;
+	unsigned long flags;
+
+	rsnd_lock(priv, flags);
+
+	dma->complete(dma);
+
+	if (dma->submit_loop)
+		rsnd_dma_continue(dma);
+
+	rsnd_unlock(priv, flags);
+}
+
+static void rsnd_dma_do_work(struct work_struct *work)
+{
+	struct rsnd_dma *dma = container_of(work, struct rsnd_dma, work);
+	struct rsnd_priv *priv = dma->priv;
+	struct device *dev = rsnd_priv_to_dev(priv);
+	struct dma_async_tx_descriptor *desc;
+	dma_addr_t buf;
+	size_t len;
+	int i;
+
+	for (i = 0; i < dma->submit_loop; i++) {
+
+		if (dma->inquiry(dma, &buf, &len) < 0)
+			return;
+
+		desc = dmaengine_prep_slave_single(
+			dma->chan, buf, len, dma->dir,
+			DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+		if (!desc) {
+			dev_err(dev, "dmaengine_prep_slave_sg() fail\n");
+			return;
+		}
+
+		desc->callback		= rsnd_dma_complete;
+		desc->callback_param	= dma;
+
+		if (dmaengine_submit(desc) < 0) {
+			dev_err(dev, "dmaengine_submit() fail\n");
+			return;
+		}
+
+	}
+
+	dma_async_issue_pending(dma->chan);
+}
+
+int rsnd_dma_available(struct rsnd_dma *dma)
+{
+	return !!dma->chan;
+}
+
+static bool rsnd_dma_filter(struct dma_chan *chan, void *param)
+{
+	chan->private = param;
+
+	return true;
+}
+
+int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma,
+		  int is_play, int id,
+		  int (*inquiry)(struct rsnd_dma *dma,
+				  dma_addr_t *buf, int *len),
+		  int (*complete)(struct rsnd_dma *dma))
+{
+	struct device *dev = rsnd_priv_to_dev(priv);
+	dma_cap_mask_t mask;
+
+	if (dma->chan) {
+		dev_err(dev, "it already has dma channel\n");
+		return -EIO;
+	}
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	dma->slave.shdma_slave.slave_id = id;
+
+	dma->chan = dma_request_channel(mask, rsnd_dma_filter,
+					&dma->slave.shdma_slave);
+	if (!dma->chan) {
+		dev_err(dev, "can't get dma channel\n");
+		return -EIO;
+	}
+
+	dma->dir = is_play ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+	dma->priv = priv;
+	dma->inquiry = inquiry;
+	dma->complete = complete;
+	INIT_WORK(&dma->work, rsnd_dma_do_work);
+
+	return 0;
+}
+
+void  rsnd_dma_quit(struct rsnd_priv *priv,
+		    struct rsnd_dma *dma)
+{
+	if (dma->chan)
+		dma_release_channel(dma->chan);
+
+	dma->chan = NULL;
+}
+
+/*
+ *	rsnd_dai functions
+ */
+#define rsnd_dai_call(rdai, io, fn)			\
+({							\
+	struct rsnd_mod *mod, *n;			\
+	int ret = 0;					\
+	for_each_rsnd_mod(mod, n, io) {			\
+		ret = rsnd_mod_call(mod, fn, rdai, io);	\
+		if (ret < 0)				\
+			break;				\
+	}						\
+	ret;						\
+})
+
+int rsnd_dai_connect(struct rsnd_dai *rdai,
+		     struct rsnd_mod *mod,
+		     struct rsnd_dai_stream *io)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+
+	if (!mod) {
+		dev_err(dev, "NULL mod\n");
+		return -EIO;
+	}
+
+	if (!list_empty(&mod->list)) {
+		dev_err(dev, "%s%d is not empty\n",
+			rsnd_mod_name(mod),
+			rsnd_mod_id(mod));
+		return -EIO;
+	}
+
+	list_add_tail(&mod->list, &io->head);
+
+	return 0;
+}
+
+int rsnd_dai_disconnect(struct rsnd_mod *mod)
+{
+	list_del_init(&mod->list);
+
+	return 0;
+}
+
+int rsnd_dai_id(struct rsnd_priv *priv, struct rsnd_dai *rdai)
+{
+	int id = rdai - priv->rdai;
+
+	if ((id < 0) || (id >= rsnd_dai_nr(priv)))
+		return -EINVAL;
+
+	return id;
+}
+
+struct rsnd_dai *rsnd_dai_get(struct rsnd_priv *priv, int id)
+{
+	return priv->rdai + id;
+}
+
+static struct rsnd_dai *rsnd_dai_to_rdai(struct snd_soc_dai *dai)
+{
+	struct rsnd_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+	return rsnd_dai_get(priv, dai->id);
+}
+
+int rsnd_dai_is_play(struct rsnd_dai *rdai, struct rsnd_dai_stream *io)
+{
+	return &rdai->playback == io;
+}
+
+/*
+ *	rsnd_soc_dai functions
+ */
+int rsnd_dai_pointer_offset(struct rsnd_dai_stream *io, int additional)
+{
+	struct snd_pcm_substream *substream = io->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int pos = io->byte_pos + additional;
+
+	pos %= (runtime->periods * io->byte_per_period);
+
+	return pos;
+}
+
+void rsnd_dai_pointer_update(struct rsnd_dai_stream *io, int byte)
+{
+	io->byte_pos += byte;
+
+	if (io->byte_pos >= io->next_period_byte) {
+		struct snd_pcm_substream *substream = io->substream;
+		struct snd_pcm_runtime *runtime = substream->runtime;
+
+		io->period_pos++;
+		io->next_period_byte += io->byte_per_period;
+
+		if (io->period_pos >= runtime->periods) {
+			io->byte_pos = 0;
+			io->period_pos = 0;
+			io->next_period_byte = io->byte_per_period;
+		}
+
+		snd_pcm_period_elapsed(substream);
+	}
+}
+
+static int rsnd_dai_stream_init(struct rsnd_dai_stream *io,
+				struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (!list_empty(&io->head))
+		return -EIO;
+
+	INIT_LIST_HEAD(&io->head);
+	io->substream		= substream;
+	io->byte_pos		= 0;
+	io->period_pos		= 0;
+	io->byte_per_period	= runtime->period_size *
+				  runtime->channels *
+				  samples_to_bytes(runtime, 1);
+	io->next_period_byte	= io->byte_per_period;
+
+	return 0;
+}
+
+static
+struct snd_soc_dai *rsnd_substream_to_dai(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	return  rtd->cpu_dai;
+}
+
+static
+struct rsnd_dai_stream *rsnd_rdai_to_io(struct rsnd_dai *rdai,
+					struct snd_pcm_substream *substream)
+{
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return &rdai->playback;
+	else
+		return &rdai->capture;
+}
+
+static int rsnd_soc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+			    struct snd_soc_dai *dai)
+{
+	struct rsnd_priv *priv = snd_soc_dai_get_drvdata(dai);
+	struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai);
+	struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream);
+	struct rsnd_mod *mod = rsnd_ssi_mod_get_frm_dai(priv,
+						rsnd_dai_id(priv, rdai),
+						rsnd_dai_is_play(rdai, io));
+	int ssi_id = rsnd_mod_id(mod);
+	int ret;
+	unsigned long flags;
+
+	rsnd_lock(priv, flags);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		ret = rsnd_dai_stream_init(io, substream);
+		if (ret < 0)
+			goto dai_trigger_end;
+
+		ret = rsnd_platform_call(priv, dai, start, ssi_id);
+		if (ret < 0)
+			goto dai_trigger_end;
+
+		ret = rsnd_gen_path_init(priv, rdai, io);
+		if (ret < 0)
+			goto dai_trigger_end;
+
+		ret = rsnd_dai_call(rdai, io, init);
+		if (ret < 0)
+			goto dai_trigger_end;
+
+		ret = rsnd_dai_call(rdai, io, start);
+		if (ret < 0)
+			goto dai_trigger_end;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		ret = rsnd_dai_call(rdai, io, stop);
+		if (ret < 0)
+			goto dai_trigger_end;
+
+		ret = rsnd_dai_call(rdai, io, quit);
+		if (ret < 0)
+			goto dai_trigger_end;
+
+		ret = rsnd_gen_path_exit(priv, rdai, io);
+		if (ret < 0)
+			goto dai_trigger_end;
+
+		ret = rsnd_platform_call(priv, dai, stop, ssi_id);
+		if (ret < 0)
+			goto dai_trigger_end;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+dai_trigger_end:
+	rsnd_unlock(priv, flags);
+
+	return ret;
+}
+
+static int rsnd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai);
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		rdai->clk_master = 1;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		rdai->clk_master = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_IF:
+		rdai->bit_clk_inv = 0;
+		rdai->frm_clk_inv = 1;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		rdai->bit_clk_inv = 1;
+		rdai->frm_clk_inv = 0;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		rdai->bit_clk_inv = 1;
+		rdai->frm_clk_inv = 1;
+		break;
+	case SND_SOC_DAIFMT_NB_NF:
+	default:
+		rdai->bit_clk_inv = 0;
+		rdai->frm_clk_inv = 0;
+		break;
+	}
+
+	/* set format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		rdai->sys_delay = 0;
+		rdai->data_alignment = 0;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		rdai->sys_delay = 1;
+		rdai->data_alignment = 0;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		rdai->sys_delay = 1;
+		rdai->data_alignment = 1;
+		break;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops rsnd_soc_dai_ops = {
+	.trigger	= rsnd_soc_dai_trigger,
+	.set_fmt	= rsnd_soc_dai_set_fmt,
+};
+
+static int rsnd_dai_probe(struct platform_device *pdev,
+			  struct rcar_snd_info *info,
+			  struct rsnd_priv *priv)
+{
+	struct snd_soc_dai_driver *drv;
+	struct rsnd_dai *rdai;
+	struct rsnd_mod *pmod, *cmod;
+	struct device *dev = rsnd_priv_to_dev(priv);
+	int dai_nr;
+	int i;
+
+	/* get max dai nr */
+	for (dai_nr = 0; dai_nr < 32; dai_nr++) {
+		pmod = rsnd_ssi_mod_get_frm_dai(priv, dai_nr, 1);
+		cmod = rsnd_ssi_mod_get_frm_dai(priv, dai_nr, 0);
+
+		if (!pmod && !cmod)
+			break;
+	}
+
+	if (!dai_nr) {
+		dev_err(dev, "no dai\n");
+		return -EIO;
+	}
+
+	drv  = devm_kzalloc(dev, sizeof(*drv)  * dai_nr, GFP_KERNEL);
+	rdai = devm_kzalloc(dev, sizeof(*rdai) * dai_nr, GFP_KERNEL);
+	if (!drv || !rdai) {
+		dev_err(dev, "dai allocate failed\n");
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < dai_nr; i++) {
+
+		pmod = rsnd_ssi_mod_get_frm_dai(priv, i, 1);
+		cmod = rsnd_ssi_mod_get_frm_dai(priv, i, 0);
+
+		/*
+		 *	init rsnd_dai
+		 */
+		INIT_LIST_HEAD(&rdai[i].playback.head);
+		INIT_LIST_HEAD(&rdai[i].capture.head);
+
+		snprintf(rdai[i].name, RSND_DAI_NAME_SIZE, "rsnd-dai.%d", i);
+
+		/*
+		 *	init snd_soc_dai_driver
+		 */
+		drv[i].name	= rdai[i].name;
+		drv[i].ops	= &rsnd_soc_dai_ops;
+		if (pmod) {
+			drv[i].playback.rates		= RSND_RATES;
+			drv[i].playback.formats		= RSND_FMTS;
+			drv[i].playback.channels_min	= 2;
+			drv[i].playback.channels_max	= 2;
+		}
+		if (cmod) {
+			drv[i].capture.rates		= RSND_RATES;
+			drv[i].capture.formats		= RSND_FMTS;
+			drv[i].capture.channels_min	= 2;
+			drv[i].capture.channels_max	= 2;
+		}
+
+		dev_dbg(dev, "%s (%s/%s)\n", rdai[i].name,
+			pmod ? "play"    : " -- ",
+			cmod ? "capture" : "  --   ");
+	}
+
+	priv->dai_nr	= dai_nr;
+	priv->daidrv	= drv;
+	priv->rdai	= rdai;
+
+	return 0;
+}
+
+static void rsnd_dai_remove(struct platform_device *pdev,
+			  struct rsnd_priv *priv)
+{
+}
+
+/*
+ *		pcm ops
+ */
+static struct snd_pcm_hardware rsnd_pcm_hardware = {
+	.info =		SNDRV_PCM_INFO_INTERLEAVED	|
+			SNDRV_PCM_INFO_MMAP		|
+			SNDRV_PCM_INFO_MMAP_VALID	|
+			SNDRV_PCM_INFO_PAUSE,
+	.formats		= RSND_FMTS,
+	.rates			= RSND_RATES,
+	.rate_min		= 8000,
+	.rate_max		= 192000,
+	.channels_min		= 2,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 64 * 1024,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 8192,
+	.periods_min		= 1,
+	.periods_max		= 32,
+	.fifo_size		= 256,
+};
+
+static int rsnd_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret = 0;
+
+	snd_soc_set_runtime_hwparams(substream, &rsnd_pcm_hardware);
+
+	ret = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+
+	return ret;
+}
+
+static int rsnd_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static snd_pcm_uframes_t rsnd_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_dai *dai = rsnd_substream_to_dai(substream);
+	struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai);
+	struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream);
+
+	return bytes_to_frames(runtime, io->byte_pos);
+}
+
+static struct snd_pcm_ops rsnd_pcm_ops = {
+	.open		= rsnd_pcm_open,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= rsnd_hw_params,
+	.hw_free	= snd_pcm_lib_free_pages,
+	.pointer	= rsnd_pointer,
+};
+
+/*
+ *		snd_soc_platform
+ */
+
+#define PREALLOC_BUFFER		(32 * 1024)
+#define PREALLOC_BUFFER_MAX	(32 * 1024)
+
+static int rsnd_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+	return snd_pcm_lib_preallocate_pages_for_all(
+		rtd->pcm,
+		SNDRV_DMA_TYPE_DEV,
+		rtd->card->snd_card->dev,
+		PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
+}
+
+static void rsnd_pcm_free(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static struct snd_soc_platform_driver rsnd_soc_platform = {
+	.ops		= &rsnd_pcm_ops,
+	.pcm_new	= rsnd_pcm_new,
+	.pcm_free	= rsnd_pcm_free,
+};
+
+static const struct snd_soc_component_driver rsnd_soc_component = {
+	.name		= "rsnd",
+};
+
+/*
+ *	rsnd probe
+ */
+static int rsnd_probe(struct platform_device *pdev)
+{
+	struct rcar_snd_info *info;
+	struct rsnd_priv *priv;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	info = pdev->dev.platform_data;
+	if (!info) {
+		dev_err(dev, "driver needs R-Car sound information\n");
+		return -ENODEV;
+	}
+
+	/*
+	 *	init priv data
+	 */
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		dev_err(dev, "priv allocate failed\n");
+		return -ENODEV;
+	}
+
+	priv->dev	= dev;
+	priv->info	= info;
+	spin_lock_init(&priv->lock);
+
+	/*
+	 *	init each module
+	 */
+	ret = rsnd_gen_probe(pdev, info, priv);
+	if (ret < 0)
+		return ret;
+
+	ret = rsnd_scu_probe(pdev, info, priv);
+	if (ret < 0)
+		return ret;
+
+	ret = rsnd_adg_probe(pdev, info, priv);
+	if (ret < 0)
+		return ret;
+
+	ret = rsnd_ssi_probe(pdev, info, priv);
+	if (ret < 0)
+		return ret;
+
+	ret = rsnd_dai_probe(pdev, info, priv);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 *	asoc register
+	 */
+	ret = snd_soc_register_platform(dev, &rsnd_soc_platform);
+	if (ret < 0) {
+		dev_err(dev, "cannot snd soc register\n");
+		return ret;
+	}
+
+	ret = snd_soc_register_component(dev, &rsnd_soc_component,
+					 priv->daidrv, rsnd_dai_nr(priv));
+	if (ret < 0) {
+		dev_err(dev, "cannot snd dai register\n");
+		goto exit_snd_soc;
+	}
+
+	dev_set_drvdata(dev, priv);
+
+	pm_runtime_enable(dev);
+
+	dev_info(dev, "probed\n");
+	return ret;
+
+exit_snd_soc:
+	snd_soc_unregister_platform(dev);
+
+	return ret;
+}
+
+static int rsnd_remove(struct platform_device *pdev)
+{
+	struct rsnd_priv *priv = dev_get_drvdata(&pdev->dev);
+
+	pm_runtime_disable(&pdev->dev);
+
+	/*
+	 *	remove each module
+	 */
+	rsnd_ssi_remove(pdev, priv);
+	rsnd_adg_remove(pdev, priv);
+	rsnd_scu_remove(pdev, priv);
+	rsnd_dai_remove(pdev, priv);
+	rsnd_gen_remove(pdev, priv);
+
+	return 0;
+}
+
+static struct platform_driver rsnd_driver = {
+	.driver	= {
+		.name	= "rcar_sound",
+	},
+	.probe		= rsnd_probe,
+	.remove		= rsnd_remove,
+};
+module_platform_driver(rsnd_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Renesas R-Car audio driver");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
+MODULE_ALIAS("platform:rcar-pcm-audio");

+ 280 - 0
sound/soc/sh/rcar/gen.c

@@ -0,0 +1,280 @@
+/*
+ * Renesas R-Car Gen1 SRU/SSI support
+ *
+ * Copyright (C) 2013 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@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 "rsnd.h"
+
+struct rsnd_gen_ops {
+	int (*path_init)(struct rsnd_priv *priv,
+			 struct rsnd_dai *rdai,
+			 struct rsnd_dai_stream *io);
+	int (*path_exit)(struct rsnd_priv *priv,
+			 struct rsnd_dai *rdai,
+			 struct rsnd_dai_stream *io);
+};
+
+struct rsnd_gen_reg_map {
+	int index;	/* -1 : not supported */
+	u32 offset_id;	/* offset of ssi0, ssi1, ssi2... */
+	u32 offset_adr;	/* offset of SSICR, SSISR, ... */
+};
+
+struct rsnd_gen {
+	void __iomem *base[RSND_BASE_MAX];
+
+	struct rsnd_gen_reg_map reg_map[RSND_REG_MAX];
+	struct rsnd_gen_ops *ops;
+};
+
+#define rsnd_priv_to_gen(p)	((struct rsnd_gen *)(p)->gen)
+
+/*
+ *		Gen2
+ *		will be filled in the future
+ */
+
+/*
+ *		Gen1
+ */
+static int rsnd_gen1_path_init(struct rsnd_priv *priv,
+			       struct rsnd_dai *rdai,
+			       struct rsnd_dai_stream *io)
+{
+	struct rsnd_mod *mod;
+	int ret;
+	int id;
+
+	/*
+	 * Gen1 is created by SRU/SSI, and this SRU is base module of
+	 * Gen2's SCU/SSIU/SSI. (Gen2 SCU/SSIU came from SRU)
+	 *
+	 * Easy image is..
+	 *	Gen1 SRU = Gen2 SCU + SSIU + etc
+	 *
+	 * Gen2 SCU path is very flexible, but, Gen1 SRU (SCU parts) is
+	 * using fixed path.
+	 *
+	 * Then, SSI id = SCU id here
+	 */
+
+	/* get SSI's ID */
+	mod = rsnd_ssi_mod_get_frm_dai(priv,
+				       rsnd_dai_id(priv, rdai),
+				       rsnd_dai_is_play(rdai, io));
+	id = rsnd_mod_id(mod);
+
+	/* 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);
+
+	return ret;
+}
+
+static int rsnd_gen1_path_exit(struct rsnd_priv *priv,
+			       struct rsnd_dai *rdai,
+			       struct rsnd_dai_stream *io)
+{
+	struct rsnd_mod *mod, *n;
+	int ret = 0;
+
+	/*
+	 * remove all mod from rdai
+	 */
+	for_each_rsnd_mod(mod, n, io)
+		ret |= rsnd_dai_disconnect(mod);
+
+	return ret;
+}
+
+static struct rsnd_gen_ops rsnd_gen1_ops = {
+	.path_init	= rsnd_gen1_path_init,
+	.path_exit	= rsnd_gen1_path_exit,
+};
+
+#define RSND_GEN1_REG_MAP(g, s, i, oi, oa)				\
+	do {								\
+		(g)->reg_map[RSND_REG_##i].index  = RSND_GEN1_##s;	\
+		(g)->reg_map[RSND_REG_##i].offset_id = oi;		\
+		(g)->reg_map[RSND_REG_##i].offset_adr = oa;		\
+	} while (0)
+
+static void rsnd_gen1_reg_map_init(struct rsnd_gen *gen)
+{
+	RSND_GEN1_REG_MAP(gen, SRU,	SRC_ROUTE_SEL,	0x0,	0x00);
+	RSND_GEN1_REG_MAP(gen, SRU,	SRC_TMG_SEL0,	0x0,	0x08);
+	RSND_GEN1_REG_MAP(gen, SRU,	SRC_TMG_SEL1,	0x0,	0x0c);
+	RSND_GEN1_REG_MAP(gen, SRU,	SRC_TMG_SEL2,	0x0,	0x10);
+	RSND_GEN1_REG_MAP(gen, SRU,	SRC_CTRL,	0x0,	0xc0);
+	RSND_GEN1_REG_MAP(gen, SRU,	SSI_MODE0,	0x0,	0xD0);
+	RSND_GEN1_REG_MAP(gen, SRU,	SSI_MODE1,	0x0,	0xD4);
+	RSND_GEN1_REG_MAP(gen, SRU,	BUSIF_MODE,	0x4,	0x20);
+	RSND_GEN1_REG_MAP(gen, SRU,	BUSIF_ADINR,	0x40,	0x214);
+
+	RSND_GEN1_REG_MAP(gen, ADG,	BRRA,		0x0,	0x00);
+	RSND_GEN1_REG_MAP(gen, ADG,	BRRB,		0x0,	0x04);
+	RSND_GEN1_REG_MAP(gen, ADG,	SSICKR,		0x0,	0x08);
+	RSND_GEN1_REG_MAP(gen, ADG,	AUDIO_CLK_SEL0,	0x0,	0x0c);
+	RSND_GEN1_REG_MAP(gen, ADG,	AUDIO_CLK_SEL1,	0x0,	0x10);
+	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,
+			   struct rcar_snd_info *info,
+			   struct rsnd_priv *priv)
+{
+	struct device *dev = rsnd_priv_to_dev(priv);
+	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);
+
+	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 (IS_ERR(gen->base[RSND_GEN1_SRU]) ||
+	    IS_ERR(gen->base[RSND_GEN1_ADG]) ||
+	    IS_ERR(gen->base[RSND_GEN1_SSI]))
+		return -ENODEV;
+
+	gen->ops = &rsnd_gen1_ops;
+	rsnd_gen1_reg_map_init(gen);
+
+	dev_dbg(dev, "Gen1 device probed\n");
+	dev_dbg(dev, "SRU : %08x => %p\n",	sru_res->start,
+						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,
+			     struct rsnd_priv *priv)
+{
+}
+
+/*
+ *		Gen
+ */
+int rsnd_gen_path_init(struct rsnd_priv *priv,
+		       struct rsnd_dai *rdai,
+		       struct rsnd_dai_stream *io)
+{
+	struct rsnd_gen *gen = rsnd_priv_to_gen(priv);
+
+	return gen->ops->path_init(priv, rdai, io);
+}
+
+int rsnd_gen_path_exit(struct rsnd_priv *priv,
+		       struct rsnd_dai *rdai,
+		       struct rsnd_dai_stream *io)
+{
+	struct rsnd_gen *gen = rsnd_priv_to_gen(priv);
+
+	return gen->ops->path_exit(priv, rdai, io);
+}
+
+void __iomem *rsnd_gen_reg_get(struct rsnd_priv *priv,
+			       struct rsnd_mod *mod,
+			       enum rsnd_reg reg)
+{
+	struct rsnd_gen *gen = rsnd_priv_to_gen(priv);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	int index;
+	u32 offset_id, offset_adr;
+
+	if (reg >= RSND_REG_MAX) {
+		dev_err(dev, "rsnd_reg reg error\n");
+		return NULL;
+	}
+
+	index		= gen->reg_map[reg].index;
+	offset_id	= gen->reg_map[reg].offset_id;
+	offset_adr	= gen->reg_map[reg].offset_adr;
+
+	if (index < 0) {
+		dev_err(dev, "unsupported reg access %d\n", reg);
+		return NULL;
+	}
+
+	if (offset_id && mod)
+		offset_id *= rsnd_mod_id(mod);
+
+	/*
+	 * index/offset were set on gen1/gen2
+	 */
+
+	return gen->base[index] + offset_id + offset_adr;
+}
+
+int rsnd_gen_probe(struct platform_device *pdev,
+		   struct rcar_snd_info *info,
+		   struct rsnd_priv *priv)
+{
+	struct device *dev = rsnd_priv_to_dev(priv);
+	struct rsnd_gen *gen;
+	int i;
+
+	gen = devm_kzalloc(dev, sizeof(*gen), GFP_KERNEL);
+	if (!gen) {
+		dev_err(dev, "GEN allocate failed\n");
+		return -ENOMEM;
+	}
+
+	priv->gen = gen;
+
+	/*
+	 * see
+	 *	rsnd_reg_get()
+	 *	rsnd_gen_probe()
+	 */
+	for (i = 0; i < RSND_REG_MAX; i++)
+		gen->reg_map[i].index = -1;
+
+	/*
+	 *	init each module
+	 */
+	if (rsnd_is_gen1(priv))
+		return rsnd_gen1_probe(pdev, info, priv);
+
+	dev_err(dev, "unknown generation R-Car sound device\n");
+
+	return -ENODEV;
+}
+
+void rsnd_gen_remove(struct platform_device *pdev,
+		     struct rsnd_priv *priv)
+{
+	if (rsnd_is_gen1(priv))
+		rsnd_gen1_remove(pdev, priv);
+}

+ 302 - 0
sound/soc/sh/rcar/rsnd.h

@@ -0,0 +1,302 @@
+/*
+ * Renesas R-Car
+ *
+ * Copyright (C) 2013 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@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.
+ */
+#ifndef RSND_H
+#define RSND_H
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/sh_dma.h>
+#include <linux/workqueue.h>
+#include <sound/rcar_snd.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+/*
+ *	pseudo register
+ *
+ * The register address offsets SRU/SCU/SSIU on Gen1/Gen2 are very different.
+ * This driver uses pseudo register in order to hide it.
+ * see gen1/gen2 for detail
+ */
+enum rsnd_reg {
+	/* SRU/SCU */
+	RSND_REG_SRC_ROUTE_SEL,
+	RSND_REG_SRC_TMG_SEL0,
+	RSND_REG_SRC_TMG_SEL1,
+	RSND_REG_SRC_TMG_SEL2,
+	RSND_REG_SRC_CTRL,
+	RSND_REG_SSI_MODE0,
+	RSND_REG_SSI_MODE1,
+	RSND_REG_BUSIF_MODE,
+	RSND_REG_BUSIF_ADINR,
+
+	/* ADG */
+	RSND_REG_BRRA,
+	RSND_REG_BRRB,
+	RSND_REG_SSICKR,
+	RSND_REG_AUDIO_CLK_SEL0,
+	RSND_REG_AUDIO_CLK_SEL1,
+	RSND_REG_AUDIO_CLK_SEL2,
+	RSND_REG_AUDIO_CLK_SEL3,
+	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,
+};
+
+struct rsnd_priv;
+struct rsnd_mod;
+struct rsnd_dai;
+struct rsnd_dai_stream;
+
+/*
+ *	R-Car basic functions
+ */
+#define rsnd_mod_read(m, r) \
+	rsnd_read(rsnd_mod_to_priv(m), m, RSND_REG_##r)
+#define rsnd_mod_write(m, r, d) \
+	rsnd_write(rsnd_mod_to_priv(m), m, RSND_REG_##r, d)
+#define rsnd_mod_bset(m, r, s, d) \
+	rsnd_bset(rsnd_mod_to_priv(m), m, RSND_REG_##r, s, d)
+
+#define rsnd_priv_read(p, r)		rsnd_read(p, NULL, RSND_REG_##r)
+#define rsnd_priv_write(p, r, d)	rsnd_write(p, NULL, RSND_REG_##r, d)
+#define rsnd_priv_bset(p, r, s, d)	rsnd_bset(p, NULL, RSND_REG_##r, s, d)
+
+u32 rsnd_read(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg);
+void rsnd_write(struct rsnd_priv *priv, struct rsnd_mod *mod,
+		enum rsnd_reg reg, u32 data);
+void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg,
+		    u32 mask, u32 data);
+
+/*
+ *	R-Car DMA
+ */
+struct rsnd_dma {
+	struct rsnd_priv	*priv;
+	struct sh_dmae_slave	slave;
+	struct work_struct	work;
+	struct dma_chan		*chan;
+	enum dma_data_direction dir;
+	int (*inquiry)(struct rsnd_dma *dma, dma_addr_t *buf, int *len);
+	int (*complete)(struct rsnd_dma *dma);
+
+	int submit_loop;
+};
+
+void rsnd_dma_start(struct rsnd_dma *dma);
+void rsnd_dma_stop(struct rsnd_dma *dma);
+int rsnd_dma_available(struct rsnd_dma *dma);
+int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma,
+	int is_play, int id,
+	int (*inquiry)(struct rsnd_dma *dma, dma_addr_t *buf, int *len),
+	int (*complete)(struct rsnd_dma *dma));
+void  rsnd_dma_quit(struct rsnd_priv *priv,
+		    struct rsnd_dma *dma);
+
+
+/*
+ *	R-Car sound mod
+ */
+
+struct rsnd_mod_ops {
+	char *name;
+	int (*init)(struct rsnd_mod *mod,
+		    struct rsnd_dai *rdai,
+		    struct rsnd_dai_stream *io);
+	int (*quit)(struct rsnd_mod *mod,
+		    struct rsnd_dai *rdai,
+		    struct rsnd_dai_stream *io);
+	int (*start)(struct rsnd_mod *mod,
+		     struct rsnd_dai *rdai,
+		     struct rsnd_dai_stream *io);
+	int (*stop)(struct rsnd_mod *mod,
+		    struct rsnd_dai *rdai,
+		    struct rsnd_dai_stream *io);
+};
+
+struct rsnd_mod {
+	int id;
+	struct rsnd_priv *priv;
+	struct rsnd_mod_ops *ops;
+	struct list_head list; /* connect to rsnd_dai playback/capture */
+	struct rsnd_dma dma;
+};
+
+#define rsnd_mod_to_priv(mod) ((mod)->priv)
+#define rsnd_mod_to_dma(mod) (&(mod)->dma)
+#define rsnd_dma_to_mod(_dma) container_of((_dma), struct rsnd_mod, dma)
+#define rsnd_mod_id(mod) ((mod)->id)
+#define for_each_rsnd_mod(pos, n, io)	\
+	list_for_each_entry_safe(pos, n, &(io)->head, list)
+#define rsnd_mod_call(mod, func, rdai, io)	\
+	(!(mod) ? -ENODEV :			\
+	 !((mod)->ops->func) ? 0 :		\
+	 (mod)->ops->func(mod, rdai, io))
+
+void rsnd_mod_init(struct rsnd_priv *priv,
+		   struct rsnd_mod *mod,
+		   struct rsnd_mod_ops *ops,
+		   int id);
+char *rsnd_mod_name(struct rsnd_mod *mod);
+
+/*
+ *	R-Car sound DAI
+ */
+#define RSND_DAI_NAME_SIZE	16
+struct rsnd_dai_stream {
+	struct list_head head; /* head of rsnd_mod list */
+	struct snd_pcm_substream *substream;
+	int byte_pos;
+	int period_pos;
+	int byte_per_period;
+	int next_period_byte;
+};
+
+struct rsnd_dai {
+	char name[RSND_DAI_NAME_SIZE];
+	struct rsnd_dai_platform_info *info; /* rcar_snd.h */
+	struct rsnd_dai_stream playback;
+	struct rsnd_dai_stream capture;
+
+	int clk_master:1;
+	int bit_clk_inv:1;
+	int frm_clk_inv:1;
+	int sys_delay:1;
+	int data_alignment:1;
+};
+
+#define rsnd_dai_nr(priv) ((priv)->dai_nr)
+#define for_each_rsnd_dai(rdai, priv, i)		\
+	for (i = 0, (rdai) = rsnd_dai_get(priv, i);	\
+	     i < rsnd_dai_nr(priv);			\
+	     i++, (rdai) = rsnd_dai_get(priv, i))
+
+struct rsnd_dai *rsnd_dai_get(struct rsnd_priv *priv, int id);
+int rsnd_dai_disconnect(struct rsnd_mod *mod);
+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);
+int rsnd_dai_id(struct rsnd_priv *priv, struct rsnd_dai *rdai);
+#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);
+
+/*
+ *	R-Car Gen1/Gen2
+ */
+int rsnd_gen_probe(struct platform_device *pdev,
+		   struct rcar_snd_info *info,
+		   struct rsnd_priv *priv);
+void rsnd_gen_remove(struct platform_device *pdev,
+		     struct rsnd_priv *priv);
+int rsnd_gen_path_init(struct rsnd_priv *priv,
+		       struct rsnd_dai *rdai,
+		       struct rsnd_dai_stream *io);
+int rsnd_gen_path_exit(struct rsnd_priv *priv,
+		       struct rsnd_dai *rdai,
+		       struct rsnd_dai_stream *io);
+void __iomem *rsnd_gen_reg_get(struct rsnd_priv *priv,
+			       struct rsnd_mod *mod,
+			       enum rsnd_reg reg);
+#define rsnd_is_gen1(s)		((s)->info->flags & RSND_GEN1)
+#define rsnd_is_gen2(s)		((s)->info->flags & RSND_GEN2)
+
+/*
+ *	R-Car ADG
+ */
+int rsnd_adg_ssi_clk_stop(struct rsnd_mod *mod);
+int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *mod, unsigned int rate);
+int rsnd_adg_probe(struct platform_device *pdev,
+		   struct rcar_snd_info *info,
+		   struct rsnd_priv *priv);
+void rsnd_adg_remove(struct platform_device *pdev,
+		   struct rsnd_priv *priv);
+
+/*
+ *	R-Car sound priv
+ */
+struct rsnd_priv {
+
+	struct device *dev;
+	struct rcar_snd_info *info;
+	spinlock_t lock;
+
+	/*
+	 * below value will be filled on rsnd_gen_probe()
+	 */
+	void *gen;
+
+	/*
+	 * below value will be filled on rsnd_scu_probe()
+	 */
+	void *scu;
+	int scu_nr;
+
+	/*
+	 * below value will be filled on rsnd_adg_probe()
+	 */
+	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;
+	struct rsnd_dai *rdai;
+	int dai_nr;
+};
+
+#define rsnd_priv_to_dev(priv)	((priv)->dev)
+#define rsnd_lock(priv, flags) spin_lock_irqsave(&priv->lock, flags)
+#define rsnd_unlock(priv, flags) spin_unlock_irqrestore(&priv->lock, flags)
+
+/*
+ *	R-Car SCU
+ */
+int rsnd_scu_probe(struct platform_device *pdev,
+		   struct rcar_snd_info *info,
+		   struct rsnd_priv *priv);
+void rsnd_scu_remove(struct platform_device *pdev,
+		     struct rsnd_priv *priv);
+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);
+struct rsnd_mod *rsnd_ssi_mod_get_frm_dai(struct rsnd_priv *priv,
+					  int dai_id, int is_play);
+
+#endif

+ 236 - 0
sound/soc/sh/rcar/scu.c

@@ -0,0 +1,236 @@
+/*
+ * Renesas R-Car SCU support
+ *
+ * Copyright (C) 2013 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@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 "rsnd.h"
+
+struct rsnd_scu {
+	struct rsnd_scu_platform_info *info; /* rcar_snd.h */
+	struct rsnd_mod mod;
+};
+
+#define rsnd_scu_mode_flags(p) ((p)->info->flags)
+
+/*
+ * ADINR
+ */
+#define OTBL_24		(0 << 16)
+#define OTBL_22		(2 << 16)
+#define OTBL_20		(4 << 16)
+#define OTBL_18		(6 << 16)
+#define OTBL_16		(8 << 16)
+
+
+#define rsnd_mod_to_scu(_mod)	\
+	container_of((_mod), struct rsnd_scu, mod)
+
+#define for_each_rsnd_scu(pos, priv, i)					\
+	for ((i) = 0;							\
+	     ((i) < rsnd_scu_nr(priv)) &&				\
+		     ((pos) = (struct rsnd_scu *)(priv)->scu + i);	\
+	     i++)
+
+static int rsnd_scu_set_route(struct rsnd_priv *priv,
+			      struct rsnd_mod *mod,
+			      struct rsnd_dai *rdai,
+			      struct rsnd_dai_stream *io)
+{
+	struct scu_route_config {
+		u32 mask;
+		int shift;
+	} routes[] = {
+		{ 0xF,  0, }, /* 0 */
+		{ 0xF,  4, }, /* 1 */
+		{ 0xF,  8, }, /* 2 */
+		{ 0x7, 12, }, /* 3 */
+		{ 0x7, 16, }, /* 4 */
+		{ 0x7, 20, }, /* 5 */
+		{ 0x7, 24, }, /* 6 */
+		{ 0x3, 28, }, /* 7 */
+		{ 0x3, 30, }, /* 8 */
+	};
+
+	u32 mask;
+	u32 val;
+	int shift;
+	int id;
+
+	/*
+	 * Gen1 only
+	 */
+	if (!rsnd_is_gen1(priv))
+		return 0;
+
+	id = rsnd_mod_id(mod);
+	if (id < 0 || id > ARRAY_SIZE(routes))
+		return -EIO;
+
+	/*
+	 * SRC_ROUTE_SELECT
+	 */
+	val = rsnd_dai_is_play(rdai, io) ? 0x1 : 0x2;
+	val = val		<< routes[id].shift;
+	mask = routes[id].mask	<< routes[id].shift;
+
+	rsnd_mod_bset(mod, SRC_ROUTE_SEL, mask, val);
+
+	/*
+	 * SRC_TIMING_SELECT
+	 */
+	shift	= (id % 4) * 8;
+	mask	= 0x1F << shift;
+	if (8 == id) /* SRU8 is very special */
+		val = id << shift;
+	else
+		val = (id + 1) << shift;
+
+	switch (id / 4) {
+	case 0:
+		rsnd_mod_bset(mod, SRC_TMG_SEL0, mask, val);
+		break;
+	case 1:
+		rsnd_mod_bset(mod, SRC_TMG_SEL1, mask, val);
+		break;
+	case 2:
+		rsnd_mod_bset(mod, SRC_TMG_SEL2, mask, val);
+		break;
+	}
+
+	return 0;
+}
+
+static int rsnd_scu_set_mode(struct rsnd_priv *priv,
+			     struct rsnd_mod *mod,
+			     struct rsnd_dai *rdai,
+			     struct rsnd_dai_stream *io)
+{
+	int id = rsnd_mod_id(mod);
+	u32 val;
+
+	if (rsnd_is_gen1(priv)) {
+		val = (1 << id);
+		rsnd_mod_bset(mod, SRC_CTRL, val, val);
+	}
+
+	return 0;
+}
+
+static int rsnd_scu_set_hpbif(struct rsnd_priv *priv,
+			      struct rsnd_mod *mod,
+			      struct rsnd_dai *rdai,
+			      struct rsnd_dai_stream *io)
+{
+	struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+	u32 adinr = runtime->channels;
+
+	switch (runtime->sample_bits) {
+	case 16:
+		adinr |= OTBL_16;
+		break;
+	case 32:
+		adinr |= OTBL_24;
+		break;
+	default:
+		return -EIO;
+	}
+
+	rsnd_mod_write(mod, BUSIF_MODE, 1);
+	rsnd_mod_write(mod, BUSIF_ADINR, adinr);
+
+	return 0;
+}
+
+static int rsnd_scu_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_scu *scu = rsnd_mod_to_scu(mod);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	u32 flags = rsnd_scu_mode_flags(scu);
+	int ret;
+
+	/*
+	 * SCU will be used if it has RSND_SCU_USB_HPBIF flags
+	 */
+	if (!(flags & RSND_SCU_USB_HPBIF)) {
+		/* it use PIO transter */
+		dev_dbg(dev, "%s%d is not used\n",
+			rsnd_mod_name(mod), rsnd_mod_id(mod));
+
+		return 0;
+	}
+
+	/* it use DMA transter */
+	ret = rsnd_scu_set_route(priv, mod, rdai, io);
+	if (ret < 0)
+		return ret;
+
+	ret = rsnd_scu_set_mode(priv, mod, rdai, io);
+	if (ret < 0)
+		return ret;
+
+	ret = rsnd_scu_set_hpbif(priv, mod, rdai, io);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(dev, "%s%d start\n", rsnd_mod_name(mod), rsnd_mod_id(mod));
+
+	return 0;
+}
+
+static struct rsnd_mod_ops rsnd_scu_ops = {
+	.name	= "scu",
+	.start	= rsnd_scu_start,
+};
+
+struct rsnd_mod *rsnd_scu_mod_get(struct rsnd_priv *priv, int id)
+{
+	BUG_ON(id < 0 || id >= rsnd_scu_nr(priv));
+
+	return &((struct rsnd_scu *)(priv->scu) + id)->mod;
+}
+
+int rsnd_scu_probe(struct platform_device *pdev,
+		   struct rcar_snd_info *info,
+		   struct rsnd_priv *priv)
+{
+	struct device *dev = rsnd_priv_to_dev(priv);
+	struct rsnd_scu *scu;
+	int i, nr;
+
+	/*
+	 * init SCU
+	 */
+	nr	= info->scu_info_nr;
+	scu	= devm_kzalloc(dev, sizeof(*scu) * nr, GFP_KERNEL);
+	if (!scu) {
+		dev_err(dev, "SCU allocate failed\n");
+		return -ENOMEM;
+	}
+
+	priv->scu_nr	= nr;
+	priv->scu	= scu;
+
+	for_each_rsnd_scu(scu, priv, i) {
+		rsnd_mod_init(priv, &scu->mod,
+			      &rsnd_scu_ops, i);
+		scu->info = &info->scu_info[i];
+
+		dev_dbg(dev, "SCU%d probed\n", i);
+	}
+	dev_dbg(dev, "scu probed\n");
+
+	return 0;
+}
+
+void rsnd_scu_remove(struct platform_device *pdev,
+		     struct rsnd_priv *priv)
+{
+}

+ 728 - 0
sound/soc/sh/rcar/ssi.c

@@ -0,0 +1,728 @@
+/*
+ * 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	DMEN		(1 << 28)	/* DMA Enable */
+#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 */
+
+/*
+ * SSIWSR
+ */
+#define CONT		(1 << 8)	/* WS Continue Function */
+
+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;
+	int dma_offset;
+	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_dma_to_ssi(dma)  rsnd_mod_to_ssi(rsnd_dma_to_mod(dma))
+#define rsnd_ssi_pio_available(ssi) ((ssi)->info->pio_irq > 0)
+#define rsnd_ssi_dma_available(ssi) \
+	rsnd_dma_available(rsnd_mod_to_dma(&(ssi)->mod))
+#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_dai_id(ssi) ((ssi)->info->dai_id)
+#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 device *dev = rsnd_priv_to_dev(priv);
+	struct rsnd_ssi *ssi;
+	u32 flags;
+	u32 val;
+	int i;
+
+	/*
+	 * SSI_MODE0
+	 */
+	ssiu->ssi_mode0 = 0;
+	for_each_rsnd_ssi(ssi, priv, i) {
+		flags = rsnd_ssi_mode_flags(ssi);
+
+		/* see also BUSIF_MODE */
+		if (!(flags & RSND_SSI_DEPENDENT)) {
+			ssiu->ssi_mode0 |= (1 << i);
+			dev_dbg(dev, "SSI%d uses INDEPENDENT mode\n", i);
+		} else {
+			dev_dbg(dev, "SSI%d uses DEPENDENT mode\n", 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,
+};
+
+static int rsnd_ssi_dma_inquiry(struct rsnd_dma *dma, dma_addr_t *buf, int *len)
+{
+	struct rsnd_ssi *ssi = rsnd_dma_to_ssi(dma);
+	struct rsnd_dai_stream *io = ssi->io;
+	struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+
+	*len = io->byte_per_period;
+	*buf = runtime->dma_addr +
+		rsnd_dai_pointer_offset(io, ssi->dma_offset + *len);
+	ssi->dma_offset = *len; /* it cares A/B plane */
+
+	return 0;
+}
+
+static int rsnd_ssi_dma_complete(struct rsnd_dma *dma)
+{
+	struct rsnd_ssi *ssi = rsnd_dma_to_ssi(dma);
+	struct rsnd_dai_stream *io = ssi->io;
+	u32 status = rsnd_mod_read(&ssi->mod, SSISR);
+
+	rsnd_ssi_record_error(ssi, status);
+
+	rsnd_dai_pointer_update(ssi->io, io->byte_per_period);
+
+	return 0;
+}
+
+static int rsnd_ssi_dma_start(struct rsnd_mod *mod,
+			      struct rsnd_dai *rdai,
+			      struct rsnd_dai_stream *io)
+{
+	struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+	struct rsnd_dma *dma = rsnd_mod_to_dma(&ssi->mod);
+
+	/* enable DMA transfer */
+	ssi->cr_etc = DMEN;
+	ssi->dma_offset = 0;
+
+	rsnd_dma_start(dma);
+
+	rsnd_ssi_hw_start(ssi, ssi->rdai, io);
+
+	/* enable WS continue */
+	if (rsnd_rdai_is_clk_master(rdai))
+		rsnd_mod_write(&ssi->mod, SSIWSR, CONT);
+
+	return 0;
+}
+
+static int rsnd_ssi_dma_stop(struct rsnd_mod *mod,
+			     struct rsnd_dai *rdai,
+			     struct rsnd_dai_stream *io)
+{
+	struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+	struct rsnd_dma *dma = rsnd_mod_to_dma(&ssi->mod);
+
+	ssi->cr_etc = 0;
+
+	rsnd_ssi_hw_stop(ssi, rdai);
+
+	rsnd_dma_stop(dma);
+
+	return 0;
+}
+
+static struct rsnd_mod_ops rsnd_ssi_dma_ops = {
+	.name	= "ssi (dma)",
+	.init	= rsnd_ssi_init,
+	.quit	= rsnd_ssi_quit,
+	.start	= rsnd_ssi_dma_start,
+	.stop	= rsnd_ssi_dma_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_frm_dai(struct rsnd_priv *priv,
+					  int dai_id, int is_play)
+{
+	struct rsnd_ssi *ssi;
+	int i, has_play;
+
+	is_play = !!is_play;
+
+	for_each_rsnd_ssi(ssi, priv, i) {
+		if (rsnd_ssi_dai_id(ssi) != dai_id)
+			continue;
+
+		has_play = !!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_PLAY);
+
+		if (is_play == has_play)
+			return &ssi->mod;
+	}
+
+	return NULL;
+}
+
+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 DMA case
+		 */
+		if (pinfo->dma_id > 0) {
+			ret = rsnd_dma_init(
+				priv, rsnd_mod_to_dma(&ssi->mod),
+				(rsnd_ssi_mode_flags(ssi) & RSND_SSI_PLAY),
+				pinfo->dma_id,
+				rsnd_ssi_dma_inquiry,
+				rsnd_ssi_dma_complete);
+			if (ret < 0)
+				dev_info(dev, "SSI DMA failed. try PIO transter\n");
+			else
+				ops	= &rsnd_ssi_dma_ops;
+
+			dev_dbg(dev, "SSI%d use DMA transfer\n", i);
+		}
+
+		/*
+		 * SSI PIO case
+		 */
+		if (!rsnd_ssi_dma_available(ssi) &&
+		     rsnd_ssi_pio_available(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;
+
+			dev_dbg(dev, "SSI%d use PIO transfer\n", i);
+		}
+
+		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);
+		if (rsnd_ssi_dma_available(ssi))
+			rsnd_dma_quit(priv, rsnd_mod_to_dma(&ssi->mod));
+	}
+
+}