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 #include "sun8i-adda-pr-regmap.h" +#include +#include /* 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,