Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bff58ea4 authored by Kuninori Morimoto's avatar Kuninori Morimoto Committed by Mark Brown
Browse files

ASoC: rsnd: add DVC support



This patch adds DVC (Digital Volume Controller)
support which is member of CMD unit.

Signed-off-by: default avatarKuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: default avatarMark Brown <broonie@linaro.org>
parent 68b6af36
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -56,9 +56,17 @@ struct rsnd_src_platform_info {
	int dma_id; /* for Gen2 SCU */
};

/*
 * flags
 */
struct rsnd_dvc_platform_info {
	u32 flags;
};

struct rsnd_dai_path_info {
	struct rsnd_ssi_platform_info *ssi;
	struct rsnd_src_platform_info *src;
	struct rsnd_dvc_platform_info *dvc;
};

struct rsnd_dai_platform_info {
@@ -83,6 +91,8 @@ struct rcar_snd_info {
	int ssi_info_nr;
	struct rsnd_src_platform_info *src_info;
	int src_info_nr;
	struct rsnd_dvc_platform_info *dvc_info;
	int dvc_info_nr;
	struct rsnd_dai_platform_info *dai_info;
	int dai_info_nr;
	int (*start)(int id);
+1 −1
Original line number Diff line number Diff line
snd-soc-rcar-objs	:= core.o gen.o src.o adg.o ssi.o
snd-soc-rcar-objs	:= core.o gen.o src.o adg.o ssi.o dvc.o
obj-$(CONFIG_SND_SOC_RCAR)	+= snd-soc-rcar.o
 No newline at end of file
+18 −0
Original line number Diff line number Diff line
@@ -57,6 +57,24 @@ static u32 rsnd_adg_ssi_ws_timing_gen2(struct rsnd_dai_stream *io)
	return (0x6 + ws) << 8;
}

int rsnd_adg_set_cmd_timsel_gen2(struct rsnd_dai *rdai,
				 struct rsnd_mod *mod,
				 struct rsnd_dai_stream *io)
{
	int id = rsnd_mod_id(mod);
	int shift = (id % 2) ? 16 : 0;
	u32 mask, val;

	val = rsnd_adg_ssi_ws_timing_gen2(io);

	val  = val	<< shift;
	mask = 0xffff	<< shift;

	rsnd_mod_bset(mod, CMDOUT_TIMSEL, mask, val);

	return 0;
}

static int rsnd_adg_set_src_timsel_gen2(struct rsnd_dai *rdai,
					struct rsnd_mod *mod,
					struct rsnd_dai_stream *io,
+20 −0
Original line number Diff line number Diff line
@@ -645,6 +645,11 @@ static int rsnd_path_init(struct rsnd_priv *priv,
	if (ret < 0)
		return ret;

	/* DVC */
	ret = rsnd_path_parse(priv, io, dvc);
	if (ret < 0)
		return ret;

	return ret;
}

@@ -869,6 +874,20 @@ static struct snd_pcm_ops rsnd_pcm_ops = {

static int rsnd_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
	struct rsnd_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
	struct rsnd_dai *rdai;
	int i, ret;

	for_each_rsnd_dai(rdai, priv, i) {
		ret = rsnd_dai_call(pcm_new, &rdai->playback, rdai, rtd);
		if (ret)
			return ret;

		ret = rsnd_dai_call(pcm_new, &rdai->capture, rdai, rtd);
		if (ret)
			return ret;
	}

	return snd_pcm_lib_preallocate_pages_for_all(
		rtd->pcm,
		SNDRV_DMA_TYPE_DEV,
@@ -908,6 +927,7 @@ static int rsnd_probe(struct platform_device *pdev)
		rsnd_gen_probe,
		rsnd_ssi_probe,
		rsnd_src_probe,
		rsnd_dvc_probe,
		rsnd_adg_probe,
		rsnd_dai_probe,
	};
+273 −0
Original line number Diff line number Diff line
/*
 * Renesas R-Car DVC support
 *
 * Copyright (C) 2014 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"

#define RSND_DVC_NAME_SIZE	16
#define RSND_DVC_VOLUME_MAX	100
#define RSND_DVC_VOLUME_NUM	2
struct rsnd_dvc {
	struct rsnd_dvc_platform_info *info; /* rcar_snd.h */
	struct rsnd_mod mod;
	struct clk *clk;
	long volume[RSND_DVC_VOLUME_NUM];
};

#define rsnd_mod_to_dvc(_mod)	\
	container_of((_mod), struct rsnd_dvc, mod)

#define for_each_rsnd_dvc(pos, priv, i)				\
	for ((i) = 0;						\
	     ((i) < rsnd_dvc_nr(priv)) &&			\
	     ((pos) = (struct rsnd_dvc *)(priv)->dvc + i);	\
	     i++)

static void rsnd_dvc_volume_update(struct rsnd_mod *mod)
{
	struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);
	u32 max = (0x00800000 - 1);
	u32 vol[RSND_DVC_VOLUME_NUM];
	int i;

	for (i = 0; i < RSND_DVC_VOLUME_NUM; i++)
		vol[i] = max / RSND_DVC_VOLUME_MAX * dvc->volume[i];

	rsnd_mod_write(mod, DVC_VOL0R, vol[0]);
	rsnd_mod_write(mod, DVC_VOL1R, vol[1]);
}

static int rsnd_dvc_init(struct rsnd_mod *dvc_mod,
			 struct rsnd_dai *rdai)
{
	struct rsnd_dvc *dvc = rsnd_mod_to_dvc(dvc_mod);
	struct rsnd_dai_stream *io = rsnd_mod_to_io(dvc_mod);
	struct rsnd_priv *priv = rsnd_mod_to_priv(dvc_mod);
	struct rsnd_mod *src_mod = rsnd_io_to_mod_src(io);
	struct device *dev = rsnd_priv_to_dev(priv);
	int dvc_id = rsnd_mod_id(dvc_mod);
	int src_id = rsnd_mod_id(src_mod);
	u32 route[] = {
		[0] = 0x30000,
		[1] = 0x30001,
		[2] = 0x40000,
		[3] = 0x10000,
		[4] = 0x20000,
		[5] = 0x40100
	};

	if (src_id >= ARRAY_SIZE(route)) {
		dev_err(dev, "DVC%d isn't connected to SRC%d\n", dvc_id, src_id);
		return -EINVAL;
	}

	clk_prepare_enable(dvc->clk);

	/*
	 * fixme
	 * it doesn't support CTU/MIX
	 */
	rsnd_mod_write(dvc_mod, CMD_ROUTE_SLCT, route[src_id]);

	rsnd_mod_write(dvc_mod, DVC_SWRSR, 0);
	rsnd_mod_write(dvc_mod, DVC_SWRSR, 1);

	rsnd_mod_write(dvc_mod, DVC_DVUIR, 1);

	rsnd_mod_write(dvc_mod, DVC_ADINR, rsnd_get_adinr(dvc_mod));

	/*  enable Volume  */
	rsnd_mod_write(dvc_mod, DVC_DVUCR, 0x100);

	/* ch0/ch1 Volume */
	rsnd_dvc_volume_update(dvc_mod);

	rsnd_mod_write(dvc_mod, DVC_DVUIR, 0);

	rsnd_mod_write(dvc_mod, DVC_DVUER, 1);

	rsnd_adg_set_cmd_timsel_gen2(rdai, dvc_mod, io);

	return 0;
}

static int rsnd_dvc_quit(struct rsnd_mod *mod,
			 struct rsnd_dai *rdai)
{
	struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);

	clk_disable_unprepare(dvc->clk);

	return 0;
}

static int rsnd_dvc_start(struct rsnd_mod *mod,
			  struct rsnd_dai *rdai)
{
	rsnd_mod_write(mod, CMD_CTRL, 0x10);

	return 0;
}

static int rsnd_dvc_stop(struct rsnd_mod *mod,
			 struct rsnd_dai *rdai)
{
	rsnd_mod_write(mod, CMD_CTRL, 0);

	return 0;
}

static int rsnd_dvc_volume_info(struct snd_kcontrol *kctrl,
			       struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = RSND_DVC_VOLUME_NUM;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = RSND_DVC_VOLUME_MAX;

	return 0;
}

static int rsnd_dvc_volume_get(struct snd_kcontrol *kctrl,
			      struct snd_ctl_elem_value *ucontrol)
{
	struct rsnd_mod *mod = snd_kcontrol_chip(kctrl);
	struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);
	int i;

	for (i = 0; i < RSND_DVC_VOLUME_NUM; i++)
		ucontrol->value.integer.value[i] = dvc->volume[i];

	return 0;
}

static int rsnd_dvc_volume_put(struct snd_kcontrol *kctrl,
			      struct snd_ctl_elem_value *ucontrol)
{
	struct rsnd_mod *mod = snd_kcontrol_chip(kctrl);
	struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);
	int i, change = 0;

	for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) {
		if (ucontrol->value.integer.value[i] < 0 ||
		    ucontrol->value.integer.value[i] > RSND_DVC_VOLUME_MAX)
			return -EINVAL;

		change |= (ucontrol->value.integer.value[i] != dvc->volume[i]);
	}

	if (change) {
		for (i = 0; i < RSND_DVC_VOLUME_NUM; i++)
			dvc->volume[i] = ucontrol->value.integer.value[i];

		rsnd_dvc_volume_update(mod);
	}

	return change;
}

static int rsnd_dvc_pcm_new(struct rsnd_mod *mod,
			    struct rsnd_dai *rdai,
			    struct snd_soc_pcm_runtime *rtd)
{
	struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
	struct device *dev = rsnd_priv_to_dev(priv);
	struct snd_card *card = rtd->card->snd_card;
	struct snd_kcontrol *kctrl;
	static struct snd_kcontrol_new knew = {
		.iface		= SNDRV_CTL_ELEM_IFACE_MIXER,
		.name		= "Playback Volume",
		.info		= rsnd_dvc_volume_info,
		.get		= rsnd_dvc_volume_get,
		.put		= rsnd_dvc_volume_put,
	};
	int ret;

	if (!rsnd_dai_is_play(rdai, io)) {
		dev_err(dev, "DVC%d is connected to Capture DAI\n",
			rsnd_mod_id(mod));
		return -EINVAL;
	}

	kctrl = snd_ctl_new1(&knew, mod);
	if (!kctrl)
		return -ENOMEM;

	ret = snd_ctl_add(card, kctrl);
	if (ret < 0)
		return ret;

	return 0;
}

static struct rsnd_mod_ops rsnd_dvc_ops = {
	.name		= "dvc (gen2)",
	.init		= rsnd_dvc_init,
	.quit		= rsnd_dvc_quit,
	.start		= rsnd_dvc_start,
	.stop		= rsnd_dvc_stop,
	.pcm_new	= rsnd_dvc_pcm_new,
};

struct rsnd_mod *rsnd_dvc_mod_get(struct rsnd_priv *priv, int id)
{
	if (WARN_ON(id < 0 || id >= rsnd_dvc_nr(priv)))
		id = 0;

	return &((struct rsnd_dvc *)(priv->dvc) + id)->mod;
}

int rsnd_dvc_probe(struct platform_device *pdev,
		   const struct rsnd_of_data *of_data,
		   struct rsnd_priv *priv)
{
	struct rcar_snd_info *info = rsnd_priv_to_info(priv);
	struct device *dev = rsnd_priv_to_dev(priv);
	struct rsnd_dvc *dvc;
	struct clk *clk;
	char name[RSND_DVC_NAME_SIZE];
	int i, nr;

	nr = info->dvc_info_nr;
	if (!nr)
		return 0;

	/* This driver doesn't support Gen1 at this point */
	if (rsnd_is_gen1(priv)) {
		dev_warn(dev, "CMD is not supported on Gen1\n");
		return -EINVAL;
	}

	dvc	= devm_kzalloc(dev, sizeof(*dvc) * nr, GFP_KERNEL);
	if (!dvc) {
		dev_err(dev, "CMD allocate failed\n");
		return -ENOMEM;
	}

	priv->dvc_nr	= nr;
	priv->dvc	= dvc;

	for_each_rsnd_dvc(dvc, priv, i) {
		snprintf(name, RSND_DVC_NAME_SIZE, "dvc.%d", i);

		clk = devm_clk_get(dev, name);
		if (IS_ERR(clk))
			return PTR_ERR(clk);

		dvc->info = &info->dvc_info[i];
		dvc->clk  = clk;

		rsnd_mod_init(priv, &dvc->mod, &rsnd_dvc_ops, RSND_MOD_DVC, i);

		dev_dbg(dev, "CMD%d probed\n", i);
	}

	return 0;
}
Loading