[alsa-devel] [RFC 2/2] ASoC: img: Add driver for Pistachio audio card
Damien Horsley
Damien.Horsley at imgtec.com
Mon Jan 25 20:12:12 CET 2016
From: "Damien.Horsley" <Damien.Horsley at imgtec.com>
Add a driver for Pistachio audio card
Signed-off-by: Damien.Horsley <Damien.Horsley at imgtec.com>
---
include/dt-bindings/sound/pistachio-audio.h | 7 +
sound/soc/img/Kconfig | 10 +
sound/soc/img/Makefile | 1 +
sound/soc/img/pistachio.c | 2249 +++++++++++++++++++++++++++
4 files changed, 2267 insertions(+)
create mode 100644 include/dt-bindings/sound/pistachio-audio.h
create mode 100644 sound/soc/img/pistachio.c
diff --git a/include/dt-bindings/sound/pistachio-audio.h b/include/dt-bindings/sound/pistachio-audio.h
new file mode 100644
index 0000000..ac34261
--- /dev/null
+++ b/include/dt-bindings/sound/pistachio-audio.h
@@ -0,0 +1,7 @@
+#ifndef __PISTACHIO_AUDIO_H
+#define __PISTACHIO_AUDIO_H
+
+#define PISTACHIO_MCLK_I2S 0
+#define PISTACHIO_MCLK_DAC 1
+
+#endif
diff --git a/sound/soc/img/Kconfig b/sound/soc/img/Kconfig
index 857a951..9482de3 100644
--- a/sound/soc/img/Kconfig
+++ b/sound/soc/img/Kconfig
@@ -50,3 +50,13 @@ config SND_SOC_IMG_PISTACHIO_INTERNAL_DAC
help
Say Y or M if you want to add support for Pistachio internal DAC
driver for Imagination Technologies Pistachio internal DAC device.
+
+config SND_SOC_IMG_PISTACHIO
+ tristate "Audio support for Pistachio SoC"
+ depends on SND_SOC_IMG
+ select SND_SOC_IMG_I2S_IN
+ select SND_SOC_IMG_I2S_OUT
+ select SND_SOC_IMG_PARALLEL_OUT
+ select SND_SOC_IMG_SPDIF_IN
+ select SND_SOC_IMG_SPDIF_OUT
+ select SND_SOC_IMG_PISTACHIO_INTERNAL_DAC
\ No newline at end of file
diff --git a/sound/soc/img/Makefile b/sound/soc/img/Makefile
index 0508c1c..e72024f 100644
--- a/sound/soc/img/Makefile
+++ b/sound/soc/img/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_SND_SOC_IMG_SPDIF_IN) += img-spdif-in.o
obj-$(CONFIG_SND_SOC_IMG_SPDIF_OUT) += img-spdif-out.o
obj-$(CONFIG_SND_SOC_IMG_PISTACHIO_INTERNAL_DAC) += pistachio-internal-dac.o
+obj-$(CONFIG_SND_SOC_IMG_PISTACHIO) += pistachio.o
\ No newline at end of file
diff --git a/sound/soc/img/pistachio.c b/sound/soc/img/pistachio.c
new file mode 100644
index 0000000..b8d387a
--- /dev/null
+++ b/sound/soc/img/pistachio.c
@@ -0,0 +1,2249 @@
+/*
+ * Pistachio audio card driver
+ *
+ * Copyright (C) 2015 Imagination Technologies Ltd.
+ *
+ * Author: Damien Horsley <Damien.Horsley at imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <dt-bindings/sound/pistachio-audio.h>
+
+#define PISTACHIO_PLL_RATE_A 147456000
+#define PISTACHIO_PLL_RATE_B 135475200
+#define PISTACHIO_MAX_DIV 256
+#define PISTACHIO_MIN_MCLK_FREQ (135475200 / 256)
+
+#define PISTACHIO_CLOCK_MASTER_EXT -1
+#define PISTACHIO_CLOCK_MASTER_LOOPBACK -2
+
+#define PISTACHIO_MAX_I2S_CODECS 12
+
+#define PISTACHIO_MAX_FS_RATES 20
+
+#define PISTACHIO_I2S_MCLK_MAX_FREQ 200000000
+#define PISTACHIO_DAC_MCLK_MAX_FREQ 200000000
+
+#define PISTACHIO_INTERNAL_DAC_PREFIX "internal-dac"
+#define PISTACHIO_I2S_OUT_PREFIX "i2s-out"
+#define PISTACHIO_I2S_IN_PREFIX "i2s-in"
+
+#define PISTACHIO_I2S_MCLK_NAME "i2s_mclk"
+#define PISTACHIO_DAC_MCLK_NAME "dac_mclk"
+
+#define PISTACHIO_I2S_OUTPUT_NAME "I2S OUTPUT"
+#define PISTACHIO_I2S_INPUT_NAME "I2S INPUT"
+
+#define PISTACHIO_I2S_LOOPBACK_REG 0x88
+#define PISTACHIO_I2S_LOOPBACK_CLK_MASK 0x3
+
+#define PISTACHIO_I2S_LOOPBACK_CLK_NONE 0
+#define PISTACHIO_I2S_LOOPBACK_CLK_LOCAL 2
+
+#define PISTACHIO_MAX_DAPM_ROUTES 6
+
+struct pistachio_audio_output {
+ unsigned int active_rate;
+};
+
+struct pistachio_parallel_out {
+ struct pistachio_audio_output output;
+ struct snd_soc_dai_link_component component;
+};
+
+struct pistachio_mclk {
+ char *name;
+ struct clk *mclk;
+ unsigned int cur_rate;
+ unsigned int max_rate;
+};
+
+struct pistachio_i2s_mclk {
+ struct pistachio_mclk *mclk;
+ unsigned int *fs_rates;
+ unsigned int num_fs_rates;
+ unsigned int min_rate;
+ unsigned int max_rate;
+};
+
+struct pistachio_codec_i2s {
+ struct pistachio_mclk *mclk;
+ struct snd_soc_dai *dai;
+ unsigned int mclk_index;
+};
+
+struct pistachio_i2s {
+ struct pistachio_i2s_mclk mclk_a;
+ struct pistachio_i2s_mclk mclk_b;
+ struct pistachio_codec_i2s *codecs;
+ struct snd_soc_dai_link_component *components;
+ unsigned int num_codecs;
+};
+
+struct pistachio_i2s_out {
+ struct pistachio_i2s i2s;
+ struct pistachio_audio_output output;
+};
+
+struct pistachio_i2s_in {
+ struct pistachio_i2s i2s;
+ unsigned int active_rate;
+ unsigned int fmt;
+ int frame_master;
+ int bitclock_master;
+};
+
+struct pistachio_i2s_codec_info_s {
+ const char *prefix;
+ const char *dai_name;
+ struct device_node *np;
+ struct pistachio_mclk *mclk;
+ unsigned int mclk_index;
+};
+
+struct pistachio_i2s_codec_info {
+ unsigned int total_codecs;
+ unsigned int unique_codecs;
+ int bitclock_master_idx;
+ int frame_master_idx;
+ struct pistachio_i2s_codec_info_s codecs[PISTACHIO_MAX_I2S_CODECS];
+};
+
+struct pistachio_i2s_mclk_info {
+ unsigned int fs_rates[PISTACHIO_MAX_FS_RATES];
+ unsigned int num_fs_rates;
+ unsigned int min_rate;
+ unsigned int max_rate;
+};
+
+struct pistachio_card {
+ struct pistachio_audio_output *spdif_out;
+ struct pistachio_parallel_out *parallel_out;
+ struct pistachio_i2s_out *i2s_out;
+ struct pistachio_i2s_in *i2s_in;
+ bool spdif_in;
+ struct snd_soc_card card;
+ struct snd_soc_jack hp_jack;
+ struct snd_soc_jack_pin hp_jack_pin;
+ struct snd_soc_jack_gpio hp_jack_gpio;
+ unsigned int mute_gpio;
+ bool mute_gpio_inverted;
+ struct mutex rate_mutex;
+ struct clk *audio_pll;
+ unsigned int audio_pll_rate;
+ struct pistachio_mclk i2s_mclk;
+ struct pistachio_mclk dac_mclk;
+ struct regmap *periph_regs;
+ struct notifier_block i2s_clk_notifier;
+ struct snd_soc_dapm_route routes[PISTACHIO_MAX_DAPM_ROUTES];
+};
+
+static const struct snd_soc_dapm_widget pistachio_card_widgets[] = {
+ SND_SOC_DAPM_CLOCK_SUPPLY(PISTACHIO_I2S_MCLK_NAME),
+ SND_SOC_DAPM_CLOCK_SUPPLY(PISTACHIO_DAC_MCLK_NAME),
+ SND_SOC_DAPM_OUTPUT(PISTACHIO_I2S_OUTPUT_NAME),
+ SND_SOC_DAPM_INPUT(PISTACHIO_I2S_INPUT_NAME)
+};
+
+static int pistachio_card_set_sysclk_s(struct pistachio_codec_i2s *codec,
+ unsigned int rate, struct device *dev)
+{
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec->dai, codec->mclk_index,
+ rate, SND_SOC_CLOCK_IN);
+ if (ret)
+ dev_err(dev, "snd_soc_dai_set_sysclk failed: %d", ret);
+
+ return ret;
+}
+
+static int pistachio_card_set_sysclk(struct pistachio_i2s *i2s,
+ struct pistachio_mclk *mclk,
+ struct device *dev)
+{
+ int i, ret;
+ struct pistachio_codec_i2s *codec;
+ unsigned int rate = mclk->cur_rate;
+
+ for (i = 0; i < i2s->num_codecs; i++) {
+ codec = &i2s->codecs[i];
+ if (codec->mclk == mclk) {
+ ret = pistachio_card_set_sysclk_s(codec, rate, dev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int pistachio_card_set_mclk_codecs(struct pistachio_card *pbc,
+ struct pistachio_mclk *mclk)
+{
+ int ret;
+ struct device *dev = pbc->card.dev;
+
+ if (pbc->i2s_out) {
+ ret = pistachio_card_set_sysclk(&pbc->i2s_out->i2s, mclk, dev);
+ if (ret)
+ return ret;
+ }
+
+ if (pbc->i2s_in) {
+ ret = pistachio_card_set_sysclk(&pbc->i2s_in->i2s, mclk, dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool pistachio_card_mclk_active(struct pistachio_card *pbc,
+ struct pistachio_mclk *mclk)
+{
+ if (pbc->i2s_out && pbc->i2s_out->output.active_rate) {
+ if (pbc->i2s_out->i2s.mclk_a.mclk == mclk)
+ return true;
+ if (pbc->i2s_out->i2s.mclk_b.mclk == mclk)
+ return true;
+ }
+
+ if (pbc->i2s_in && pbc->i2s_in->active_rate) {
+ if (pbc->i2s_in->i2s.mclk_a.mclk == mclk)
+ return true;
+ if (pbc->i2s_in->i2s.mclk_b.mclk == mclk)
+ return true;
+ }
+
+ return false;
+}
+
+static int pistachio_card_set_mclk(struct pistachio_card *pbc,
+ struct pistachio_mclk *mclk,
+ unsigned int rate)
+{
+ int ret;
+ unsigned int old_rate = mclk->cur_rate;
+ struct device *dev = pbc->card.dev;
+
+ if (pistachio_card_mclk_active(pbc, mclk)) {
+ dev_err(dev, "%s in use, cannot change rate\n", mclk->name);
+ return -EBUSY;
+ }
+
+ /*
+ * Set cur_rate before the clk_set_rate call to stop the i2s
+ * mclk rate change callback rejecting the change
+ */
+ mclk->cur_rate = rate;
+ ret = clk_set_rate(mclk->mclk, rate);
+ if (ret) {
+ dev_err(dev, "clk_set_rate(%s, %u) failed: %d\n",
+ mclk->name, rate, ret);
+ mclk->cur_rate = old_rate;
+ return ret;
+ }
+
+ return pistachio_card_set_mclk_codecs(pbc, mclk);
+}
+
+static int pistachio_card_set_pll_rate(struct pistachio_card *pbc,
+ unsigned int rate)
+{
+ int ret;
+ unsigned int old_i2s_rate;
+ struct device *dev = pbc->card.dev;
+
+ /*
+ * If any configured streams are currently using a clock derived
+ * from the audio pll, a pll rate change cannot take place
+ */
+ if ((pbc->spdif_out && pbc->spdif_out->active_rate) ||
+ (pbc->parallel_out && pbc->parallel_out->output.active_rate) ||
+ (pbc->i2s_out && pbc->i2s_out->output.active_rate) ||
+ (pbc->i2s_in && pbc->i2s_in->active_rate &&
+ pbc->i2s_in->i2s.mclk_a.mclk)) {
+ dev_err(dev, "audio pll in use, cannot change rate\n");
+ return -EBUSY;
+ }
+
+ /*
+ * Set cur_rate before the clk_set_rate call to stop the i2s
+ * mclk rate change callback rejecting the change
+ */
+ old_i2s_rate = pbc->i2s_mclk.cur_rate;
+ pbc->i2s_mclk.cur_rate = rate / (pbc->audio_pll_rate / old_i2s_rate);
+
+ ret = clk_set_rate(pbc->audio_pll, rate);
+ if (ret) {
+ dev_err(dev, "clk_set_rate(audio_pll, %u) failed: %d\n",
+ rate, ret);
+ pbc->i2s_mclk.cur_rate = old_i2s_rate;
+ return ret;
+ }
+
+ pbc->audio_pll_rate = rate;
+
+ pbc->dac_mclk.cur_rate = rate / (pbc->audio_pll_rate /
+ pbc->dac_mclk.cur_rate);
+
+ ret = pistachio_card_set_mclk_codecs(pbc, &pbc->i2s_mclk);
+ if (ret)
+ return ret;
+
+ return pistachio_card_set_mclk_codecs(pbc, &pbc->dac_mclk);
+}
+
+static void pistachio_card_rate_err(struct pistachio_card *pbc,
+ struct pistachio_i2s_mclk *mclk_a,
+ struct pistachio_i2s_mclk *mclk_b,
+ unsigned int rate_a, unsigned int rate_b)
+{
+ char *dir_a, *dir_b;
+ struct device *dev = pbc->card.dev;
+
+ if (pbc->i2s_out && ((mclk_a == &pbc->i2s_out->i2s.mclk_a) ||
+ (mclk_a == &pbc->i2s_out->i2s.mclk_b))) {
+ dir_a = "I2S out";
+ } else {
+ dir_a = "I2S in";
+ }
+
+ if (pbc->i2s_out && ((mclk_b == &pbc->i2s_out->i2s.mclk_a) ||
+ (mclk_b == &pbc->i2s_out->i2s.mclk_b))) {
+ dir_b = "I2S out";
+ } else {
+ dir_b = "I2S in";
+ }
+
+ if (!mclk_b) {
+ dev_err(dev, "No valid rate for %s (%s sample rate %u)\n",
+ mclk_a->mclk->name, dir_a, rate_a);
+ } else {
+ dev_err(dev,
+ "No valid rate for %s (%s sample rate %u, %s sample rate %u)\n",
+ mclk_a->mclk->name, dir_a, rate_a, dir_b, rate_b);
+ }
+}
+
+static bool pistachio_card_mclk_ok(struct pistachio_i2s_mclk *mclk,
+ unsigned int rate)
+{
+ int i;
+ unsigned int mclk_rate;
+
+ if (!mclk)
+ return true;
+
+ mclk_rate = mclk->mclk->cur_rate;
+
+ if ((mclk_rate < mclk->min_rate) || (mclk_rate > mclk->max_rate))
+ return false;
+
+ for (i = 0; i < mclk->num_fs_rates; i++)
+ if ((rate * mclk->fs_rates[i]) == mclk_rate)
+ return true;
+
+ return false;
+}
+
+static int pistachio_card_get_mclk_rate(struct pistachio_card *pbc,
+ struct pistachio_i2s_mclk *mclk_a, struct pistachio_i2s_mclk *mclk_b,
+ unsigned int rate_a, unsigned int rate_b, unsigned int *p_mclk_rate)
+{
+ int i, j;
+ unsigned int div, total_div, mclk_rate;
+
+ /*
+ * If the current system clock rate is sufficient for the given
+ * sample rates, do not change the rate.
+ */
+ if (pistachio_card_mclk_ok(mclk_a, rate_a) &&
+ pistachio_card_mclk_ok(mclk_b, rate_b)) {
+ *p_mclk_rate = mclk_a->mclk->cur_rate;
+ return 0;
+ }
+
+ /* Calculate total divide (internal divide and Nfs combined) */
+ total_div = pbc->audio_pll_rate / rate_a;
+
+ /* Attempt to find an mclk rate that satisfies the constraints */
+ for (i = 0; i < mclk_a->num_fs_rates; i++) {
+
+ div = total_div / mclk_a->fs_rates[i];
+
+ if (div > PISTACHIO_MAX_DIV)
+ continue;
+
+ mclk_rate = pbc->audio_pll_rate / div;
+
+ if ((mclk_rate < mclk_a->min_rate) ||
+ (mclk_rate > mclk_a->max_rate) ||
+ (mclk_rate > mclk_a->mclk->max_rate))
+ continue;
+
+ if ((rate_a * mclk_a->fs_rates[i] * div) !=
+ pbc->audio_pll_rate)
+ continue;
+
+ if (!mclk_b)
+ break;
+
+ if ((mclk_rate < mclk_b->min_rate) ||
+ (mclk_rate > mclk_b->max_rate))
+ continue;
+
+ for (j = 0; j < mclk_b->num_fs_rates; j++)
+ if ((rate_b * mclk_b->fs_rates[j] * div) ==
+ pbc->audio_pll_rate)
+ break;
+
+ if (j != mclk_b->num_fs_rates)
+ break;
+ }
+
+ if (i == mclk_a->num_fs_rates) {
+ pistachio_card_rate_err(pbc, mclk_a, mclk_b, rate_a, rate_b);
+ return -EINVAL;
+ }
+
+ *p_mclk_rate = mclk_rate;
+
+ return 0;
+}
+
+static int pistachio_card_update_mclk(struct pistachio_card *pbc,
+ struct pistachio_i2s_mclk *mclk_a, struct pistachio_i2s_mclk *mclk_b,
+ unsigned int rate_a, unsigned int rate_b)
+{
+ struct pistachio_mclk *mclk = mclk_a->mclk;
+ unsigned int mclk_rate;
+ int ret;
+
+ ret = pistachio_card_get_mclk_rate(pbc, mclk_a, mclk_b, rate_a,
+ rate_b, &mclk_rate);
+ if (ret)
+ return ret;
+
+ if (mclk->cur_rate != mclk_rate)
+ return pistachio_card_set_mclk(pbc, mclk, mclk_rate);
+
+ return 0;
+}
+
+static int pistachio_card_update_mclk_single(struct pistachio_card *pbc,
+ struct pistachio_i2s_mclk *mclk, unsigned int rate)
+{
+ return pistachio_card_update_mclk(pbc, mclk, NULL, rate, 0);
+}
+
+static int pistachio_card_get_pll_rate(unsigned int rate, struct device *dev)
+{
+ switch (rate) {
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ case 192000:
+ return PISTACHIO_PLL_RATE_A;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ case 176400:
+ return PISTACHIO_PLL_RATE_B;
+ default:
+ dev_err(dev, "No suitable pll rate for sample rate %u\n", rate);
+ return -EINVAL;
+ }
+}
+
+static int _pistachio_card_change_rate(struct pistachio_card *pbc,
+ unsigned int rate,
+ struct pistachio_i2s *i2s)
+{
+ int ret;
+ unsigned int pll_rate;
+
+ ret = pistachio_card_get_pll_rate(rate, pbc->card.dev);
+ if (ret < 0)
+ return ret;
+
+ pll_rate = ret;
+
+ if (pbc->audio_pll_rate != pll_rate) {
+ ret = pistachio_card_set_pll_rate(pbc, pll_rate);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Nothing more to do if an mclk is not used. The individual
+ * cpu-dai drivers will make the required clock changes
+ */
+ if (!i2s)
+ return 0;
+
+ ret = pistachio_card_update_mclk_single(pbc, &i2s->mclk_a, rate);
+ if (ret)
+ return ret;
+
+ if (!i2s->mclk_b.mclk)
+ return 0;
+
+ return pistachio_card_update_mclk_single(pbc, &i2s->mclk_b, rate);
+}
+
+static int pistachio_card_change_rate(struct pistachio_card *pbc,
+ unsigned int rate, struct pistachio_i2s *i2s,
+ unsigned int *active_rate)
+{
+ int ret;
+
+ mutex_lock(&pbc->rate_mutex);
+ *active_rate = 0;
+ ret = _pistachio_card_change_rate(pbc, rate, i2s);
+ if (!ret)
+ *active_rate = rate;
+ mutex_unlock(&pbc->rate_mutex);
+
+ return ret;
+}
+
+static int pistachio_card_i2s_link_init(struct pistachio_i2s *i2s,
+ struct snd_soc_pcm_runtime *rtd,
+ struct device *dev)
+{
+ int ret, i;
+ unsigned long rate;
+ struct pistachio_codec_i2s *codec;
+
+ for (i = 0; i < i2s->num_codecs; i++) {
+ codec = &i2s->codecs[i];
+ codec->dai = rtd->codec_dais[i];
+ if (codec->mclk) {
+ rate = codec->mclk->cur_rate;
+ ret = pistachio_card_set_sysclk_s(codec, rate, dev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int pistachio_card_i2s_out_link_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+ struct device *dev = pbc->card.dev;
+
+ return pistachio_card_i2s_link_init(&pbc->i2s_out->i2s, rtd, dev);
+}
+
+static int pistachio_card_i2s_in_link_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+ int ret, i;
+ unsigned int fmt;
+ struct device *dev = pbc->card.dev;
+ u32 val;
+
+ ret = pistachio_card_i2s_link_init(&pbc->i2s_in->i2s, rtd, dev);
+ if (ret)
+ return ret;
+
+ if (pbc->i2s_in->frame_master == PISTACHIO_CLOCK_MASTER_LOOPBACK)
+ val = PISTACHIO_I2S_LOOPBACK_CLK_LOCAL;
+ else
+ val = PISTACHIO_I2S_LOOPBACK_CLK_NONE;
+
+ ret = regmap_update_bits(pbc->periph_regs, PISTACHIO_I2S_LOOPBACK_REG,
+ PISTACHIO_I2S_LOOPBACK_CLK_MASK, val);
+ if (ret) {
+ dev_err(pbc->card.dev, "regmap_update_bits failed: %d\n", ret);
+ return ret;
+ }
+
+ fmt = pbc->i2s_in->fmt | SND_SOC_DAIFMT_CBM_CFM;
+ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "snd_soc_dai_set_fmt (cpu) failed: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < pbc->i2s_in->i2s.num_codecs; i++) {
+ fmt = pbc->i2s_in->fmt;
+
+ if (i == pbc->i2s_in->frame_master)
+ if (i == pbc->i2s_in->bitclock_master)
+ fmt |= SND_SOC_DAIFMT_CBM_CFM;
+ else
+ fmt |= SND_SOC_DAIFMT_CBS_CFM;
+ else
+ if (i == pbc->i2s_in->bitclock_master)
+ fmt |= SND_SOC_DAIFMT_CBM_CFS;
+ else
+ fmt |= SND_SOC_DAIFMT_CBS_CFS;
+
+ ret = snd_soc_dai_set_fmt(rtd->codec_dais[i], fmt);
+ if (ret) {
+ dev_err(dev, "snd_soc_dai_set_fmt failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void pistachio_card_parallel_out_shutdown(struct snd_pcm_substream *st)
+{
+ struct snd_soc_pcm_runtime *rtd = st->private_data;
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+ pbc->parallel_out->output.active_rate = 0;
+}
+
+static int pistachio_card_parallel_out_hw_params(struct snd_pcm_substream *st,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = st->private_data;
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+ return pistachio_card_change_rate(pbc, params_rate(params), NULL,
+ &pbc->parallel_out->output.active_rate);
+}
+
+static struct snd_soc_ops pistachio_card_parallel_out_ops = {
+ .shutdown = pistachio_card_parallel_out_shutdown,
+ .hw_params = pistachio_card_parallel_out_hw_params
+};
+
+static void pistachio_card_spdif_out_shutdown(struct snd_pcm_substream *st)
+{
+ struct snd_soc_pcm_runtime *rtd = st->private_data;
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+ pbc->spdif_out->active_rate = 0;
+}
+
+static int pistachio_card_spdif_out_hw_params(struct snd_pcm_substream *st,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = st->private_data;
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+ return pistachio_card_change_rate(pbc, params_rate(params), NULL,
+ &pbc->spdif_out->active_rate);
+}
+
+static struct snd_soc_ops pistachio_card_spdif_out_ops = {
+ .shutdown = pistachio_card_spdif_out_shutdown,
+ .hw_params = pistachio_card_spdif_out_hw_params
+};
+
+static void pistachio_card_i2s_out_shutdown(struct snd_pcm_substream *st)
+{
+ struct snd_soc_pcm_runtime *rtd = st->private_data;
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+ pbc->i2s_out->output.active_rate = 0;
+}
+
+static int pistachio_card_i2s_out_hw_params(struct snd_pcm_substream *st,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = st->private_data;
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+ return pistachio_card_change_rate(pbc, params_rate(params),
+ &pbc->i2s_out->i2s,
+ &pbc->i2s_out->output.active_rate);
+}
+
+static struct snd_soc_ops pistachio_card_i2s_out_ops = {
+ .shutdown = pistachio_card_i2s_out_shutdown,
+ .hw_params = pistachio_card_i2s_out_hw_params
+};
+
+static void pistachio_card_i2s_in_shutdown(struct snd_pcm_substream *st)
+{
+ struct snd_soc_pcm_runtime *rtd = st->private_data;
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+ pbc->i2s_in->active_rate = 0;
+}
+
+static int pistachio_card_i2s_in_hw_params(struct snd_pcm_substream *st,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = st->private_data;
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+ return pistachio_card_change_rate(pbc, params_rate(params),
+ &pbc->i2s_in->i2s,
+ &pbc->i2s_in->active_rate);
+}
+
+static struct snd_soc_ops pistachio_card_i2s_in_ops = {
+ .shutdown = pistachio_card_i2s_in_shutdown,
+ .hw_params = pistachio_card_i2s_in_hw_params
+};
+
+static int pistachio_card_parse_spdif_out(struct device_node *node,
+ struct pistachio_card *pbc,
+ struct snd_soc_dai_link *link)
+{
+ struct device_node *np;
+ struct device *dev = pbc->card.dev;
+
+ pbc->spdif_out = devm_kzalloc(pbc->card.dev, sizeof(*pbc->spdif_out),
+ GFP_KERNEL);
+ if (!pbc->spdif_out)
+ return -ENOMEM;
+
+ link->name = link->stream_name = "pistachio-spdif-out";
+
+ np = of_parse_phandle(node, "cpu-dai", 0);
+ if (!np) {
+ dev_err(dev, "Failed to parse cpu-dai (%s)\n", node->name);
+ return -EINVAL;
+ }
+
+ link->cpu_of_node = np;
+ link->platform_of_node = np;
+ link->codec_dai_name = "snd-soc-dummy-dai";
+ link->codec_name = "snd-soc-dummy";
+ link->ops = &pistachio_card_spdif_out_ops;
+
+ return 0;
+}
+
+static int pistachio_card_parse_spdif_in(struct device_node *node,
+ struct pistachio_card *pbc,
+ struct snd_soc_dai_link *link)
+{
+ struct device_node *np;
+ struct device *dev = pbc->card.dev;
+
+ pbc->spdif_in = true;
+
+ link->name = link->stream_name = "pistachio-spdif-in";
+
+ np = of_parse_phandle(node, "cpu-dai", 0);
+ if (!np) {
+ dev_err(dev, "Failed to parse cpu-dai (%s)\n", node->name);
+ return -EINVAL;
+ }
+
+ link->cpu_of_node = np;
+ link->platform_of_node = np;
+ link->codec_dai_name = "snd-soc-dummy-dai";
+ link->codec_name = "snd-soc-dummy";
+
+ return 0;
+}
+
+static int pistachio_card_parse_parallel_out(struct device_node *node,
+ struct pistachio_card *pbc,
+ struct snd_soc_dai_link *link)
+{
+ struct device_node *np;
+ int ret;
+ struct device *dev = pbc->card.dev;
+
+ pbc->parallel_out = devm_kzalloc(pbc->card.dev,
+ sizeof(*pbc->parallel_out),
+ GFP_KERNEL);
+ if (!pbc->parallel_out)
+ return -ENOMEM;
+
+ link->name = link->stream_name = "pistachio-parallel-out";
+
+ np = of_parse_phandle(node, "cpu-dai", 0);
+ if (!np) {
+ dev_err(dev, "Failed to parse cpu-dai (%s)\n", node->name);
+ return -EINVAL;
+ }
+
+ link->cpu_of_node = np;
+ link->platform_of_node = np;
+ link->codecs = &pbc->parallel_out->component;
+
+ np = of_parse_phandle(node, "sound-dai", 0);
+ if (!np) {
+ dev_err(dev, "Failed to parse sound-dai (%s)\n", node->name);
+ return -EINVAL;
+ }
+ link->codecs[0].of_node = np;
+ link->num_codecs = 1;
+ ret = snd_soc_of_get_dai_name(node, &link->codecs[0].dai_name);
+ if (ret) {
+ dev_err(dev, "snd_soc_of_get_dai_name failed (%s): %d\n",
+ node->name, ret);
+ return ret;
+ }
+
+ link->ops = &pistachio_card_parallel_out_ops;
+
+ return 0;
+}
+
+static int pistachio_card_parse_i2s_mclk(struct pistachio_card *pbc,
+ struct device_node *np, struct pistachio_mclk *mclk,
+ struct pistachio_i2s_mclk_info *fs)
+{
+ int ret, i, j, k, num_fs_rates;
+ u32 min_fs_rate, max_fs_rate, max_rate;
+ u32 fs_rates[PISTACHIO_MAX_FS_RATES];
+ struct device *dev = pbc->card.dev;
+
+ ret = of_property_read_u32(np, "mclk-min-fs-freq", &min_fs_rate);
+ if (ret) {
+ dev_err(dev, "Failed to read mclk-min-fs-freq (%s): %d\n",
+ np->name, ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "mclk-max-fs-freq", &max_fs_rate);
+ if (ret) {
+ dev_err(dev, "Failed to read mclk-max-fs-freq (%s): %d\n",
+ np->name, ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "mclk-max-freq", &max_rate);
+ if (ret) {
+ dev_err(dev, "Failed to read mclk-max-freq (%s): %d\n",
+ np->name, ret);
+ return ret;
+ }
+
+ if ((max_fs_rate < PISTACHIO_MIN_MCLK_FREQ) ||
+ (max_fs_rate > max_rate) ||
+ (max_fs_rate < min_fs_rate)) {
+ dev_err(dev, "Invalid min/max rate (%s)\n", np->name);
+ return -EINVAL;
+ }
+
+ if (min_fs_rate > fs->min_rate)
+ fs->min_rate = min_fs_rate;
+
+ if (max_fs_rate < fs->max_rate)
+ fs->max_rate = max_fs_rate;
+
+ if (max_rate < mclk->max_rate)
+ mclk->max_rate = max_rate;
+
+ if (fs->min_rate > fs->max_rate) {
+ dev_err(dev, "No valid frequency range remaining for %s\n",
+ mclk->name);
+ return -EINVAL;
+ }
+
+ num_fs_rates = of_property_count_u32_elems(np, "mclk-fs");
+ if (num_fs_rates < 0) {
+ dev_err(dev, "of_property_count_u32_elems failed: %d (%s)\n",
+ num_fs_rates, np->name);
+ return num_fs_rates;
+ }
+
+ if (!num_fs_rates || (num_fs_rates > PISTACHIO_MAX_FS_RATES)) {
+ dev_err(dev, "Invalid fs-rates count: %d (%s)\n",
+ num_fs_rates, np->name);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_array(np, "mclk-fs", fs_rates,
+ num_fs_rates);
+ if (ret) {
+ dev_err(dev, "of_property_read_u32_array failed: %d (%s)\n",
+ ret, np->name);
+ return ret;
+ }
+
+ /*
+ * If this is the first fs-rates list for this combination
+ * of {i2s direction, mclk}, this list defines the
+ * current fs-rate list for this combination. Else, this list
+ * subtracts any fs-rates that are not present in both lists
+ */
+ if (!fs->num_fs_rates) {
+ /* Remove any duplicates while copying */
+ for (i = 0, k = 0; i < num_fs_rates; i++) {
+ for (j = 0; j < k; j++)
+ if (fs->fs_rates[j] == fs_rates[i])
+ break;
+ if (j == k)
+ fs->fs_rates[k++] = fs_rates[i];
+ }
+ fs->num_fs_rates = k;
+ } else {
+ for (j = 0; j < fs->num_fs_rates; j++) {
+ for (i = 0; i < num_fs_rates; i++)
+ if (fs->fs_rates[j] == fs_rates[i])
+ break;
+
+ if (i == num_fs_rates) {
+ for (k = j; k < (fs->num_fs_rates - 1); k++)
+ fs->fs_rates[k] = fs->fs_rates[k + 1];
+ fs->num_fs_rates--;
+
+ if (!fs->num_fs_rates) {
+ dev_err(dev, "No fs rates remaining for %s\n",
+ mclk->name);
+ return -EINVAL;
+ }
+
+ j--;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int pistachio_card_parse_i2s_codec(struct device_node *np,
+ struct device_node *subnode, int index, struct pistachio_card *pbc,
+ struct device_node *codec, struct pistachio_i2s_codec_info *codec_info,
+ struct pistachio_mclk *mclk)
+{
+ int i, ret;
+ struct pistachio_i2s_codec_info_s *info;
+ struct device *dev = pbc->card.dev;
+
+ if (codec_info->total_codecs == PISTACHIO_MAX_I2S_CODECS) {
+ dev_err(dev, "Too many codecs\n");
+ of_node_put(codec);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < codec_info->total_codecs; i++)
+ if (codec_info->codecs[i].np == codec)
+ break;
+
+ if (i == codec_info->total_codecs)
+ codec_info->unique_codecs++;
+
+ info = &codec_info->codecs[codec_info->total_codecs++];
+ info->np = codec;
+ info->prefix = subnode->name;
+ info->mclk = mclk;
+
+ ret = of_property_read_u32(subnode, "mclk-index", &info->mclk_index);
+ if (ret)
+ info->mclk_index = 0;
+
+ ret = snd_soc_of_get_dai_name(subnode, &info->dai_name);
+ if (ret) {
+ dev_err(dev, "snd_soc_of_get_dai_name failed: %d (%s)\n",
+ ret, subnode->name);
+ return ret;
+ }
+
+ if (of_property_read_bool(subnode, "frame-master")) {
+ if (codec_info->frame_master_idx != -1) {
+ dev_err(dev, "Multiple frame clock masters (%s)\n",
+ np->name);
+ return -EINVAL;
+ }
+ codec_info->frame_master_idx = index;
+ }
+
+ if (of_property_read_bool(subnode, "bitclock-master")) {
+ if (codec_info->bitclock_master_idx != -1) {
+ dev_err(dev, "Multiple bit clock masters (%s)\n",
+ np->name);
+ return -EINVAL;
+ }
+ codec_info->bitclock_master_idx = index;
+ }
+
+ return 0;
+}
+
+static int pistachio_card_parse_i2s_codecs(struct device_node *np,
+ struct pistachio_card *pbc,
+ struct pistachio_i2s_codec_info *codec_info,
+ struct pistachio_i2s_mclk_info *i2s_fs_info,
+ struct pistachio_i2s_mclk_info *dac_fs_info)
+{
+ int i, ret;
+ struct device_node *subnode, *codec;
+ u32 mclk_id;
+ struct pistachio_mclk *mclk;
+ struct pistachio_i2s_mclk_info *fs_info;
+ struct device *dev = pbc->card.dev;
+
+ i = 0;
+ for_each_child_of_node(np, subnode) {
+
+ ret = of_property_read_u32(subnode, "mclk", &mclk_id);
+ if (ret) {
+ mclk = NULL;
+ } else {
+ switch (mclk_id) {
+ case PISTACHIO_MCLK_I2S:
+ mclk = &pbc->i2s_mclk;
+ fs_info = i2s_fs_info;
+ break;
+ case PISTACHIO_MCLK_DAC:
+ mclk = &pbc->dac_mclk;
+ fs_info = dac_fs_info;
+ break;
+ default:
+ dev_err(dev, "Invalid mclk id: %u (%s)\n",
+ mclk_id, subnode->name);
+ ret = -EINVAL;
+ goto err_subnode;
+ }
+ ret = pistachio_card_parse_i2s_mclk(pbc, subnode, mclk,
+ fs_info);
+ if (ret)
+ goto err_subnode;
+ }
+
+ codec = of_parse_phandle(subnode, "sound-dai", 0);
+ if (!codec)
+ continue;
+
+ ret = pistachio_card_parse_i2s_codec(np, subnode, i, pbc,
+ codec, codec_info, mclk);
+ if (ret)
+ goto err_subnode;
+
+ i++;
+ }
+
+ return 0;
+
+err_subnode:
+ of_node_put(subnode);
+
+ return ret;
+}
+
+static int pistachio_card_mclk_copy(struct pistachio_mclk *mclk,
+ struct pistachio_i2s_mclk *mclk_i2s, struct pistachio_card *pbc,
+ struct pistachio_i2s_mclk_info *mclk_info)
+{
+ unsigned int size;
+
+ mclk_i2s->mclk = mclk;
+ mclk_i2s->num_fs_rates = mclk_info->num_fs_rates;
+
+ size = sizeof(*mclk_i2s->fs_rates) * mclk_i2s->num_fs_rates;
+ mclk_i2s->fs_rates = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+ if (!mclk_i2s->fs_rates)
+ return -ENOMEM;
+
+ memcpy(mclk_i2s->fs_rates, mclk_info->fs_rates, size);
+
+ mclk_i2s->min_rate = mclk_info->min_rate;
+ mclk_i2s->max_rate = mclk_info->max_rate;
+
+ return 0;
+}
+
+static int pistachio_card_parse_i2s_common(struct device_node *node,
+ struct pistachio_card *pbc, struct pistachio_i2s *i2s,
+ struct snd_soc_dai_link *link,
+ struct pistachio_i2s_codec_info *codec_info,
+ struct pistachio_i2s_mclk_info *i2s_mclk_info,
+ struct pistachio_i2s_mclk_info *dac_mclk_info)
+{
+ int ret, i;
+ unsigned int initial_codecs = codec_info->total_codecs, size;
+ struct pistachio_i2s_codec_info_s *codecs;
+ struct pistachio_i2s_mclk *mclk;
+
+ codecs = &codec_info->codecs[initial_codecs];
+
+ ret = pistachio_card_parse_i2s_codecs(node, pbc, codec_info,
+ i2s_mclk_info, dac_mclk_info);
+ i2s->num_codecs = codec_info->total_codecs - initial_codecs;
+ if (ret)
+ goto err_codec_info;
+
+ mclk = &i2s->mclk_a;
+
+ if (i2s_mclk_info->num_fs_rates) {
+ ret = pistachio_card_mclk_copy(&pbc->i2s_mclk, mclk,
+ pbc, i2s_mclk_info);
+ if (ret)
+ goto err_codec_info;
+
+ mclk = &i2s->mclk_b;
+ }
+
+ if (dac_mclk_info->num_fs_rates) {
+ ret = pistachio_card_mclk_copy(&pbc->dac_mclk, mclk,
+ pbc, dac_mclk_info);
+ if (ret)
+ goto err_codec_info;
+ }
+
+ /* Use the dummy codec if there are no codec drivers in this link */
+ if (!i2s->num_codecs) {
+ link->codec_dai_name = "snd-soc-dummy-dai";
+ link->codec_name = "snd-soc-dummy";
+ return 0;
+ }
+
+ size = sizeof(*i2s->codecs) * i2s->num_codecs;
+ i2s->codecs = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+ if (!i2s->codecs) {
+ ret = -ENOMEM;
+ goto err_codec_info;
+ }
+
+ for (i = 0; i < i2s->num_codecs; i++) {
+ i2s->codecs[i].mclk = codecs[i].mclk;
+ i2s->codecs[i].mclk_index = codecs[i].mclk_index;
+ }
+
+ size = sizeof(*i2s->components) * i2s->num_codecs;
+ i2s->components = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+ if (!i2s->components) {
+ ret = -ENOMEM;
+ goto err_codec_info;
+ }
+
+ for (i = 0; i < i2s->num_codecs; i++) {
+ i2s->components[i].dai_name = codecs[i].dai_name;
+ i2s->components[i].of_node = codecs[i].np;
+ }
+
+ link->codecs = i2s->components;
+ link->num_codecs = i2s->num_codecs;
+
+ return 0;
+
+err_codec_info:
+ for (i = 0; i < i2s->num_codecs; i++)
+ of_node_put(codecs[i].np);
+
+ return ret;
+}
+
+static void pistachio_card_add_i2s_clk_route(struct pistachio_card *pbc,
+ struct pistachio_i2s_mclk *mclk,
+ char *cpu_dai_wname)
+{
+ struct snd_soc_dapm_route *route;
+
+ /*
+ * Add a route connecting the clock supply widget to the i2s
+ * Playback/Capture widget if the mclk is used in this path
+ */
+ if (!mclk->mclk)
+ return;
+
+ route = &pbc->routes[pbc->card.num_dapm_routes++];
+ route->source = mclk->mclk->name;
+ route->sink = cpu_dai_wname;
+}
+
+static void pistachio_card_add_i2s_routes(struct pistachio_card *pbc,
+ struct pistachio_i2s *i2s)
+{
+ char *cpu_dai_wname;
+ struct snd_soc_dapm_route *route;
+
+ route = &pbc->routes[pbc->card.num_dapm_routes++];
+
+ /*
+ * dapm requires a full path (source to sink) for the clock supply
+ * widgets to turn on/off as expected. Create routes linking the
+ * i2s Playback/Capture widgets to Inputs/Outputs as required to
+ * create these paths
+ */
+ if (i2s == &pbc->i2s_out->i2s) {
+ cpu_dai_wname = PISTACHIO_I2S_OUT_PREFIX " Playback";
+ route->source = cpu_dai_wname;
+ route->sink = PISTACHIO_I2S_OUTPUT_NAME;
+ } else {
+ cpu_dai_wname = PISTACHIO_I2S_IN_PREFIX " Capture";
+ route->source = PISTACHIO_I2S_INPUT_NAME;
+ route->sink = cpu_dai_wname;
+ }
+
+ pistachio_card_add_i2s_clk_route(pbc, &i2s->mclk_a, cpu_dai_wname);
+
+ pistachio_card_add_i2s_clk_route(pbc, &i2s->mclk_b, cpu_dai_wname);
+}
+
+static int pistachio_card_parse_i2s_out(struct device_node *i2s_out_np,
+ struct pistachio_card *pbc, struct snd_soc_dai_link *link,
+ struct pistachio_i2s_codec_info *codec_info)
+{
+ struct device *dev = pbc->card.dev;
+ struct device_node *np;
+ unsigned int fmt;
+ int ret;
+ struct pistachio_i2s_mclk_info i2s_mclk_info;
+ struct pistachio_i2s_mclk_info dac_mclk_info;
+
+ pbc->i2s_out = devm_kzalloc(dev, sizeof(*pbc->i2s_out), GFP_KERNEL);
+ if (!pbc->i2s_out)
+ return -ENOMEM;
+
+ link->name = link->stream_name = "pistachio-i2s-out";
+
+ np = of_parse_phandle(i2s_out_np, "cpu-dai", 0);
+ if (!np) {
+ dev_err(dev, "Failed to parse cpu-dai (%s)", i2s_out_np->name);
+ return -EINVAL;
+ }
+
+ link->cpu_of_node = np;
+ link->platform_of_node = np;
+
+ fmt = snd_soc_of_parse_daifmt(i2s_out_np, NULL, NULL, NULL);
+ fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+ fmt |= SND_SOC_DAIFMT_CBS_CFS;
+ link->dai_fmt = fmt;
+
+ /*
+ * Internal i2s out controller uses i2s_mclk and
+ * accepts 256fs,384fs
+ */
+ i2s_mclk_info.fs_rates[0] = 256;
+ i2s_mclk_info.fs_rates[1] = 384;
+ i2s_mclk_info.num_fs_rates = 2;
+ i2s_mclk_info.min_rate = 0;
+ i2s_mclk_info.max_rate = PISTACHIO_I2S_MCLK_MAX_FREQ;
+ dac_mclk_info.num_fs_rates = 0;
+ dac_mclk_info.min_rate = 0;
+ dac_mclk_info.max_rate = PISTACHIO_DAC_MCLK_MAX_FREQ;
+
+ codec_info->bitclock_master_idx = 0;
+ codec_info->frame_master_idx = 0;
+
+ ret = pistachio_card_parse_i2s_common(i2s_out_np, pbc,
+ &pbc->i2s_out->i2s, link, codec_info,
+ &i2s_mclk_info, &dac_mclk_info);
+ if (ret)
+ return ret;
+
+ pistachio_card_add_i2s_routes(pbc, &pbc->i2s_out->i2s);
+
+ link->init = pistachio_card_i2s_out_link_init;
+ link->ops = &pistachio_card_i2s_out_ops;
+
+ return 0;
+}
+
+static int pistachio_card_parse_i2s_in(struct device_node *i2s_in_np,
+ struct pistachio_card *pbc, struct snd_soc_dai_link *link,
+ bool i2s_loopback, struct pistachio_i2s_codec_info *codec_info)
+{
+ int ret;
+ struct device *dev = pbc->card.dev;
+ unsigned int fmt;
+ struct device_node *np;
+ struct pistachio_i2s_mclk_info i2s_mclk_info;
+ struct pistachio_i2s_mclk_info dac_mclk_info;
+
+ pbc->i2s_in = devm_kzalloc(dev, sizeof(*pbc->i2s_in), GFP_KERNEL);
+ if (!pbc->i2s_in)
+ return -ENOMEM;
+
+ link->name = link->stream_name = "pistachio-i2s-in";
+
+ np = of_parse_phandle(i2s_in_np, "cpu-dai", 0);
+ if (!np) {
+ dev_err(dev, "Failed to parse cpu-dai (%s)", i2s_in_np->name);
+ return -EINVAL;
+ }
+
+ link->cpu_of_node = np;
+ link->platform_of_node = np;
+
+ fmt = snd_soc_of_parse_daifmt(i2s_in_np, NULL, NULL, NULL);
+ fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+ pbc->i2s_in->fmt = fmt;
+
+ i2s_mclk_info.num_fs_rates = 0;
+ i2s_mclk_info.min_rate = 0;
+ i2s_mclk_info.max_rate = PISTACHIO_I2S_MCLK_MAX_FREQ;
+ dac_mclk_info.num_fs_rates = 0;
+ dac_mclk_info.min_rate = 0;
+ dac_mclk_info.max_rate = PISTACHIO_DAC_MCLK_MAX_FREQ;
+
+ codec_info->bitclock_master_idx = -1;
+ codec_info->frame_master_idx = -1;
+
+ ret = pistachio_card_parse_i2s_common(i2s_in_np, pbc,
+ &pbc->i2s_in->i2s, link, codec_info,
+ &i2s_mclk_info, &dac_mclk_info);
+ if (ret)
+ return ret;
+
+ if (i2s_loopback) {
+ pbc->i2s_in->frame_master = PISTACHIO_CLOCK_MASTER_LOOPBACK;
+ pbc->i2s_in->bitclock_master = PISTACHIO_CLOCK_MASTER_LOOPBACK;
+ } else if ((codec_info->bitclock_master_idx == -1) ||
+ (codec_info->frame_master_idx == -1)) {
+ pbc->i2s_in->frame_master = PISTACHIO_CLOCK_MASTER_EXT;
+ pbc->i2s_in->bitclock_master = PISTACHIO_CLOCK_MASTER_EXT;
+ } else {
+ pbc->i2s_in->frame_master = codec_info->frame_master_idx;
+ pbc->i2s_in->bitclock_master = codec_info->bitclock_master_idx;
+ }
+
+ pistachio_card_add_i2s_routes(pbc, &pbc->i2s_in->i2s);
+
+ link->init = pistachio_card_i2s_in_link_init;
+
+ /*
+ * If no mclks are used by i2s in, there is nothing for
+ * the ops callbacks to do, so leave this as NULL
+ */
+ if (pbc->i2s_in->i2s.mclk_a.mclk)
+ link->ops = &pistachio_card_i2s_in_ops;
+
+ return 0;
+}
+
+static int pistachio_card_prefixes(struct pistachio_card *pbc,
+ struct pistachio_i2s_codec_info *codec_info,
+ struct snd_soc_dai_link *i2s_out, struct snd_soc_dai_link *i2s_in,
+ struct snd_soc_dai_link *parallel_out)
+{
+ int i, j, n;
+ unsigned int size;
+ struct pistachio_i2s_codec_info_s *codecs;
+ struct snd_soc_codec_conf *conf, *c;
+
+ n = codec_info->unique_codecs;
+
+ if (parallel_out)
+ n++;
+
+ if (i2s_out)
+ n++;
+
+ if (i2s_in)
+ n++;
+
+ codecs = codec_info->codecs;
+
+ size = sizeof(*pbc->card.codec_conf) * n;
+ pbc->card.codec_conf = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+ if (!pbc->card.codec_conf)
+ return -ENOMEM;
+
+ conf = pbc->card.codec_conf;
+
+ /* Create prefixes for unique codecs only */
+ for (i = 0; i < codec_info->total_codecs; i++) {
+ for (j = 0; j < i; j++)
+ if (codecs[j].np == codecs[i].np)
+ break;
+ if (j == i) {
+ conf->of_node = codecs[i].np;
+ conf->name_prefix = codecs[i].prefix;
+ conf++;
+ }
+ }
+
+ if (i2s_out) {
+ conf->of_node = i2s_out->cpu_of_node;
+ conf->name_prefix = PISTACHIO_I2S_OUT_PREFIX;
+ conf++;
+ }
+
+ if (i2s_in) {
+ conf->of_node = i2s_in->cpu_of_node;
+ conf->name_prefix = PISTACHIO_I2S_IN_PREFIX;
+ conf++;
+ }
+
+ if (parallel_out) {
+ conf->of_node = parallel_out->codecs[0].of_node;
+ conf->name_prefix = PISTACHIO_INTERNAL_DAC_PREFIX;
+ conf++;
+ }
+
+ pbc->card.num_configs = n;
+
+ /* Check for prefix clashes */
+ for (i = 0; i < n; i++) {
+ conf = &pbc->card.codec_conf[i];
+ for (j = i + 1; j < n; j++) {
+ c = &pbc->card.codec_conf[j];
+ if (!strcasecmp(conf->name_prefix, c->name_prefix)) {
+ dev_err(pbc->card.dev, "Prefix clash: %s\n",
+ conf->name_prefix);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int pistachio_card_parse_of(struct device_node *node,
+ struct pistachio_card *pbc)
+{
+ int ret = 0;
+ unsigned int size, gpio;
+ struct device_node *spdif_out_np, *spdif_in_np, *parallel_out_np;
+ struct device_node *i2s_out_np, *i2s_in_np;
+ struct snd_soc_dai_link *link, *prl_out, *i2s_out, *i2s_in;
+ enum of_gpio_flags flags;
+ struct pistachio_i2s_codec_info codec_info;
+ bool i2s_loopback;
+ struct device *dev = pbc->card.dev;
+
+ pbc->periph_regs = syscon_regmap_lookup_by_phandle(node, "img,cr-periph");
+ if (IS_ERR(pbc->periph_regs)) {
+ dev_err(dev, "syscon_regmap_lookup_by_phandle failed: %ld\n",
+ PTR_ERR(pbc->periph_regs));
+ return PTR_ERR(pbc->periph_regs);
+ }
+
+ if (of_property_read_bool(node, "img,widgets")) {
+ ret = snd_soc_of_parse_audio_simple_widgets(&pbc->card, "img,widgets");
+ if (ret) {
+ dev_err(dev, "img,widgets parse failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (of_property_read_bool(node, "img,routing")) {
+ ret = snd_soc_of_parse_audio_routing(&pbc->card, "img,routing");
+ if (ret) {
+ dev_err(dev, "img,routing parse failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ spdif_out_np = of_get_child_by_name(node, "spdif-out");
+ if (spdif_out_np)
+ pbc->card.num_links++;
+
+ spdif_in_np = of_get_child_by_name(node, "spdif-in");
+ if (spdif_in_np)
+ pbc->card.num_links++;
+
+ parallel_out_np = of_get_child_by_name(node, "parallel-out");
+ if (parallel_out_np)
+ pbc->card.num_links++;
+
+ i2s_out_np = of_get_child_by_name(node, "i2s-out");
+ if (i2s_out_np)
+ pbc->card.num_links++;
+
+ i2s_in_np = of_get_child_by_name(node, "i2s-in");
+ if (i2s_in_np)
+ pbc->card.num_links++;
+
+ i2s_loopback = of_property_read_bool(node, "img,i2s-clk-loopback");
+ if (i2s_loopback && (!i2s_out_np || !i2s_in_np)) {
+ dev_err(dev, "img,i2s-clk-loopback specified when i2s-out/i2s-in are not present\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ if (!pbc->card.num_links) {
+ dev_err(dev, "No dai links on card\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ size = sizeof(*pbc->card.dai_link) * pbc->card.num_links;
+ pbc->card.dai_link = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+ if (!pbc->card.dai_link) {
+ ret = -ENOMEM;
+ goto end;
+ }
+
+ codec_info.total_codecs = 0;
+ codec_info.unique_codecs = 0;
+
+ link = pbc->card.dai_link;
+
+ if (spdif_out_np) {
+ ret = pistachio_card_parse_spdif_out(spdif_out_np, pbc, link);
+ if (ret)
+ goto end;
+ link++;
+ }
+
+ if (spdif_in_np) {
+ ret = pistachio_card_parse_spdif_in(spdif_in_np, pbc, link);
+ if (ret)
+ goto end;
+ link++;
+ }
+
+ if (parallel_out_np) {
+ ret = pistachio_card_parse_parallel_out(parallel_out_np, pbc,
+ link);
+ if (ret)
+ goto end;
+
+ prl_out = link++;
+ } else {
+ prl_out = NULL;
+ }
+
+ if (i2s_out_np) {
+ ret = pistachio_card_parse_i2s_out(i2s_out_np, pbc, link,
+ &codec_info);
+ if (ret)
+ goto end;
+
+ i2s_out = link++;
+ } else {
+ i2s_out = NULL;
+ }
+
+ if (i2s_in_np) {
+ ret = pistachio_card_parse_i2s_in(i2s_in_np, pbc, link,
+ i2s_loopback, &codec_info);
+ if (ret)
+ goto end;
+
+ i2s_in = link;
+ } else {
+ i2s_in = NULL;
+ }
+
+ ret = pistachio_card_prefixes(pbc, &codec_info, i2s_out,
+ i2s_in, prl_out);
+ if (ret)
+ goto end;
+
+ gpio = of_get_named_gpio_flags(node, "img,hp-det-gpio", 0, &flags);
+ pbc->hp_jack_gpio.gpio = gpio;
+ pbc->hp_jack_gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW);
+ if (pbc->hp_jack_gpio.gpio == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto end;
+ }
+
+ gpio = of_get_named_gpio_flags(node, "img,mute-gpio", 0, &flags);
+ pbc->mute_gpio = gpio;
+ pbc->mute_gpio_inverted = !!(flags & OF_GPIO_ACTIVE_LOW);
+ if (pbc->mute_gpio == -EPROBE_DEFER)
+ ret = -EPROBE_DEFER;
+
+end:
+ if (spdif_out_np)
+ of_node_put(spdif_out_np);
+ if (spdif_in_np)
+ of_node_put(spdif_in_np);
+ if (parallel_out_np)
+ of_node_put(parallel_out_np);
+ if (i2s_out_np)
+ of_node_put(i2s_out_np);
+ if (i2s_in_np)
+ of_node_put(i2s_in_np);
+
+ return ret;
+}
+
+static void pistachio_card_unref(struct pistachio_card *pbc)
+{
+ int i, j;
+ struct snd_soc_dai_link *link;
+
+ link = pbc->card.dai_link;
+ if (!link)
+ return;
+
+ for (i = 0; i < pbc->card.num_links; i++, link++) {
+ if (link->cpu_of_node)
+ of_node_put(link->cpu_of_node);
+ for (j = 0; j < link->num_codecs; j++)
+ of_node_put(link->codecs[j].of_node);
+ }
+}
+
+static int pistachio_card_init_clk(struct device *dev, char *name,
+ struct clk **pclk, unsigned int rate)
+{
+ struct clk *clk;
+ int ret;
+
+ clk = devm_clk_get(dev, name);
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "devm_clk_get failed for %s: %d",
+ name, ret);
+ return ret;
+ }
+
+ ret = clk_set_rate(clk, rate);
+ if (ret) {
+ dev_err(dev, "clk_set_rate(%s, %u) failed: %d",
+ name, rate, ret);
+ return ret;
+ }
+
+ *pclk = clk;
+
+ return 0;
+}
+
+static int pistachio_card_get_mute(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = gpio_get_value_cansleep(pbc->mute_gpio);
+ if (ret < 0)
+ return ret;
+
+ if (pbc->mute_gpio_inverted)
+ ucontrol->value.integer.value[0] = !ret;
+ else
+ ucontrol->value.integer.value[0] = !!ret;
+
+ return 0;
+}
+
+static int pistachio_card_set_mute(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(card);
+ int val;
+
+ if (pbc->mute_gpio_inverted)
+ val = !ucontrol->value.integer.value[0];
+ else
+ val = ucontrol->value.integer.value[0];
+
+ gpio_set_value_cansleep(pbc->mute_gpio, val);
+
+ return 0;
+}
+
+static int pistachio_card_info_sample_rates(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 192000;
+
+ return 0;
+}
+
+static int pistachio_card_set_sample_rates_mclk(struct pistachio_card *pbc,
+ struct pistachio_mclk *mclk, unsigned int i2s_out_rate,
+ unsigned int i2s_in_rate)
+{
+ struct pistachio_i2s_mclk *mclk_a, *mclk_b;
+ unsigned int rate_a, rate_b;
+
+ mclk_a = NULL;
+ mclk_b = NULL;
+ rate_a = i2s_out_rate;
+ rate_b = i2s_in_rate;
+
+ if (i2s_out_rate) {
+ if (pbc->i2s_out->i2s.mclk_a.mclk == mclk)
+ mclk_a = &pbc->i2s_out->i2s.mclk_a;
+ else if (pbc->i2s_out->i2s.mclk_b.mclk == mclk)
+ mclk_a = &pbc->i2s_out->i2s.mclk_b;
+ }
+ if (i2s_in_rate) {
+ if (pbc->i2s_in->i2s.mclk_a.mclk == mclk)
+ mclk_b = &pbc->i2s_in->i2s.mclk_a;
+ else if (pbc->i2s_in->i2s.mclk_b.mclk == mclk)
+ mclk_b = &pbc->i2s_in->i2s.mclk_b;
+ }
+
+ if (!mclk_a) {
+ mclk_a = mclk_b;
+ rate_a = rate_b;
+ mclk_b = NULL;
+ }
+
+ if (mclk_a)
+ return pistachio_card_update_mclk(pbc, mclk_a, mclk_b,
+ rate_a, rate_b);
+
+ return 0;
+}
+
+static int pistachio_card_set_sample_rates(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(card);
+ int ret;
+ unsigned int pll_rate, i2s_out_rate, i2s_in_rate;
+ struct device *dev = pbc->card.dev;
+
+ i2s_out_rate = 0;
+ i2s_in_rate = 0;
+
+ if (pbc->i2s_out)
+ i2s_out_rate = ucontrol->value.integer.value[0];
+
+ if (pbc->i2s_in && pbc->i2s_in->i2s.mclk_a.mclk)
+ i2s_in_rate = ucontrol->value.integer.value[1];
+
+ if (!i2s_out_rate && !i2s_in_rate)
+ return 0;
+
+ pll_rate = 0;
+
+ if (i2s_out_rate) {
+ ret = pistachio_card_get_pll_rate(i2s_out_rate, dev);
+ if (ret < 0)
+ return ret;
+
+ pll_rate = ret;
+ }
+
+ if (i2s_in_rate) {
+ ret = pistachio_card_get_pll_rate(i2s_in_rate, dev);
+ if (ret < 0)
+ return ret;
+
+ if (pll_rate && (ret != pll_rate)) {
+ dev_err(dev, "Conflicting pll rate requirements\n");
+ return -EINVAL;
+ }
+
+ pll_rate = ret;
+ }
+
+ mutex_lock(&pbc->rate_mutex);
+
+ if (pbc->audio_pll_rate != pll_rate) {
+ ret = pistachio_card_set_pll_rate(pbc, pll_rate);
+ if (ret) {
+ mutex_unlock(&pbc->rate_mutex);
+ return ret;
+ }
+ }
+
+ ret = pistachio_card_set_sample_rates_mclk(pbc, &pbc->i2s_mclk,
+ i2s_out_rate, i2s_in_rate);
+ if (ret) {
+ mutex_unlock(&pbc->rate_mutex);
+ return ret;
+ }
+
+ ret = pistachio_card_set_sample_rates_mclk(pbc, &pbc->dac_mclk,
+ i2s_out_rate, i2s_in_rate);
+
+ mutex_unlock(&pbc->rate_mutex);
+
+ return ret;
+}
+
+static struct snd_kcontrol_new pistachio_controls[] = {
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "I2S Rates",
+ .info = pistachio_card_info_sample_rates,
+ .put = pistachio_card_set_sample_rates
+ },
+};
+
+static int pistachio_card_i2s_clk_cb(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct clk_notifier_data *ndata = data;
+ struct pistachio_card *pbc;
+
+ pbc = container_of(nb, struct pistachio_card, i2s_clk_notifier);
+
+ switch (event) {
+ case PRE_RATE_CHANGE:
+ /* Allow changes made by the card driver only */
+ if (ndata->new_rate == pbc->i2s_mclk.cur_rate)
+ return NOTIFY_OK;
+ else
+ return NOTIFY_STOP;
+ case POST_RATE_CHANGE:
+ case ABORT_RATE_CHANGE:
+ return NOTIFY_OK;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+#ifdef DEBUG
+
+static void pistachio_card_info_fmt(struct pistachio_card *pbc,
+ unsigned int fmt)
+{
+ struct device *dev = pbc->card.dev;
+ char *text_a, *text_b;
+
+ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S)
+ text_a = "I2S";
+ else
+ text_a = "Left Justified";
+ dev_dbg(dev, " Format: %s\n", text_a);
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ text_a = "No";
+ text_b = "No";
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ text_a = "Yes";
+ text_b = "No";
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ text_a = "No";
+ text_b = "Yes";
+ break;
+ default:
+ text_a = "Yes";
+ text_b = "Yes";
+ break;
+ }
+ dev_dbg(dev, " Frame Clock Inverted: %s\n", text_a);
+ dev_dbg(dev, " Bit Clock Inverted: %s\n", text_b);
+
+ if ((fmt & SND_SOC_DAIFMT_CLOCK_MASK) == SND_SOC_DAIFMT_CONT)
+ text_a = "Yes";
+ else
+ text_a = "No";
+ dev_dbg(dev, " Continuous Clock: %s\n", text_a);
+}
+
+static void pistachio_card_info_mclk(struct pistachio_card *pbc,
+ struct pistachio_i2s_mclk *mclk)
+{
+ struct device *dev = pbc->card.dev;
+ int i;
+
+ dev_dbg(dev, " Min FS Freq: %u\n", mclk->min_rate);
+ dev_dbg(dev, " Max FS Freq: %u\n", mclk->max_rate);
+ dev_dbg(dev, " FS Rates:\n");
+
+ for (i = 0; i < mclk->num_fs_rates; i++)
+ dev_dbg(dev, " %u\n", mclk->fs_rates[i]);
+}
+
+static void pistachio_card_info_mclks(struct pistachio_card *pbc,
+ struct pistachio_i2s *i2s)
+{
+ struct pistachio_i2s_mclk *i2s_mclk;
+ struct pistachio_i2s_mclk *dac_mclk;
+ struct device *dev = pbc->card.dev;
+
+ if (i2s->mclk_a.mclk == &pbc->i2s_mclk)
+ i2s_mclk = &i2s->mclk_a;
+ else if (pbc->i2s_in->i2s.mclk_b.mclk == &pbc->i2s_mclk)
+ i2s_mclk = &i2s->mclk_b;
+ else
+ i2s_mclk = NULL;
+
+ if (i2s_mclk) {
+ dev_dbg(dev, " I2S MCLK\n");
+ pistachio_card_info_mclk(pbc, i2s_mclk);
+ } else {
+ dev_dbg(dev, " I2S MCLK NOT USED\n");
+ }
+
+ dev_dbg(dev, "\n");
+
+ if (i2s->mclk_a.mclk == &pbc->dac_mclk)
+ dac_mclk = &i2s->mclk_a;
+ else if (i2s->mclk_b.mclk == &pbc->dac_mclk)
+ dac_mclk = &i2s->mclk_b;
+ else
+ dac_mclk = NULL;
+
+ if (dac_mclk) {
+ dev_dbg(dev, " DAC MCLK\n");
+ pistachio_card_info_mclk(pbc, dac_mclk);
+ } else {
+ dev_dbg(dev, " DAC MCLK NOT USED\n");
+ }
+}
+
+static void pistachio_card_info_i2s_out(struct pistachio_card *pbc,
+ struct snd_soc_dai_link *link)
+{
+ int i, j;
+ struct snd_soc_dai_link_component *components;
+ struct snd_soc_codec_conf *confs;
+ struct device *dev = pbc->card.dev;
+ char *text;
+
+ components = pbc->i2s_out->i2s.components;
+ confs = pbc->card.codec_conf;
+
+ dev_dbg(dev, "I2S OUT\n");
+ dev_dbg(dev, "\n");
+ if (pbc->i2s_in && (pbc->i2s_in->frame_master ==
+ PISTACHIO_CLOCK_MASTER_LOOPBACK))
+ text = "(Dual Frame + Bit Clock Master)";
+ else
+ text = "(Frame + Bit Clock Master)";
+ dev_dbg(dev, " CPU DAI\n");
+ dev_dbg(dev, " i2s-out (%s) %s\n",
+ link->cpu_of_node->name, text);
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, " CODECS\n");
+
+ for (i = 0; i < pbc->i2s_out->i2s.num_codecs; i++) {
+ for (j = 0; j < pbc->card.num_configs; j++)
+ if (confs[j].of_node == components[i].of_node)
+ break;
+
+ dev_dbg(dev, " %s (%s) (%s)\n", confs[j].name_prefix,
+ confs[j].of_node->name, components[i].dai_name);
+ }
+ dev_dbg(dev, "\n");
+
+ pistachio_card_info_mclks(pbc, &pbc->i2s_out->i2s);
+
+ dev_dbg(dev, "\n");
+
+ pistachio_card_info_fmt(pbc, link->dai_fmt);
+
+ dev_dbg(dev, "\n");
+}
+
+static void pistachio_card_info_i2s_in(struct pistachio_card *pbc,
+ struct snd_soc_dai_link *link)
+{
+ int i, j;
+ struct snd_soc_dai_link_component *components;
+ struct snd_soc_codec_conf *confs;
+ char *text;
+ struct device *dev = pbc->card.dev;
+
+ components = pbc->i2s_in->i2s.components;
+ confs = pbc->card.codec_conf;
+
+ dev_dbg(dev, "I2S IN\n");
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, " CPU DAI\n");
+ dev_dbg(dev, " i2s-in (%s)\n", link->cpu_of_node->name);
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, " CODECS\n");
+
+ for (i = 0; i < pbc->i2s_in->i2s.num_codecs; i++) {
+ for (j = 0; j < pbc->card.num_configs; j++)
+ if (confs[j].of_node == components[i].of_node)
+ break;
+
+ if (i == pbc->i2s_in->frame_master)
+ if (i == pbc->i2s_in->bitclock_master)
+ text = "(Frame + Bit Clock Master)";
+ else
+ text = "(Frame Master)";
+ else
+ if (i == pbc->i2s_in->bitclock_master)
+ text = "(Bitclock Master)";
+ else
+ text = "";
+
+ dev_dbg(dev, " %s (%s) (%s) %s\n", confs[j].name_prefix,
+ confs[j].of_node->name, components[i].dai_name, text);
+ }
+ dev_dbg(dev, "\n");
+
+ pistachio_card_info_mclks(pbc, &pbc->i2s_in->i2s);
+
+ dev_dbg(dev, "\n");
+
+ pistachio_card_info_fmt(pbc, pbc->i2s_in->fmt);
+
+ dev_dbg(dev, "\n");
+}
+
+static void pistachio_card_info(struct pistachio_card *pbc)
+{
+ struct device *dev = pbc->card.dev;
+ struct snd_soc_codec_conf *conf;
+ struct snd_soc_dai_link *link;
+ char *text;
+
+ link = pbc->card.dai_link;
+
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, "####################################################\n");
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, "Pistachio Audio Card\n");
+ dev_dbg(dev, "\n");
+
+ if (pbc->spdif_out) {
+ dev_dbg(dev, "SPDIF OUT\n");
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, " CPU DAI\n");
+ dev_dbg(dev, " spdif-out (%s)\n",
+ link->cpu_of_node->name);
+ dev_dbg(dev, "\n");
+ link++;
+ }
+ if (pbc->spdif_in) {
+ dev_dbg(dev, "SPDIF IN\n");
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, " CPU DAI\n");
+ dev_dbg(dev, " spdif-in (%s)\n",
+ link->cpu_of_node->name);
+ dev_dbg(dev, "\n");
+ link++;
+ }
+ if (pbc->parallel_out) {
+ dev_dbg(dev, "PARALLEL OUT\n");
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, " CPU DAI\n");
+ dev_dbg(dev, " parallel-out (%s)\n",
+ link->cpu_of_node->name);
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, " CODEC\n");
+ conf = &pbc->card.codec_conf[pbc->card.num_configs - 1];
+ dev_dbg(dev, " %s (%s) (%s)\n", conf->name_prefix,
+ conf->of_node->name,
+ pbc->parallel_out->component.dai_name);
+ dev_dbg(dev, "\n");
+ link++;
+ }
+ if (pbc->i2s_out) {
+ pistachio_card_info_i2s_out(pbc, link);
+ link++;
+ }
+ if (pbc->i2s_in)
+ pistachio_card_info_i2s_in(pbc, link);
+
+ dev_dbg(dev, "I2S MCLK Max Freq: %u\n", pbc->i2s_mclk.max_rate);
+ dev_dbg(dev, "DAC MCLK Max Freq: %u\n", pbc->dac_mclk.max_rate);
+ dev_dbg(dev, "\n");
+
+ if (gpio_is_valid(pbc->mute_gpio)) {
+ if (pbc->mute_gpio_inverted)
+ text = "(Active Low)";
+ else
+ text = "(Active High)";
+ dev_dbg(dev, "Mute: GPIO %u %s\n", pbc->mute_gpio, text);
+ }
+ if (gpio_is_valid(pbc->hp_jack_gpio.gpio)) {
+ if (pbc->hp_jack_gpio.invert)
+ text = "(Active Low)";
+ else
+ text = "(Active High)";
+ dev_dbg(dev, "Headphone-Detect: GPIO %u %s\n",
+ pbc->hp_jack_gpio.gpio, text);
+ }
+ dev_dbg(dev, "\n");
+ dev_dbg(dev, "####################################################\n");
+ dev_dbg(dev, "\n");
+}
+
+#endif
+
+static int pistachio_card_probe(struct platform_device *pdev)
+{
+ struct pistachio_card *pbc;
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ int ret;
+ unsigned long gpio_flags;
+ struct snd_kcontrol_new *control;
+
+ if (!np || !of_device_is_available(np))
+ return -EINVAL;
+
+ pbc = devm_kzalloc(dev, sizeof(*pbc), GFP_KERNEL);
+ if (!pbc)
+ return -ENOMEM;
+
+ snd_soc_card_set_drvdata(&pbc->card, pbc);
+
+ pbc->card.owner = THIS_MODULE;
+ pbc->card.dev = dev;
+ pbc->card.name = "pistachio-card";
+
+ pbc->i2s_mclk.name = PISTACHIO_I2S_MCLK_NAME;
+ pbc->i2s_mclk.max_rate = PISTACHIO_I2S_MCLK_MAX_FREQ;
+ pbc->dac_mclk.name = PISTACHIO_DAC_MCLK_NAME;
+ pbc->dac_mclk.max_rate = PISTACHIO_DAC_MCLK_MAX_FREQ;
+
+ mutex_init(&pbc->rate_mutex);
+
+ pbc->hp_jack_gpio.gpio = -ENOENT;
+ pbc->mute_gpio = -ENOENT;
+
+ pbc->card.dapm_widgets = pistachio_card_widgets;
+ pbc->card.num_dapm_widgets = ARRAY_SIZE(pistachio_card_widgets);
+
+ pbc->card.dapm_routes = pbc->routes;
+
+ ret = pistachio_card_parse_of(np, pbc);
+ if (ret)
+ goto err;
+
+ pbc->audio_pll_rate = PISTACHIO_PLL_RATE_B;
+ ret = pistachio_card_init_clk(dev, "audio_pll", &pbc->audio_pll,
+ pbc->audio_pll_rate);
+ if (ret)
+ goto err;
+
+ pbc->i2s_mclk.cur_rate = PISTACHIO_MIN_MCLK_FREQ;
+ ret = pistachio_card_init_clk(dev, PISTACHIO_I2S_MCLK_NAME,
+ &pbc->i2s_mclk.mclk,
+ pbc->i2s_mclk.cur_rate);
+ if (ret)
+ goto err;
+
+ pbc->dac_mclk.cur_rate = PISTACHIO_MIN_MCLK_FREQ;
+ ret = pistachio_card_init_clk(dev, PISTACHIO_DAC_MCLK_NAME,
+ &pbc->dac_mclk.mclk,
+ pbc->dac_mclk.cur_rate);
+ if (ret)
+ goto err;
+
+ pbc->i2s_clk_notifier.notifier_call = pistachio_card_i2s_clk_cb;
+ ret = clk_notifier_register(pbc->i2s_mclk.mclk,
+ &pbc->i2s_clk_notifier);
+ if (ret) {
+ dev_err(dev, "clk_notifier_register failed: %d", ret);
+ goto err;
+ }
+
+ ret = devm_snd_soc_register_card(dev, &pbc->card);
+ if (ret) {
+ dev_err(dev, "devm_snd_soc_register_card failed: %d", ret);
+ goto err_notifier;
+ }
+
+ ret = snd_soc_add_card_controls(&pbc->card, pistachio_controls,
+ ARRAY_SIZE(pistachio_controls));
+ if (ret) {
+ dev_err(dev, "snd_soc_add_card_controls failed: %d", ret);
+ goto err_notifier;
+ }
+
+ if (gpio_is_valid(pbc->hp_jack_gpio.gpio)) {
+ pbc->hp_jack_pin.pin = "Headphones";
+ pbc->hp_jack_pin.mask = SND_JACK_HEADPHONE;
+ pbc->hp_jack_gpio.name = "Headphone detection";
+ pbc->hp_jack_gpio.report = SND_JACK_HEADPHONE;
+ pbc->hp_jack_gpio.debounce_time = 150;
+ ret = snd_soc_card_jack_new(&pbc->card, "Headphones",
+ SND_JACK_HEADPHONE, &pbc->hp_jack,
+ &pbc->hp_jack_pin, 1);
+ if (ret) {
+ dev_err(dev, "snd_soc_card_jack_new failed: %d", ret);
+ goto err_notifier;
+ }
+ ret = snd_soc_jack_add_gpios(&pbc->hp_jack, 1,
+ &pbc->hp_jack_gpio);
+ if (ret) {
+ dev_err(dev, "snd_soc_jack_add_gpios failed: %d", ret);
+ goto err_notifier;
+ }
+ }
+
+ if (gpio_is_valid(pbc->mute_gpio)) {
+ if (pbc->mute_gpio_inverted)
+ gpio_flags = GPIOF_OUT_INIT_HIGH;
+ else
+ gpio_flags = GPIOF_OUT_INIT_LOW;
+ ret = gpio_request_one(pbc->mute_gpio, gpio_flags, "Mute");
+ if (ret) {
+ dev_err(dev, "gpio_request_one failed: %d", ret);
+ goto err_jack;
+ }
+ control = devm_kzalloc(dev, sizeof(*control), GFP_KERNEL);
+ if (!control) {
+ ret = -ENOMEM;
+ goto err_mute;
+ }
+ control->access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+ control->iface = SNDRV_CTL_ELEM_IFACE_CARD;
+ control->name = "Mute Switch";
+ control->info = snd_ctl_boolean_mono_info;
+ control->get = pistachio_card_get_mute;
+ control->put = pistachio_card_set_mute;
+ ret = snd_soc_add_card_controls(&pbc->card, control, 1);
+ if (ret) {
+ dev_err(dev, "mute control add failed: %d", ret);
+ goto err_mute;
+ }
+ }
+
+#ifdef DEBUG
+ pistachio_card_info(pbc);
+#endif
+
+ return 0;
+
+err_mute:
+ if (gpio_is_valid(pbc->mute_gpio))
+ gpio_free(pbc->mute_gpio);
+err_jack:
+ if (gpio_is_valid(pbc->hp_jack_gpio.gpio))
+ snd_soc_jack_free_gpios(&pbc->hp_jack, 1, &pbc->hp_jack_gpio);
+err_notifier:
+ clk_notifier_unregister(pbc->i2s_mclk.mclk, &pbc->i2s_clk_notifier);
+err:
+ pistachio_card_unref(pbc);
+
+ return ret;
+}
+
+static int pistachio_card_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct pistachio_card *pbc = snd_soc_card_get_drvdata(card);
+
+ if (gpio_is_valid(pbc->mute_gpio))
+ gpio_free(pbc->mute_gpio);
+ if (gpio_is_valid(pbc->hp_jack_gpio.gpio))
+ snd_soc_jack_free_gpios(&pbc->hp_jack, 1, &pbc->hp_jack_gpio);
+ clk_notifier_unregister(pbc->i2s_mclk.mclk, &pbc->i2s_clk_notifier);
+ pistachio_card_unref(pbc);
+
+ return 0;
+}
+
+static const struct of_device_id pistachio_card_of_match[] = {
+ { .compatible = "img,pistachio-audio" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, pistachio_card_of_match);
+
+static struct platform_driver pistachio_card = {
+ .driver = {
+ .name = "pistachio-card",
+ .of_match_table = pistachio_card_of_match,
+ },
+ .probe = pistachio_card_probe,
+ .remove = pistachio_card_remove,
+};
+module_platform_driver(pistachio_card);
+
+MODULE_DESCRIPTION("Pistachio audio card driver");
+MODULE_AUTHOR("Damien Horsley <Damien.Horsley at imgtec.com>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
More information about the Alsa-devel
mailing list