mirror of
https://github.com/clockworkpi/Kernel.git
synced 2025-12-12 07:38:50 +01:00
384 lines
13 KiB
Diff
384 lines
13 KiB
Diff
diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c
|
|
index be872eefa61e..ca5f4e43476b 100644
|
|
--- a/sound/soc/sunxi/sun8i-codec-analog.c
|
|
+++ b/sound/soc/sunxi/sun8i-codec-analog.c
|
|
@@ -19,6 +19,8 @@
|
|
#include <sound/tlv.h>
|
|
|
|
#include "sun8i-adda-pr-regmap.h"
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
|
|
/* Codec analog control register offsets and bit fields */
|
|
#define SUN8I_ADDA_HP_VOLC 0x00
|
|
@@ -113,6 +115,81 @@
|
|
#define SUN8I_ADDA_ADC_AP_EN_ADCLEN 6
|
|
#define SUN8I_ADDA_ADC_AP_EN_ADCG 0
|
|
|
|
+/* Analog control register access bits */
|
|
+#define ADDA_PR 0x0 /* PRCM base + 0x1c0 */
|
|
+#define ADDA_PR_RESET BIT(28)
|
|
+#define ADDA_PR_WRITE BIT(24)
|
|
+#define ADDA_PR_ADDR_SHIFT 16
|
|
+#define ADDA_PR_ADDR_MASK GENMASK(4, 0)
|
|
+#define ADDA_PR_DATA_IN_SHIFT 8
|
|
+#define ADDA_PR_DATA_IN_MASK GENMASK(7, 0)
|
|
+#define ADDA_PR_DATA_OUT_SHIFT 0
|
|
+#define ADDA_PR_DATA_OUT_MASK GENMASK(7, 0)
|
|
+
|
|
+/* regmap access bits */
|
|
+static int adda_reg_read(void *context, unsigned int reg, unsigned int *val)
|
|
+{
|
|
+ void __iomem *base = (void __iomem *)context;
|
|
+ u32 tmp;
|
|
+
|
|
+ /* De-assert reset */
|
|
+ writel(readl(base) | ADDA_PR_RESET, base);
|
|
+
|
|
+ /* Clear write bit */
|
|
+ writel(readl(base) & ~ADDA_PR_WRITE, base);
|
|
+
|
|
+ /* Set register address */
|
|
+ tmp = readl(base);
|
|
+ tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
|
|
+ tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
|
|
+ writel(tmp, base);
|
|
+
|
|
+ /* Read back value */
|
|
+ *val = readl(base) & ADDA_PR_DATA_OUT_MASK;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adda_reg_write(void *context, unsigned int reg, unsigned int val)
|
|
+{
|
|
+ void __iomem *base = (void __iomem *)context;
|
|
+ u32 tmp;
|
|
+
|
|
+ /* De-assert reset */
|
|
+ writel(readl(base) | ADDA_PR_RESET, base);
|
|
+
|
|
+ /* Set register address */
|
|
+ tmp = readl(base);
|
|
+ tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
|
|
+ tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
|
|
+ writel(tmp, base);
|
|
+
|
|
+ /* Set data to write */
|
|
+ tmp = readl(base);
|
|
+ tmp &= ~(ADDA_PR_DATA_IN_MASK << ADDA_PR_DATA_IN_SHIFT);
|
|
+ tmp |= (val & ADDA_PR_DATA_IN_MASK) << ADDA_PR_DATA_IN_SHIFT;
|
|
+ writel(tmp, base);
|
|
+
|
|
+ /* Set write bit to signal a write */
|
|
+ writel(readl(base) | ADDA_PR_WRITE, base);
|
|
+
|
|
+ /* Clear write bit */
|
|
+ writel(readl(base) & ~ADDA_PR_WRITE, base);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct regmap_config adda_pr_regmap_cfg = {
|
|
+ .name = "adda-pr",
|
|
+ .reg_bits = 5,
|
|
+ .reg_stride = 1,
|
|
+ .val_bits = 8,
|
|
+ .reg_read = adda_reg_read,
|
|
+ .reg_write = adda_reg_write,
|
|
+ .fast_io = true,
|
|
+ .max_register = 24,
|
|
+};
|
|
+
|
|
/* mixer controls */
|
|
static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = {
|
|
SOC_DAPM_DOUBLE_R("DAC Playback Switch",
|
|
@@ -316,7 +393,7 @@ static const struct snd_soc_dapm_route sun8i_codec_mixer_routes[] = {
|
|
/* headphone specific controls, widgets, and routes */
|
|
static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1);
|
|
static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = {
|
|
- SOC_SINGLE_TLV("Headphone Playback Volume",
|
|
+ SOC_SINGLE_TLV("Master Playback Volume",
|
|
SUN8I_ADDA_HP_VOLC,
|
|
SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0,
|
|
sun8i_codec_hp_vol_scale),
|
|
@@ -673,7 +750,7 @@ struct sun8i_codec_analog_quirks {
|
|
|
|
static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = {
|
|
.has_headphone = true,
|
|
- .has_hmic = true,
|
|
+// .has_hmic = true,
|
|
.has_linein = true,
|
|
.has_mbias = true,
|
|
.has_mic2 = true,
|
|
@@ -817,10 +894,148 @@ static const struct of_device_id sun8i_codec_analog_of_match[] = {
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);
|
|
|
|
+#define SUNXI_HMIC_ENABLE (0x4)
|
|
+#define SUNXI_HMIC_CTL (0x8)
|
|
+#define SUNXI_HMIC_DATA (0xc)
|
|
+
|
|
+/*
|
|
+* SUNXI_HMIC_CTL
|
|
+*HMIC Control Register
|
|
+*CONFIG_ARCH_SUN8IW5:0x1c8
|
|
+*/
|
|
+#define HMIC_M (28)
|
|
+#define HMIC_N (24)
|
|
+#define HMIC_DIRQ (23)
|
|
+#define HMIC_TH1_HYS (21)
|
|
+#define HMIC_EARPHONE_OUT_IRQ_EN (20)
|
|
+#define HMIC_EARPHONE_IN_IRQ_EN (19)
|
|
+#define HMIC_KEY_UP_IRQ_EN (18)
|
|
+#define HMIC_KEY_DOWN_IRQ_EN (17)
|
|
+#define HMIC_DATA_IRQ_EN (16)
|
|
+#define HMIC_DS_SAMP (14)
|
|
+#define HMIC_TH2_HYS (13)
|
|
+#define HMIC_TH2_KEY (8)
|
|
+#define HMIC_SF_SMOOTH_FIL (6)
|
|
+#define KEY_UP_IRQ_PEND (5)
|
|
+#define HMIC_TH1_EARPHONE (0)
|
|
+
|
|
+/*
|
|
+* SUNXI_HMIC_DATA
|
|
+*HMIC Data Register
|
|
+*
|
|
+*CONFIG_ARCH_SUN8IW5:0x1cc
|
|
+*/
|
|
+#define HMIC_EARPHONE_OUT_IRQ_PEND (20)
|
|
+#define HMIC_EARPHONE_IN_IRQ_PEND (19)
|
|
+#define HMIC_KEY_UP_IRQ_PEND (18)
|
|
+#define HMIC_KEY_DOWN_IRQ_PEND (17)
|
|
+#define HMIC_DATA_IRQ_PEND (16)
|
|
+#define HMIC_ADC_DATA (0)
|
|
+
|
|
+#define HP_VOLC (0x00)
|
|
+#define LOMIXSC (0x01)
|
|
+#define ROMIXSC (0x02)
|
|
+#define DAC_PA_SRC (0x03)
|
|
+#define PAEN_HP_CTRL (0x07)
|
|
+#define ADDA_APT2 (0x12)
|
|
+#define MIC1G_MICBIAS_CTRL (0x0B)
|
|
+#define PA_ANTI_POP_REG_CTRL (0x0E)
|
|
+#define PA_SLOPE_SELECT (3)
|
|
+#define PA_ANTI_POP_EN (0)
|
|
+
|
|
+static void __iomem *sun8i_codec_analog_base;
|
|
+static struct gpio_desc * speaker_amplifier_gpio;
|
|
+
|
|
+static int hmic_wrreg_prcm_bits(unsigned short reg, unsigned int mask, unsigned int value)
|
|
+{
|
|
+ unsigned int old, new;
|
|
+
|
|
+ adda_reg_read(sun8i_codec_analog_base, reg, &old);
|
|
+ new = (old & ~mask) | value;
|
|
+ adda_reg_write(sun8i_codec_analog_base, reg,new);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int hmic_wr_prcm_control(u32 reg, u32 mask, u32 shift, u32 val)
|
|
+{
|
|
+ u32 reg_val;
|
|
+ reg_val = val << shift;
|
|
+ mask = mask << shift;
|
|
+ hmic_wrreg_prcm_bits(reg, mask, reg_val);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int hmic_wrreg_bits(unsigned short reg, unsigned int mask, unsigned int value)
|
|
+{
|
|
+ unsigned int old, new;
|
|
+
|
|
+ old = readl(sun8i_codec_analog_base + reg);
|
|
+ new = (old & ~mask) | value;
|
|
+
|
|
+ writel(new, sun8i_codec_analog_base + reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int hmic_wr_control(u32 reg, u32 mask, u32 shift, u32 val)
|
|
+{
|
|
+ u32 reg_val;
|
|
+ reg_val = val << shift;
|
|
+ mask = mask << shift;
|
|
+ hmic_wrreg_bits(reg, mask, reg_val);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static irqreturn_t sunxi_codec_analog_irq(int irq, void *dev_id)
|
|
+{
|
|
+ u32 tmp;
|
|
+
|
|
+ hmic_wr_control(SUNXI_HMIC_DATA, 0x1, HMIC_KEY_DOWN_IRQ_PEND, 0x1);
|
|
+ hmic_wr_control(SUNXI_HMIC_DATA, 0x1, HMIC_EARPHONE_IN_IRQ_PEND, 0x1);
|
|
+ hmic_wr_control(SUNXI_HMIC_DATA, 0x1, HMIC_KEY_UP_IRQ_PEND, 0x1);
|
|
+ hmic_wr_control(SUNXI_HMIC_DATA, 0x1, HMIC_EARPHONE_OUT_IRQ_PEND, 0x1);
|
|
+ hmic_wr_control(SUNXI_HMIC_DATA, 0x1, HMIC_DATA_IRQ_PEND, 0x1);
|
|
+
|
|
+ tmp = readl(sun8i_codec_analog_base + SUNXI_HMIC_DATA);
|
|
+ if(tmp & 0x1f)
|
|
+ gpiod_set_value(speaker_amplifier_gpio, 0);
|
|
+ else
|
|
+ gpiod_set_value(speaker_amplifier_gpio, 1);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void sunxi_hppa_enable(void) {
|
|
+ /*fix the resume blaze blaze noise*/
|
|
+ hmic_wr_prcm_control(ADDA_APT2, 0x1, PA_SLOPE_SELECT, 0x0);
|
|
+ hmic_wr_prcm_control(SUN8I_ADDA_PAEN_HP_CTRL, 0x3, SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL, 0x1);
|
|
+ hmic_wr_prcm_control(PA_ANTI_POP_REG_CTRL, 0x7, PA_ANTI_POP_EN, 0x2);
|
|
+ usleep_range(100,200);
|
|
+ /*enable pa*/
|
|
+ hmic_wr_prcm_control(SUN8I_ADDA_PAEN_HP_CTRL, 0x1, SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0x1);
|
|
+}
|
|
+
|
|
+static void sunxi_hbias_enable(void) {
|
|
+ /*audio codec hardware bug. the HBIASADCEN bit must be enable in init*/
|
|
+ hmic_wr_prcm_control(SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 0x1, SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE, 0x1);
|
|
+ hmic_wr_prcm_control(SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 0x1, SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN, 0x1);
|
|
+}
|
|
+
|
|
+static void codec_init_events(void)
|
|
+{
|
|
+ /*fix the resume blaze blaze noise*/
|
|
+ sunxi_hppa_enable();
|
|
+ msleep(450);
|
|
+ /*audio codec hardware bug. the HBIASADCEN bit must be enable in init*/
|
|
+ sunxi_hbias_enable();
|
|
+}
|
|
+
|
|
static int sun8i_codec_analog_probe(struct platform_device *pdev)
|
|
{
|
|
struct regmap *regmap;
|
|
void __iomem *base;
|
|
+ int irq, ret;
|
|
|
|
base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(base)) {
|
|
@@ -828,6 +1043,38 @@ static int sun8i_codec_analog_probe(struct platform_device *pdev)
|
|
return PTR_ERR(base);
|
|
}
|
|
|
|
+ sun8i_codec_analog_base = base;
|
|
+ speaker_amplifier_gpio = devm_gpiod_get_optional(&pdev->dev, "speaker-amplifier", GPIOD_OUT_HIGH);
|
|
+ if (!IS_ERR_OR_NULL(speaker_amplifier_gpio)) {
|
|
+
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0xf, HMIC_M, 0x0); /*0xf should be get from hw_debug 28*/
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0xf, HMIC_N, 0x0); /*0xf should be get from hw_debug 24 0xf*/
|
|
+// hmic_wr_control(SUNXI_HMIC_CTL, 0x1, HMIC_DIRQ, 0x1); /*23*/
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0x1, HMIC_EARPHONE_OUT_IRQ_EN, 0x1); /*20*/
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0x1, HMIC_EARPHONE_IN_IRQ_EN, 0x1); /*19*/
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0x1, HMIC_KEY_UP_IRQ_EN, 0x1); /*18*/
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0x1, HMIC_KEY_DOWN_IRQ_EN, 0x1); /*17*/
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0x1, HMIC_DATA_IRQ_EN, 0x1); /*16*/
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0x3, HMIC_DS_SAMP, 0x0); /*14 */
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0x1f, HMIC_TH2_KEY, 0x0); /*0xf should be get from hw_debug 8*/
|
|
+ hmic_wr_control(SUNXI_HMIC_CTL, 0x1f, HMIC_TH1_EARPHONE, 0x1); /*0x1 should be get from hw_debug 0*/
|
|
+
|
|
+ irq = platform_get_irq(pdev, 0);
|
|
+ if (irq < 0) {
|
|
+ dev_err(&pdev->dev, "Can't retrieve our interrupt\n");
|
|
+ return irq;
|
|
+ }
|
|
+
|
|
+ ret = devm_request_irq(&pdev->dev, irq, sunxi_codec_analog_irq, 0, "audio_hmic_irq", NULL);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "can't register interrupt handler irq %d: %d\n",
|
|
+ irq, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ codec_init_events();
|
|
+ }
|
|
+
|
|
regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base);
|
|
if (IS_ERR(regmap)) {
|
|
dev_err(&pdev->dev, "Failed to create regmap\n");
|
|
diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c
|
|
index 518bfb724a5b..b6a90e33f20a 100644
|
|
--- a/sound/soc/sunxi/sun8i-codec.c
|
|
+++ b/sound/soc/sunxi/sun8i-codec.c
|
|
@@ -257,6 +257,9 @@ static int sun8i_codec_update_sample_rate(struct sun8i_codec *scodec)
|
|
regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL,
|
|
SUN8I_SYS_SR_CTRL_AIF1_FS_MASK,
|
|
hw_rate << SUN8I_SYS_SR_CTRL_AIF1_FS);
|
|
+ regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL,
|
|
+ SUN8I_SYS_SR_CTRL_AIF2_FS_MASK,
|
|
+ hw_rate << SUN8I_SYS_SR_CTRL_AIF2_FS);
|
|
|
|
return 0;
|
|
}
|
|
@@ -288,6 +291,9 @@ static int sun8i_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_MASK,
|
|
SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_AIF2);
|
|
} else {
|
|
+ regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(SUN8I_CODEC_AIF1),
|
|
+ BIT(SUN8I_AIF_CLK_CTRL_MSTR_MOD),
|
|
+ value << SUN8I_AIF_CLK_CTRL_MSTR_MOD);
|
|
regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id),
|
|
BIT(SUN8I_AIF_CLK_CTRL_MSTR_MOD),
|
|
value << SUN8I_AIF_CLK_CTRL_MSTR_MOD);
|
|
@@ -321,6 +327,9 @@ static int sun8i_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
if (format != 3)
|
|
return -EINVAL;
|
|
} else {
|
|
+ regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(SUN8I_CODEC_AIF1),
|
|
+ SUN8I_AIF_CLK_CTRL_DATA_FMT_MASK,
|
|
+ format << SUN8I_AIF_CLK_CTRL_DATA_FMT);
|
|
regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id),
|
|
SUN8I_AIF_CLK_CTRL_DATA_FMT_MASK,
|
|
format << SUN8I_AIF_CLK_CTRL_DATA_FMT);
|
|
@@ -365,6 +374,9 @@ static int sun8i_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
invert ^= scodec->quirks->lrck_inversion;
|
|
}
|
|
|
|
+ regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(SUN8I_CODEC_AIF1),
|
|
+ SUN8I_AIF_CLK_CTRL_CLK_INV_MASK,
|
|
+ invert << SUN8I_AIF_CLK_CTRL_CLK_INV);
|
|
regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id),
|
|
SUN8I_AIF_CLK_CTRL_CLK_INV_MASK,
|
|
invert << SUN8I_AIF_CLK_CTRL_CLK_INV);
|
|
@@ -519,6 +531,9 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
|
|
return -EINVAL;
|
|
}
|
|
|
|
+ regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(SUN8I_CODEC_AIF1),
|
|
+ SUN8I_AIF_CLK_CTRL_WORD_SIZ_MASK,
|
|
+ word_size << SUN8I_AIF_CLK_CTRL_WORD_SIZ);
|
|
regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id),
|
|
SUN8I_AIF_CLK_CTRL_WORD_SIZ_MASK,
|
|
word_size << SUN8I_AIF_CLK_CTRL_WORD_SIZ);
|
|
@@ -548,6 +563,9 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
|
|
clk_reg = SUN8I_AIF_CLK_CTRL(dai->id);
|
|
}
|
|
|
|
+ regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(SUN8I_CODEC_AIF1),
|
|
+ SUN8I_AIF_CLK_CTRL_LRCK_DIV_MASK,
|
|
+ (lrck_div_order - 4) << SUN8I_AIF_CLK_CTRL_LRCK_DIV);
|
|
regmap_update_bits(scodec->regmap, clk_reg,
|
|
SUN8I_AIF_CLK_CTRL_LRCK_DIV_MASK,
|
|
(lrck_div_order - 4) << SUN8I_AIF_CLK_CTRL_LRCK_DIV);
|
|
@@ -557,6 +575,9 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
|
|
if (bclk_div < 0)
|
|
return bclk_div;
|
|
|
|
+ regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(SUN8I_CODEC_AIF1),
|
|
+ SUN8I_AIF_CLK_CTRL_BCLK_DIV_MASK,
|
|
+ bclk_div << SUN8I_AIF_CLK_CTRL_BCLK_DIV);
|
|
regmap_update_bits(scodec->regmap, clk_reg,
|
|
SUN8I_AIF_CLK_CTRL_BCLK_DIV_MASK,
|
|
bclk_div << SUN8I_AIF_CLK_CTRL_BCLK_DIV);
|
|
@@ -1226,6 +1247,7 @@ static const struct snd_soc_component_driver sun8i_soc_component = {
|
|
};
|
|
|
|
static const struct regmap_config sun8i_codec_regmap_config = {
|
|
+ .name = "sun8i_codec",
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|