SmartAudio/lichee/linux-4.9/sound/soc/sunxi/sunxi_ahub.c

942 lines
31 KiB
C
Executable File

/*
* sound\soc\sunxi\sunxi_ahub.c
* (C) Copyright 2015-2017
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Wolfgang huang <huangjinhui@allwinnertechtech.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/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/regulator/consumer.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/pm.h>
#include <linux/of_gpio.h>
#include <linux/workqueue.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include "sunxi_ahub.h"
#include "sunxi-pcm.h"
#define DRV_NAME "sunxi-ahub"
struct sunxi_ahub_priv {
struct device *dev;
void __iomem *membase;
struct regmap *regmap;
struct clk *pllclk;
struct clk *moduleclk;
} sunxi_ahub_dev;
static struct sunxi_ahub_priv *sunxi_ahub = &sunxi_ahub_dev;
static const struct regmap_config sunxi_ahub_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUNXI_AHUB_DAM_GAIN_CTL7(1),
.cache_type = REGCACHE_RBTREE,
};
unsigned int sunxi_ahub_read(unsigned int reg)
{
return readl(sunxi_ahub->membase + reg);
}
/* reslove conflict with regmap_update_bits using spin_lock */
int sunxi_ahub_update_bits(
unsigned int reg,
unsigned int mask, unsigned int val)
{
unsigned int tmp, orig;
orig = readl(sunxi_ahub->membase + reg);
tmp = orig & ~mask;
tmp |= val & mask;
if (tmp != orig)
writel(tmp, sunxi_ahub->membase + reg);
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_ahub_update_bits);
struct regmap *sunxi_ahub_regmap_init(struct platform_device *pdev)
{
struct resource res, *memregion;
struct device_node *np = pdev->dev.of_node;
int ret;
if (!sunxi_ahub->regmap) {
ret = of_address_to_resource(np, 0, &res);
if (ret)
return NULL;
memregion = devm_request_mem_region(&pdev->dev, res.start,
resource_size(&res), DRV_NAME);
if (!memregion)
return NULL;
sunxi_ahub->membase = ioremap(res.start, resource_size(&res));
if (!sunxi_ahub->membase)
return NULL;
sunxi_ahub->regmap = devm_regmap_init_mmio(&pdev->dev,
sunxi_ahub->membase,
&sunxi_ahub_regmap_config);
if (IS_ERR_OR_NULL(sunxi_ahub->regmap)) {
ret = PTR_ERR(sunxi_ahub->regmap);
return NULL;
}
}
return sunxi_ahub->regmap;
}
EXPORT_SYMBOL_GPL(sunxi_ahub_regmap_init);
static const char * const apbif_mux_text[] = {
"NONE",
"APBIF_TXDIF0",
"APBIF_TXDIF1",
"APBIF_TXDIF2",
"I2S0_TXDIF",
"I2S1_TXDIF",
"I2S2_TXDIF",
"I2S3_TXDIF",
"DAM0_TXDIF",
"DAM1_TXDIF",
};
static const unsigned int apbif_mux_values[] = {
0,
1<<I2S_RX_APBIF_TXDIF0,
1<<I2S_RX_APBIF_TXDIF1,
1<<I2S_RX_APBIF_TXDIF2,
1<<I2S_RX_I2S0_TXDIF,
1<<I2S_RX_I2S1_TXDIF,
1<<I2S_RX_I2S2_TXDIF,
1<<I2S_RX_I2S3_TXDIF,
1<<I2S_RX_DAM0_TXDIF,
1<<I2S_RX_DAM1_TXDIF,
};
#define AHUB_MUX_ENUM_DECL(name, reg) \
SOC_VALUE_ENUM_SINGLE_DECL(name, reg, 0, 0xffffffff, \
apbif_mux_text, apbif_mux_values)
static AHUB_MUX_ENUM_DECL(apbif0, SUNXI_AHUB_APBIF_RXFIFO_CONT(0));
static AHUB_MUX_ENUM_DECL(apbif1, SUNXI_AHUB_APBIF_RXFIFO_CONT(1));
static AHUB_MUX_ENUM_DECL(apbif2, SUNXI_AHUB_APBIF_RXFIFO_CONT(2));
static AHUB_MUX_ENUM_DECL(i2s0, SUNXI_AHUB_I2S_RXCONT(0));
static AHUB_MUX_ENUM_DECL(i2s1, SUNXI_AHUB_I2S_RXCONT(1));
static AHUB_MUX_ENUM_DECL(i2s2, SUNXI_AHUB_I2S_RXCONT(2));
static AHUB_MUX_ENUM_DECL(i2s3, SUNXI_AHUB_I2S_RXCONT(3));
static AHUB_MUX_ENUM_DECL(dam0chan0, SUNXI_AHUB_DAM_RX0_SRC(0));
static AHUB_MUX_ENUM_DECL(dam0chan1, SUNXI_AHUB_DAM_RX1_SRC(0));
static AHUB_MUX_ENUM_DECL(dam0chan2, SUNXI_AHUB_DAM_RX2_SRC(0));
static AHUB_MUX_ENUM_DECL(dam1chan0, SUNXI_AHUB_DAM_RX0_SRC(1));
static AHUB_MUX_ENUM_DECL(dam1chan1, SUNXI_AHUB_DAM_RX1_SRC(1));
static AHUB_MUX_ENUM_DECL(dam1chan2, SUNXI_AHUB_DAM_RX2_SRC(1));
#define APBIF_RX_MUX_CONTROLS(num) \
static const struct snd_kcontrol_new apbif##num##_rx_mux = \
SOC_DAPM_ENUM("APBIF##num Rx Mux", apbif##num);
#define I2S_RX_MUX_CONTROLS(num) \
static const struct snd_kcontrol_new i2s##num##_rx_mux = \
SOC_DAPM_ENUM("I2S##num RX Mux", i2s##num);
#define DAM0_RX_MUX_CONTROLS(chan) \
static const struct snd_kcontrol_new dam0chan##chan##_rx_mux = \
SOC_DAPM_ENUM("DAM0 Chan##chan Rx Mux", dam0chan##chan);
#define DAM1_RX_MUX_CONTROLS(chan) \
static const struct snd_kcontrol_new dam1chan##chan##_rx_mux = \
SOC_DAPM_ENUM("DAM1 Chan##chan Rx Mux", dam1chan##chan);
#define AHUB_MUX(name, ctrl) \
SND_SOC_DAPM_MUX(name, SND_SOC_NOPM, 0, 0, &ctrl)
#define AHUB_MUX_DAM0(name, ctrl, ch) \
SND_SOC_DAPM_MUX(name, \
SUNXI_AHUB_DAM_CTL(0), DAM_CTL_RX0EN+ch, 0, &ctrl)
#define AHUB_MUX_DAM1(name, ctrl, ch) \
SND_SOC_DAPM_MUX(name, \
SUNXI_AHUB_DAM_CTL(1), DAM_CTL_RX0EN+ch, 0, &ctrl)
/*three apbif dev group */
APBIF_RX_MUX_CONTROLS(0)
APBIF_RX_MUX_CONTROLS(1)
APBIF_RX_MUX_CONTROLS(2)
/* four i2s dev group */
I2S_RX_MUX_CONTROLS(0)
I2S_RX_MUX_CONTROLS(1)
I2S_RX_MUX_CONTROLS(2)
I2S_RX_MUX_CONTROLS(3)
/* two digital audio mux & three chan dev group */
DAM0_RX_MUX_CONTROLS(0)
DAM0_RX_MUX_CONTROLS(1)
DAM0_RX_MUX_CONTROLS(2)
DAM1_RX_MUX_CONTROLS(0)
DAM1_RX_MUX_CONTROLS(1)
DAM1_RX_MUX_CONTROLS(2)
struct str_conv {
char *str;
int regbit;
};
/* I2S module usage control by cpu_dai */
static struct str_conv mod_str_conv[] = {
{"APBIF0 DAC", 31},
{"APBIF1 DAC", 30},
{"APBIF2 DAC", 29},
{"APBIF0 ADC", 27},
{"APBIF1 ADC", 26},
{"APBIF2 ADC", 25},
{"DAM0 Mixer", 15},
{"DAM1 Mixer", 14},
};
static int sunxi_ahub_mod_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
struct sunxi_ahub_priv *sunxi_ahub =
snd_soc_codec_get_drvdata(codec);
int reg_bit = 0;
int i;
for (i = 0; i < ARRAY_SIZE(mod_str_conv); i++)
if (!strncmp(mod_str_conv[i].str, w->name,
strlen(mod_str_conv[i].str))) {
reg_bit = mod_str_conv[i].regbit;
break;
}
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_RST,
(0x1<<reg_bit), (0x1<<reg_bit));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_GAT,
(0x1<<reg_bit), (0x1<<reg_bit));
break;
case SND_SOC_DAPM_POST_PMD:
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_GAT,
(0x1<<reg_bit), (0x0<<reg_bit));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_RST,
(0x1<<reg_bit), (0x0<<reg_bit));
break;
default:
break;
}
return 0;
}
static const struct snd_kcontrol_new ahub_controls[] = {
SOC_SINGLE("I2S0 Loopback Debug", SUNXI_AHUB_I2S_CTL(0), I2S_CTL_LOOP, 1, 0),
SOC_SINGLE("I2S1 Loopback Debug", SUNXI_AHUB_I2S_CTL(1), I2S_CTL_LOOP, 1, 0),
SOC_SINGLE("I2S2 Loopback Debug", SUNXI_AHUB_I2S_CTL(2), I2S_CTL_LOOP, 1, 0),
SOC_SINGLE("I2S3 Loopback Debug", SUNXI_AHUB_I2S_CTL(3), I2S_CTL_LOOP, 1, 0),
};
static const struct snd_soc_dapm_widget sunxi_ahub_codec_dapm_widgets[] = {
/* APBIF module output & input widgets */
SND_SOC_DAPM_AIF_IN_E("APBIF0 DAC", "AIF1 Playback", 0,
SND_SOC_NOPM, 0, 0,
sunxi_ahub_mod_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_AIF_OUT_E("APBIF0 ADC", "AIF1 Capture", 0,
SND_SOC_NOPM, 0, 0,
sunxi_ahub_mod_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_AIF_IN_E("APBIF1 DAC", "AIF2 Playback", 0,
SND_SOC_NOPM, 0, 0,
sunxi_ahub_mod_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_AIF_OUT_E("APBIF1 ADC", "AIF2 Capture", 0,
SND_SOC_NOPM, 0, 0,
sunxi_ahub_mod_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_AIF_IN_E("APBIF2 DAC", "AIF3 Playback", 0,
SND_SOC_NOPM, 0, 0,
sunxi_ahub_mod_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_AIF_OUT_E("APBIF2 ADC", "AIF3 Capture", 0,
SND_SOC_NOPM, 0, 0,
sunxi_ahub_mod_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
AHUB_MUX("APBIF0 Src Select", apbif0_rx_mux),
AHUB_MUX("APBIF1 Src Select", apbif1_rx_mux),
AHUB_MUX("APBIF2 Src Select", apbif2_rx_mux),
AHUB_MUX("I2S0 Src Select", i2s0_rx_mux),
AHUB_MUX("I2S1 Src Select", i2s1_rx_mux),
AHUB_MUX("I2S2 Src Select", i2s2_rx_mux),
AHUB_MUX("I2S3 Src Select", i2s3_rx_mux),
AHUB_MUX_DAM0("DAM0Chan0 Src Select", dam0chan0_rx_mux, 0),
AHUB_MUX_DAM0("DAM0Chan1 Src Select", dam0chan1_rx_mux, 1),
AHUB_MUX_DAM0("DAM0Chan2 Src Select", dam0chan2_rx_mux, 2),
AHUB_MUX_DAM1("DAM1Chan0 Src Select", dam1chan0_rx_mux, 0),
AHUB_MUX_DAM1("DAM1Chan1 Src Select", dam1chan1_rx_mux, 1),
AHUB_MUX_DAM1("DAM1Chan2 Src Select", dam1chan2_rx_mux, 2),
SND_SOC_DAPM_OUT_DRV_E("DAM0 Mixer", SUNXI_AHUB_DAM_CTL(0),
DAM_CTL_TXEN, 0, NULL, 0, sunxi_ahub_mod_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_OUT_DRV_E("DAM1 Mixer", SUNXI_AHUB_DAM_CTL(1),
DAM_CTL_TXEN, 0, NULL, 0, sunxi_ahub_mod_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_INPUT("I2S0 DAC"),
SND_SOC_DAPM_INPUT("I2S1 DAC"),
SND_SOC_DAPM_INPUT("I2S2 DAC"),
SND_SOC_DAPM_INPUT("I2S3 DAC"),
SND_SOC_DAPM_OUTPUT("I2S0 ADC"),
SND_SOC_DAPM_OUTPUT("I2S1 ADC"),
SND_SOC_DAPM_OUTPUT("I2S2 ADC"),
SND_SOC_DAPM_OUTPUT("I2S3 ADC"),
/*build some virt widget for dam*/
SND_SOC_DAPM_OUTPUT("DAM0 OUTPUT"),
SND_SOC_DAPM_OUTPUT("DAM1 OUTPUT"),
SND_SOC_DAPM_INPUT("DAM0 INPUT"),
SND_SOC_DAPM_INPUT("DAM1 INPUT"),
};
static const struct snd_soc_dapm_route sunxi_ahub_codec_dapm_routes[] = {
/* APBIF0 from DMA to RX Mux routes */
{"APBIF0 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"APBIF1 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"APBIF2 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"I2S0 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"I2S1 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"I2S2 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"I2S3 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"DAM0Chan0 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"DAM0Chan1 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"DAM0Chan2 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"DAM1Chan0 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"DAM1Chan1 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
{"DAM1Chan2 Src Select", "APBIF_TXDIF0", "APBIF0 DAC"},
/* APBIF1 from DMA to RX Mux routes */
{"APBIF0 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"APBIF1 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"APBIF2 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"I2S0 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"I2S1 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"I2S2 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"I2S3 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"DAM0Chan0 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"DAM0Chan1 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"DAM0Chan2 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"DAM1Chan0 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"DAM1Chan1 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
{"DAM1Chan2 Src Select", "APBIF_TXDIF1", "APBIF1 DAC"},
/* APBIF2 from DMA to RX Mux routes */
{"APBIF0 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"APBIF1 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"APBIF2 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"I2S0 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"I2S1 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"I2S2 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"I2S3 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"DAM0Chan0 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"DAM0Chan1 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"DAM0Chan2 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"DAM1Chan0 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"DAM1Chan1 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
{"DAM1Chan2 Src Select", "APBIF_TXDIF2", "APBIF2 DAC"},
/* I2S0 to RX Mux routes */
{"APBIF0 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"APBIF1 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"APBIF2 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"I2S0 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"I2S1 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"I2S2 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"I2S3 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"DAM0Chan0 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"DAM0Chan1 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"DAM0Chan2 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"DAM1Chan0 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"DAM1Chan1 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
{"DAM1Chan2 Src Select", "I2S0_TXDIF", "I2S0 DAC"},
/* I2S1 to RX Mux routes */
{"APBIF0 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"APBIF1 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"APBIF2 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"I2S0 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"I2S1 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"I2S2 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"I2S3 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"DAM0Chan0 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"DAM0Chan1 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"DAM0Chan2 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"DAM1Chan0 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"DAM1Chan1 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
{"DAM1Chan2 Src Select", "I2S1_TXDIF", "I2S1 DAC"},
/* I2S2 to RX Mux routes */
{"APBIF0 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"APBIF1 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"APBIF2 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"I2S0 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"I2S1 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"I2S2 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"I2S3 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"DAM0Chan0 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"DAM0Chan1 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"DAM0Chan2 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"DAM1Chan0 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"DAM1Chan1 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
{"DAM1Chan2 Src Select", "I2S2_TXDIF", "I2S2 DAC"},
/* I2S3 to RX Mux routes */
{"APBIF0 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"APBIF1 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"APBIF2 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"I2S0 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"I2S1 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"I2S2 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"I2S3 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"DAM0Chan0 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"DAM0Chan1 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"DAM0Chan2 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"DAM1Chan0 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"DAM1Chan1 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
{"DAM1Chan2 Src Select", "I2S3_TXDIF", "I2S3 DAC"},
#if 0
/* DAM0 Audio Mixer output route */
{"DAM0 Mixer", NULL, "DAM0Chan0 Src Select"},
{"DAM0 Mixer", NULL, "DAM0Chan1 Src Select"},
{"DAM0 Mixer", NULL, "DAM0Chan2 Src Select"},
{"DAM1 Mixer", NULL, "DAM1Chan0 Src Select"},
{"DAM1 Mixer", NULL, "DAM1Chan1 Src Select"},
{"DAM1 Mixer", NULL, "DAM1Chan2 Src Select"},
#endif
/* DAM0 Audio Mixer output route */
{"APBIF0 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"APBIF1 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"APBIF2 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"I2S0 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"I2S1 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"I2S2 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"I2S3 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"DAM0Chan0 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"DAM0Chan1 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"DAM0Chan2 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"DAM1Chan0 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"DAM1Chan1 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
{"DAM1Chan2 Src Select", "DAM0_TXDIF", "DAM0 Mixer"},
/* DAM1 Audio Mixer output route */
{"APBIF0 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"APBIF1 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"APBIF2 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"I2S0 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"I2S1 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"I2S2 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"I2S3 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"DAM0Chan0 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"DAM0Chan1 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"DAM0Chan2 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"DAM1Chan0 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"DAM1Chan1 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
{"DAM1Chan2 Src Select", "DAM1_TXDIF", "DAM1 Mixer"},
/* Mixer to APBIF Capture */
{"APBIF0 ADC", NULL, "APBIF0 Src Select"},
{"APBIF1 ADC", NULL, "APBIF1 Src Select"},
{"APBIF2 ADC", NULL, "APBIF2 Src Select"},
/* Mixer to I2S OUT(as ahub side says) */
{"I2S0 ADC", NULL, "I2S0 Src Select"},
{"I2S1 ADC", NULL, "I2S1 Src Select"},
{"I2S2 ADC", NULL, "I2S2 Src Select"},
{"I2S3 ADC", NULL, "I2S3 Src Select"},
{"DAM0 Mixer", NULL, "DAM0 INPUT"},
{"DAM1 Mixer", NULL, "DAM1 INPUT"},
{"DAM0 OUTPUT", NULL, "DAM0Chan0 Src Select"},
{"DAM0 OUTPUT", NULL, "DAM0Chan1 Src Select"},
{"DAM0 OUTPUT", NULL, "DAM0Chan1 Src Select"},
{"DAM1 OUTPUT", NULL, "DAM1Chan0 Src Select"},
{"DAM1 OUTPUT", NULL, "DAM1Chan1 Src Select"},
{"DAM1 OUTPUT", NULL, "DAM1Chan1 Src Select"},
};
static void sunxi_ahub_codec_init(struct snd_soc_codec *codec)
{
struct sunxi_ahub_priv *sunxi_ahub = snd_soc_codec_get_drvdata(codec);
int i;
/* if we used the audio hub, so we default setting HDMI clk from ahub */
regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_CTL, 1<<HDMI_SRC_SEL);
for (i = 0; i < 2; i++) {
/* setting audio hub default channel line configure */
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_MIX_CTL0(i), 0x01110000);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_MIX_CTL1(i), 0x03330222);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_MIX_CTL2(i), 0x05550444);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_MIX_CTL3(i), 0x07770666);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_MIX_CTL4(i), 0x09990888);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_MIX_CTL5(i), 0x0bbb0aaa);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_MIX_CTL6(i), 0x0ddd0ccc);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_MIX_CTL7(i), 0x0fff0eee);
/* setting default audio hub volume */
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_GAIN_CTL0(i), 0x01110111);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_GAIN_CTL1(i), 0x01110111);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_GAIN_CTL2(i), 0x01110111);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_GAIN_CTL3(i), 0x01110111);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_GAIN_CTL4(i), 0x01110111);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_GAIN_CTL5(i), 0x01110111);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_GAIN_CTL6(i), 0x01110111);
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_DAM_GAIN_CTL7(i), 0x01110111);
}
}
static int sunxi_ahub_codec_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct sunxi_ahub_priv *sunxi_ahub =
snd_soc_codec_get_drvdata(dai->codec);
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct sunxi_hdmi_priv *sunxi_hdmi = snd_soc_card_get_drvdata(card);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* special handle for HDMI rawdata mode */
if (sunxi_hdmi->hdmi_format > 1) {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TX_CTL(dai->id),
(7<<APBIF_TX_WS), (7<<APBIF_TX_WS));
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id),
(1<<APBIF_TX_TXIM), (0<<APBIF_TX_TXIM));
} else {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TX_CTL(dai->id),
(7<<APBIF_TX_WS), (3<<APBIF_TX_WS));
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id),
(1<<APBIF_TX_TXIM), (1<<APBIF_TX_TXIM));
}
} else {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RX_CTL(dai->id),
(7<<APBIF_RX_WS), (3<<APBIF_RX_WS));
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RXFIFO_CTL(dai->id),
(3<<APBIF_RX_RXOM), (1<<APBIF_RX_RXOM));
}
break;
case SNDRV_PCM_FORMAT_S24_LE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TX_CTL(dai->id),
(7<<APBIF_TX_WS), (5<<APBIF_TX_WS));
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id),
(1<<APBIF_TX_TXIM), (1<<APBIF_TX_TXIM));
} else {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RX_CTL(dai->id),
(7<<APBIF_RX_WS), (5<<APBIF_RX_WS));
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RXFIFO_CTL(dai->id),
(3<<APBIF_RX_RXOM), (1<<APBIF_RX_RXOM));
}
break;
case SNDRV_PCM_FORMAT_S32_LE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TX_CTL(dai->id),
(7<<APBIF_TX_WS), (7<<APBIF_TX_WS));
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id),
(1<<APBIF_TX_TXIM), (1<<APBIF_TX_TXIM));
} else {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RX_CTL(dai->id),
(7<<APBIF_RX_WS), (7<<APBIF_RX_WS));
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RXFIFO_CTL(dai->id),
(3<<APBIF_RX_RXOM), (1<<APBIF_RX_RXOM));
}
break;
default:
dev_info(sunxi_ahub->dev, "unsupport format");
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TX_CTL(dai->id),
(0xf<<APBIF_TX_CHAN_NUM),
((params_channels(params)-1)<<APBIF_TX_CHAN_NUM));
else
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RX_CTL(dai->id),
(0xf<<APBIF_RX_CHAN_NUM),
((params_channels(params)-1)<<APBIF_RX_CHAN_NUM));
/*
* we should using this as the demand chans, but we can't distinguish
* stream type(playback or capture). so we can't make it done on demand,
* so we just make all dam rx channel number as the sunxi_ahub->channel.
*/
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(0),
(0xf<<DAM_CTL_RX0_NUM),
((params_channels(params)-1)<<DAM_CTL_RX0_NUM));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(0),
(0xf<<DAM_CTL_RX1_NUM),
((params_channels(params)-1)<<DAM_CTL_RX1_NUM));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(0),
(0xf<<DAM_CTL_RX2_NUM),
((params_channels(params)-1)<<DAM_CTL_RX2_NUM));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(0),
(0xf<<DAM_CTL_TX_NUM),
((params_channels(params)-1)<<DAM_CTL_TX_NUM));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(1),
(0xf<<DAM_CTL_RX0_NUM),
((params_channels(params)-1)<<DAM_CTL_RX0_NUM));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(1),
(0xf<<DAM_CTL_RX1_NUM),
((params_channels(params)-1)<<DAM_CTL_RX1_NUM));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(1),
(0xf<<DAM_CTL_RX2_NUM),
((params_channels(params)-1)<<DAM_CTL_RX2_NUM));
regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(1),
(0xf<<DAM_CTL_TX_NUM),
((params_channels(params)-1)<<DAM_CTL_TX_NUM));
return 0;
}
static int sunxi_ahub_codec_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
sunxi_ahub_update_bits(
SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id),
(1<<APBIF_TX_FTX), (1<<APBIF_TX_FTX));
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
default:
return -EINVAL;
}
return 0;
}
static int sunxi_ahub_codec_dai_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
struct sunxi_ahub_priv *sunxi_ahub = snd_soc_dai_get_drvdata(dai);
if (clk_set_rate(sunxi_ahub->pllclk, freq)) {
dev_err(sunxi_ahub->dev, "set pllclk rate failed\n");
return -EINVAL;
}
return 0;
}
static int sunxi_ahub_codec_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sunxi_ahub_priv *sunxi_ahub = snd_soc_dai_get_drvdata(dai);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id),
(1<<APBIF_TX_FTX), (1<<APBIF_TX_FTX));
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TX_IRQ_STA(dai->id),
(1<<APBIF_TX_OV_PEND|1<<APBIF_TX_EM_PEND));
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_TXFIFO_CNT(dai->id), 0);
} else {
regmap_update_bits(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RXFIFO_CTL(dai->id),
(1<<APBIF_RX_FRX), (1<<APBIF_RX_FRX));
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RX_IRQ_STA(dai->id),
(1<<APBIF_RX_UV_PEND|1<<APBIF_RX_AV_PEND));
regmap_write(sunxi_ahub->regmap,
SUNXI_AHUB_APBIF_RXFIFO_CNT(dai->id), 0);
}
return 0;
}
static const struct snd_soc_dai_ops sunxi_ahub_codec_dai_ops = {
.hw_params = sunxi_ahub_codec_dai_hw_params,
.set_sysclk = sunxi_ahub_codec_dai_set_sysclk,
.trigger = sunxi_ahub_codec_dai_trigger,
.prepare = sunxi_ahub_codec_dai_prepare,
};
/* ahub codec dai */
static struct snd_soc_dai_driver sunxi_ahub_codec_dais[] = {
{
.name = "sunxi-ahub-aif1",
.id = 0,
.playback = {
.stream_name = "AIF1 Playback",
.channels_min = 1,
.channels_max = 16,
.rates = SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE,
},
.capture = {
.stream_name = "AIF1 Capture",
.channels_min = 1,
.channels_max = 16,
.rates = SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &sunxi_ahub_codec_dai_ops,
},
{
.name = "sunxi-ahub-aif2",
.id = 1,
.playback = {
.stream_name = "AIF2 Playback",
.channels_min = 1,
.channels_max = 16,
.rates = SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE,
},
.capture = {
.stream_name = "AIF2 Capture",
.channels_min = 1,
.channels_max = 16,
.rates = SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &sunxi_ahub_codec_dai_ops,
},
{
.name = "sunxi-ahub-aif3",
.id = 2,
.playback = {
.stream_name = "AIF3 Playback",
.channels_min = 1,
.channels_max = 16,
.rates = SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE,
},
.capture = {
.stream_name = "AIF3 Capture",
.channels_min = 1,
.channels_max = 16,
.rates = SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &sunxi_ahub_codec_dai_ops,
}
};
static int sunxi_ahub_codec_probe(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
snd_soc_dapm_new_controls(dapm, sunxi_ahub_codec_dapm_widgets,
ARRAY_SIZE(sunxi_ahub_codec_dapm_widgets));
snd_soc_dapm_add_routes(dapm, sunxi_ahub_codec_dapm_routes,
ARRAY_SIZE(sunxi_ahub_codec_dapm_routes));
snd_soc_add_codec_controls(codec, ahub_controls,
ARRAY_SIZE(ahub_controls));
sunxi_ahub_codec_init(codec);
return 0;
}
static int sunxi_ahub_codec_suspend(struct snd_soc_codec *codec)
{
struct sunxi_ahub_priv *sunxi_ahub = snd_soc_codec_get_drvdata(codec);
pr_debug("Enter %s\n", __func__);
clk_disable_unprepare(sunxi_ahub->moduleclk);
clk_disable_unprepare(sunxi_ahub->pllclk);
pr_debug("End %s\n", __func__);
return 0;
}
static int sunxi_ahub_codec_resume(struct snd_soc_codec *codec)
{
struct sunxi_ahub_priv *sunxi_ahub = snd_soc_codec_get_drvdata(codec);
pr_debug("Enter %s\n", __func__);
if (clk_prepare_enable(sunxi_ahub->pllclk)) {
dev_err(sunxi_ahub->dev, "pllclk resume failed\n");
return -EBUSY;
}
if (clk_prepare_enable(sunxi_ahub->moduleclk)) {
dev_err(sunxi_ahub->dev, "moduleclk resume failed\n");
return -EBUSY;
}
sunxi_ahub_codec_init(codec);
sunxi_ahub_cpudai_init();
pr_debug("End %s\n", __func__);
return 0;
}
static struct snd_soc_codec_driver soc_ahub_dev_sunxi = {
.probe = sunxi_ahub_codec_probe,
.suspend = sunxi_ahub_codec_suspend,
.resume = sunxi_ahub_codec_resume,
.ignore_pmdown_time = 1,
};
static int sunxi_ahub_dev_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int ret;
dev_set_drvdata(&pdev->dev, sunxi_ahub);
sunxi_ahub->dev = &pdev->dev;
sunxi_ahub->pllclk = of_clk_get(np, 0);
if (IS_ERR_OR_NULL(sunxi_ahub->pllclk)) {
dev_err(&pdev->dev, "pllclk get failed\n");
ret = PTR_ERR(sunxi_ahub->pllclk);
goto err_node_put;
}
sunxi_ahub->moduleclk = of_clk_get(np, 1);
if (IS_ERR_OR_NULL(sunxi_ahub->moduleclk)) {
dev_err(&pdev->dev, "moduleclk get failed\n");
ret = PTR_ERR(sunxi_ahub->moduleclk);
goto err_pllclk_put;
}
if (clk_set_parent(sunxi_ahub->moduleclk, sunxi_ahub->pllclk)) {
dev_err(&pdev->dev, "set parent of moduleclk to pllclk fail\n");
ret = -EBUSY;
goto err_moduleclk_put;
}
clk_prepare_enable(sunxi_ahub->pllclk);
clk_prepare_enable(sunxi_ahub->moduleclk);
sunxi_ahub->regmap = sunxi_ahub_regmap_init(pdev);
if (!sunxi_ahub->regmap) {
dev_err(&pdev->dev, "regmap not init ok\n");
ret = -ENOMEM;
goto err_moduleclk_put;
}
ret = snd_soc_register_codec(&pdev->dev, &soc_ahub_dev_sunxi,
sunxi_ahub_codec_dais,
ARRAY_SIZE(sunxi_ahub_codec_dais));
if (ret) {
dev_err(&pdev->dev, "component register failed\n");
ret = -ENOMEM;
goto err_moduleclk_put;
}
return 0;
err_moduleclk_put:
clk_put(sunxi_ahub->moduleclk);
err_pllclk_put:
clk_put(sunxi_ahub->pllclk);
err_node_put:
of_node_put(np);
return ret;
}
static int __exit sunxi_ahub_dev_remove(struct platform_device *pdev)
{
struct sunxi_ahub_priv *sunxi_ahub = dev_get_drvdata(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
clk_put(sunxi_ahub->moduleclk);
clk_put(sunxi_ahub->pllclk);
return 0;
}
static const struct of_device_id sunxi_ahub_of_match[] = {
{ .compatible = "allwinner,sunxi-ahub", },
{},
};
static struct platform_driver sunxi_ahub_driver = {
.probe = sunxi_ahub_dev_probe,
.remove = __exit_p(sunxi_ahub_dev_remove),
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = sunxi_ahub_of_match,
},
};
module_platform_driver(sunxi_ahub_driver);
MODULE_AUTHOR("wolfgang huang <huangjinhui@allwinnertech.com>");
MODULE_DESCRIPTION("SUNXI Audio Hub Codec ASoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:sunxi-ahub");