mirror of https://github.com/OpenIPC/firmware.git
1066 lines
30 KiB
Diff
1066 lines
30 KiB
Diff
diff -drupN a/sound/soc/sunxi/sunxi-spdif.c b/sound/soc/sunxi/sunxi-spdif.c
|
|
--- a/sound/soc/sunxi/sunxi-spdif.c 1970-01-01 03:00:00.000000000 +0300
|
|
+++ b/sound/soc/sunxi/sunxi-spdif.c 2022-06-12 05:28:14.000000000 +0300
|
|
@@ -0,0 +1,1061 @@
|
|
+/*
|
|
+ * sound\soc\sunxi\sunxi-spdif.c
|
|
+ * (C) Copyright 2014-2016
|
|
+ * allwinnertech Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * wolfgang huang <huangjinhui@allwinnertech.com>
|
|
+ *
|
|
+ * some simple description for this code
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/dma/sunxi-dma.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <sound/core.h>
|
|
+#include <sound/pcm.h>
|
|
+#include <sound/pcm_params.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <linux/sunxi-gpio.h>
|
|
+#include <sound/initval.h>
|
|
+#include <sound/soc.h>
|
|
+#include "sunxi-spdif.h"
|
|
+#include "sunxi-pcm.h"
|
|
+
|
|
+#define DRV_NAME "sunxi-spdif"
|
|
+
|
|
+struct sunxi_spdif_info {
|
|
+ struct device *dev;
|
|
+ void __iomem *membase;
|
|
+ struct regmap *regmap;
|
|
+ struct mutex mutex;
|
|
+ struct clk *pllclk;
|
|
+#ifdef SPDIF_PLL_AUDIO_X4
|
|
+ struct clk *pllclkx4;
|
|
+#endif
|
|
+ struct clk *moduleclk;
|
|
+ struct snd_soc_dai_driver dai;
|
|
+ struct sunxi_dma_params playback_dma_param;
|
|
+ struct sunxi_dma_params capture_dma_param;
|
|
+ struct pinctrl *pinctrl;
|
|
+ struct pinctrl_state *pinstate;
|
|
+#ifdef SPDIF_PINCTRL_STATE_DEFAULT_B
|
|
+ struct pinctrl_state *pinstate_b;
|
|
+#endif
|
|
+ struct pinctrl_state *pinstate_sleep;
|
|
+ unsigned int rate;
|
|
+ unsigned int active;
|
|
+ bool configured;
|
|
+};
|
|
+
|
|
+struct sample_rate {
|
|
+ unsigned int samplerate;
|
|
+ unsigned int rate_bit;
|
|
+};
|
|
+
|
|
+
|
|
+struct spdif_gpio_ {
|
|
+ u32 gpio;
|
|
+ bool cfg;
|
|
+};
|
|
+struct spdif_gpio_ spdif_gpio;
|
|
+
|
|
+/* Origin freq convert */
|
|
+static const struct sample_rate sample_rate_orig[] = {
|
|
+ {44100, 0xF},
|
|
+ {48000, 0xD},
|
|
+ {24000, 0x9},
|
|
+ {32000, 0xC},
|
|
+ {96000, 0x5},
|
|
+ {192000, 0x1},
|
|
+ {22050, 0xB},
|
|
+ {88200, 0x7},
|
|
+ {176400, 0x3},
|
|
+};
|
|
+
|
|
+static const struct sample_rate sample_rate_freq[] = {
|
|
+ {44100, 0x0},
|
|
+ {48000, 0x2},
|
|
+ {24000, 0x6},
|
|
+ {32000, 0x3},
|
|
+ {96000, 0xA},
|
|
+ {192000, 0xE},
|
|
+ {22050, 0x4},
|
|
+ {88200, 0x8},
|
|
+ {176400, 0xC},
|
|
+};
|
|
+
|
|
+
|
|
+static int sunxi_spdif_set_audio_mode(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_codec_get_drvdata(codec);
|
|
+ unsigned int reg_val;
|
|
+
|
|
+ regmap_read(sunxi_spdif->regmap, SUNXI_SPDIF_TXCH_STA0, ®_val);
|
|
+
|
|
+ switch (ucontrol->value.integer.value[0]) {
|
|
+ case 0:
|
|
+ case 1:
|
|
+ reg_val = 0;
|
|
+ break;
|
|
+ case 2:
|
|
+ reg_val = 1;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCFG,
|
|
+ (1<<TXCFG_DATA_TYPE), (reg_val<<TXCFG_DATA_TYPE));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCH_STA0,
|
|
+ (1<<TXCHSTA0_AUDIO), (reg_val<<TXCHSTA0_AUDIO));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_RXCH_STA0,
|
|
+ (1<<RXCHSTA0_AUDIO), (reg_val<<RXCHSTA0_AUDIO));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_get_audio_mode(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_codec_get_drvdata(codec);
|
|
+ unsigned int reg_val;
|
|
+
|
|
+ regmap_read(sunxi_spdif->regmap, SUNXI_SPDIF_TXCFG, ®_val);
|
|
+ reg_val = (reg_val & (1<<TXCFG_DATA_TYPE)) ? 1 : 0;
|
|
+ ucontrol->value.integer.value[0] = reg_val + 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const char *spdif_format_function[] = {"null", "pcm", "DTS"};
|
|
+static const struct soc_enum spdif_format_enum[] = {
|
|
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spdif_format_function),
|
|
+ spdif_format_function),
|
|
+};
|
|
+
|
|
+
|
|
+static int sunxi_spdif_get_hub_mode(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_codec_get_drvdata(codec);
|
|
+ unsigned int reg_val;
|
|
+
|
|
+ regmap_read(sunxi_spdif->regmap, SUNXI_SPDIF_FIFO_CTL, ®_val);
|
|
+
|
|
+ ucontrol->value.integer.value[0] =
|
|
+ ((reg_val & (1<<FIFO_CTL_HUBEN)) ? 2 : 1);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_set_hub_mode(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_codec_get_drvdata(codec);
|
|
+
|
|
+ switch (ucontrol->value.integer.value[0]) {
|
|
+ case 0:
|
|
+ case 1:
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_FIFO_CTL,
|
|
+ (1<<FIFO_CTL_HUBEN), (0<<FIFO_CTL_HUBEN));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCFG,
|
|
+ (1<<TXCFG_TXEN), (0<<TXCFG_TXEN));
|
|
+ break;
|
|
+ case 2:
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_FIFO_CTL,
|
|
+ (1<<FIFO_CTL_HUBEN), (1<<FIFO_CTL_HUBEN));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCFG,
|
|
+ (1<<TXCFG_TXEN), (1<<TXCFG_TXEN));
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/* sunxi spdif hub mdoe select */
|
|
+static const char *spdif_hub_function[] = {"null",
|
|
+ "hub_disable", "hub_enable"};
|
|
+
|
|
+static const struct soc_enum spdif_hub_mode_enum[] = {
|
|
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spdif_hub_function),
|
|
+ spdif_hub_function),
|
|
+};
|
|
+
|
|
+/* dts pcm Audio Mode Select */
|
|
+static const struct snd_kcontrol_new sunxi_spdif_controls[] = {
|
|
+ SOC_ENUM_EXT("spdif audio format Function", spdif_format_enum[0],
|
|
+ sunxi_spdif_get_audio_mode, sunxi_spdif_set_audio_mode),
|
|
+ SOC_ENUM_EXT("sunxi spdif hub mode", spdif_hub_mode_enum[0],
|
|
+ sunxi_spdif_get_hub_mode, sunxi_spdif_set_hub_mode),
|
|
+#ifdef SPDIF_LOOPBACK_DEBUG
|
|
+ SOC_SINGLE("sunxi spdif loopback debug", SUNXI_SPDIF_CTL,
|
|
+ CTL_LOOP_EN, 1, 0),
|
|
+#endif
|
|
+};
|
|
+
|
|
+static void sunxi_spdif_txctrl_enable(struct sunxi_spdif_info *sunxi_spdif,
|
|
+ int enable)
|
|
+{
|
|
+ if (enable) {
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCFG,
|
|
+ (1<<TXCFG_TXEN), (1<<TXCFG_TXEN));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_INT,
|
|
+ (1<<INT_TXDRQEN), (1<<INT_TXDRQEN));
|
|
+ } else {
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCFG,
|
|
+ (1<<TXCFG_TXEN), (0<<TXCFG_TXEN));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_INT,
|
|
+ (1<<INT_TXDRQEN), (0<<INT_TXDRQEN));
|
|
+ }
|
|
+}
|
|
+
|
|
+static void sunxi_spdif_rxctrl_enable(struct sunxi_spdif_info *sunxi_spdif,
|
|
+ int enable)
|
|
+{
|
|
+ if (enable) {
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_INT,
|
|
+ (1<<INT_RXDRQEN), (1<<INT_RXDRQEN));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_RXCFG,
|
|
+ (1<<RXCFG_RXEN), (1<<RXCFG_RXEN));
|
|
+ } else {
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_RXCFG,
|
|
+ (1<<RXCFG_RXEN), (0<<RXCFG_RXEN));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_INT,
|
|
+ (1<<INT_RXDRQEN), (0<<INT_RXDRQEN));
|
|
+ }
|
|
+}
|
|
+
|
|
+static void sunxi_spdif_init(struct sunxi_spdif_info *sunxi_spdif)
|
|
+{
|
|
+ /* FIFO CTL register default setting */
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_FIFO_CTL,
|
|
+ (CTL_TXTL_MASK << FIFO_CTL_TXTL),
|
|
+ (CTL_TXTL_DEFAULT << FIFO_CTL_TXTL));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_FIFO_CTL,
|
|
+ (CTL_RXTL_MASK << FIFO_CTL_RXTL),
|
|
+ (CTL_RXTL_DEFAULT << FIFO_CTL_RXTL));
|
|
+
|
|
+ regmap_write(sunxi_spdif->regmap, SUNXI_SPDIF_TXCH_STA0,
|
|
+ 0x2 << TXCHSTA0_CHNUM);
|
|
+ regmap_write(sunxi_spdif->regmap, SUNXI_SPDIF_RXCH_STA0,
|
|
+ 0x2 << RXCHSTA0_CHNUM);
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_dai_hw_params(struct snd_pcm_substream *substream,
|
|
+ struct snd_pcm_hw_params *params,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+ unsigned int reg_temp;
|
|
+ unsigned int tx_input_mode = 0;
|
|
+ unsigned int rx_output_mode = 0;
|
|
+ unsigned int i;
|
|
+ unsigned int origin_freq_bit = 0, sample_freq_bit = 0;
|
|
+
|
|
+ /* two substream should be warking on same samplerate */
|
|
+ mutex_lock(&sunxi_spdif->mutex);
|
|
+ if (sunxi_spdif->active > 1) {
|
|
+ if (params_rate(params) != sunxi_spdif->rate) {
|
|
+ mutex_unlock(&sunxi_spdif->mutex);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+ mutex_unlock(&sunxi_spdif->mutex);
|
|
+
|
|
+ switch (params_format(params)) {
|
|
+ case SNDRV_PCM_FORMAT_S16_LE:
|
|
+ reg_temp = 0;
|
|
+ tx_input_mode = 1;
|
|
+ rx_output_mode = 3;
|
|
+ break;
|
|
+ case SNDRV_PCM_FORMAT_S20_3LE:
|
|
+ reg_temp = 1;
|
|
+ tx_input_mode = 0;
|
|
+ rx_output_mode = 0;
|
|
+ break;
|
|
+ case SNDRV_PCM_FORMAT_S32_LE:
|
|
+ /* only for the compatible of tinyalsa */
|
|
+ case SNDRV_PCM_FORMAT_S24_LE:
|
|
+ reg_temp = 2;
|
|
+ tx_input_mode = 0;
|
|
+ rx_output_mode = 0;
|
|
+ break;
|
|
+ default:
|
|
+ pr_err("[sunxi-spdif] params_format[%d] error!\n",
|
|
+ params_format(params));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(sample_rate_orig); i++) {
|
|
+ if (params_rate(params) == sample_rate_orig[i].samplerate) {
|
|
+ origin_freq_bit = sample_rate_orig[i].rate_bit;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(sample_rate_freq); i++) {
|
|
+ if (params_rate(params) == sample_rate_freq[i].samplerate) {
|
|
+ sample_freq_bit = sample_rate_freq[i].rate_bit;
|
|
+ sunxi_spdif->rate = sample_rate_freq[i].samplerate;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCFG,
|
|
+ (3<<TXCFG_SAMPLE_BIT), (reg_temp<<TXCFG_SAMPLE_BIT));
|
|
+
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_FIFO_CTL,
|
|
+ (1<<FIFO_CTL_TXIM),
|
|
+ (tx_input_mode << FIFO_CTL_TXIM));
|
|
+
|
|
+ if (params_channels(params) == 1) {
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_TXCFG, (1<<TXCFG_SINGLE_MOD),
|
|
+ (1<<TXCFG_SINGLE_MOD));
|
|
+ } else {
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_TXCFG, (1<<TXCFG_SINGLE_MOD),
|
|
+ (0<<TXCFG_SINGLE_MOD));
|
|
+ }
|
|
+
|
|
+ /* samplerate conversion */
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCH_STA0,
|
|
+ (0xF<<TXCHSTA0_SAMFREQ),
|
|
+ (sample_freq_bit<<TXCHSTA0_SAMFREQ));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_TXCH_STA1,
|
|
+ (0xF<<TXCHSTA1_ORISAMFREQ),
|
|
+ (origin_freq_bit<<TXCHSTA1_ORISAMFREQ));
|
|
+ switch (reg_temp) {
|
|
+ case 0:
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_TXCH_STA1,
|
|
+ (0xF<<TXCHSTA1_MAXWORDLEN),
|
|
+ (2<<TXCHSTA1_MAXWORDLEN));
|
|
+ break;
|
|
+ case 1:
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_TXCH_STA1,
|
|
+ (0xF<<TXCHSTA1_MAXWORDLEN),
|
|
+ (0xC<<TXCHSTA1_MAXWORDLEN));
|
|
+ break;
|
|
+ case 2:
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_TXCH_STA1,
|
|
+ (0xF<<TXCHSTA1_MAXWORDLEN),
|
|
+ (0xB<<TXCHSTA1_MAXWORDLEN));
|
|
+ break;
|
|
+ default:
|
|
+ pr_debug("[sunxi-spdif]unexpection error\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ } else {
|
|
+ /*
|
|
+ * FIXME, not sync as spec says, just test 16bit & 24bit,
|
|
+ * using 3 working ok
|
|
+ */
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_FIFO_CTL,
|
|
+ (0x3 << FIFO_CTL_RXOM),
|
|
+ (rx_output_mode << FIFO_CTL_RXOM));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_RXCH_STA0,
|
|
+ (0xF<<RXCHSTA0_SAMFREQ),
|
|
+ (sample_freq_bit<<RXCHSTA0_SAMFREQ));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_RXCH_STA1,
|
|
+ (0xF<<RXCHSTA1_ORISAMFREQ),
|
|
+ (origin_freq_bit<<RXCHSTA1_ORISAMFREQ));
|
|
+
|
|
+ switch (reg_temp) {
|
|
+ case 0:
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_RXCH_STA1,
|
|
+ (0xF<<RXCHSTA1_MAXWORDLEN),
|
|
+ (2<<RXCHSTA1_MAXWORDLEN));
|
|
+ break;
|
|
+ case 1:
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_RXCH_STA1,
|
|
+ (0xF<<RXCHSTA1_MAXWORDLEN),
|
|
+ (0xC<<RXCHSTA1_MAXWORDLEN));
|
|
+ break;
|
|
+ case 2:
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_RXCH_STA1,
|
|
+ (0xF<<RXCHSTA1_MAXWORDLEN),
|
|
+ (0xB<<RXCHSTA1_MAXWORDLEN));
|
|
+ break;
|
|
+ default:
|
|
+ pr_debug("[sunxi-spdif]unexpection error\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
+ unsigned int freq, int dir)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+
|
|
+ pr_debug("Enter %s\n", __func__);
|
|
+ mutex_lock(&sunxi_spdif->mutex);
|
|
+ if (sunxi_spdif->active == 0) {
|
|
+ pr_debug("active: %u\n", sunxi_spdif->active);
|
|
+ if (clk_set_rate(sunxi_spdif->pllclk, freq)) {
|
|
+ dev_err(sunxi_spdif->dev,
|
|
+ "pllclk set rate to %uHz failed\n", freq);
|
|
+ mutex_unlock(&sunxi_spdif->mutex);
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ }
|
|
+ sunxi_spdif->active++;
|
|
+ mutex_unlock(&sunxi_spdif->mutex);
|
|
+ pr_debug("End %s\n", __func__);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_dai_set_clkdiv(struct snd_soc_dai *dai,
|
|
+ int clk_id, int clk_div)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+
|
|
+ pr_debug("Enter %s\n", __func__);
|
|
+
|
|
+ mutex_lock(&sunxi_spdif->mutex);
|
|
+ if (sunxi_spdif->configured == false) {
|
|
+ switch (clk_id) {
|
|
+ case 0:
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_TXCFG,
|
|
+ (0x1F<<TXCFG_CLK_DIV_RATIO),
|
|
+ ((clk_div-1)<<TXCFG_CLK_DIV_RATIO));
|
|
+ break;
|
|
+ case 1:
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ sunxi_spdif->configured = true;
|
|
+ mutex_unlock(&sunxi_spdif->mutex);
|
|
+
|
|
+ pr_debug("End %s\n", __func__);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_dai_startup(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ snd_soc_dai_set_dma_data(dai, substream,
|
|
+ &sunxi_spdif->playback_dma_param);
|
|
+ else
|
|
+ snd_soc_dai_set_dma_data(dai, substream,
|
|
+ &sunxi_spdif->capture_dma_param);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void sunxi_spdif_dai_shutdown(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+
|
|
+ mutex_lock(&sunxi_spdif->mutex);
|
|
+ if (sunxi_spdif->active) {
|
|
+ sunxi_spdif->active--;
|
|
+ if (sunxi_spdif->active == 0)
|
|
+ sunxi_spdif->configured = false;
|
|
+ }
|
|
+ mutex_unlock(&sunxi_spdif->mutex);
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_trigger(struct snd_pcm_substream *substream,
|
|
+ int cmd, struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+ int ret = 0;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case SNDRV_PCM_TRIGGER_START:
|
|
+ case SNDRV_PCM_TRIGGER_RESUME:
|
|
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
+ if (spdif_gpio.cfg)
|
|
+ gpio_set_value(spdif_gpio.gpio, 1);
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ sunxi_spdif_txctrl_enable(sunxi_spdif, 1);
|
|
+ else
|
|
+ sunxi_spdif_rxctrl_enable(sunxi_spdif, 1);
|
|
+ break;
|
|
+ case SNDRV_PCM_TRIGGER_STOP:
|
|
+ case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ sunxi_spdif_txctrl_enable(sunxi_spdif, 0);
|
|
+ else
|
|
+ sunxi_spdif_rxctrl_enable(sunxi_spdif, 0);
|
|
+ if (spdif_gpio.cfg)
|
|
+ gpio_set_value(spdif_gpio.gpio, 0);
|
|
+ break;
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* Flush FIFO & Interrupt */
|
|
+static int sunxi_spdif_prepare(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+ unsigned int reg_val;
|
|
+
|
|
+ /*as you need to clean up TX or RX FIFO , need to turn off GEN bit*/
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_CTL,
|
|
+ (1 << CTL_GEN_EN), (0 << CTL_GEN_EN));
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ regmap_update_bits(sunxi_spdif->regmap,
|
|
+ SUNXI_SPDIF_FIFO_CTL,
|
|
+ (1<<FIFO_CTL_FTX), (1<<FIFO_CTL_FTX));
|
|
+ regmap_write(sunxi_spdif->regmap, SUNXI_SPDIF_TXCNT, 0);
|
|
+ } else {
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_FIFO_CTL,
|
|
+ (1<<FIFO_CTL_FRX), (1<<FIFO_CTL_FRX));
|
|
+ regmap_write(sunxi_spdif->regmap, SUNXI_SPDIF_RXCNT, 0);
|
|
+ }
|
|
+
|
|
+ /* clear all interrupt status */
|
|
+ regmap_read(sunxi_spdif->regmap, SUNXI_SPDIF_INT_STA, ®_val);
|
|
+ regmap_write(sunxi_spdif->regmap, SUNXI_SPDIF_INT_STA, reg_val);
|
|
+
|
|
+ /* need reset */
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_CTL,
|
|
+ (1 << CTL_RESET), (1 << CTL_RESET));
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_CTL,
|
|
+ (1 << CTL_GEN_EN), (1 << CTL_GEN_EN));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_probe(struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+
|
|
+ mutex_init(&sunxi_spdif->mutex);
|
|
+
|
|
+ sunxi_spdif_init(sunxi_spdif);
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_CTL,
|
|
+ (1<<CTL_GEN_EN), (1<<CTL_GEN_EN));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_remove(struct snd_soc_dai *dai)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_suspend(struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+ int ret;
|
|
+
|
|
+ pr_debug("[SPDIF]Enter %s\n", __func__);
|
|
+
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_CTL,
|
|
+ (1<<CTL_GEN_EN), (0<<CTL_GEN_EN));
|
|
+
|
|
+ if (sunxi_spdif->pinstate_sleep) {
|
|
+ ret = pinctrl_select_state(sunxi_spdif->pinctrl,
|
|
+ sunxi_spdif->pinstate_sleep);
|
|
+ if (ret) {
|
|
+ dev_err(sunxi_spdif->dev,
|
|
+ "pinstate sleep select failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (sunxi_spdif->pinctrl != NULL)
|
|
+ devm_pinctrl_put(sunxi_spdif->pinctrl);
|
|
+
|
|
+ pr_debug("[sunxi-spdif]sunxi_spdif->clk_enable: %d\n",
|
|
+ sunxi_spdif->active);
|
|
+
|
|
+ sunxi_spdif->pinctrl = NULL;
|
|
+ sunxi_spdif->pinstate = NULL;
|
|
+#ifdef SPDIF_PINCTRL_STATE_DEFAULT_B
|
|
+ sunxi_spdif->pinstate_b = NULL;
|
|
+#endif
|
|
+ sunxi_spdif->pinstate_sleep = NULL;
|
|
+ if (sunxi_spdif->moduleclk != NULL)
|
|
+ clk_disable_unprepare(sunxi_spdif->moduleclk);
|
|
+#ifdef SPDIF_PLL_AUDIO_X4
|
|
+ if (sunxi_spdif->pllclkx4 != NULL)
|
|
+ clk_disable_unprepare(sunxi_spdif->pllclkx4);
|
|
+#endif
|
|
+ if (sunxi_spdif->pllclk != NULL)
|
|
+ clk_disable_unprepare(sunxi_spdif->pllclk);
|
|
+
|
|
+ pr_debug("[SPDIF]End %s\n", __func__);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_spdif_resume(struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = snd_soc_dai_get_drvdata(dai);
|
|
+ int ret;
|
|
+
|
|
+ pr_debug("[sunxi-spdif]Enter %s\n", __func__);
|
|
+
|
|
+ if (sunxi_spdif->pllclk != NULL) {
|
|
+ ret = clk_prepare_enable(sunxi_spdif->pllclk);
|
|
+ if (ret)
|
|
+ pr_err("[%s] pllclk prepare enable error:%d\n",
|
|
+ __func__, ret);
|
|
+ }
|
|
+#ifdef SPDIF_PLL_AUDIO_X4
|
|
+ if (sunxi_spdif->pllclkx4 != NULL) {
|
|
+ ret = clk_prepare_enable(sunxi_spdif->pllclkx4);
|
|
+ if (ret)
|
|
+ pr_err("[%s] pllclkx4 prepare enable error:%d\n",
|
|
+ __func__, ret);
|
|
+ }
|
|
+#endif
|
|
+ if (sunxi_spdif->moduleclk != NULL) {
|
|
+ ret = clk_prepare_enable(sunxi_spdif->moduleclk);
|
|
+ if (ret)
|
|
+ pr_err("[%s] modlule clk prepare enable error:%d\n",
|
|
+ __func__, ret);
|
|
+ }
|
|
+
|
|
+ if (sunxi_spdif->pinctrl == NULL) {
|
|
+ sunxi_spdif->pinctrl = devm_pinctrl_get(sunxi_spdif->dev);
|
|
+ if (IS_ERR_OR_NULL(sunxi_spdif->pinctrl)) {
|
|
+ dev_err(sunxi_spdif->dev,
|
|
+ "Can't get sunxi spdif pinctrl\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!sunxi_spdif->pinstate) {
|
|
+ sunxi_spdif->pinstate = pinctrl_lookup_state(
|
|
+ sunxi_spdif->pinctrl, PINCTRL_STATE_DEFAULT);
|
|
+ if (IS_ERR_OR_NULL(sunxi_spdif->pinstate)) {
|
|
+ dev_err(sunxi_spdif->dev,
|
|
+ "Can't get spdif pinctrl default state\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ }
|
|
+
|
|
+#ifdef SPDIF_PINCTRL_STATE_DEFAULT_B
|
|
+ if (!sunxi_spdif->pinstate_b) {
|
|
+ sunxi_spdif->pinstate_b = pinctrl_lookup_state(
|
|
+ sunxi_spdif->pinctrl,
|
|
+ SPDIF_PINCTRL_STATE_DEFAULT_B);
|
|
+ if (IS_ERR_OR_NULL(sunxi_spdif->pinstate_b)) {
|
|
+ dev_err(sunxi_spdif->dev,
|
|
+ "Can't get spdif pinctrl pins_b state\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ if (!sunxi_spdif->pinstate_sleep) {
|
|
+ sunxi_spdif->pinstate_sleep = pinctrl_lookup_state(
|
|
+ sunxi_spdif->pinctrl, PINCTRL_STATE_SLEEP);
|
|
+ if (IS_ERR_OR_NULL(sunxi_spdif->pinstate_sleep)) {
|
|
+ dev_err(sunxi_spdif->dev,
|
|
+ "Can't get spdif pinctrl sleep state\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ret = pinctrl_select_state(sunxi_spdif->pinctrl, sunxi_spdif->pinstate);
|
|
+ if (ret) {
|
|
+ dev_err(sunxi_spdif->dev, "select pin default state failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+#ifdef SPDIF_PINCTRL_STATE_DEFAULT_B
|
|
+ ret = pinctrl_select_state(sunxi_spdif->pinctrl, sunxi_spdif->pinstate_b);
|
|
+ if (ret) {
|
|
+ dev_err(sunxi_spdif->dev, "select pins_b state failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ sunxi_spdif_init(sunxi_spdif);
|
|
+ regmap_update_bits(sunxi_spdif->regmap, SUNXI_SPDIF_CTL,
|
|
+ (1<<CTL_GEN_EN), (1<<CTL_GEN_EN));
|
|
+
|
|
+ pr_debug("[sunxi-spdif]End %s\n", __func__);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#define SUNXI_SPDIF_RATES (SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT)
|
|
+
|
|
+static struct snd_soc_dai_ops sunxi_spdif_dai_ops = {
|
|
+ .hw_params = sunxi_spdif_dai_hw_params,
|
|
+ .set_clkdiv = sunxi_spdif_dai_set_clkdiv,
|
|
+ .set_sysclk = sunxi_spdif_dai_set_sysclk,
|
|
+ .startup = sunxi_spdif_dai_startup,
|
|
+ .shutdown = sunxi_spdif_dai_shutdown,
|
|
+ .trigger = sunxi_spdif_trigger,
|
|
+ .prepare = sunxi_spdif_prepare,
|
|
+};
|
|
+
|
|
+static struct snd_soc_dai_driver sunxi_spdif_dai = {
|
|
+ .probe = sunxi_spdif_probe,
|
|
+ .suspend = sunxi_spdif_suspend,
|
|
+ .resume = sunxi_spdif_resume,
|
|
+ .remove = sunxi_spdif_remove,
|
|
+ .playback = {
|
|
+ .channels_min = 1,
|
|
+ .channels_max = 2,
|
|
+ .rates = SUNXI_SPDIF_RATES,
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
|
|
+ | SNDRV_PCM_FMTBIT_S24_LE
|
|
+ | SNDRV_PCM_FMTBIT_S32_LE,
|
|
+ },
|
|
+ .capture = {
|
|
+ .channels_min = 1,
|
|
+ .channels_max = 2,
|
|
+ .rates = SUNXI_SPDIF_RATES,
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
+ SNDRV_PCM_FMTBIT_S24_LE,
|
|
+ },
|
|
+ .ops = &sunxi_spdif_dai_ops,
|
|
+};
|
|
+
|
|
+static const struct snd_soc_component_driver sunxi_spdif_component = {
|
|
+ .name = DRV_NAME,
|
|
+ .controls = sunxi_spdif_controls,
|
|
+ .num_controls = ARRAY_SIZE(sunxi_spdif_controls),
|
|
+};
|
|
+
|
|
+static const struct regmap_config sunxi_spdif_regmap_config = {
|
|
+ .reg_bits = 32,
|
|
+ .reg_stride = 4,
|
|
+ .val_bits = 32,
|
|
+ .max_register = SUNXI_SPDIF_RXCH_STA1,
|
|
+ .cache_type = REGCACHE_NONE,
|
|
+};
|
|
+
|
|
+static int sunxi_spdif_dev_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct resource res, *memregion;
|
|
+ struct device_node *node = pdev->dev.of_node;
|
|
+ struct sunxi_spdif_info *sunxi_spdif;
|
|
+ struct gpio_config config;
|
|
+ int ret;
|
|
+
|
|
+ sunxi_spdif = devm_kzalloc(&pdev->dev,
|
|
+ sizeof(struct sunxi_spdif_info), GFP_KERNEL);
|
|
+ if (!sunxi_spdif) {
|
|
+ ret = -ENOMEM;
|
|
+ goto err_node_put;
|
|
+ }
|
|
+ dev_set_drvdata(&pdev->dev, sunxi_spdif);
|
|
+ sunxi_spdif->dev = &pdev->dev;
|
|
+ sunxi_spdif->dai = sunxi_spdif_dai;
|
|
+ sunxi_spdif->dai.name = dev_name(&pdev->dev);
|
|
+
|
|
+ ret = of_address_to_resource(node, 0, &res);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Can't parse device node resource\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ memregion = devm_request_mem_region(&pdev->dev, res.start,
|
|
+ resource_size(&res), DRV_NAME);
|
|
+ if (memregion == NULL) {
|
|
+ dev_err(&pdev->dev, "Memory region already claimed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_devm_kfree;
|
|
+ }
|
|
+
|
|
+ sunxi_spdif->membase = ioremap(res.start, resource_size(&res));
|
|
+ if (sunxi_spdif->membase == NULL) {
|
|
+ dev_err(&pdev->dev, "Can't remap sunxi spdif registers\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_devm_kfree;
|
|
+ }
|
|
+
|
|
+ sunxi_spdif->regmap = devm_regmap_init_mmio(&pdev->dev,
|
|
+ sunxi_spdif->membase, &sunxi_spdif_regmap_config);
|
|
+ if (IS_ERR(sunxi_spdif->regmap)) {
|
|
+ dev_err(&pdev->dev, "regmap sunxi spdif membase failed\n");
|
|
+ ret = PTR_ERR(sunxi_spdif->regmap);
|
|
+ goto err_iounmap;
|
|
+ }
|
|
+
|
|
+ sunxi_spdif->pllclk = of_clk_get(node, 0);
|
|
+ if (IS_ERR(sunxi_spdif->pllclk)) {
|
|
+ dev_err(&pdev->dev, "Can't get pll_audo clk clocks!\n");
|
|
+ ret = PTR_ERR(sunxi_spdif->pllclk);
|
|
+ goto err_iounmap;
|
|
+ }
|
|
+
|
|
+#ifdef SPDIF_PLL_AUDIO_X4
|
|
+ sunxi_spdif->pllclkx4 = of_clk_get(node, 1);
|
|
+ if (IS_ERR(sunxi_spdif->pllclkx4)) {
|
|
+ dev_err(&pdev->dev, "Can't get pll_audiox4 clk clocks!\n");
|
|
+ ret = PTR_ERR(sunxi_spdif->pllclkx4);
|
|
+ goto err_pllclk_put;
|
|
+ }
|
|
+
|
|
+ sunxi_spdif->moduleclk = of_clk_get(node, 2);
|
|
+ if (IS_ERR(sunxi_spdif->moduleclk)) {
|
|
+ dev_err(&pdev->dev, "Can't get spdif clocks\n");
|
|
+ ret = PTR_ERR(sunxi_spdif->moduleclk);
|
|
+ goto err_pllclkx4_put;
|
|
+ } else {
|
|
+ if (clk_set_parent(sunxi_spdif->moduleclk,
|
|
+ sunxi_spdif->pllclkx4)) {
|
|
+ dev_err(&pdev->dev,
|
|
+ "set parent of moduleclk to pllclk failed!\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_moduleclk_put;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (clk_prepare_enable(sunxi_spdif->pllclk)) {
|
|
+ dev_err(&pdev->dev, "pllclk enable failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_moduleclk_put;
|
|
+ }
|
|
+
|
|
+ if (clk_prepare_enable(sunxi_spdif->pllclkx4)) {
|
|
+ dev_err(&pdev->dev, "pllclkx4 enable failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_pllclk_disable;
|
|
+ }
|
|
+
|
|
+ if (clk_prepare_enable(sunxi_spdif->moduleclk)) {
|
|
+ dev_err(&pdev->dev, "moduleclk enable failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_pllclkx4_disable;
|
|
+ }
|
|
+#else
|
|
+ sunxi_spdif->moduleclk = of_clk_get(node, 1);
|
|
+ if (IS_ERR(sunxi_spdif->moduleclk)) {
|
|
+ dev_err(&pdev->dev, "Can't get spdif clocks\n");
|
|
+ ret = PTR_ERR(sunxi_spdif->moduleclk);
|
|
+ goto err_pllclk_put;
|
|
+ } else {
|
|
+ if (clk_set_parent(sunxi_spdif->moduleclk,
|
|
+ sunxi_spdif->pllclk)) {
|
|
+ dev_err(&pdev->dev,
|
|
+ "set parent of moduleclk to pllclk failed!\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_moduleclk_put;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (clk_prepare_enable(sunxi_spdif->pllclk)) {
|
|
+ dev_err(&pdev->dev, "pllclk enable failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_moduleclk_put;
|
|
+ }
|
|
+
|
|
+ if (clk_prepare_enable(sunxi_spdif->moduleclk)) {
|
|
+ dev_err(&pdev->dev, "moduleclk enable failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_pllclk_disable;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ sunxi_spdif->playback_dma_param.dma_addr =
|
|
+ res.start + SUNXI_SPDIF_TXFIFO;
|
|
+ sunxi_spdif->playback_dma_param.dma_drq_type_num = DRQDST_SPDIFTX;
|
|
+ sunxi_spdif->playback_dma_param.dst_maxburst = 8;
|
|
+ sunxi_spdif->playback_dma_param.src_maxburst = 8;
|
|
+
|
|
+ sunxi_spdif->capture_dma_param.dma_addr =
|
|
+ res.start + SUNXI_SPDIF_RXFIFO;
|
|
+ sunxi_spdif->capture_dma_param.dma_drq_type_num = DRQSRC_SPDIFRX;
|
|
+ sunxi_spdif->capture_dma_param.src_maxburst = 8;
|
|
+ sunxi_spdif->capture_dma_param.dst_maxburst = 8;
|
|
+
|
|
+ sunxi_spdif->pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
+ if (IS_ERR_OR_NULL(sunxi_spdif->pinctrl)) {
|
|
+ dev_err(&pdev->dev,
|
|
+ "request pinctrl handle for audio failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_moduleclk_disable;
|
|
+ }
|
|
+
|
|
+ sunxi_spdif->pinstate = pinctrl_lookup_state(sunxi_spdif->pinctrl,
|
|
+ PINCTRL_STATE_DEFAULT);
|
|
+ if (IS_ERR_OR_NULL(sunxi_spdif->pinstate)) {
|
|
+ dev_err(&pdev->dev, "lookup pin default state failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_pinctrl_put;
|
|
+ }
|
|
+
|
|
+#ifdef SPDIF_PINCTRL_STATE_DEFAULT_B
|
|
+ sunxi_spdif->pinstate_b = pinctrl_lookup_state(sunxi_spdif->pinctrl,
|
|
+ SPDIF_PINCTRL_STATE_DEFAULT_B);
|
|
+ if (IS_ERR_OR_NULL(sunxi_spdif->pinstate_b)) {
|
|
+ dev_err(&pdev->dev, "Can't get spdif pinctrl default state\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_pinctrl_put;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ sunxi_spdif->pinstate_sleep = pinctrl_lookup_state(
|
|
+ sunxi_spdif->pinctrl, PINCTRL_STATE_SLEEP);
|
|
+ if (IS_ERR_OR_NULL(sunxi_spdif->pinstate_sleep)) {
|
|
+ dev_err(&pdev->dev, "lookup pin sleep state failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_pinctrl_put;
|
|
+ }
|
|
+
|
|
+ ret = pinctrl_select_state(sunxi_spdif->pinctrl, sunxi_spdif->pinstate);
|
|
+ if (ret) {
|
|
+ dev_err(sunxi_spdif->dev, "select pin default state failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_pinctrl_put;
|
|
+ }
|
|
+
|
|
+#ifdef SPDIF_PINCTRL_STATE_DEFAULT_B
|
|
+ ret = pinctrl_select_state(sunxi_spdif->pinctrl, sunxi_spdif->pinstate_b);
|
|
+ if (ret) {
|
|
+ dev_err(sunxi_spdif->dev, "select pins_b state failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_pinctrl_put;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ /*initial speaker gpio */
|
|
+ spdif_gpio.gpio = of_get_named_gpio_flags(node, "gpio-spdif", 0,
|
|
+ (enum of_gpio_flags *)&config);
|
|
+ if (!gpio_is_valid(spdif_gpio.gpio)) {
|
|
+ pr_err("failed get gpio-spdif gpio from dts,spdif_gpio:%d\n",
|
|
+ spdif_gpio.gpio);
|
|
+ spdif_gpio.cfg = 0;
|
|
+ } else {
|
|
+ ret = devm_gpio_request(&pdev->dev, spdif_gpio.gpio, "SPDIF");
|
|
+ if (ret) {
|
|
+ spdif_gpio.cfg = 0;
|
|
+ pr_err("failed to request gpio-spdif gpio\n");
|
|
+ } else {
|
|
+ spdif_gpio.cfg = 1;
|
|
+ gpio_direction_output(spdif_gpio.gpio, 0);
|
|
+ }
|
|
+ }
|
|
+ ret = snd_soc_register_component(&pdev->dev, &sunxi_spdif_component,
|
|
+ &sunxi_spdif->dai, 1);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
|
|
+ ret = -ENOMEM;
|
|
+ goto err_pinctrl_put;
|
|
+ }
|
|
+
|
|
+ ret = asoc_dma_platform_register(&pdev->dev, 0);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
|
|
+ ret = -ENOMEM;
|
|
+ goto err_unregister_component;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_unregister_component:
|
|
+ snd_soc_unregister_component(&pdev->dev);
|
|
+err_pinctrl_put:
|
|
+ devm_pinctrl_put(sunxi_spdif->pinctrl);
|
|
+err_moduleclk_disable:
|
|
+ clk_disable_unprepare(sunxi_spdif->moduleclk);
|
|
+#ifdef SPDIF_PLL_AUDIO_X4
|
|
+err_pllclkx4_disable:
|
|
+ clk_disable_unprepare(sunxi_spdif->pllclkx4);
|
|
+#endif
|
|
+err_pllclk_disable:
|
|
+ clk_disable_unprepare(sunxi_spdif->pllclk);
|
|
+err_moduleclk_put:
|
|
+ clk_put(sunxi_spdif->moduleclk);
|
|
+#ifdef SPDIF_PLL_AUDIO_X4
|
|
+err_pllclkx4_put:
|
|
+ clk_put(sunxi_spdif->pllclkx4);
|
|
+#endif
|
|
+err_pllclk_put:
|
|
+ clk_put(sunxi_spdif->pllclk);
|
|
+err_iounmap:
|
|
+ iounmap(sunxi_spdif->membase);
|
|
+err_devm_kfree:
|
|
+ devm_kfree(&pdev->dev, sunxi_spdif);
|
|
+err_node_put:
|
|
+ of_node_put(node);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int __exit sunxi_spdif_dev_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct sunxi_spdif_info *sunxi_spdif = dev_get_drvdata(&pdev->dev);
|
|
+
|
|
+ asoc_dma_platform_unregister(&pdev->dev);
|
|
+ snd_soc_unregister_component(&pdev->dev);
|
|
+ clk_disable_unprepare(sunxi_spdif->moduleclk);
|
|
+ clk_put(sunxi_spdif->moduleclk);
|
|
+#ifdef SPDIF_PLL_AUDIO_X4
|
|
+ clk_disable_unprepare(sunxi_spdif->pllclkx4);
|
|
+ clk_put(sunxi_spdif->pllclkx4);
|
|
+#endif
|
|
+ clk_disable_unprepare(sunxi_spdif->pllclk);
|
|
+ clk_put(sunxi_spdif->pllclk);
|
|
+
|
|
+ iounmap(sunxi_spdif->membase);
|
|
+ devm_kfree(&pdev->dev, sunxi_spdif);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id sunxi_spdif_of_match[] = {
|
|
+ { .compatible = "allwinner,sunxi-spdif", },
|
|
+ {},
|
|
+};
|
|
+
|
|
+static struct platform_driver sunxi_spdif_driver = {
|
|
+ .probe = sunxi_spdif_dev_probe,
|
|
+ .remove = __exit_p(sunxi_spdif_dev_remove),
|
|
+ .driver = {
|
|
+ .name = DRV_NAME,
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = sunxi_spdif_of_match,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(sunxi_spdif_driver);
|
|
+
|
|
+MODULE_AUTHOR("wolfgang huang <huangjinhui@allwinnertech.com>");
|
|
+MODULE_DESCRIPTION("SUNXI SPDIF ASoC Interface");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_ALIAS("platform:sunxi-spdif");
|