From e0f5d091602011cea9d1e1ccbd91bd594b90bbc2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E2=80=9Cjenny=2Ezhang=E2=80=9D?= <“jenny.zhang@starfivetech.com”> Date: Wed, 15 Dec 2021 22:14:04 -0800 Subject: [PATCH] [alsa] Add jh7110 audio module driver code --- arch/riscv/boot/dts/starfive/starfive_jh7110.dts | 135 ++ sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ac101.c | 1693 ++++++++++++++++++++++ sound/soc/codecs/ac101_regs.h | 432 ++++++ sound/soc/codecs/ac108.c | 1583 ++++++++++++++++++++ sound/soc/codecs/ac108.h | 774 ++++++++++ sound/soc/codecs/ac10x.h | 152 ++ sound/soc/codecs/wm8960.c | 32 +- sound/soc/dwc/dwc-i2s.c | 4 +- sound/soc/starfive/Kconfig | 40 + sound/soc/starfive/Makefile | 13 + sound/soc/starfive/pwmdac.h | 164 +++ sound/soc/starfive/starfive_pdm.c | 378 +++++ sound/soc/starfive/starfive_pdm.h | 59 + sound/soc/starfive/starfive_pwmdac.c | 797 ++++++++++ sound/soc/starfive/starfive_pwmdac_pcm.c | 251 ++++ sound/soc/starfive/starfive_pwmdac_transmitter.c | 98 ++ sound/soc/starfive/starfive_spdif.c | 399 +++++ sound/soc/starfive/starfive_spdif.h | 170 +++ sound/soc/starfive/starfive_spdif_pcm.c | 304 ++++ sound/soc/starfive/starfive_tdm.c | 439 ++++++ sound/soc/starfive/starfive_tdm.h | 138 ++ 25 files changed, 8061 insertions(+), 3 deletions(-) create mode 100644 sound/soc/codecs/ac101.c create mode 100644 sound/soc/codecs/ac101_regs.h create mode 100644 sound/soc/codecs/ac108.c create mode 100644 sound/soc/codecs/ac108.h create mode 100644 sound/soc/codecs/ac10x.h create mode 100644 sound/soc/starfive/Kconfig create mode 100644 sound/soc/starfive/Makefile create mode 100644 sound/soc/starfive/pwmdac.h create mode 100644 sound/soc/starfive/starfive_pdm.c create mode 100644 sound/soc/starfive/starfive_pdm.h create mode 100644 sound/soc/starfive/starfive_pwmdac.c create mode 100644 sound/soc/starfive/starfive_pwmdac_pcm.c create mode 100644 sound/soc/starfive/starfive_pwmdac_transmitter.c create mode 100644 sound/soc/starfive/starfive_spdif.c create mode 100644 sound/soc/starfive/starfive_spdif.h create mode 100644 sound/soc/starfive/starfive_spdif_pcm.c create mode 100644 sound/soc/starfive/starfive_tdm.c create mode 100644 sound/soc/starfive/starfive_tdm.h diff --git a/arch/riscv/boot/dts/starfive/starfive_jh7110.dts b/arch/riscv/boot/dts/starfive/starfive_jh7110.dts index b5eed5c..21af3ca 100755 --- a/arch/riscv/boot/dts/starfive/starfive_jh7110.dts +++ b/arch/riscv/boot/dts/starfive/starfive_jh7110.dts @@ -412,6 +412,21 @@ i2c-scl-falling-time-ns = <3000>; auto_calc_scl_lhcnt; status = "okay"; + + ac108_a: ac108@3b { + compatible = "x-power,ac108_0"; + reg = <0x3b>; + #sound-dai-cells = <0>; + data-protocol = <0>; + }; + + wm8960: codec@1a { + compatible = "wlf,wm8960"; + reg = <0x1a>; + #sound-dai-cells = <0>; + + wlf,shared-lrclk; + }; }; /*emmc*/ @@ -589,8 +604,126 @@ clock-names = "gpu_core_clk","gpu_sys_clk"; current-clock = <8000000>; status = "okay"; + }; + tdm: tdm@10090000 { + compatible = "starfive,tdm"; + reg = <0x0 0x10090000 0x0 0x1000>; + reg-names = "tdm"; + clocks = <&audioclk>; + clock-names = "audioclk"; + dmas = <&dma 20 1>, <&dma 21 1>; + dma-names = "rx","tx"; + #sound-dai-cells = <0>; + }; + + spdif0: spdif0@100a0000 { + compatible = "starfive,sf-spdif"; + reg = <0x0 0x100a0000 0x0 0x1000>; + interrupt-parent = <&plic>; + interrupts = <84>; + interrupt-names = "tx"; + clocks = <&audioclk>; + clock-names = "audioclk"; + #sound-dai-cells = <0>; + }; + + pwmdac: pwmdac@100b0000 { + compatible = "sf,pwmdac"; + reg = <0x0 0x100b0000 0x0 0x1000>; + clocks = <&apb0clk>; + dmas = <&dma 22 1>; + dma-names = "tx"; + #sound-dai-cells = <0>; + }; + + i2stx: i2stx@100c0000 { + compatible = "snps,designware-i2stx"; + reg = <0x0 0x100c0000 0x0 0x1000>; + interrupt-names = "tx"; + clocks = <&apb0clk>; + clock-names = "i2sclk"; + #sound-dai-cells = <0>; + dmas = <&dma 28>; + dma-names = "rx"; + }; + + pdm: pdm@100d0000 { + compatible = "starfive,sf-pdm"; + reg = <0x0 0x100d0000 0x0 0x1000>; + reg-names = "pdm"; + clocks = <&audioclk>; + clock-names = "audioclk"; + #sound-dai-cells = <0>; + }; + + i2srx_3ch: i2srx-3ch@100e0000 { + compatible = "snps,designware-i2srx"; + reg = <0x0 0x100e0000 0x0 0x1000>; + interrupt-parent = <&plic>; + /*interrupts = <42>, <43>, <44>;*/ + /*interrupt-names = "rx-ch0","rx-ch1","rx-ch2";*/ + interrupts = <42>; + interrupt-names = "rx"; + clocks = <&apb0clk>; + clock-names = "i2sclk"; + #sound-dai-cells = <0>; }; + i2stx_4ch0: i2stx-4ch0@120b0000 { + compatible = "snps,designware-i2stx-4ch0"; + reg = <0x0 0x120b0000 0x0 0x1000>; + interrupt-parent = <&plic>; + interrupts = <58>; + interrupt-names = "tx"; + clocks = <&apb0clk>; + clock-names = "i2sclk"; + #sound-dai-cells = <0>; + }; + + i2stx_4ch1: i2sdac1@120c0000 { + compatible = "snps,designware-i2stx-4ch1"; + reg = <0x0 0x120c0000 0x0 0x1000>; + interrupt-parent = <&plic>; + interrupts = <59>; + interrupt-names = "tx"; + clocks = <&apb0clk>; + clock-names = "i2sclk"; + #sound-dai-cells = <0>; + }; + + + ac108_mclk: ac108_mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24576000>; + }; + + wm8960_mclk: wm8960_mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <4000000>; + }; + + spdif_transmitter: spdif_transmitter { + compatible = "linux,spdif-dit"; + #sound-dai-cells = <0>; + }; + + spdif_receiver: spdif_receiver { + compatible = "linux,spdif-dir"; + #sound-dai-cells = <0>; + }; + + pwmdac_codec: pwmdac-transmitter { + compatible = "linux,pwmdac-dit"; + #sound-dai-cells = <0>; + }; + + dmic_codec: dmic_codec { + compatible = "dmic-codec"; + #sound-dai-cells = <0>; + }; + spi0:spi0@10060000 { compatible = "arm,pl022", "arm,primecell"; reg = <0x0 0x10060000 0x0 0x10000>; @@ -638,3 +771,5 @@ }; }; }; + +#include "starfive_jh7110_audio.dtsi" \ No newline at end of file diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 5dcf77a..a4088d5 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -92,6 +92,7 @@ source "sound/soc/uniphier/Kconfig" source "sound/soc/ux500/Kconfig" source "sound/soc/xilinx/Kconfig" source "sound/soc/xtensa/Kconfig" +source "sound/soc/starfive/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index a7b37c0..18d0b54 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -62,3 +62,4 @@ obj-$(CONFIG_SND_SOC) += uniphier/ obj-$(CONFIG_SND_SOC) += ux500/ obj-$(CONFIG_SND_SOC) += xilinx/ obj-$(CONFIG_SND_SOC) += xtensa/ +obj-$(CONFIG_SND_SOC) += starfive/ \ No newline at end of file diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 216cea0..d09d89f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -14,6 +14,7 @@ menu "CODEC drivers" config SND_SOC_ALL_CODECS tristate "Build all ASoC CODEC drivers" depends on COMPILE_TEST + imply SND_SOC_AC108 imply SND_SOC_88PM860X imply SND_SOC_L3 imply SND_SOC_AB8500_CODEC @@ -306,6 +307,10 @@ config SND_SOC_ALL_CODECS If unsure select "N". +config SND_SOC_AC108 + tristate "AC108" + depends on I2C + config SND_SOC_88PM860X tristate depends on MFD_88PM860X diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8dcea2c..641464c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -2,6 +2,7 @@ snd-soc-88pm860x-objs := 88pm860x-codec.o snd-soc-ab8500-codec-objs := ab8500-codec.o snd-soc-ac97-objs := ac97.o +snd-soc-ac108-objs := ac108.o ac101.o snd-soc-ad1836-objs := ad1836.o snd-soc-ad193x-objs := ad193x.o snd-soc-ad193x-spi-objs := ad193x-spi.o @@ -331,6 +332,7 @@ snd-soc-simple-mux-objs := simple-mux.o obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o obj-$(CONFIG_SND_SOC_AB8500_CODEC) += snd-soc-ab8500-codec.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AC108) += snd-soc-ac108.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o obj-$(CONFIG_SND_SOC_AD193X_SPI) += snd-soc-ad193x-spi.o diff --git a/sound/soc/codecs/ac101.c b/sound/soc/codecs/ac101.c new file mode 100644 index 0000000..0a32950 --- /dev/null +++ b/sound/soc/codecs/ac101.c @@ -0,0 +1,1693 @@ +/* + * ac101.c + * + * (C) Copyright 2017-2018 + * Seeed Technology Co., Ltd. + * + * PeterYang + * + * (C) Copyright 2014-2017 + * Reuuimlla Technology Co., Ltd. + * + * huangxin + * liushaohua + * + * X-Powers AC101 codec driver + * + * 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. + * + */ + +/* #undef AC101_DEBG + * use 'make DEBUG=1' to enable debugging + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ac101_regs.h" +#include "ac10x.h" + +/* + * *** To sync channels *** + * + * 1. disable clock in codec hw_params() + * 2. clear fifo in bcm2835 hw_params() + * 3. clear fifo in bcm2385 prepare() + * 4. enable RX in bcm2835 trigger() + * 5. enable clock in machine trigger() + */ + +/*Default initialize configuration*/ +static bool speaker_double_used = 1; +static int double_speaker_val = 0x1B; +static int single_speaker_val = 0x19; +static int headset_val = 0x3B; +static int mainmic_val = 0x4; +static int headsetmic_val = 0x4; +static bool dmic_used = 0; +static int adc_digital_val = 0xb0b0; +static bool drc_used = false; + +#define AC101_RATES (SNDRV_PCM_RATE_8000_96000 & \ + ~(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200)) +#define AC101_FORMATS (/*SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE |*/ \ + SNDRV_PCM_FMTBIT_S32_LE | \ + 0) + +static struct ac10x_priv* static_ac10x; + + +int ac101_read(struct snd_soc_codec *codec, unsigned reg) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int r, v = 0; + + if ((r = regmap_read(ac10x->regmap101, reg, &v)) < 0) { + dev_err(codec->dev, "read reg %02X fail\n", + reg); + return r; + } + return v; +} + +int ac101_write(struct snd_soc_codec *codec, unsigned reg, unsigned val) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int v; + + v = regmap_write(ac10x->regmap101, reg, val); + return v; +} + +int ac101_update_bits(struct snd_soc_codec *codec, unsigned reg, + unsigned mask, unsigned value +) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int v; + + v = regmap_update_bits(ac10x->regmap101, reg, mask, value); + return v; +} + + + +#ifdef CONFIG_AC101_SWITCH_DETECT +/******************************************************************************/ +/********************************switch****************************************/ +/******************************************************************************/ +#define KEY_HEADSETHOOK 226 /* key define */ +#define HEADSET_FILTER_CNT (10) + +/* + * switch_hw_config:config the 53 codec register + */ +static void switch_hw_config(struct snd_soc_codec *codec) +{ + int r; + + AC101_DBG(); + + /*HMIC/MMIC BIAS voltage level select:2.5v*/ + ac101_update_bits(codec, OMIXER_BST1_CTRL, (0xf<state:%d\n", ac10x->state); + + input_report_switch(ac10x->inpdev, SW_HEADPHONE_INSERT, ac10x->state); + input_sync(ac10x->inpdev); + return; +} + +/* + * work_cb_clear_irq: clear audiocodec pending and Record the interrupt. + */ +static void work_cb_clear_irq(struct work_struct *work) +{ + int reg_val = 0; + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, work_clear_irq); + struct snd_soc_codec *codec = ac10x->codec; + + ac10x->irq_cntr++; + + reg_val = ac101_read(codec, HMIC_STS); + if (BIT(HMIC_PULLOUT_PEND) & reg_val) { + ac10x->pullout_cntr++; + AC101_DBG("ac10x->pullout_cntr: %d\n", ac10x->pullout_cntr); + } + + reg_val |= HMIC_PEND_ALL; + ac101_write(codec, HMIC_STS, reg_val); + + reg_val = ac101_read(codec, HMIC_STS); + if ((reg_val & HMIC_PEND_ALL) != 0){ + reg_val |= HMIC_PEND_ALL; + ac101_write(codec, HMIC_STS, reg_val); + } + + if (cancel_work_sync(&ac10x->work_switch) != 0) { + ac10x->irq_cntr--; + } + + if (0 == schedule_work(&ac10x->work_switch)) { + ac10x->irq_cntr--; + AC101_DBG("[work_cb_clear_irq] add work struct failed!\n"); + } +} + +enum { + HBIAS_LEVEL_1 = 0x02, + HBIAS_LEVEL_2 = 0x0B, + HBIAS_LEVEL_3 = 0x13, + HBIAS_LEVEL_4 = 0x17, + HBIAS_LEVEL_5 = 0x19, +}; + +static int __ac101_get_hmic_data(struct snd_soc_codec *codec) { + #ifdef AC101_DEBG + static long counter; + #endif + int r, d; + + d = GET_HMIC_DATA(ac101_read(codec, HMIC_STS)); + + r = 0x1 << HMIC_DATA_PEND; + ac101_write(codec, HMIC_STS, r); + + /* prevent i2c accessing too frequently */ + usleep_range(1500, 3000); + + AC101_DBG("HMIC_DATA(%3ld): %02X\n", counter++, d); + return d; +} + +/* + * work_cb_earphone_switch: judge the status of the headphone + */ +static void work_cb_earphone_switch(struct work_struct *work) +{ + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, work_switch); + struct snd_soc_codec *codec = ac10x->codec; + + static int hook_flag1 = 0, hook_flag2 = 0; + static int KEY_VOLUME_FLAG = 0; + + unsigned filter_buf = 0; + int filt_index = 0; + int t = 0; + + ac10x->irq_cntr--; + + /* read HMIC_DATA */ + t = __ac101_get_hmic_data(codec); + + if ((t >= HBIAS_LEVEL_2) && (ac10x->mode == FOUR_HEADPHONE_PLUGIN)) { + t = __ac101_get_hmic_data(codec); + + if (t >= HBIAS_LEVEL_5){ + msleep(150); + t = __ac101_get_hmic_data(codec); + if (((t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1 - 1) || t >= HBIAS_LEVEL_5) + && (ac10x->pullout_cntr == 0)) { + input_report_key(ac10x->inpdev, KEY_HEADSETHOOK, 1); + input_sync(ac10x->inpdev); + + AC101_DBG("KEY_HEADSETHOOK1\n"); + + if (hook_flag1 != hook_flag2) + hook_flag1 = hook_flag2 = 0; + hook_flag1++; + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } else if (t >= HBIAS_LEVEL_4) { + msleep(80); + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_5 && t >= HBIAS_LEVEL_4 && (ac10x->pullout_cntr == 0)) { + KEY_VOLUME_FLAG = 1; + input_report_key(ac10x->inpdev, KEY_VOLUMEUP, 1); + input_sync(ac10x->inpdev); + input_report_key(ac10x->inpdev, KEY_VOLUMEUP, 0); + input_sync(ac10x->inpdev); + + AC101_DBG("HMIC_DATA: %d KEY_VOLUMEUP\n", t); + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } else if (t >= HBIAS_LEVEL_3){ + msleep(80); + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_4 && t >= HBIAS_LEVEL_3 && (ac10x->pullout_cntr == 0)){ + KEY_VOLUME_FLAG = 1; + input_report_key(ac10x->inpdev, KEY_VOLUMEDOWN, 1); + input_sync(ac10x->inpdev); + input_report_key(ac10x->inpdev, KEY_VOLUMEDOWN, 0); + input_sync(ac10x->inpdev); + AC101_DBG("KEY_VOLUMEDOWN\n"); + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } + } else if ((t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1) && (ac10x->mode == FOUR_HEADPHONE_PLUGIN)) { + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1) { + if (KEY_VOLUME_FLAG) { + KEY_VOLUME_FLAG = 0; + } + if (hook_flag1 == (++hook_flag2)) { + hook_flag1 = hook_flag2 = 0; + input_report_key(ac10x->inpdev, KEY_HEADSETHOOK, 0); + input_sync(ac10x->inpdev); + + AC101_DBG("KEY_HEADSETHOOK0\n"); + } + } + } else { + while (ac10x->irq_cntr == 0 && ac10x->irq != 0) { + msleep(20); + + t = __ac101_get_hmic_data(codec); + + if (filt_index <= HEADSET_FILTER_CNT) { + if (filt_index++ == 0) { + filter_buf = t; + } else if (filter_buf != t) { + filt_index = 0; + } + continue; + } + + filt_index = 0; + if (filter_buf >= HBIAS_LEVEL_2) { + ac10x->mode = THREE_HEADPHONE_PLUGIN; + ac10x->state = 2; + } else if (filter_buf >= HBIAS_LEVEL_1 - 1) { + ac10x->mode = FOUR_HEADPHONE_PLUGIN; + ac10x->state = 1; + } else { + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = 0; + } + switch_status_update(ac10x); + ac10x->pullout_cntr = 0; + break; + } + } +} + +/* + * audio_hmic_irq: the interrupt handlers + */ +static irqreturn_t audio_hmic_irq(int irq, void *para) +{ + struct ac10x_priv *ac10x = (struct ac10x_priv *)para; + if (ac10x == NULL) { + return -EINVAL; + } + + if (0 == schedule_work(&ac10x->work_clear_irq)){ + AC101_DBG("[audio_hmic_irq] work already in queue_codec_irq, adding failed!\n"); + } + return IRQ_HANDLED; +} + +static int ac101_switch_probe(struct ac10x_priv *ac10x) { + struct i2c_client *i2c = ac10x->i2c101; + long ret; + + ac10x->gpiod_irq = devm_gpiod_get_optional(&i2c->dev, "switch-irq", GPIOD_IN); + if (IS_ERR(ac10x->gpiod_irq)) { + ac10x->gpiod_irq = NULL; + dev_err(&i2c->dev, "failed get switch-irq in device tree\n"); + goto _err_irq; + } + + gpiod_direction_input(ac10x->gpiod_irq); + + ac10x->irq = gpiod_to_irq(ac10x->gpiod_irq); + if (IS_ERR_VALUE(ac10x->irq)) { + pr_info("[ac101] map gpio to irq failed, errno = %ld\n", ac10x->irq); + ac10x->irq = 0; + goto _err_irq; + } + + /* request irq, set irq type to falling edge trigger */ + ret = devm_request_irq(ac10x->codec->dev, ac10x->irq, audio_hmic_irq, IRQF_TRIGGER_FALLING, "SWTICH_EINT", ac10x); + if (IS_ERR_VALUE(ret)) { + pr_info("[ac101] request virq %ld failed, errno = %ld\n", ac10x->irq, ret); + goto _err_irq; + } + + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = -1; + + /*use for judge the state of switch*/ + INIT_WORK(&ac10x->work_switch, work_cb_earphone_switch); + INIT_WORK(&ac10x->work_clear_irq, work_cb_clear_irq); + + /********************create input device************************/ + ac10x->inpdev = devm_input_allocate_device(ac10x->codec->dev); + if (!ac10x->inpdev) { + AC101_DBG("input_allocate_device: not enough memory for input device\n"); + ret = -ENOMEM; + goto _err_input_allocate_device; + } + + ac10x->inpdev->name = "seed-voicecard-headset"; + ac10x->inpdev->phys = dev_name(ac10x->codec->dev); + ac10x->inpdev->id.bustype = BUS_I2C; + ac10x->inpdev->dev.parent = ac10x->codec->dev; + input_set_drvdata(ac10x->inpdev, ac10x->codec); + + ac10x->inpdev->evbit[0] = BIT_MASK(EV_KEY) | BIT(EV_SW); + + set_bit(KEY_HEADSETHOOK, ac10x->inpdev->keybit); + set_bit(KEY_VOLUMEUP, ac10x->inpdev->keybit); + set_bit(KEY_VOLUMEDOWN, ac10x->inpdev->keybit); + input_set_capability(ac10x->inpdev, EV_SW, SW_HEADPHONE_INSERT); + + ret = input_register_device(ac10x->inpdev); + if (ret) { + AC101_DBG("input_register_device: input_register_device failed\n"); + goto _err_input_register_device; + } + + /* the first headset state checking */ + switch_hw_config(ac10x->codec); + ac10x->irq_cntr = 1; + schedule_work(&ac10x->work_switch); + + return 0; + +_err_input_register_device: +_err_input_allocate_device: + + if (ac10x->irq) { + devm_free_irq(&i2c->dev, ac10x->irq, ac10x); + ac10x->irq = 0; + } +_err_irq: + return ret; +} +/******************************************************************************/ +/********************************switch****************************************/ +/******************************************************************************/ +#endif + + + +void drc_config(struct snd_soc_codec *codec) +{ + int reg_val; + reg_val = ac101_read(codec, 0xa3); + reg_val &= ~(0x7ff<<0); + reg_val |= 1<<0; + ac101_write(codec, 0xa3, reg_val); + ac101_write(codec, 0xa4, 0x2baf); + + reg_val = ac101_read(codec, 0xa5); + reg_val &= ~(0x7ff<<0); + reg_val |= 1<<0; + ac101_write(codec, 0xa5, reg_val); + ac101_write(codec, 0xa6, 0x2baf); + + reg_val = ac101_read(codec, 0xa7); + reg_val &= ~(0x7ff<<0); + ac101_write(codec, 0xa7, reg_val); + ac101_write(codec, 0xa8, 0x44a); + + reg_val = ac101_read(codec, 0xa9); + reg_val &= ~(0x7ff<<0); + ac101_write(codec, 0xa9, reg_val); + ac101_write(codec, 0xaa, 0x1e06); + + reg_val = ac101_read(codec, 0xab); + reg_val &= ~(0x7ff<<0); + reg_val |= (0x352<<0); + ac101_write(codec, 0xab, reg_val); + ac101_write(codec, 0xac, 0x6910); + + reg_val = ac101_read(codec, 0xad); + reg_val &= ~(0x7ff<<0); + reg_val |= (0x77a<<0); + ac101_write(codec, 0xad, reg_val); + ac101_write(codec, 0xae, 0xaaaa); + + reg_val = ac101_read(codec, 0xaf); + reg_val &= ~(0x7ff<<0); + reg_val |= (0x2de<<0); + ac101_write(codec, 0xaf, reg_val); + ac101_write(codec, 0xb0, 0xc982); + + ac101_write(codec, 0x16, 0x9f9f); + +} + +void drc_enable(struct snd_soc_codec *codec,bool on) +{ + int reg_val; + if (on) { + ac101_write(codec, 0xb5, 0xA080); + reg_val = ac101_read(codec, MOD_CLK_ENA); + reg_val |= (0x1<<6); + ac101_write(codec, MOD_CLK_ENA, reg_val); + reg_val = ac101_read(codec, MOD_RST_CTRL); + reg_val |= (0x1<<6); + ac101_write(codec, MOD_RST_CTRL, reg_val); + + reg_val = ac101_read(codec, 0xa0); + reg_val |= (0x7<<0); + ac101_write(codec, 0xa0, reg_val); + } else { + ac101_write(codec, 0xb5, 0x0); + reg_val = ac101_read(codec, MOD_CLK_ENA); + reg_val &= ~(0x1<<6); + ac101_write(codec, MOD_CLK_ENA, reg_val); + reg_val = ac101_read(codec, MOD_RST_CTRL); + reg_val &= ~(0x1<<6); + ac101_write(codec, MOD_RST_CTRL, reg_val); + + reg_val = ac101_read(codec, 0xa0); + reg_val &= ~(0x7<<0); + ac101_write(codec, 0xa0, reg_val); + } +} + +void set_configuration(struct snd_soc_codec *codec) +{ + if (speaker_double_used) { + ac101_update_bits(codec, SPKOUT_CTRL, (0x1f<dac_mutex); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + AC101_DBG(); + if (ac10x->dac_enable == 0){ + /*enable dac module clk*/ + ac101_update_bits(codec, MOD_CLK_ENA, (0x1<dac_enable++; + break; + case SND_SOC_DAPM_POST_PMD: + if (ac10x->dac_enable != 0){ + ac10x->dac_enable = 0; + + ac101_update_bits(codec, DAC_DIG_CTRL, (0x1<dac_mutex); + return 0; +} + +static int ac101_headphone_event(struct snd_soc_codec* codec, int event) { + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /*open*/ + AC101_DBG("post:open\n"); + ac101_update_bits(codec, OMIXER_DACA_CTRL, (0xf<codec, SYSCLK_CTRL); + return (reg_val & (0x1<aif1_clken == 0){ + ret = ac101_update_bits(codec, SYSCLK_CTRL, (0x1<aif1_clken++; + } + } + break; + case SND_SOC_DAPM_POST_PMD: + if (ac10x->aif1_clken != 0) { + /* disable aif1clk & sysclk */ + ret = ac101_update_bits(codec, SYSCLK_CTRL, (0x1<aif1_clken = 0; + } + break; + } + } + + AC101_DBG("event=%d pre_up/%d post_down/%d\n", event, SND_SOC_DAPM_PRE_PMU, SND_SOC_DAPM_POST_PMD); + + return ret; +} + +/** + * snd_ac101_get_volsw - single mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +static int snd_ac101_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol +){ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int val, mask = (1 << fls(mc->max)) - 1; + unsigned int invert = mc->invert; + int ret; + + if ((ret = ac101_read(static_ac10x->codec, mc->reg)) < 0) + return ret; + + val = (ret >> mc->shift) & mask; + ucontrol->value.integer.value[0] = val - mc->min; + if (invert) { + ucontrol->value.integer.value[0] = + mc->max - ucontrol->value.integer.value[0]; + } + + if (snd_soc_volsw_is_stereo(mc)) { + val = (ret >> mc->rshift) & mask; + ucontrol->value.integer.value[1] = val - mc->min; + if (invert) { + ucontrol->value.integer.value[1] = + mc->max - ucontrol->value.integer.value[1]; + } + } + return 0; +} + +/** + * snd_ac101_put_volsw - single mixer put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +static int snd_ac101_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol +){ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int sign_bit = mc->sign_bit; + unsigned int val, mask = (1 << fls(mc->max)) - 1; + unsigned int invert = mc->invert; + int ret; + + if (sign_bit) + mask = BIT(sign_bit + 1) - 1; + + val = ((ucontrol->value.integer.value[0] + mc->min) & mask); + if (invert) { + val = mc->max - val; + } + + ret = ac101_update_bits(static_ac10x->codec, mc->reg, mask << mc->shift, val << mc->shift); + + if (! snd_soc_volsw_is_stereo(mc)) { + return ret; + } + val = ((ucontrol->value.integer.value[1] + mc->min) & mask); + if (invert) { + val = mc->max - val; + } + + ret = ac101_update_bits(static_ac10x->codec, mc->reg, mask << mc->rshift, val << mc->rshift); + return ret; +} + + +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -11925, 75, 0); +static const DECLARE_TLV_DB_SCALE(dac_mix_vol_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(dig_vol_tlv, -7308, 116, 0); +static const DECLARE_TLV_DB_SCALE(speaker_vol_tlv, -4800, 150, 0); +static const DECLARE_TLV_DB_SCALE(headphone_vol_tlv, -6300, 100, 0); + +static struct snd_kcontrol_new ac101_controls[] = { + /*DAC*/ + SOC_DOUBLE_TLV("DAC volume", DAC_VOL_CTRL, DAC_VOL_L, DAC_VOL_R, 0xff, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("DAC mixer gain", DAC_MXR_GAIN, DACL_MXR_GAIN, DACR_MXR_GAIN, 0xf, 0, dac_mix_vol_tlv), + SOC_SINGLE_TLV("digital volume", DAC_DBG_CTRL, DVC, 0x3f, 1, dig_vol_tlv), + SOC_SINGLE_TLV("speaker volume", SPKOUT_CTRL, SPK_VOL, 0x1f, 0, speaker_vol_tlv), + SOC_SINGLE_TLV("headphone volume", HPOUT_CTRL, HP_VOL, 0x3f, 0, headphone_vol_tlv), +}; + +/* PLL divisors */ +struct pll_div { + unsigned int pll_in; + unsigned int pll_out; + int m; + int n_i; + int n_f; +}; + +struct aif1_fs { + unsigned samp_rate; + int bclk_div; + int srbit; + #define _SERIES_24_576K 0 + #define _SERIES_22_579K 1 + int series; +}; + +struct kv_map { + int val; + int bit; +}; + +/* + * Note : pll code from original tdm/i2s driver. + * freq_out = freq_in * N/(M*(2k+1)) , k=1,N=N_i+N_f,N_f=factor*0.2; + * N_i[0,1023], N_f_factor[0,7], m[1,64]=REG_VAL[1-63,0] + */ +static const struct pll_div codec_pll_div[] = { + {128000, _FREQ_22_579K, 1, 529, 1}, + {192000, _FREQ_22_579K, 1, 352, 4}, + {256000, _FREQ_22_579K, 1, 264, 3}, + {384000, _FREQ_22_579K, 1, 176, 2}, /*((176+2*0.2)*6000000)/(38*(2*1+1))*/ + {1411200, _FREQ_22_579K, 1, 48, 0}, + {2822400, _FREQ_22_579K, 1, 24, 0}, /* accurate, 11025 * 256 */ + {5644800, _FREQ_22_579K, 1, 12, 0}, /* accurate, 22050 * 256 */ + {6000000, _FREQ_22_579K, 38, 429, 0}, /*((429+0*0.2)*6000000)/(38*(2*1+1))*/ + {11289600, _FREQ_22_579K, 1, 6, 0}, /* accurate, 44100 * 256 */ + {13000000, _FREQ_22_579K, 19, 99, 0}, + {19200000, _FREQ_22_579K, 25, 88, 1}, + {24000000, _FREQ_22_579K, 63, 177, 4}, /* 22577778 Hz */ + + {128000, _FREQ_24_576K, 1, 576, 0}, + {192000, _FREQ_24_576K, 1, 384, 0}, + {256000, _FREQ_24_576K, 1, 288, 0}, + {384000, _FREQ_24_576K, 1, 192, 0}, + {2048000, _FREQ_24_576K, 1, 36, 0}, /* accurate, 8000 * 256 */ + {3072000, _FREQ_24_576K, 1, 24, 0}, /* accurate, 12000 * 256 */ + {4096000, _FREQ_24_576K, 1, 18, 0}, /* accurate, 16000 * 256 */ + {6000000, _FREQ_24_576K, 25, 307, 1}, + {6144000, _FREQ_24_576K, 4, 48, 0}, /* accurate, 24000 * 256 */ + {12288000, _FREQ_24_576K, 8, 48, 0}, /* accurate, 48000 * 256 */ + {13000000, _FREQ_24_576K, 42, 238, 1}, + {19200000, _FREQ_24_576K, 25, 96, 0}, + {24000000, _FREQ_24_576K, 25, 76, 4}, /* accurate */ + + {_FREQ_22_579K, _FREQ_22_579K, 8, 24, 0}, /* accurate, 88200 * 256 */ + {_FREQ_24_576K, _FREQ_24_576K, 8, 24, 0}, /* accurate, 96000 * 256 */ +}; + +static const struct aif1_fs codec_aif1_fs[] = { + {8000, 12, 0}, + {11025, 8, 1, _SERIES_22_579K}, + {12000, 8, 2}, + {16000, 6, 3}, + {22050, 4, 4, _SERIES_22_579K}, + {24000, 4, 5}, + /* {32000, 3, 6}, dividing by 3 is not support */ + {44100, 2, 7, _SERIES_22_579K}, + {48000, 2, 8}, + {96000, 1, 9}, +}; + +static const struct kv_map codec_aif1_lrck[] = { + {16, 0}, + {32, 1}, + {64, 2}, + {128, 3}, + {256, 4}, +}; + +static const struct kv_map codec_aif1_wsize[] = { + {8, 0}, + {16, 1}, + {20, 2}, + {24, 3}, + {32, 3}, +}; + +static const unsigned ac101_bclkdivs[] = { + 1, 2, 4, 6, + 8, 12, 16, 24, + 32, 48, 64, 96, + 128, 192, 0, 0, +}; + +static int ac101_aif_play(struct ac10x_priv* ac10x) { + struct snd_soc_codec * codec = ac10x->codec; + + late_enable_dac(codec, SND_SOC_DAPM_PRE_PMU); + ac101_headphone_event(codec, SND_SOC_DAPM_POST_PMU); + if (drc_used) { + drc_enable(codec, 1); + } + + /* Enable Left & Right Speaker */ + ac101_update_bits(codec, SPKOUT_CTRL, (0x1 << LSPK_EN) | (0x1 << RSPK_EN), (0x1 << LSPK_EN) | (0x1 << RSPK_EN)); + if (ac10x->gpiod_spk_amp_gate) { + gpiod_set_value(ac10x->gpiod_spk_amp_gate, 1); + } + return 0; +} + +static void ac10x_work_aif_play(struct work_struct *work) { + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, dlywork.work); + + ac101_aif_play(ac10x); + return; +} + +int ac101_aif_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + AC101_DBG("mute=%d\n", mute); + + ac101_write(codec, DAC_VOL_CTRL, mute? 0: 0xA0A0); + + if (!mute) { + #if _MASTER_MULTI_CODEC != _MASTER_AC101 + /* enable global clock */ + ac10x->aif1_clken = 0; + ac101_aif1clk(codec, SND_SOC_DAPM_PRE_PMU, 0); + ac101_aif_play(ac10x); + #else + schedule_delayed_work(&ac10x->dlywork, msecs_to_jiffies(50)); + #endif + } else { + #if _MASTER_MULTI_CODEC == _MASTER_AC101 + cancel_delayed_work_sync(&ac10x->dlywork); + #endif + + if (ac10x->gpiod_spk_amp_gate) { + gpiod_set_value(ac10x->gpiod_spk_amp_gate, 0); + } + /* Disable Left & Right Speaker */ + ac101_update_bits(codec, SPKOUT_CTRL, (0x1 << LSPK_EN) | (0x1 << RSPK_EN), (0x0 << LSPK_EN) | (0x0 << RSPK_EN)); + if (drc_used) { + drc_enable(codec, 0); + } + ac101_headphone_event(codec, SND_SOC_DAPM_PRE_PMD); + late_enable_dac(codec, SND_SOC_DAPM_POST_PMD); + + #if _MASTER_MULTI_CODEC != _MASTER_AC101 + ac10x->aif1_clken = 1; + ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); + #endif + } + return 0; +} + +void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + AC101_DBG("stream = %s, play: %d, capt: %d, active: %d\n", + snd_pcm_stream_str(substream), + codec_dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], codec_dai->stream_active[SNDRV_PCM_STREAM_CAPTURE], + snd_soc_dai_active(codec_dai)); + + if (!snd_soc_dai_active(codec_dai)) { + ac10x->aif1_clken = 1; + ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); + } else { + ac101_aif1clk(codec, SND_SOC_DAPM_PRE_PMU, 0); + } +} + +static int ac101_set_pll(struct snd_soc_dai *codec_dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int i, m, n_i, n_f; + + AC101_DBG("pll_id:%d\n", pll_id); + + /* clear volatile reserved bits*/ + ac101_update_bits(codec, SYSCLK_CTRL, 0xFF & ~(0x1 << SYSCLK_ENA), 0x0); + + /* select aif1 clk srouce from mclk1 */ + ac101_update_bits(codec, SYSCLK_CTRL, (0x3< _FREQ_24_576K)) { + return -EINVAL; + } else if ((freq_in == _FREQ_24_576K) || (freq_in == _FREQ_22_579K)) { + if (pll_id == AC101_MCLK1) { + /*select aif1 clk source from mclk1*/ + ac101_update_bits(codec, SYSCLK_CTRL, (0x3<codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int reg_val, freq_out; + unsigned channels; + + AC101_DBG("+++\n"); + + if (_MASTER_MULTI_CODEC == _MASTER_AC101 && ac101_sysclk_started()) { + /* not configure hw_param twice if stream is playback, tell the caller it's started */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + return 1; + } + } + + /* get channels count & slot size */ + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + aif1_slot_size = 32; + break; + case SNDRV_PCM_FORMAT_S16_LE: + default: + aif1_slot_size = 16; + break; + } + + /* set LRCK/BCLK ratio */ + aif1_lrck_div = aif1_slot_size * channels; + for (i = 0; i < ARRAY_SIZE(codec_aif1_lrck); i++) { + if (codec_aif1_lrck[i].val == aif1_lrck_div) { + break; + } + } + ac101_update_bits(codec, AIF_CLK_CTRL, (0x7<stream_active[SNDRV_PCM_STREAM_CAPTURE] && dmic_used && codec_aif1_fs[i].samp_rate == 44100) { + ac101_update_bits(codec, AIF_SR_CTRL, (0xf< 2) reg_val = 2; + ac101_update_bits(codec, AIF1_ADCDAT_CTRL, 0x3 << AIF1_SLOT_SIZ, reg_val << AIF1_SLOT_SIZ); + + /* setting pll if it's master mode */ + reg_val = ac101_read(codec, AIF_CLK_CTRL); + if ((reg_val & (0x1 << AIF1_MSTR_MOD)) == 0) { + unsigned bclkdiv; + + ac101_set_pll(codec_dai, AC101_MCLK1, 0, ac10x->sysclk, freq_out); + + bclkdiv = freq_out / (aif1_lrck_div * params_rate(params)); + for (i = 0; i < ARRAY_SIZE(ac101_bclkdivs) - 1; i++) { + if (ac101_bclkdivs[i] >= bclkdiv) { + break; + } + } + ac101_update_bits(codec, AIF_CLK_CTRL, (0xf<codec; + + AC101_DBG(); + + /* + * master or slave selection + * 0 = Master mode + * 1 = Slave mode + */ + reg_val = ac101_read(codec, AIF_CLK_CTRL); + reg_val &= ~(0x1<codec; + + AC101_DBG("\n\n\n"); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + } + return 0; +} + +#if _MASTER_MULTI_CODEC == _MASTER_AC101 +static int ac101_set_clock(int y_start_n_stop) { + int r; + + if (y_start_n_stop) { + /* enable global clock */ + r = ac101_aif1clk(static_ac10x->codec, SND_SOC_DAPM_PRE_PMU, 1); + } else { + /* disable global clock */ + static_ac10x->aif1_clken = 1; + r = ac101_aif1clk(static_ac10x->codec, SND_SOC_DAPM_POST_PMD, 0); + } + return r; +} +#endif + +int ac101_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + AC101_DBG("stream=%s cmd=%d\n", + snd_pcm_stream_str(substream), + cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + #if _MASTER_MULTI_CODEC == _MASTER_AC101 + if (ac10x->aif1_clken == 0){ + /* + * enable aif1clk, it' here due to reduce time between 'AC108 Sysclk Enable' and 'AC101 Sysclk Enable' + * Or else the two AC108 chips lost the sync. + */ + ret = 0; + ret = ret || ac101_update_bits(codec, MOD_CLK_ENA, (0x1<codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + AC101_DBG("id=%d freq=%d, dir=%d\n", + clk_id, freq, dir); + + ac10x->sysclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops ac101_aif1_dai_ops = { + //.startup = ac101_audio_startup, + //.shutdown = ac101_aif_shutdown, + //.set_sysclk = ac101_set_dai_sysclk, + //.set_pll = ac101_set_pll, + //.set_fmt = ac101_set_dai_fmt, + //.hw_params = ac101_hw_params, + //.trigger = ac101_trigger, + //.digital_mute = ac101_aif_mute, +}; + +static struct snd_soc_dai_driver ac101_dai[] = { + { + .name = "ac10x-aif1", + .id = AIF1_CLK, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = AC101_RATES, + .formats = AC101_FORMATS, + }, + #if 0 + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = AC101_RATES, + .formats = AC101_FORMATS, + }, + #endif + .ops = &ac101_aif1_dai_ops, + } +}; +#endif + +static void codec_resume_work(struct work_struct *work) +{ + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, codec_resume); + struct snd_soc_codec *codec = ac10x->codec; + + AC101_DBG("+++\n"); + + set_configuration(codec); + if (drc_used) { + drc_config(codec); + } + /*enable this bit to prevent leakage from ldoin*/ + ac101_update_bits(codec, ADDA_TUNE3, (0x1<bias_level = level; + return 0; +} + +int ac101_codec_probe(struct snd_soc_codec *codec) +{ + int ret = 0; + struct ac10x_priv *ac10x; + + ac10x = dev_get_drvdata(codec->dev); + if (ac10x == NULL) { + AC101_DBG("not set client data!\n"); + return -ENOMEM; + } + ac10x->codec = codec; + + INIT_DELAYED_WORK(&ac10x->dlywork, ac10x_work_aif_play); + INIT_WORK(&ac10x->codec_resume, codec_resume_work); + ac10x->dac_enable = 0; + ac10x->aif1_clken = 0; + mutex_init(&ac10x->dac_mutex); + + #if _MASTER_MULTI_CODEC == _MASTER_AC101 + //seeed_voice_card_register_set_clock(SNDRV_PCM_STREAM_PLAYBACK, ac101_set_clock); + #endif + + set_configuration(ac10x->codec); + + /*enable this bit to prevent leakage from ldoin*/ + ac101_update_bits(codec, ADDA_TUNE3, (0x1<get = snd_ac101_get_volsw; + skn->put = snd_ac101_put_volsw; + } + ret = snd_soc_add_codec_controls(codec, ac101_controls, ARRAY_SIZE(ac101_controls)); + if (ret) { + pr_err("[ac10x] Failed to register audio mode control, " + "will continue without it.\n"); + } + + #ifdef CONFIG_AC101_SWITCH_DETECT + ret = ac101_switch_probe(ac10x); + if (ret) { + // not care the switch return value + } + #endif + + return 0; +} + +/* power down chip */ +int ac101_codec_remove(struct snd_soc_codec *codec) +{ + #ifdef CONFIG_AC101_SWITCH_DETECT + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (ac10x->irq) { + devm_free_irq(codec->dev, ac10x->irq, ac10x); + ac10x->irq = 0; + } + + if (cancel_work_sync(&ac10x->work_switch) != 0) { + } + + if (cancel_work_sync(&ac10x->work_clear_irq) != 0) { + } + + if (ac10x->inpdev) { + input_unregister_device(ac10x->inpdev); + ac10x->inpdev = NULL; + } + #endif + + return 0; +} + +int ac101_codec_suspend(struct snd_soc_codec *codec) +{ + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + AC101_DBG("[codec]:suspend\n"); + regcache_cache_only(ac10x->regmap101, true); + return 0; +} + +int ac101_codec_resume(struct snd_soc_codec *codec) +{ + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int ret; + + AC101_DBG("[codec]:resume"); + + /* Sync reg_cache with the hardware */ + regcache_cache_only(ac10x->regmap101, false); + ret = regcache_sync(ac10x->regmap101); + if (ret != 0) { + dev_err(codec->dev, "Failed to sync register cache: %d\n", ret); + regcache_cache_only(ac10x->regmap101, true); + return ret; + } + + #ifdef CONFIG_AC101_SWITCH_DETECT + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = -1; + #endif + + ac101_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + schedule_work(&ac10x->codec_resume); + return 0; +} + +/***************************************************************************/ +static ssize_t ac101_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ac10x_priv *ac10x = dev_get_drvdata(dev); + int val = 0, flag = 0; + u16 value_w, value_r; + u8 reg, num, i=0; + + val = simple_strtol(buf, NULL, 16); + flag = (val >> 24) & 0xF; + if (flag) { + reg = (val >> 16) & 0xFF; + value_w = val & 0xFFFF; + ac101_write(ac10x->codec, reg, value_w); + printk("write 0x%x to reg:0x%x\n", value_w, reg); + } else { + reg = (val >> 8) & 0xFF; + num = val & 0xff; + printk("\n"); + printk("read:start add:0x%x,count:0x%x\n", reg, num); + + regcache_cache_bypass(ac10x->regmap101, true); + do { + value_r = ac101_read(ac10x->codec, reg); + printk("0x%x: 0x%04x ", reg++, value_r); + if (++i % 4 == 0 || i == num) + printk("\n"); + } while (i < num); + regcache_cache_bypass(ac10x->regmap101, false); + } + return count; +} +static ssize_t ac101_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + printk("echo flag|reg|val > ac10x\n"); + printk("eg read star addres=0x06,count 0x10:echo 0610 >ac10x\n"); + printk("eg write value:0x13fe to address:0x06 :echo 10613fe > ac10x\n"); + return 0; +} +static DEVICE_ATTR(ac10x, 0644, ac101_debug_show, ac101_debug_store); + +static struct attribute *audio_debug_attrs[] = { + &dev_attr_ac10x.attr, + NULL, +}; + +static struct attribute_group audio_debug_attr_group = { + .name = "ac101_debug", + .attrs = audio_debug_attrs, +}; +/***************************************************************************/ + +/************************************************************/ +static bool ac101_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PLL_CTRL2: + case HMIC_STS: + return true; + } + return false; +} + +static const struct regmap_config ac101_regmap = { + .reg_bits = 8, + .val_bits = 16, + .reg_stride = 1, + .max_register = 0xB5, + .cache_type = REGCACHE_FLAT, + .volatile_reg = ac101_volatile_reg, +}; + +/* Sync reg_cache from the hardware */ +int ac10x_fill_regcache(struct device* dev, struct regmap* map) { + int r, i, n; + int v; + + n = regmap_get_max_register(map); + for (i = 0; i < n; i++) { + regcache_cache_bypass(map, true); + r = regmap_read(map, i, &v); + if (r) { + dev_err(dev, "failed to read register %d\n", i); + continue; + } + regcache_cache_bypass(map, false); + + regcache_cache_only(map, true); + r = regmap_write(map, i, v); + regcache_cache_only(map, false); + } + regcache_cache_bypass(map, false); + regcache_cache_only(map, false); + + return 0; +} + +int ac101_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct ac10x_priv *ac10x = i2c_get_clientdata(i2c); + int ret = 0; + unsigned v = 0; + + AC101_DBG(); + + static_ac10x = ac10x; + + ac10x->regmap101 = devm_regmap_init_i2c(i2c, &ac101_regmap); + if (IS_ERR(ac10x->regmap101)) { + ret = PTR_ERR(ac10x->regmap101); + dev_err(&i2c->dev, "Fail to initialize I/O: %d\n", ret); + return ret; + } + + /* Chip reset */ + regcache_cache_only(ac10x->regmap101, false); + ret = regmap_write(ac10x->regmap101, CHIP_AUDIO_RST, 0); + msleep(50); + + /* sync regcache for FLAT type */ + ac10x_fill_regcache(&i2c->dev, ac10x->regmap101); + + ret = regmap_read(ac10x->regmap101, CHIP_AUDIO_RST, &v); + if (ret < 0) { + dev_err(&i2c->dev, "failed to read vendor ID: %d\n", ret); + return ret; + } + + if (v != AC101_CHIP_ID) { + dev_err(&i2c->dev, "chip is not AC101 (%X)\n", v); + dev_err(&i2c->dev, "Expected %X\n", AC101_CHIP_ID); + return -ENODEV; + } + + ret = sysfs_create_group(&i2c->dev.kobj, &audio_debug_attr_group); + if (ret) { + pr_err("failed to create attr group\n"); + } + + ac10x->gpiod_spk_amp_gate = devm_gpiod_get_optional(&i2c->dev, "spk-amp-switch", GPIOD_OUT_LOW); + if (IS_ERR(ac10x->gpiod_spk_amp_gate)) { + ac10x->gpiod_spk_amp_gate = NULL; + dev_err(&i2c->dev, "failed get spk-amp-switch in device tree\n"); + } + + return 0; +} + +void ac101_shutdown(struct i2c_client *i2c) +{ + struct ac10x_priv *ac10x = i2c_get_clientdata(i2c); + struct snd_soc_codec *codec = ac10x->codec; + int reg_val; + + if (codec == NULL) { + pr_err(": no sound card.\n"); + return; + } + + /*set headphone volume to 0*/ + reg_val = ac101_read(codec, HPOUT_CTRL); + reg_val &= ~(0x3f<dev.kobj, &audio_debug_attr_group); + return 0; +} + +MODULE_DESCRIPTION("ASoC ac10x driver"); +MODULE_AUTHOR("huangxin,liushaohua"); +MODULE_AUTHOR("PeterYang"); diff --git a/sound/soc/codecs/ac101_regs.h b/sound/soc/codecs/ac101_regs.h new file mode 100644 index 0000000..8be570e --- /dev/null +++ b/sound/soc/codecs/ac101_regs.h @@ -0,0 +1,432 @@ +/* + * ac101_regs.h + * + * (C) Copyright 2017-2018 + * Seeed Technology Co., Ltd. + * + * PeterYang + * + * (C) Copyright 2010-2017 + * Reuuimlla Technology Co., Ltd. + * huangxin + * + * 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. + * + */ +#ifndef __AC101_REGS_H__ +#define __AC101_REGS_H__ + +/*pll source*/ +#define AC101_MCLK1 1 +#define AC101_MCLK2 2 +#define AC101_BCLK1 3 +#define AC101_BCLK2 4 + +#define AIF1_CLK 1 +#define AIF2_CLK 2 + +#define CHIP_AUDIO_RST 0x0 +#define PLL_CTRL1 0x1 +#define PLL_CTRL2 0x2 +#define SYSCLK_CTRL 0x3 +#define MOD_CLK_ENA 0x4 +#define MOD_RST_CTRL 0x5 +#define AIF_SR_CTRL 0x6 + +#define AIF1_CLK_CTRL 0x10 +#define AIF1_ADCDAT_CTRL 0x11 +#define AIF1_DACDAT_CTRL 0x12 +#define AIF1_MXR_SRC 0x13 +#define AIF1_VOL_CTRL1 0x14 +#define AIF1_VOL_CTRL2 0x15 +#define AIF1_VOL_CTRL3 0x16 +#define AIF1_VOL_CTRL4 0x17 +#define AIF1_MXR_GAIN 0x18 +#define AIF1_RXD_CTRL 0x19 +#define ADC_DIG_CTRL 0x40 +#define ADC_VOL_CTRL 0x41 +#define ADC_DBG_CTRL 0x42 + +#define HMIC_CTRL1 0x44 +#define HMIC_CTRL2 0x45 +#define HMIC_STS 0x46 + +#define DAC_DIG_CTRL 0x48 +#define DAC_VOL_CTRL 0x49 +#define DAC_DBG_CTRL 0x4a +#define DAC_MXR_SRC 0x4c +#define DAC_MXR_GAIN 0x4d + +#define ADC_APC_CTRL 0x50 +#define ADC_SRC 0x51 +#define ADC_SRCBST_CTRL 0x52 +#define OMIXER_DACA_CTRL 0x53 +#define OMIXER_SR 0x54 +#define OMIXER_BST1_CTRL 0x55 +#define HPOUT_CTRL 0x56 +#define ESPKOUT_CTRL 0x57 +#define SPKOUT_CTRL 0x58 +#define LOUT_CTRL 0x59 +#define ADDA_TUNE1 0x5a +#define ADDA_TUNE2 0x5b +#define ADDA_TUNE3 0x5c +#define HPOUT_STR 0x5d + +/*CHIP_AUDIO_RST*/ +#define AC101_CHIP_ID 0x0101 + +/*PLL_CTRL1*/ +#define DPLL_DAC_BIAS 14 +#define PLL_POSTDIV_M 8 +#define CLOSE_LOOP 6 +#define INT 0 + +/*PLL_CTRL2*/ +#define PLL_EN 15 +#define PLL_LOCK_STATUS 14 +#define PLL_PREDIV_NI 4 +#define PLL_POSTDIV_NF 0 + +/*SYSCLK_CTRL*/ +#define PLLCLK_ENA 15 +#define PLLCLK_SRC 12 +#define AIF1CLK_ENA 11 +#define AIF1CLK_SRC 8 +#define AIF2CLK_ENA 7 +#define AIF2CLK_SRC 4 +#define SYSCLK_ENA 3 +#define SYSCLK_SRC 0 + +/*MOD_CLK_ENA*/ +#define MOD_CLK_AIF1 15 +#define MOD_CLK_AIF2 14 +#define MOD_CLK_AIF3 13 +#define MOD_CLK_SRC1 11 +#define MOD_CLK_SRC2 10 +#define MOD_CLK_HPF_AGC 7 +#define MOD_CLK_HPF_DRC 6 +#define MOD_CLK_ADC_DIG 3 +#define MOD_CLK_DAC_DIG 2 + +/*MOD_RST_CTRL*/ +#define MOD_RESET_CTL 0 +#define MOD_RESET_AIF1 15 +#define MOD_RESET_AIF2 14 +#define MOD_RESET_AIF3 13 +#define MOD_RESET_SRC1 11 +#define MOD_RESET_SRC2 10 +#define MOD_RESET_HPF_AGC 7 +#define MOD_RESET_HPF_DRC 6 +#define MOD_RESET_ADC_DIG 3 +#define MOD_RESET_DAC_DIG 2 + +/*AIF_SR_CTRL*/ +#define AIF1_FS 12 //AIF1 Sample Rate +#define AIF2_FS 8 //AIF2 Sample Rate +#define SRC1_ENA 3 +#define SRC1_SRC 2 +#define SRC2_ENA 1 +#define SRC2_SRC 0 + +/*AIF1LCK_CTRL*/ +#define AIF1_MSTR_MOD 15 +#define AIF1_BCLK_INV 14 +#define AIF1_LRCK_INV 13 +#define AIF1_BCLK_DIV 9 +#define AIF1_LRCK_DIV 6 +#define AIF1_WORK_SIZ 4 +#define AIF1_DATA_FMT 2 +#define DSP_MONO_PCM 1 +#define AIF1_TDMM_ENA 0 + +/*AIF1_ADCDAT_CTRL*/ +#define AIF1_AD0L_ENA 15 +#define AIF1_AD0R_ENA 14 +#define AIF1_AD1L_ENA 13 +#define AIF1_AD1R_ENA 12 +#define AIF1_AD0L_SRC 10 +#define AIF1_AD0R_SRC 8 +#define AIF1_AD1L_SRC 6 +#define AIF1_AD1R_SRC 4 +#define AIF1_ADCP_ENA 3 +#define AIF1_ADUL_ENA 2 +#define AIF1_SLOT_SIZ 0 + +/*AIF1_DACDAT_CTRL*/ +#define AIF1_DA0L_ENA 15 +#define AIF1_DA0R_ENA 14 +#define AIF1_DA1L_ENA 13 +#define AIF1_DA1R_ENA 12 +#define AIF1_DA0L_SRC 10 +#define AIF1_DA0R_SRC 8 +#define AIF1_DA1L_SRC 6 +#define AIF1_DA1R_SRC 4 +#define AIF1_DACP_ENA 3 +#define AIF1_DAUL_ENA 2 +#define AIF1_SLOT_SIZ 0 + +/*AIF1_MXR_SRC*/ +#define AIF1_AD0L_AIF1_DA0L_MXR 15 +#define AIF1_AD0L_AIF2_DACL_MXR 14 +#define AIF1_AD0L_ADCL_MXR 13 +#define AIF1_AD0L_AIF2_DACR_MXR 12 +#define AIF1_AD0R_AIF1_DA0R_MXR 11 +#define AIF1_AD0R_AIF2_DACR_MXR 10 +#define AIF1_AD0R_ADCR_MXR 9 +#define AIF1_AD0R_AIF2_DACL_MXR 8 +#define AIF1_AD1L_AIF2_DACL_MXR 7 +#define AIF1_AD1L_ADCL_MXR 6 +#define AIF1_AD1L_MXR_SRC 6 +#define AIF1_AD1R_AIF2_DACR_MXR 3 +#define AIF1_AD1R_ADCR_MXR 2 +#define AIF1_AD1R_MXR_SRC 2 + +/*AIF1_VOL_CTRL1*/ +#define AIF1_AD0L_VOL 8 +#define AIF1_AD0R_VOL 0 + +/*AIF1_VOL_CTRL2*/ +#define AIF1_AD1L_VOL 8 +#define AIF1_AD1R_VOL 0 + +/*AIF1_VOL_CTRL3*/ +#define AIF1_DA0L_VOL 8 +#define AIF1_DA0R_VOL 0 + +/*AIF1_VOL_CTRL4*/ +#define AIF1_DA1L_VOL 8 +#define AIF1_DA1R_VOL 0 + +/*AIF1_MXR_GAIN*/ +#define AIF1_AD0L_MXR_GAIN 12 +#define AIF1_AD0R_MXR_GAIN 8 +#define AIF1_AD1L_MXR_GAIN 6 +#define AIF1_AD1R_MXR_GAIN 2 + +/*AIF1_RXD_CTRL*/ +#define AIF1_N_DATA_DISCARD 8 + +/*ADC_DIG_CTRL*/ +#define ENAD 15 +#define ENDM 14 +#define ADFIR32 13 +#define ADOUT_DTS 2 +#define ADOUT_DLY 1 + +/*ADC_VOL_CTRL*/ +#define ADC_VOL_L 8 +#define ADC_VOL_R 0 + +/*ADC_DBG_CTRL*/ +#define ADSW 15 +#define DMIC_CLK_PIN_CTRL 12 + +/*HMIC_CTRL1*/ +#define HMIC_M 12 +#define HMIC_N 8 +#define HMIC_DATA_IRQ_MODE 7 +#define HMIC_TH1_HYSTERESIS 5 +#define HMIC_PULLOUT_IRQ 4 +#define HMIC_PLUGIN_IRQ 3 +#define HMIC_KEYUP_IRQ 2 +#define HMIC_KEYDOWN_IRQ 1 +#define HMIC_DATA_IRQ_EN 0 + +/*HMIC_CTRL2*/ +#define HMIC_SAMPLE_SELECT 14 +#define HMIC_TH2_HYSTERESIS 13 +#define HMIC_TH2 8 +#define HMIC_SF 6 +#define KEYUP_CLEAR 5 +#define HMIC_TH1 0 + +/*HMIC_STS*/ +#define HMIC_DATA 8 +#define GET_HMIC_DATA(r) (((r) >> HMIC_DATA) & 0x1F) +#define HMIC_PULLOUT_PEND 4 +#define HMIC_PLUGIN_PEND 3 +#define HMIC_KEYUP_PEND 2 +#define HMKC_KEYDOWN_PEND 1 +#define HMIC_DATA_PEND 0 +#define HMIC_PEND_ALL (0x1F) + +/*DAC_DIG_CTRL*/ +#define ENDA 15 +#define ENHPF 14 +#define DAFIR32 13 +#define MODQU 8 + +/*DAC_VOL_CTRL*/ +#define DAC_VOL_L 8 +#define DAC_VOL_R 0 + +/*DAC_DBG_CTRL*/ +#define DASW 15 +#define ENDWA_N 14 +#define DAC_MOD_DBG 13 +#define DAC_PTN_SEL 6 +#define DVC 0 + +/*DAC_MXR_SRC*/ +#define DACL_MXR_AIF1_DA0L 15 +#define DACL_MXR_AIF1_DA1L 14 +#define DACL_MXR_AIF2_DACL 13 +#define DACL_MXR_ADCL 12 +#define DACL_MXR_SRC 12 +#define DACR_MXR_AIF1_DA0R 11 +#define DACR_MXR_AIF1_DA1R 10 +#define DACR_MXR_AIF2_DACR 9 +#define DACR_MXR_ADCR 8 +#define DACR_MXR_SRC 8 + +/*DAC_MXR_GAIN*/ +#define DACL_MXR_GAIN 12 +#define DACR_MXR_GAIN 8 + +/*ADC_APC_CTRL*/ +#define ADCREN 15 +#define ADCRG 12 +#define ADCLEN 11 +#define ADCLG 8 +#define MBIASEN 7 +#define MMIC_BIAS_CHOP_EN 6 +#define MMIC_BIAS_CHOP_CKS 4 +#define HBIASMOD 2 +#define HBIASEN 1 +#define HBIASADCEN 0 + +/*ADC_SRC*/ +#define RADCMIXMUTEMIC1BOOST (13) +#define RADCMIXMUTEMIC2BOOST (12) +#define RADCMIXMUTELINEINLR (11) +#define RADCMIXMUTELINEINR (10) +#define RADCMIXMUTEAUXINR (9) +#define RADCMIXMUTEROUTPUT (8) +#define RADCMIXMUTELOUTPUT (7) +#define LADCMIXMUTEMIC1BOOST (6) +#define LADCMIXMUTEMIC2BOOST (5) +#define LADCMIXMUTELINEINLR (4) +#define LADCMIXMUTELINEINL (3) +#define LADCMIXMUTEAUXINL (2) +#define LADCMIXMUTELOUTPUT (1) +#define LADCMIXMUTEROUTPUT (0) + + +/*ADC_SRCBST_CTRL*/ +#define MIC1AMPEN 15 +#define ADC_MIC1G 12 +#define MIC2AMPEN 11 +#define ADC_MIC2G 8 +#define MIC2SLT 7 +#define LINEIN_PREG 4 +#define AUXI_PREG 0 + +/*OMIXER_DACA_CTRL*/ +#define DACAREN 15 +#define DACALEN 14 +#define RMIXEN 13 +#define LMIXEN 12 +#define HPOUTPUTENABLE 8 + +/*OMIXER_SR*/ +#define RMIXMUTEMIC1BOOST (13) +#define RMIXMUTEMIC2BOOST (12) +#define RMIXMUTELINEINLR (11) +#define RMIXMUTELINEINR (10) +#define RMIXMUTEAUXINR (9) +#define RMIXMUTEDACR (8) +#define RMIXMUTEDACL (7) +#define LMIXMUTEMIC1BOOST (6) +#define LMIXMUTEMIC2BOOST (5) +#define LMIXMUTELINEINLR (4) +#define LMIXMUTELINEINL (3) +#define LMIXMUTEAUXINL (2) +#define LMIXMUTEDACL (1) +#define LMIXMUTEDACR (0) + +/*OMIXER_BST1_CTRL*/ +#define BIASVOLTAGE 12 +#define AXG 9 +#define OMIXER_MIC1G 6 +#define OMIXER_MIC2G 3 +#define LINEING 0 + +/*HPOUT_CTRL*/ +#define RHPS 15 +#define LHPS 14 +#define RHPPA_MUTE 13 +#define LHPPA_MUTE 12 +#define HPPA_EN 11 +#define HP_VOL 4 +#define HPPA_DEL 2 +#define HPPA_IS 0 + +/*ESPKOUT_CTRL*/ +#define EAR_RAMP_TIME 11 +#define ESPA_OUT_CURRENT 9 +#define ESPSR 7 +#define ESPPA_MUTE 6 +#define ESPPA_EN 5 +#define ESP_VOL 0 + +/*SPKOUT_CTRL*/ +#define HPCALICKS 13 +#define RSPKS 12 +#define RSPKINVEN 11 +#define RSPK_EN 9 +#define LSPKS 8 +#define LSPKINVEN 7 +#define LSPK_EN 5 +#define SPK_VOL 0 + +/*LOUT_CTRL*/ +#define LINEOUTG 5 +#define LINEOUTEN 4 +#define LINEOUTS0 3 +#define LINEOUTS1 2 +#define LINEOUTS2 1 +#define LINEOUTS3 0 + +/*ADDA_TUNE1*/ +#define CURRENT_TEST_SELECT 14 +#define BIHE_CTRL 12 +#define DITHER 11 +#define DITHER_CLK 9 +#define ZERO_CROSSOVER_EN 8 +#define ZERO_CROSSOVER_TIME 7 +#define EAR_SPEED_SELECT 6 +#define REF_CHOPPEN_CKS 4 +#define OPMIC_BIAS_CUR 0 + +/*ADDA_TUNE2*/ +#define OPDAC_BIAS_CUR 14 +#define OPDRV_BIAS_CUR 12 +#define OPMIX_BIAS_CUR 10 +#define OPEAR_BIAS_CUR 8 +#define OPVR_BIAS_CUR 6 +#define OPAAF_BIAS_CUR 4 +#define OPADC1_BIAS_CUR 2 +#define OPADC2_BIAS_CUR 0 + +/*ADDA_TUNE3*/ +#define LDOEN 15 +#define LDO_SEL 12 +#define BIASCALIVERIFY 11 +#define BIASMODE 10 +#define BIASCALIDATA 9 +#define OSCS 1 +#define OSCEN 0 + +/*HPOUT_STR*/ +#define HPVL_SOFT_MOD 14 +#define HPVL_STEP_CTRL 8 +#define DACA_CHND_ENA 7 +#define HPPA_MXRD_ENA 6 +#define HPVL_CTRL_OUT 0 + +#endif//__AC101_REGS_H__ diff --git a/sound/soc/codecs/ac108.c b/sound/soc/codecs/ac108.c new file mode 100644 index 0000000..316a835 --- /dev/null +++ b/sound/soc/codecs/ac108.c @@ -0,0 +1,1583 @@ +/* + * ac10x.c -- ac10x ALSA SoC Audio driver + * + * + * Author: Baozhu Zuo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* #undef DEBUG + * use 'make DEBUG=1' to enable debugging + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ac108.h" +#include "ac10x.h" + +#define _USE_CAPTURE 1 +#define _MASTER_INDEX 0 + +/** + * TODO: + * 1, add PM API: ac108_suspend,ac108_resume + * 2,0x65-0x6a + * 3,0x76-0x79 high 4bit + */ +struct pll_div { + unsigned int freq_in; + unsigned int freq_out; + unsigned int m1; + unsigned int m2; + unsigned int n; + unsigned int k1; + unsigned int k2; +}; + +static struct ac10x_priv *ac10x; + +struct real_val_to_reg_val { + unsigned int real_val; + unsigned int reg_val; +}; + +static const struct real_val_to_reg_val ac108_sample_rate[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 96000, 9 }, +}; + +/* Sample resolution */ +static const struct real_val_to_reg_val ac108_samp_res[] = { + { 8, 1 }, + { 12, 2 }, + { 16, 3 }, + { 20, 4 }, + { 24, 5 }, + { 28, 6 }, + { 32, 7 }, +}; + +static const unsigned ac108_bclkdivs[] = { + 0, 1, 2, 4, + 6, 8, 12, 16, + 24, 32, 48, 64, + 96, 128, 176, 192, +}; + +/* FOUT =(FIN * N) / [(M1+1) * (M2+1)*(K1+1)*(K2+1)] ; M1[0,31], M2[0,1], N[0,1023], K1[0,31], K2[0,1] */ +static const struct pll_div ac108_pll_div_list[] = { + { 400000, _FREQ_24_576K, 0, 0, 614, 4, 1 }, + { 512000, _FREQ_24_576K, 0, 0, 960, 9, 1 }, //_FREQ_24_576K/48 + { 768000, _FREQ_24_576K, 0, 0, 640, 9, 1 }, //_FREQ_24_576K/32 + { 800000, _FREQ_24_576K, 0, 0, 614, 9, 1 }, + { 1024000, _FREQ_24_576K, 0, 0, 480, 9, 1 }, //_FREQ_24_576K/24 + { 1600000, _FREQ_24_576K, 0, 0, 307, 9, 1 }, + { 2048000, _FREQ_24_576K, 0, 0, 240, 9, 1 }, /* accurate, 8000 * 256 */ + { 3072000, _FREQ_24_576K, 0, 0, 160, 9, 1 }, /* accurate, 12000 * 256 */ + { 4096000, _FREQ_24_576K, 2, 0, 360, 9, 1 }, /* accurate, 16000 * 256 */ + { 6000000, _FREQ_24_576K, 4, 0, 410, 9, 1 }, + { 12000000, _FREQ_24_576K, 9, 0, 410, 9, 1 }, + { 13000000, _FREQ_24_576K, 8, 0, 340, 9, 1 }, + { 15360000, _FREQ_24_576K, 12, 0, 415, 9, 1 }, + { 16000000, _FREQ_24_576K, 12, 0, 400, 9, 1 }, + { 19200000, _FREQ_24_576K, 15, 0, 410, 9, 1 }, + { 19680000, _FREQ_24_576K, 15, 0, 400, 9, 1 }, + { 24000000, _FREQ_24_576K, 4, 0, 128,24, 0 }, // accurate, 24M -> 24.576M */ + + { 400000, _FREQ_22_579K, 0, 0, 566, 4, 1 }, + { 512000, _FREQ_22_579K, 0, 0, 880, 9, 1 }, + { 768000, _FREQ_22_579K, 0, 0, 587, 9, 1 }, + { 800000, _FREQ_22_579K, 0, 0, 567, 9, 1 }, + { 1024000, _FREQ_22_579K, 0, 0, 440, 9, 1 }, + { 1600000, _FREQ_22_579K, 1, 0, 567, 9, 1 }, + { 2048000, _FREQ_22_579K, 0, 0, 220, 9, 1 }, + { 3072000, _FREQ_22_579K, 0, 0, 148, 9, 1 }, + { 4096000, _FREQ_22_579K, 2, 0, 330, 9, 1 }, + { 6000000, _FREQ_22_579K, 2, 0, 227, 9, 1 }, + { 12000000, _FREQ_22_579K, 8, 0, 340, 9, 1 }, + { 13000000, _FREQ_22_579K, 9, 0, 350, 9, 1 }, + { 15360000, _FREQ_22_579K, 10, 0, 325, 9, 1 }, + { 16000000, _FREQ_22_579K, 11, 0, 340, 9, 1 }, + { 19200000, _FREQ_22_579K, 13, 0, 330, 9, 1 }, + { 19680000, _FREQ_22_579K, 14, 0, 345, 9, 1 }, + { 24000000, _FREQ_22_579K, 24, 0, 588,24, 0 }, // accurate, 24M -> 22.5792M */ + + + { _FREQ_24_576K / 1, _FREQ_24_576K, 9, 0, 200, 9, 1 }, //_FREQ_24_576K + { _FREQ_24_576K / 2, _FREQ_24_576K, 9, 0, 400, 9, 1 }, /*12288000,accurate, 48000 * 256 */ + { _FREQ_24_576K / 4, _FREQ_24_576K, 4, 0, 400, 9, 1 }, /*6144000, accurate, 24000 * 256 */ + { _FREQ_24_576K / 16, _FREQ_24_576K, 0, 0, 320, 9, 1 }, //1536000 + { _FREQ_24_576K / 64, _FREQ_24_576K, 0, 0, 640, 4, 1 }, //384000 + { _FREQ_24_576K / 96, _FREQ_24_576K, 0, 0, 960, 4, 1 }, //256000 + { _FREQ_24_576K / 128, _FREQ_24_576K, 0, 0, 512, 1, 1 }, //192000 + { _FREQ_24_576K / 176, _FREQ_24_576K, 0, 0, 880, 4, 0 }, //140000 + { _FREQ_24_576K / 192, _FREQ_24_576K, 0, 0, 960, 4, 0 }, //128000 + + { _FREQ_22_579K / 1, _FREQ_22_579K, 9, 0, 200, 9, 1 }, //_FREQ_22_579K + { _FREQ_22_579K / 2, _FREQ_22_579K, 9, 0, 400, 9, 1 }, /*11289600,accurate, 44100 * 256 */ + { _FREQ_22_579K / 4, _FREQ_22_579K, 4, 0, 400, 9, 1 }, /*5644800, accurate, 22050 * 256 */ + { _FREQ_22_579K / 16, _FREQ_22_579K, 0, 0, 320, 9, 1 }, //1411200 + { _FREQ_22_579K / 64, _FREQ_22_579K, 0, 0, 640, 4, 1 }, //352800 + { _FREQ_22_579K / 96, _FREQ_22_579K, 0, 0, 960, 4, 1 }, //235200 + { _FREQ_22_579K / 128, _FREQ_22_579K, 0, 0, 512, 1, 1 }, //176400 + { _FREQ_22_579K / 176, _FREQ_22_579K, 0, 0, 880, 4, 0 }, //128290 + { _FREQ_22_579K / 192, _FREQ_22_579K, 0, 0, 960, 4, 0 }, //117600 + + { _FREQ_22_579K / 6, _FREQ_22_579K, 2, 0, 360, 9, 1 }, //3763200 + { _FREQ_22_579K / 8, _FREQ_22_579K, 0, 0, 160, 9, 1 }, /*2822400, accurate, 11025 * 256 */ + { _FREQ_22_579K / 12, _FREQ_22_579K, 0, 0, 240, 9, 1 }, //1881600 + { _FREQ_22_579K / 24, _FREQ_22_579K, 0, 0, 480, 9, 1 }, //940800 + { _FREQ_22_579K / 32, _FREQ_22_579K, 0, 0, 640, 9, 1 }, //705600 + { _FREQ_22_579K / 48, _FREQ_22_579K, 0, 0, 960, 9, 1 }, //470400 +}; + + +/* AC108 definition */ +#define AC108_CHANNELS_MAX 8 /* range[1, 16] */ +#define AC108_RATES (SNDRV_PCM_RATE_8000_96000 & \ + ~(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)) +#define AC108_FORMATS (/*SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE |*/ \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const DECLARE_TLV_DB_SCALE(tlv_adc_pga_gain, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(tlv_ch_digital_vol, -11925,75,0); + +int ac10x_read(u8 reg, u8* rt_val, struct regmap* i2cm) { + int r, v = 0; + + if ((r = regmap_read(i2cm, reg, &v)) < 0) { + pr_err("ac10x_read error->[REG-0x%02x]\n", reg); + } else { + *rt_val = v; + } + return r; +} + +int ac10x_write(u8 reg, u8 val, struct regmap* i2cm) { + int r; + + if ((r = regmap_write(i2cm, reg, val)) < 0) { + pr_err("ac10x_write error->[REG-0x%02x,val-0x%02x]\n", reg, val); + } + return r; +} + +int ac10x_update_bits(u8 reg, u8 mask, u8 val, struct regmap* i2cm) { + int r; + + if ((r = regmap_update_bits(i2cm, reg, mask, val)) < 0) { + pr_err("%s() error->[REG-0x%02x,val-0x%02x]\n", __func__, reg, val); + } + return r; +} + +/** + * snd_ac108_get_volsw - single mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +static int snd_ac108_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol +){ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mask = (1 << fls(mc->max)) - 1; + unsigned int invert = mc->invert; + int ret, chip = mc->autodisable; + u8 val; + + if ((ret = ac10x_read(mc->reg, &val, ac10x->i2cmap[chip])) < 0) + return ret; + + val = ((val >> mc->shift) & mask) - mc->min; + if (invert) { + val = mc->max - val; + } + ucontrol->value.integer.value[0] = val; + return 0; +} + +/** + * snd_ac108_put_volsw - single mixer put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +static int snd_ac108_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol +){ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int sign_bit = mc->sign_bit; + unsigned int val, mask = (1 << fls(mc->max)) - 1; + unsigned int invert = mc->invert; + int ret, chip = mc->autodisable; + + if (sign_bit) + mask = BIT(sign_bit + 1) - 1; + + val = ((ucontrol->value.integer.value[0] + mc->min) & mask); + if (invert) { + val = mc->max - val; + } + + mask = mask << mc->shift; + val = val << mc->shift; + + ret = ac10x_update_bits(mc->reg, mask, val, ac10x->i2cmap[chip]); + return ret; +} + +#define SOC_AC108_SINGLE_TLV(xname, reg, shift, max, invert, chip, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, .get = snd_ac108_get_volsw,\ + .put = snd_ac108_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, chip) } + +/* single ac108 */ +static const struct snd_kcontrol_new ac108_snd_controls[] = { + /* ### chip 0 ### */ + /*0x70: ADC1 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH1 digital volume", ADC1_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x71: ADC2 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH2 digital volume", ADC2_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x72: ADC3 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH3 digital volume", ADC3_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x73: ADC4 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH4 digital volume", ADC4_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + + /*0x90: Analog PGA1 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC1 PGA gain", ANA_PGA1_CTRL, ADC1_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x91: Analog PGA2 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC2 PGA gain", ANA_PGA2_CTRL, ADC2_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x92: Analog PGA3 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC3 PGA gain", ANA_PGA3_CTRL, ADC3_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x93: Analog PGA4 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC4 PGA gain", ANA_PGA4_CTRL, ADC4_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), +}; +/* multiple ac108s */ +static const struct snd_kcontrol_new ac108tdm_snd_controls[] = { + /* ### chip 1 ### */ + /*0x70: ADC1 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH1 digital volume", ADC1_DVOL_CTRL, 0, 0xff, 0, 1, tlv_ch_digital_vol), + /*0x71: ADC2 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH2 digital volume", ADC2_DVOL_CTRL, 0, 0xff, 0, 1, tlv_ch_digital_vol), + /*0x72: ADC3 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH3 digital volume", ADC3_DVOL_CTRL, 0, 0xff, 0, 1, tlv_ch_digital_vol), + /*0x73: ADC4 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH4 digital volume", ADC4_DVOL_CTRL, 0, 0xff, 0, 1, tlv_ch_digital_vol), + + /*0x90: Analog PGA1 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC1 PGA gain", ANA_PGA1_CTRL, ADC1_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), + /*0x91: Analog PGA2 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC2 PGA gain", ANA_PGA2_CTRL, ADC2_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), + /*0x92: Analog PGA3 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC3 PGA gain", ANA_PGA3_CTRL, ADC3_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), + /*0x93: Analog PGA4 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC4 PGA gain", ANA_PGA4_CTRL, ADC4_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), + + /* ### chip 0 ### */ + /*0x70: ADC1 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH5 digital volume", ADC1_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x71: ADC2 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH6 digital volume", ADC2_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x72: ADC3 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH7 digital volume", ADC3_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x73: ADC4 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH8 digital volume", ADC4_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + + /*0x90: Analog PGA1 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC5 PGA gain", ANA_PGA1_CTRL, ADC1_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x91: Analog PGA2 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC6 PGA gain", ANA_PGA2_CTRL, ADC2_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x92: Analog PGA3 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC7 PGA gain", ANA_PGA3_CTRL, ADC3_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x93: Analog PGA4 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC8 PGA gain", ANA_PGA4_CTRL, ADC4_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), +}; + + +static const struct snd_soc_dapm_widget ac108_dapm_widgets[] = { + //input widgets + SND_SOC_DAPM_INPUT("MIC1P"), + SND_SOC_DAPM_INPUT("MIC1N"), + + SND_SOC_DAPM_INPUT("MIC2P"), + SND_SOC_DAPM_INPUT("MIC2N"), + + SND_SOC_DAPM_INPUT("MIC3P"), + SND_SOC_DAPM_INPUT("MIC3N"), + + SND_SOC_DAPM_INPUT("MIC4P"), + SND_SOC_DAPM_INPUT("MIC4N"), + + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + + /*0xa0: ADC1 Analog Control 1 Register*/ + /*0xa1-0xa6:use the defualt value*/ + SND_SOC_DAPM_AIF_IN("Channel 1 AAF", "Capture", 0, ANA_ADC1_CTRL1, ADC1_DSM_ENABLE, 1), + SND_SOC_DAPM_SUPPLY("Channel 1 EN", ANA_ADC1_CTRL1, ADC1_PGA_ENABLE, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC1BIAS", ANA_ADC1_CTRL1, ADC1_MICBIAS_EN, 1), + + /*0xa7: ADC2 Analog Control 1 Register*/ + /*0xa8-0xad:use the defualt value*/ + SND_SOC_DAPM_AIF_IN("Channel 2 AAF", "Capture", 0, ANA_ADC2_CTRL1, ADC2_DSM_ENABLE, 1), + SND_SOC_DAPM_SUPPLY("Channel 2 EN", ANA_ADC2_CTRL1, ADC2_PGA_ENABLE, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC2BIAS", ANA_ADC2_CTRL1, ADC2_MICBIAS_EN, 1), + + /*0xae: ADC3 Analog Control 1 Register*/ + /*0xaf-0xb4:use the defualt value*/ + SND_SOC_DAPM_AIF_IN("Channel 3 AAF", "Capture", 0, ANA_ADC3_CTRL1, ADC3_DSM_ENABLE, 1), + SND_SOC_DAPM_SUPPLY("Channel 3 EN", ANA_ADC3_CTRL1, ADC3_PGA_ENABLE, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC3BIAS", ANA_ADC3_CTRL1, ADC3_MICBIAS_EN, 1), + + /*0xb5: ADC4 Analog Control 1 Register*/ + /*0xb6-0xbb:use the defualt value*/ + SND_SOC_DAPM_AIF_IN("Channel 4 AAF", "Capture", 0, ANA_ADC4_CTRL1, ADC4_DSM_ENABLE, 1), + SND_SOC_DAPM_SUPPLY("Channel 4 EN", ANA_ADC4_CTRL1, ADC4_PGA_ENABLE, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC4BIAS", ANA_ADC4_CTRL1, ADC4_MICBIAS_EN, 1), + + + /*0x61: ADC Digital Part Enable Register*/ + SND_SOC_DAPM_SUPPLY("ADC EN", ADC_DIG_EN, 4, 1, NULL, 0), + SND_SOC_DAPM_ADC("ADC1", "Capture", ADC_DIG_EN, 0, 1), + SND_SOC_DAPM_ADC("ADC2", "Capture", ADC_DIG_EN, 1, 1), + SND_SOC_DAPM_ADC("ADC3", "Capture", ADC_DIG_EN, 2, 1), + SND_SOC_DAPM_ADC("ADC4", "Capture", ADC_DIG_EN, 3, 1), + + SND_SOC_DAPM_SUPPLY("ADC1 CLK", ANA_ADC4_CTRL7, ADC1_CLK_GATING, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC2 CLK", ANA_ADC4_CTRL7, ADC2_CLK_GATING, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC3 CLK", ANA_ADC4_CTRL7, ADC3_CLK_GATING, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC4 CLK", ANA_ADC4_CTRL7, ADC4_CLK_GATING, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DSM EN", ANA_ADC4_CTRL6, DSM_DEMOFF, 1, NULL, 0), + + /*0x62:Digital MIC Enable Register*/ + SND_SOC_DAPM_MICBIAS("DMIC1 enable", DMIC_EN, 0, 0), + SND_SOC_DAPM_MICBIAS("DMIC2 enable", DMIC_EN, 1, 0), +}; + +static const struct snd_soc_dapm_route ac108_dapm_routes[] = { + + { "ADC1", NULL, "Channel 1 AAF" }, + { "ADC2", NULL, "Channel 2 AAF" }, + { "ADC3", NULL, "Channel 3 AAF" }, + { "ADC4", NULL, "Channel 4 AAF" }, + + { "Channel 1 AAF", NULL, "MIC1BIAS" }, + { "Channel 2 AAF", NULL, "MIC2BIAS" }, + { "Channel 3 AAF", NULL, "MIC3BIAS" }, + { "Channel 4 AAF", NULL, "MIC4BIAS" }, + + { "MIC1BIAS", NULL, "ADC1 CLK" }, + { "MIC2BIAS", NULL, "ADC2 CLK" }, + { "MIC3BIAS", NULL, "ADC3 CLK" }, + { "MIC4BIAS", NULL, "ADC4 CLK" }, + + + { "ADC1 CLK", NULL, "DSM EN" }, + { "ADC2 CLK", NULL, "DSM EN" }, + { "ADC3 CLK", NULL, "DSM EN" }, + { "ADC4 CLK", NULL, "DSM EN" }, + + + { "DSM EN", NULL, "ADC EN" }, + + { "Channel 1 EN", NULL, "DSM EN" }, + { "Channel 2 EN", NULL, "DSM EN" }, + { "Channel 3 EN", NULL, "DSM EN" }, + { "Channel 4 EN", NULL, "DSM EN" }, + + + { "MIC1P", NULL, "Channel 1 EN" }, + { "MIC1N", NULL, "Channel 1 EN" }, + + { "MIC2P", NULL, "Channel 2 EN" }, + { "MIC2N", NULL, "Channel 2 EN" }, + + { "MIC3P", NULL, "Channel 3 EN" }, + { "MIC3N", NULL, "Channel 3 EN" }, + + { "MIC4P", NULL, "Channel 4 EN" }, + { "MIC4N", NULL, "Channel 4 EN" }, + +}; + +static int ac108_multi_write(u8 reg, u8 val, struct ac10x_priv *ac10x) { + u8 i; + for (i = 0; i < ac10x->codec_cnt; i++) { + ac10x_write(reg, val, ac10x->i2cmap[i]); + } + return 0; +} + +static int ac108_multi_update_bits(u8 reg, u8 mask, u8 val, struct ac10x_priv *ac10x) { + int r = 0; + u8 i; + + for (i = 0; i < ac10x->codec_cnt; i++) { + r |= ac10x_update_bits(reg, mask, val, ac10x->i2cmap[i]); + } + return r; +} + +static unsigned int ac108_codec_read(struct snd_soc_codec *codec, unsigned int reg) { + unsigned char val_r; + struct ac10x_priv *ac10x = dev_get_drvdata(codec->dev); + /*read one chip is fine*/ + ac10x_read(reg, &val_r, ac10x->i2cmap[_MASTER_INDEX]); + return val_r; +} + +static int ac108_codec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { + struct ac10x_priv *ac10x = dev_get_drvdata(codec->dev); + ac108_multi_write(reg, val, ac10x); + return 0; +} + +/** + * The Power management related registers are Reg01h~Reg09h + * 0x01-0x05,0x08,use the default value + * @author baozhu (17-6-21) + * + * @param ac10x + */ +static void ac108_configure_power(struct ac10x_priv *ac10x) { + /** + * 0x06:Enable Analog LDO + */ + ac108_multi_update_bits(PWR_CTRL6, 0x01 << LDO33ANA_ENABLE, 0x01 << LDO33ANA_ENABLE, ac10x); + /** + * 0x07: + * Control VREF output and micbias voltage ? + * REF faststart disable, enable Enable VREF (needed for Analog + * LDO and MICBIAS) + */ + ac108_multi_update_bits(PWR_CTRL7, 0x1f << VREF_SEL | 0x01 << VREF_FASTSTART_ENABLE | 0x01 << VREF_ENABLE, + 0x13 << VREF_SEL | 0x00 << VREF_FASTSTART_ENABLE | 0x01 << VREF_ENABLE, ac10x); + /** + * 0x09: + * Disable fast-start circuit on VREFP + * VREFP_RESCTRL=00=1 MOhm + * IGEN_TRIM=100=+25% + * Enable VREFP (needed by all audio input channels) + */ + ac108_multi_update_bits(PWR_CTRL9, 0x01 << VREFP_FASTSTART_ENABLE | 0x03 << VREFP_RESCTRL | 0x07 << IGEN_TRIM | 0x01 << VREFP_ENABLE, + 0x00 << VREFP_FASTSTART_ENABLE | 0x00 << VREFP_RESCTRL | 0x04 << IGEN_TRIM | 0x01 << VREFP_ENABLE, + ac10x); +} + +/** + * The clock management related registers are Reg20h~Reg25h + * The PLL management related registers are Reg10h~Reg18h. + * @author baozhu (17-6-20) + * + * @param ac10x + * @param rate : sample rate + * + * @return int : fail or success + */ +static int ac108_config_pll(struct ac10x_priv *ac10x, unsigned rate, unsigned lrck_ratio) { + unsigned int i = 0; + struct pll_div ac108_pll_div = { 0 }; + + if (ac10x->clk_id == SYSCLK_SRC_PLL) { + unsigned pll_src, pll_freq_in; + + if (lrck_ratio == 0) { + /* PLL clock source from MCLK */ + pll_freq_in = ac10x->sysclk; + pll_src = 0x0; + } else { + /* PLL clock source from BCLK */ + pll_freq_in = rate * lrck_ratio; + pll_src = 0x1; + } + + /* FOUT =(FIN * N) / [(M1+1) * (M2+1)*(K1+1)*(K2+1)] */ + for (i = 0; i < ARRAY_SIZE(ac108_pll_div_list); i++) { + if (ac108_pll_div_list[i].freq_in == pll_freq_in && ac108_pll_div_list[i].freq_out % rate == 0) { + ac108_pll_div = ac108_pll_div_list[i]; + dev_dbg(&ac10x->i2c[_MASTER_INDEX]->dev, "AC108 PLL freq_in match:%u, freq_out:%u\n\n", + ac108_pll_div.freq_in, ac108_pll_div.freq_out); + break; + } + } + /* 0x11,0x12,0x13,0x14: Config PLL DIV param M1/M2/N/K1/K2 */ + ac108_multi_update_bits(PLL_CTRL5, 0x1f << PLL_POSTDIV1 | 0x01 << PLL_POSTDIV2, + ac108_pll_div.k1 << PLL_POSTDIV1 | ac108_pll_div.k2 << PLL_POSTDIV2, ac10x); + ac108_multi_update_bits(PLL_CTRL4, 0xff << PLL_LOOPDIV_LSB, (unsigned char)ac108_pll_div.n << PLL_LOOPDIV_LSB, ac10x); + ac108_multi_update_bits(PLL_CTRL3, 0x03 << PLL_LOOPDIV_MSB, (ac108_pll_div.n >> 8) << PLL_LOOPDIV_MSB, ac10x); + ac108_multi_update_bits(PLL_CTRL2, 0x1f << PLL_PREDIV1 | 0x01 << PLL_PREDIV2, + ac108_pll_div.m1 << PLL_PREDIV1 | ac108_pll_div.m2 << PLL_PREDIV2, ac10x); + + /*0x18: PLL clk lock enable*/ + ac108_multi_update_bits(PLL_LOCK_CTRL, 0x1 << PLL_LOCK_EN, 0x1 << PLL_LOCK_EN, ac10x); + + /** + * 0x20: enable pll, pll source from mclk/bclk, sysclk source from pll, enable sysclk + */ + ac108_multi_update_bits(SYSCLK_CTRL, 0x01 << PLLCLK_EN | 0x03 << PLLCLK_SRC | 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, + 0x01 << PLLCLK_EN |pll_src<< PLLCLK_SRC | 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, ac10x); + ac10x->mclk = ac108_pll_div.freq_out; + } + if (ac10x->clk_id == SYSCLK_SRC_MCLK) { + /** + *0x20: sysclk source from mclk, enable sysclk + */ + ac108_multi_update_bits(SYSCLK_CTRL, 0x01 << PLLCLK_EN | 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, + 0x00 << PLLCLK_EN | 0x00 << SYSCLK_SRC | 0x01 << SYSCLK_EN, ac10x); + ac10x->mclk = ac10x->sysclk; + } + + return 0; +} + +/* + * support no more than 16 slots. + */ +static int ac108_multi_chips_slots(struct ac10x_priv *ac, int slots) { + int i; + + /* + * codec0 enable slots 2,3,0,1 when 1 codec + * + * codec0 enable slots 6,7,0,1 when 2 codec + * codec1 enable slots 2,3,4,5 + * + * ... + */ + for (i = 0; i < ac->codec_cnt; i++) { + /* rotate map, due to channels rotated by CPU_DAI */ + const unsigned vec_mask[] = { + 0x3 << 6 | 0x3, // slots 6,7,0,1 + 0xF << 2, // slots 2,3,4,5 + 0, + 0, + }; + const unsigned vec_maps[] = { + /* + * chip 0, + * mic 0 sample -> slot 6 + * mic 1 sample -> slot 7 + * mic 2 sample -> slot 0 + * mic 3 sample -> slot 1 + */ + 0x0 << 12 | 0x1 << 14 | 0x2 << 0 | 0x3 << 2, + /* + * chip 1, + * mic 0 sample -> slot 2 + * mic 1 sample -> slot 3 + * mic 2 sample -> slot 4 + * mic 3 sample -> slot 5 + */ + 0x0 << 4 | 0x1 << 6 | 0x2 << 8 | 0x3 << 10, + 0, + 0, + }; + unsigned vec; + + /* 0x38-0x3A I2S_TX1_CTRLx */ + if (ac->codec_cnt == 1) { + vec = 0xFUL; + } else { + vec = vec_mask[i]; + } + ac10x_write(I2S_TX1_CTRL1, slots - 1, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CTRL2, (vec >> 0) & 0xFF, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CTRL3, (vec >> 8) & 0xFF, ac->i2cmap[i]); + + /* 0x3C-0x3F I2S_TX1_CHMP_CTRLx */ + if (ac->codec_cnt == 1) { + vec = (0x2 << 0 | 0x3 << 2 | 0x0 << 4 | 0x1 << 6); + } else if (ac->codec_cnt == 2) { + vec = vec_maps[i]; + } + + ac10x_write(I2S_TX1_CHMP_CTRL1, (vec >> 0) & 0xFF, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CHMP_CTRL2, (vec >> 8) & 0xFF, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CHMP_CTRL3, (vec >> 16) & 0xFF, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CHMP_CTRL4, (vec >> 24) & 0xFF, ac->i2cmap[i]); + } + return 0; +} + +static int ac108_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { + unsigned int i, channels, samp_res, rate; + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + unsigned bclkdiv; + int ret = 0; + u8 v; + + dev_dbg(dai->dev, "%s() stream=%s play:%d capt:%d +++\n", __func__, + snd_pcm_stream_str(substream), + dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]); + + if (ac10x->i2c101) { + ret = ac101_hw_params(substream, params, dai); + if (ret > 0) { + dev_dbg(dai->dev, "%s() L%d returned\n", __func__, __LINE__); + /* not configure hw_param twice */ + return 0; + } + } + + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE && dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK]) + || (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai->stream_active[SNDRV_PCM_STREAM_CAPTURE])) { + /* not configure hw_param twice */ + /* return 0; */ + } + + channels = params_channels(params); + + /* Master mode, to clear cpu_dai fifos, output bclk without lrck */ + ac10x_read(I2S_CTRL, &v, ac10x->i2cmap[_MASTER_INDEX]); + if (v & (0x01 << BCLK_IOEN)) { + ac10x_update_bits(I2S_CTRL, 0x1 << LRCK_IOEN, 0x0 << LRCK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + samp_res = 0; + break; + case SNDRV_PCM_FORMAT_S16_LE: + samp_res = 2; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + samp_res = 3; + break; + case SNDRV_PCM_FORMAT_S24_LE: + samp_res = 4; + break; + case SNDRV_PCM_FORMAT_S32_LE: + samp_res = 6; + break; + default: + pr_err("AC108 don't supported the sample resolution: %u\n", params_format(params)); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(ac108_sample_rate); i++) { + if (ac108_sample_rate[i].real_val == params_rate(params) / (ac10x->data_protocol + 1UL)) { + rate = i; + break; + } + } + if (i >= ARRAY_SIZE(ac108_sample_rate)) { + return -EINVAL; + } + + if (channels == 8 && ac108_sample_rate[rate].real_val == 96000) { + /* 24.576M bit clock is not support by ac108 */ + return -EINVAL; + } + + dev_dbg(dai->dev, "rate: %d , channels: %d , samp_res: %d", + ac108_sample_rate[rate].real_val, + channels, + ac108_samp_res[samp_res].real_val); + + /** + * 0x33: + * The 8-Low bit of LRCK period value. It is used to program + * the number of BCLKs per channel of sample frame. This value + * is interpreted as follow: + * The 8-Low bit of LRCK period value. It is used to program + * the number of BCLKs per channel of sample frame. This value + * is interpreted as follow: PCM mode: Number of BCLKs within + * (Left + Right) channel width I2S / Left-Justified / + * Right-Justified mode: Number of BCLKs within each individual + * channel width (Left or Right) N+1 + * For example: + * n = 7: 8 BCLK width + * … + * n = 1023: 1024 BCLKs width + * 0X32[0:1]: + * The 2-High bit of LRCK period value. + */ + if (ac10x->i2s_mode != PCM_FORMAT) { + if (ac10x->data_protocol) { + ac108_multi_write(I2S_LRCK_CTRL2, ac108_samp_res[samp_res].real_val - 1, ac10x); + /*encoding mode, the max LRCK period value < 32,so the 2-High bit is zero*/ + ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, 0x00, ac10x); + } else { + /*TDM mode or normal mode*/ + //ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, 0x00, ac10x); + ac108_multi_write(I2S_LRCK_CTRL2, ac108_samp_res[samp_res].real_val - 1, ac10x); + ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, 0x00, ac10x); + } + } else { + unsigned div; + + /*TDM mode or normal mode*/ + div = ac108_samp_res[samp_res].real_val * channels - 1; + ac108_multi_write(I2S_LRCK_CTRL2, (div & 0xFF), ac10x); + ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, (div >> 8) << 0, ac10x); + } + + /** + * 0x35: + * TX Encoding mode will add 4bits to mark channel number + * TODO: need a chat to explain this + */ + ac108_multi_update_bits(I2S_FMT_CTRL2, 0x07 << SAMPLE_RESOLUTION | 0x07 << SLOT_WIDTH_SEL, + ac108_samp_res[samp_res].reg_val << SAMPLE_RESOLUTION + | ac108_samp_res[samp_res].reg_val << SLOT_WIDTH_SEL, ac10x); + + /** + * 0x60: + * ADC Sample Rate synchronised with I2S1 clock zone + */ + ac108_multi_update_bits(ADC_SPRC, 0x0f << ADC_FS_I2S1, ac108_sample_rate[rate].reg_val << ADC_FS_I2S1, ac10x); + ac108_multi_write(HPF_EN, 0x0F, ac10x); + + if (ac10x->i2c101 && _MASTER_MULTI_CODEC == _MASTER_AC101) { + ac108_config_pll(ac10x, ac108_sample_rate[rate].real_val, ac108_samp_res[samp_res].real_val * channels); + } else { + ac108_config_pll(ac10x, ac108_sample_rate[rate].real_val, 0); + } + + /* + * master mode only + */ + bclkdiv = ac10x->mclk / (ac108_sample_rate[rate].real_val * channels * ac108_samp_res[samp_res].real_val); + for (i = 0; i < ARRAY_SIZE(ac108_bclkdivs) - 1; i++) { + if (ac108_bclkdivs[i] >= bclkdiv) { + break; + } + } + ac108_multi_update_bits(I2S_BCLK_CTRL, 0x0F << BCLKDIV, i << BCLKDIV, ac10x); + + /* + * slots allocation for each chip + */ + ac108_multi_chips_slots(ac10x, channels); + + /*0x21: Module clock enable*/ + ac108_multi_write(MOD_CLK_EN, 1 << I2S | 1 << ADC_DIGITAL | 1 << MIC_OFFSET_CALIBRATION | 1 << ADC_ANALOG, ac10x); + /*0x22: Module reset de-asserted*/ + ac108_multi_write(MOD_RST_CTRL, 1 << I2S | 1 << ADC_DIGITAL | 1 << MIC_OFFSET_CALIBRATION | 1 << ADC_ANALOG, ac10x); + + ac108_multi_write(I2S_TX1_CHMP_CTRL1, 0xE4, ac10x); + ac108_multi_write(I2S_TX1_CHMP_CTRL2, 0xE4, ac10x); + ac108_multi_write(I2S_TX1_CHMP_CTRL3, 0xE4, ac10x); + ac108_multi_write(I2S_TX1_CHMP_CTRL4, 0xE4, ac10x); + + ac108_multi_write(I2S_TX2_CHMP_CTRL1, 0xE4, ac10x); + ac108_multi_write(I2S_TX2_CHMP_CTRL2, 0xE4, ac10x); + ac108_multi_write(I2S_TX2_CHMP_CTRL3, 0xE4, ac10x); + ac108_multi_write(I2S_TX2_CHMP_CTRL4, 0xE4, ac10x); + + dev_dbg(dai->dev, "%s() stream=%s ---\n", __func__, + snd_pcm_stream_str(substream)); + + return 0; +} + +static int ac108_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { + + struct ac10x_priv *ac10x = snd_soc_dai_get_drvdata(dai); + + freq = 24000000; + clk_id = SYSCLK_SRC_PLL; + + pr_info("%s :%d\n", __FUNCTION__, freq); + + switch (clk_id) { + case SYSCLK_SRC_MCLK: + ac108_multi_update_bits(SYSCLK_CTRL, 0x1 << SYSCLK_SRC, SYSCLK_SRC_MCLK << SYSCLK_SRC, ac10x); + break; + case SYSCLK_SRC_PLL: + ac108_multi_update_bits(SYSCLK_CTRL, 0x1 << SYSCLK_SRC, SYSCLK_SRC_PLL << SYSCLK_SRC, ac10x); + break; + default: + return -EINVAL; + } + ac10x->sysclk = freq; + ac10x->clk_id = clk_id; + + return 0; +} + +/** + * The i2s format management related registers are Reg + * 30h~Reg36h + * 33h,35h will be set in ac108_hw_params, It's BCLK width and + * Sample Resolution. + * @author baozhu (17-6-20) + * + * @param dai + * @param fmt + * + * @return int + */ +static int ac108_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { + unsigned char tx_offset, lrck_polarity, brck_polarity; + struct ac10x_priv *ac10x = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s\n", __FUNCTION__); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /*AC108 Master*/ + if (! ac10x->i2c101 || _MASTER_MULTI_CODEC == _MASTER_AC108) { + dev_dbg(dai->dev, "AC108 set to work as Master\n"); + /** + * 0x30:chip is master mode ,BCLK & LRCK output + */ + ac108_multi_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN | 0x03 << SDO1_EN | 0x1 << TXEN | 0x1 << GEN, + 0x00 << LRCK_IOEN | 0x03 << SDO1_EN | 0x1 << TXEN | 0x1 << GEN, ac10x); + /* multi_chips: only one chip set as Master, and the others also need to set as Slave */ + ac10x_update_bits(I2S_CTRL, 0x3 << LRCK_IOEN, 0x01 << BCLK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); + break; + } else { + /* TODO: Both cpu_dai and codec_dai(AC108) be set as slave in DTS */ + dev_dbg(dai->dev, "used as slave when AC101 is master\n"); + } + case SND_SOC_DAIFMT_CBS_CFS: /*AC108 Slave*/ + dev_dbg(dai->dev, "AC108 set to work as Slave\n"); + /** + * 0x30:chip is slave mode, BCLK & LRCK input,enable SDO1_EN and + * SDO2_EN, Transmitter Block Enable, Globe Enable + */ + ac108_multi_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN | 0x03 << SDO1_EN | 0x1 << TXEN | 0x1 << GEN, + 0x00 << LRCK_IOEN | 0x03 << SDO1_EN | 0x0 << TXEN | 0x0 << GEN, ac10x); + break; + default: + pr_err("AC108 Master/Slave mode config error:%u\n\n", (fmt & SND_SOC_DAIFMT_MASTER_MASK) >> 12); + return -EINVAL; + } + + /*AC108 config I2S/LJ/RJ/PCM format*/ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dev_dbg(dai->dev, "AC108 config I2S format\n"); + ac10x->i2s_mode = LEFT_JUSTIFIED_FORMAT; + tx_offset = 1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dev_dbg(dai->dev, "AC108 config RIGHT-JUSTIFIED format\n"); + ac10x->i2s_mode = RIGHT_JUSTIFIED_FORMAT; + tx_offset = 0; + break; + case SND_SOC_DAIFMT_LEFT_J: + dev_dbg(dai->dev, "AC108 config LEFT-JUSTIFIED format\n"); + ac10x->i2s_mode = LEFT_JUSTIFIED_FORMAT; + tx_offset = 0; + break; + case SND_SOC_DAIFMT_DSP_A: + dev_dbg(dai->dev, "AC108 config PCM-A format\n"); + ac10x->i2s_mode = PCM_FORMAT; + tx_offset = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + dev_dbg(dai->dev, "AC108 config PCM-B format\n"); + ac10x->i2s_mode = PCM_FORMAT; + tx_offset = 0; + break; + default: + pr_err("AC108 I2S format config error:%u\n\n", fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + /*AC108 config BCLK&LRCK polarity*/ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + dev_dbg(dai->dev, "AC108 config BCLK&LRCK polarity: BCLK_normal,LRCK_normal\n"); + brck_polarity = BCLK_NORMAL_DRIVE_N_SAMPLE_P; + lrck_polarity = LRCK_LEFT_HIGH_RIGHT_LOW; + break; + case SND_SOC_DAIFMT_NB_IF: + dev_dbg(dai->dev, "AC108 config BCLK&LRCK polarity: BCLK_normal,LRCK_invert\n"); + brck_polarity = BCLK_NORMAL_DRIVE_N_SAMPLE_P; + lrck_polarity = LRCK_LEFT_LOW_RIGHT_HIGH; + break; + case SND_SOC_DAIFMT_IB_NF: + dev_dbg(dai->dev, "AC108 config BCLK&LRCK polarity: BCLK_invert,LRCK_normal\n"); + brck_polarity = BCLK_INVERT_DRIVE_P_SAMPLE_N; + lrck_polarity = LRCK_LEFT_HIGH_RIGHT_LOW; + break; + case SND_SOC_DAIFMT_IB_IF: + dev_dbg(dai->dev, "AC108 config BCLK&LRCK polarity: BCLK_invert,LRCK_invert\n"); + brck_polarity = BCLK_INVERT_DRIVE_P_SAMPLE_N; + lrck_polarity = LRCK_LEFT_LOW_RIGHT_HIGH; + break; + default: + pr_err("AC108 config BCLK/LRCLK polarity error:%u\n\n", (fmt & SND_SOC_DAIFMT_INV_MASK) >> 8); + return -EINVAL; + } + + ac108_configure_power(ac10x); + + /** + *0x31: 0: normal mode, negative edge drive and positive edge sample + 1: invert mode, positive edge drive and negative edge sample + */ + ac108_multi_update_bits(I2S_BCLK_CTRL, 0x01 << BCLK_POLARITY, brck_polarity << BCLK_POLARITY, ac10x); + /** + * 0x32: same as 0x31 + */ + ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x01 << LRCK_POLARITY, lrck_polarity << LRCK_POLARITY, ac10x); + /** + * 0x34:Encoding Mode Selection,Mode + * Selection,data is offset by 1 BCLKs to LRCK + * normal mode for the last half cycle of BCLK in the slot ? + * turn to hi-z state (TDM) when not transferring slot ? + */ + ac108_multi_update_bits(I2S_FMT_CTRL1, 0x01 << ENCD_SEL | 0x03 << MODE_SEL | 0x01 << TX2_OFFSET | + 0x01 << TX1_OFFSET | 0x01 << TX_SLOT_HIZ | 0x01 << TX_STATE, + ac10x->data_protocol << ENCD_SEL | + ac10x->i2s_mode << MODE_SEL | + tx_offset << TX2_OFFSET | + tx_offset << TX1_OFFSET | + 0x00 << TX_SLOT_HIZ | + 0x01 << TX_STATE, ac10x); + + /** + * 0x60: + * MSB / LSB First Select: This driver only support MSB First Select . + * OUT2_MUTE,OUT1_MUTE shoule be set in widget. + * LRCK = 1 BCLK width + * Linear PCM + * + * TODO:pcm mode, bit[0:1] and bit[2] is special + */ + ac108_multi_update_bits(I2S_FMT_CTRL3, 0x01 << TX_MLS | 0x03 << SEXT | 0x01 << LRCK_WIDTH | 0x03 << TX_PDM, + 0x00 << TX_MLS | 0x03 << SEXT | 0x00 << LRCK_WIDTH | 0x00 << TX_PDM, ac10x); + + ac108_multi_write(HPF_EN, 0x00, ac10x); + + if (ac10x->i2c101) { + return ac101_set_dai_fmt(dai, fmt); + } + return 0; +} + +/* + * due to miss channels order in cpu_dai, we meed defer the clock starting. + */ +static int ac108_set_clock(int y_start_n_stop) { + u8 reg; + int ret = 0; + + dev_dbg(ac10x->codec->dev, "%s() L%d cmd:%d\n", __func__, __LINE__, y_start_n_stop); + + /* spin_lock move to machine trigger */ + + if (y_start_n_stop && ac10x->sysclk_en == 0) { + /* enable lrck clock */ + ac10x_read(I2S_CTRL, ®, ac10x->i2cmap[_MASTER_INDEX]); + if (reg & (0x01 << BCLK_IOEN)) { + ret = ret || ac10x_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN, 0x03 << LRCK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); + } + + /*0x10: PLL Common voltage enable, PLL enable */ + ret = ret || ac108_multi_update_bits(PLL_CTRL1, 0x01 << PLL_EN | 0x01 << PLL_COM_EN, + 0x01 << PLL_EN | 0x01 << PLL_COM_EN, ac10x); + /* enable global clock */ + ret = ret || ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, 0x1 << TXEN | 0x1 << GEN, ac10x); + + ac10x->sysclk_en = 1UL; + } else if (!y_start_n_stop && ac10x->sysclk_en != 0) { + /* disable global clock */ + ret = ret || ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, 0x0 << TXEN | 0x0 << GEN, ac10x); + + /*0x10: PLL Common voltage disable, PLL disable */ + ret = ret || ac108_multi_update_bits(PLL_CTRL1, 0x01 << PLL_EN | 0x01 << PLL_COM_EN, + 0x00 << PLL_EN | 0x00 << PLL_COM_EN, ac10x); + + /* disable lrck clock if it's enabled */ + ac10x_read(I2S_CTRL, ®, ac10x->i2cmap[_MASTER_INDEX]); + if (reg & (0x01 << LRCK_IOEN)) { + ret = ret || ac10x_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN, 0x01 << BCLK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); + } + if (!ret) { + ac10x->sysclk_en = 0UL; + } + } + + return ret; +} + +static int ac108_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 r; + + dev_dbg(dai->dev, "%s() stream=%s\n", + __func__, + snd_pcm_stream_str(substream)); + + if (ac10x->i2c101 && _MASTER_MULTI_CODEC == _MASTER_AC101) { + ac101_trigger(substream, SNDRV_PCM_TRIGGER_START, dai); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + return 0; + } + } + + ac10x_read(I2S_CTRL, &r, ac10x->i2cmap[_MASTER_INDEX]); + if ((r & (0x01 << BCLK_IOEN)) && (r & (0x01 << LRCK_IOEN)) == 0) { + /* disable global clock */ + ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, 0x0 << TXEN | 0x0 << GEN, ac10x); + } + + /* delayed clock starting, move to machine trigger() */ + ac108_set_clock(1); + + return 0; +} + +static int ac108_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + unsigned long flags; + int ret = 0; + u8 r; + + dev_dbg(dai->dev, "%s() stream=%s cmd=%d\n", + __FUNCTION__, + snd_pcm_stream_str(substream), + cmd); + + spin_lock_irqsave(&ac10x->lock, flags); + + if (ac10x->i2c101 && _MASTER_MULTI_CODEC == _MASTER_AC101) { + ac101_trigger(substream, cmd, dai); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + goto __ret; + } + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* disable global clock if lrck disabled */ + ac10x_read(I2S_CTRL, &r, ac10x->i2cmap[_MASTER_INDEX]); + if ((r & (0x01 << BCLK_IOEN)) && (r & (0x01 << LRCK_IOEN)) == 0) { + /* disable global clock */ + ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, 0x0 << TXEN | 0x0 << GEN, ac10x); + } + + /* delayed clock starting, move to machine trigger() */ + //msleep(10); + ac108_set_clock(1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + //msleep(10); + ac108_set_clock(0); + break; + default: + ret = -EINVAL; + } + +__ret: + spin_unlock_irqrestore(&ac10x->lock, flags); + + return ret; +} + +int ac108_audio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai +) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (ac10x->i2c101) { + return ac101_audio_startup(substream, dai); + } + return 0; +} + +void ac108_aif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai +) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + ac108_set_clock(0); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /*0x21: Module clock disable */ + ac108_multi_write(MOD_CLK_EN, 0x0, ac10x); + /*0x22: Module reset asserted */ + ac108_multi_write(MOD_RST_CTRL, 0x0, ac10x); + } + + if (ac10x->i2c101) { + ac101_aif_shutdown(substream, dai); + } +} + +int ac108_aif_mute(struct snd_soc_dai *dai, int mute, int stream) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (ac10x->i2c101) { + return ac101_aif_mute(dai, mute); + } + return 0; +} + +static const struct snd_soc_dai_ops ac108_dai_ops = { + .startup = ac108_audio_startup, + .shutdown = ac108_aif_shutdown, + + /*DAI clocking configuration*/ + .set_sysclk = ac108_set_sysclk, + + /*ALSA PCM audio operations*/ + .hw_params = ac108_hw_params, + .prepare = ac108_prepare, + /*.trigger = ac108_trigger, */ + .mute_stream = ac108_aif_mute, + + /*DAI format configuration*/ + .set_fmt = ac108_set_fmt, + + // .hw_free = ac108_hw_free, +}; + +static struct snd_soc_dai_driver ac108_dai0 = { + .name = "ac10x-codec0", + #if _USE_CAPTURE + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = AC108_CHANNELS_MAX, + .rates = AC108_RATES, + .formats = AC108_FORMATS, + }, + #endif + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = AC108_CHANNELS_MAX, + .rates = AC108_RATES, + .formats = AC108_FORMATS, + }, + .ops = &ac108_dai_ops, +}; + +static struct snd_soc_dai_driver ac108_dai1 = { + .name = "ac10x-codec1", + #if _USE_CAPTURE + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = AC108_CHANNELS_MAX, + .rates = AC108_RATES, + .formats = AC108_FORMATS, + }, + #endif + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = AC108_CHANNELS_MAX, + .rates = AC108_RATES, + .formats = AC108_FORMATS, + }, + .ops = &ac108_dai_ops, +}; + +static struct snd_soc_dai_driver *ac108_dai[] = { + &ac108_dai0, + &ac108_dai1, +}; + +static int ac108_add_widgets(struct snd_soc_codec *codec) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + const struct snd_kcontrol_new* snd_kcntl = ac108_snd_controls; + int ctrl_cnt = ARRAY_SIZE(ac108_snd_controls); + + /* only register controls correspond to exist chips */ + if (ac10x->tdm_chips_cnt >= 2) { + snd_kcntl = ac108tdm_snd_controls; + ctrl_cnt = ARRAY_SIZE(ac108tdm_snd_controls); + } + snd_soc_add_codec_controls(codec, snd_kcntl, ctrl_cnt); + + snd_soc_dapm_new_controls(dapm, ac108_dapm_widgets,ARRAY_SIZE(ac108_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, ac108_dapm_routes, ARRAY_SIZE(ac108_dapm_routes)); + + return 0; +} + +static int ac108_codec_probe(struct snd_soc_codec *codec) { + spin_lock_init(&ac10x->lock); + + ac10x->codec = codec; + dev_set_drvdata(codec->dev, ac10x); + ac108_add_widgets(codec); + + if (ac10x->i2c101) { + ac101_codec_probe(codec); + } + + return 0; +} + +static int ac108_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "AC108 level:%d\n", level); + + switch (level) { + case SND_SOC_BIAS_ON: + ac108_multi_update_bits(ANA_ADC1_CTRL1, 0x01 << ADC1_MICBIAS_EN, 0x01 << ADC1_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC2_CTRL1, 0x01 << ADC2_MICBIAS_EN, 0x01 << ADC2_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC3_CTRL1, 0x01 << ADC3_MICBIAS_EN, 0x01 << ADC3_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC4_CTRL1, 0x01 << ADC4_MICBIAS_EN, 0x01 << ADC4_MICBIAS_EN, ac10x); + break; + + case SND_SOC_BIAS_PREPARE: + /* Put the MICBIASes into regulating mode */ + break; + + case SND_SOC_BIAS_STANDBY: + break; + + case SND_SOC_BIAS_OFF: + ac108_multi_update_bits(ANA_ADC1_CTRL1, 0x01 << ADC1_MICBIAS_EN, 0x00 << ADC1_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC2_CTRL1, 0x01 << ADC2_MICBIAS_EN, 0x00 << ADC2_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC3_CTRL1, 0x01 << ADC3_MICBIAS_EN, 0x00 << ADC3_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC4_CTRL1, 0x01 << ADC4_MICBIAS_EN, 0x00 << ADC4_MICBIAS_EN, ac10x); + break; + } + + if (ac10x->i2c101) { + ac101_set_bias_level(codec, level); + } + return 0; +} + +int ac108_codec_remove(struct snd_soc_codec *codec) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (! ac10x->i2c101) { + return 0; + } + return ac101_codec_remove(codec); +} +#if __NO_SND_SOC_CODEC_DRV +void ac108_codec_remove_void(struct snd_soc_codec *codec) { + ac108_codec_remove(codec); +} +#define ac108_codec_remove ac108_codec_remove_void +#endif + +int ac108_codec_suspend(struct snd_soc_codec *codec) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int i; + + for (i = 0; i < ac10x->codec_cnt; i++) { + regcache_cache_only(ac10x->i2cmap[i], true); + } + + if (! ac10x->i2c101) { + return 0; + } + return ac101_codec_suspend(codec); +} + +int ac108_codec_resume(struct snd_soc_codec *codec) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int i, ret; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ac10x->codec_cnt; i++) { + regcache_cache_only(ac10x->i2cmap[i], false); + ret = regcache_sync(ac10x->i2cmap[i]); + if (ret != 0) { + dev_err(codec->dev, "Failed to sync i2cmap%d register cache: %d\n", i, ret); + regcache_cache_only(ac10x->i2cmap[i], true); + } + } + + if (! ac10x->i2c101) { + return 0; + } + return ac101_codec_resume(codec); +} + +static struct snd_soc_codec_driver ac10x_soc_codec_driver = { + .probe = ac108_codec_probe, + .remove = ac108_codec_remove, + .suspend = ac108_codec_suspend, + .resume = ac108_codec_resume, + .set_bias_level = ac108_set_bias_level, + .read = ac108_codec_read, + .write = ac108_codec_write, +}; + +static ssize_t ac108_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + int val = 0, flag = 0; + u8 i = 0, reg, num, value_w, value_r[4]; + + val = simple_strtol(buf, NULL, 16); + flag = (val >> 16) & 0xF; + + if (flag) { + reg = (val >> 8) & 0xFF; + value_w = val & 0xFF; + ac108_multi_write(reg, value_w, ac10x); + printk("Write 0x%02x to REG:0x%02x\n", value_w, reg); + } else { + int k; + + reg = (val >> 8) & 0xFF; + num = val & 0xff; + printk("\nRead: start REG:0x%02x,count:0x%02x\n", reg, num); + + for (k = 0; k < ac10x->codec_cnt; k++) { + regcache_cache_bypass(ac10x->i2cmap[k], true); + } + do { + + memset(value_r, 0, sizeof value_r); + + for (k = 0; k < ac10x->codec_cnt; k++) { + ac10x_read(reg, &value_r[k], ac10x->i2cmap[k]); + } + if (ac10x->codec_cnt >= 2) { + printk("REG[0x%02x]: 0x%02x 0x%02x", reg, value_r[0], value_r[1]); + } else { + printk("REG[0x%02x]: 0x%02x", reg, value_r[0]); + } + reg++; + + if ((++i == num) || (i % 4 == 0)) { + printk("\n"); + } + } while (i < num); + for (k = 0; k < ac10x->codec_cnt; k++) { + regcache_cache_bypass(ac10x->i2cmap[k], false); + } + } + + return count; +} + +static ssize_t ac108_show(struct device *dev, struct device_attribute *attr, char *buf) { +#if 1 + printk("echo flag|reg|val > ac108\n"); + printk("eg read star addres=0x06,count 0x10:echo 0610 >ac108\n"); + printk("eg write value:0xfe to address:0x06 :echo 106fe > ac108\n"); + return 0; +#else + return snprintf(buf, PAGE_SIZE,"echo flag|reg|val > ac108\n" + "eg read star addres=0x06,count 0x10:echo 0610 >ac108\n" + "eg write value:0xfe to address:0x06 :echo 106fe > ac108\n"); +#endif +} + +static DEVICE_ATTR(ac108, 0644, ac108_show, ac108_store); +static struct attribute *ac108_debug_attrs[] = { + &dev_attr_ac108.attr, + NULL, +}; +static struct attribute_group ac108_debug_attr_group = { + .name = "ac108_debug", + .attrs = ac108_debug_attrs, +}; + +static const struct regmap_config ac108_regmap = { + .reg_bits = 8, + .val_bits = 8, + .reg_stride = 1, + .max_register = 0xDF, + .cache_type = REGCACHE_FLAT, +}; +static int ac108_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *i2c_id) { + struct device_node *np = i2c->dev.of_node; + unsigned int val = 0; + int ret = 0, index; + + if (ac10x == NULL) { + ac10x = kzalloc(sizeof(struct ac10x_priv), GFP_KERNEL); + if (ac10x == NULL) { + dev_err(&i2c->dev, "Unable to allocate ac10x private data\n"); + return -ENOMEM; + } + } + + index = (int)i2c_id->driver_data; + if (index == AC101_I2C_ID) { + ac10x->i2c101 = i2c; + i2c_set_clientdata(i2c, ac10x); + ret = ac101_probe(i2c, i2c_id); + if (ret) { + ac10x->i2c101 = NULL; + return ret; + } + goto __ret; + } + + ret = of_property_read_u32(np, "data-protocol", &val); + if (ret) { + pr_err("Please set data-protocol.\n"); + return -EINVAL; + } + ac10x->data_protocol = val; + + if (of_property_read_u32(np, "tdm-chips-count", &val)) val = 1; + ac10x->tdm_chips_cnt = val; + + pr_err(" ac10x i2c_id number: %d\n", index); + pr_err(" ac10x data protocol: %d\n", ac10x->data_protocol); + + ac10x->i2c[index] = i2c; + ac10x->i2cmap[index] = devm_regmap_init_i2c(i2c, &ac108_regmap); + if (IS_ERR(ac10x->i2cmap[index])) { + ret = PTR_ERR(ac10x->i2cmap[index]); + dev_err(&i2c->dev, "Fail to initialize i2cmap%d I/O: %d\n", index, ret); + return ret; + } + + /* + * Writing this register with 0x12 + * will resets all register to their default state. + */ + regcache_cache_only(ac10x->i2cmap[index], false); + + ret = regmap_write(ac10x->i2cmap[index], CHIP_RST, CHIP_RST_VAL); + msleep(1); + + /* sync regcache for FLAT type */ + ac10x_fill_regcache(&i2c->dev, ac10x->i2cmap[index]); + + ac10x->codec_cnt++; + pr_err(" ac10x codec count : %d\n", ac10x->codec_cnt); + + ret = sysfs_create_group(&i2c->dev.kobj, &ac108_debug_attr_group); + if (ret) { + pr_err("failed to create attr group\n"); + } + +__ret: + /* It's time to bind codec to i2c[_MASTER_INDEX] when all i2c are ready */ + if ((ac10x->codec_cnt != 0 && ac10x->tdm_chips_cnt < 2) + || (ac10x->i2c[0] && ac10x->i2c[1] && ac10x->i2c101)) { + //seeed_voice_card_register_set_clock(SNDRV_PCM_STREAM_CAPTURE, ac108_set_clock); + /* no playback stream */ + if (! ac10x->i2c101) { + memset(&ac108_dai[_MASTER_INDEX]->playback, '\0', sizeof ac108_dai[_MASTER_INDEX]->playback); + } + ret = snd_soc_register_codec(&ac10x->i2c[_MASTER_INDEX]->dev, &ac10x_soc_codec_driver, + ac108_dai[_MASTER_INDEX], 1); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register ac10x codec: %d\n", ret); + } + } + return ret; +} + +static int ac108_i2c_remove(struct i2c_client *i2c) { + if (ac10x->codec != NULL) { + snd_soc_unregister_codec(&ac10x->i2c[_MASTER_INDEX]->dev); + ac10x->codec = NULL; + } + if (i2c == ac10x->i2c101) { + ac101_remove(ac10x->i2c101); + ac10x->i2c101 = NULL; + goto __ret; + } + + if (i2c == ac10x->i2c[0]) { + ac10x->i2c[0] = NULL; + } + if (i2c == ac10x->i2c[1]) { + ac10x->i2c[1] = NULL; + } + + sysfs_remove_group(&i2c->dev.kobj, &ac108_debug_attr_group); + +__ret: + if (!ac10x->i2c[0] && !ac10x->i2c[1] && !ac10x->i2c101) { + kfree(ac10x); + ac10x = NULL; + } + return 0; +} + +static const struct i2c_device_id ac108_i2c_id[] = { + { "ac108_0", 0 }, + { "ac108_1", 1 }, + { "ac108_2", 2 }, + { "ac108_3", 3 }, + { "ac101", AC101_I2C_ID }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ac108_i2c_id); + +static const struct of_device_id ac108_of_match[] = { + { .compatible = "x-power,ac108_0", }, + { .compatible = "x-power,ac108_1", }, + { .compatible = "x-power,ac108_2", }, + { .compatible = "x-power,ac108_3", }, + { .compatible = "x-power,ac101", }, + { } +}; +MODULE_DEVICE_TABLE(of, ac108_of_match); + +static struct i2c_driver ac108_i2c_driver = { + .driver = { + .name = "ac10x-codec", + .of_match_table = ac108_of_match, + }, + .probe = ac108_i2c_probe, + .remove = ac108_i2c_remove, + .id_table = ac108_i2c_id, +}; + +module_i2c_driver(ac108_i2c_driver); + +MODULE_DESCRIPTION("ASoC AC108 driver"); +MODULE_AUTHOR("Baozhu Zuo"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ac108.h b/sound/soc/codecs/ac108.h new file mode 100644 index 0000000..d40edfc --- /dev/null +++ b/sound/soc/codecs/ac108.h @@ -0,0 +1,774 @@ +/* + * ac108.h -- ac108 ALSA Soc Audio driver + * + * Author: panjunwen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _AC108_H +#define _AC108_H + + +/*** AC108 Codec Register Define***/ + +//Chip Reset +#define CHIP_RST 0x00 +#define CHIP_RST_VAL 0x12 + +//Power Control +#define PWR_CTRL1 0x01 +#define PWR_CTRL2 0x02 +#define PWR_CTRL3 0x03 +#define PWR_CTRL4 0x04 +#define PWR_CTRL5 0x05 +#define PWR_CTRL6 0x06 +#define PWR_CTRL7 0x07 +#define PWR_CTRL8 0x08 +#define PWR_CTRL9 0x09 + +//PLL Configure Control +#define PLL_CTRL1 0x10 +#define PLL_CTRL2 0x11 +#define PLL_CTRL3 0x12 +#define PLL_CTRL4 0x13 +#define PLL_CTRL5 0x14 +#define PLL_CTRL6 0x16 +#define PLL_CTRL7 0x17 +#define PLL_LOCK_CTRL 0x18 + +//System Clock Control +#define SYSCLK_CTRL 0x20 +#define MOD_CLK_EN 0x21 +#define MOD_RST_CTRL 0x22 +#define DSM_CLK_CTRL 0x25 + +//I2S Common Control +#define I2S_CTRL 0x30 +#define I2S_BCLK_CTRL 0x31 +#define I2S_LRCK_CTRL1 0x32 +#define I2S_LRCK_CTRL2 0x33 +#define I2S_FMT_CTRL1 0x34 +#define I2S_FMT_CTRL2 0x35 +#define I2S_FMT_CTRL3 0x36 + +//I2S TX1 Control +#define I2S_TX1_CTRL1 0x38 +#define I2S_TX1_CTRL2 0x39 +#define I2S_TX1_CTRL3 0x3A +#define I2S_TX1_CHMP_CTRL1 0x3C +#define I2S_TX1_CHMP_CTRL2 0x3D +#define I2S_TX1_CHMP_CTRL3 0x3E +#define I2S_TX1_CHMP_CTRL4 0x3F + +//I2S TX2 Control +#define I2S_TX2_CTRL1 0x40 +#define I2S_TX2_CTRL2 0x41 +#define I2S_TX2_CTRL3 0x42 +#define I2S_TX2_CHMP_CTRL1 0x44 +#define I2S_TX2_CHMP_CTRL2 0x45 +#define I2S_TX2_CHMP_CTRL3 0x46 +#define I2S_TX2_CHMP_CTRL4 0x47 + +//I2S RX1 Control +#define I2S_RX1_CTRL1 0x50 +#define I2S_RX1_CHMP_CTRL1 0x54 +#define I2S_RX1_CHMP_CTRL2 0x55 +#define I2S_RX1_CHMP_CTRL3 0x56 +#define I2S_RX1_CHMP_CTRL4 0x57 + +//I2S Loopback Debug +#define I2S_LPB_DEBUG 0x58 + +//ADC Common Control +#define ADC_SPRC 0x60 +#define ADC_DIG_EN 0x61 +#define DMIC_EN 0x62 +#define ADC_DSR 0x63 +#define ADC_FIR 0x64 +#define ADC_DDT_CTRL 0x65 + +//HPF Control +#define HPF_EN 0x66 +#define HPF_COEF_REGH1 0x67 +#define HPF_COEF_REGH2 0x68 +#define HPF_COEF_REGL1 0x69 +#define HPF_COEF_REGL2 0x6A +#define HPF_GAIN_REGH1 0x6B +#define HPF_GAIN_REGH2 0x6C +#define HPF_GAIN_REGL1 0x6D +#define HPF_GAIN_REGL2 0x6E + +//ADC Digital Channel Volume Control +#define ADC1_DVOL_CTRL 0x70 +#define ADC2_DVOL_CTRL 0x71 +#define ADC3_DVOL_CTRL 0x72 +#define ADC4_DVOL_CTRL 0x73 + +//ADC Digital Mixer Source and Gain Control +#define ADC1_DMIX_SRC 0x76 +#define ADC2_DMIX_SRC 0x77 +#define ADC3_DMIX_SRC 0x78 +#define ADC4_DMIX_SRC 0x79 + +//ADC Digital Debug Control +#define ADC_DIG_DEBUG 0x7F + +//I2S Pad Drive Control +#define I2S_DAT_PADDRV_CTRL 0x80 +#define I2S_CLK_PADDRV_CTRL 0x81 + +//Analog PGA Control +#define ANA_PGA1_CTRL 0x90 +#define ANA_PGA2_CTRL 0x91 +#define ANA_PGA3_CTRL 0x92 +#define ANA_PGA4_CTRL 0x93 + +//MIC Offset Control +#define MIC_OFFSET_CTRL1 0x96 +#define MIC_OFFSET_CTRL2 0x97 +#define MIC1_OFFSET_STATU1 0x98 +#define MIC1_OFFSET_STATU2 0x99 +#define MIC2_OFFSET_STATU1 0x9A +#define MIC2_OFFSET_STATU2 0x9B +#define MIC3_OFFSET_STATU1 0x9C +#define MIC3_OFFSET_STATU2 0x9D +#define MIC4_OFFSET_STATU1 0x9E +#define MIC4_OFFSET_STATU2 0x9F + +//ADC1 Analog Control +#define ANA_ADC1_CTRL1 0xA0 +#define ANA_ADC1_CTRL2 0xA1 +#define ANA_ADC1_CTRL3 0xA2 +#define ANA_ADC1_CTRL4 0xA3 +#define ANA_ADC1_CTRL5 0xA4 +#define ANA_ADC1_CTRL6 0xA5 +#define ANA_ADC1_CTRL7 0xA6 + +//ADC2 Analog Control +#define ANA_ADC2_CTRL1 0xA7 +#define ANA_ADC2_CTRL2 0xA8 +#define ANA_ADC2_CTRL3 0xA9 +#define ANA_ADC2_CTRL4 0xAA +#define ANA_ADC2_CTRL5 0xAB +#define ANA_ADC2_CTRL6 0xAC +#define ANA_ADC2_CTRL7 0xAD + +//ADC3 Analog Control +#define ANA_ADC3_CTRL1 0xAE +#define ANA_ADC3_CTRL2 0xAF +#define ANA_ADC3_CTRL3 0xB0 +#define ANA_ADC3_CTRL4 0xB1 +#define ANA_ADC3_CTRL5 0xB2 +#define ANA_ADC3_CTRL6 0xB3 +#define ANA_ADC3_CTRL7 0xB4 + +//ADC4 Analog Control +#define ANA_ADC4_CTRL1 0xB5 +#define ANA_ADC4_CTRL2 0xB6 +#define ANA_ADC4_CTRL3 0xB7 +#define ANA_ADC4_CTRL4 0xB8 +#define ANA_ADC4_CTRL5 0xB9 +#define ANA_ADC4_CTRL6 0xBA +#define ANA_ADC4_CTRL7 0xBB + +//GPIO Configure +#define GPIO_CFG1 0xC0 +#define GPIO_CFG2 0xC1 +#define GPIO_DAT 0xC2 +#define GPIO_DRV 0xC3 +#define GPIO_PULL 0xC4 +#define GPIO_INT_CFG 0xC5 +#define GPIO_INT_EN 0xC6 +#define GPIO_INT_STATUS 0xC7 + +//Misc +#define BGTC_DAT 0xD1 +#define BGVC_DAT 0xD2 +#define PRNG_CLK_CTRL 0xDF + + + +/*** AC108 Codec Register Bit Define***/ + +/*PWR_CTRL1*/ +#define CP12_CTRL 4 +#define CP12_SENSE_SELECT 3 + +/*PWR_CTRL2*/ +#define CP12_SENSE_FILT 6 +#define CP12_COMP_FF_EN 3 +#define CP12_FORCE_ENABLE 2 +#define CP12_FORCE_RSTB 1 + +/*PWR_CTRL3*/ +#define LDO33DIG_CTRL 0 + +/*PWR_CTRL6*/ +#define LDO33ANA_2XHDRM 2 +#define LDO33ANA_ENABLE 0 + +/*PWR_CTRL7*/ +#define VREF_SEL 3 +#define VREF_FASTSTART_ENABLE 1 +#define VREF_ENABLE 0 + +/*PWR_CTRL9*/ +#define VREFP_FASTSTART_ENABLE 7 +#define VREFP_RESCTRL 5 +#define VREFP_LPMODE 4 +#define IGEN_TRIM 1 +#define VREFP_ENABLE 0 + + +/*PLL_CTRL1*/ +#define PLL_IBIAS 4 +#define PLL_NDET 3 +#define PLL_LOCKED_STATUS 2 +#define PLL_COM_EN 1 +#define PLL_EN 0 + +/*PLL_CTRL2*/ +#define PLL_PREDIV2 5 +#define PLL_PREDIV1 0 + +/*PLL_CTRL3*/ +#define PLL_LOOPDIV_MSB 0 + +/*PLL_CTRL4*/ +#define PLL_LOOPDIV_LSB 0 + +/*PLL_CTRL5*/ +#define PLL_POSTDIV2 5 +#define PLL_POSTDIV1 0 + +/*PLL_CTRL6*/ +#define PLL_LDO 6 +#define PLL_CP 0 + +/*PLL_CTRL7*/ +#define PLL_CAP 6 +#define PLL_RES 4 +#define PLL_TEST_EN 0 + +/*PLL_LOCK_CTRL*/ +#define LOCK_LEVEL1 2 +#define LOCK_LEVEL2 1 +#define PLL_LOCK_EN 0 + + +/*SYSCLK_CTRL*/ +#define PLLCLK_EN 7 +#define PLLCLK_SRC 4 +#define SYSCLK_SRC 3 +#define SYSCLK_EN 0 + +/*MOD_CLK_EN & MOD_RST_CTRL*/ +#define I2S 7 +#define ADC_DIGITAL 4 +#define MIC_OFFSET_CALIBRATION 1 +#define ADC_ANALOG 0 + +/*DSM_CLK_CTRL*/ +#define MIC_OFFSET_DIV 4 +#define DSM_CLK_SEL 0 + + +/*I2S_CTRL*/ +#define BCLK_IOEN 7 +#define LRCK_IOEN 6 +#define SDO2_EN 5 +#define SDO1_EN 4 +#define TXEN 2 +#define RXEN 1 +#define GEN 0 + +/*I2S_BCLK_CTRL*/ +#define EDGE_TRANSFER 5 +#define BCLK_POLARITY 4 +#define BCLKDIV 0 + +/*I2S_LRCK_CTRL1*/ +#define LRCK_POLARITY 4 +#define LRCK_PERIODH 0 + +/*I2S_LRCK_CTRL2*/ +#define LRCK_PERIODL 0 + +/*I2S_FMT_CTRL1*/ +#define ENCD_SEL 6 +#define MODE_SEL 4 +#define TX2_OFFSET 3 +#define TX1_OFFSET 2 +#define TX_SLOT_HIZ 1 +#define TX_STATE 0 + +/*I2S_FMT_CTRL2*/ +#define SLOT_WIDTH_SEL 4 +#define SAMPLE_RESOLUTION 0 + +/*I2S_FMT_CTRL3*/ +#define TX_MLS 7 +#define SEXT 5 +#define OUT2_MUTE 4 +#define OUT1_MUTE 3 +#define LRCK_WIDTH 2 +#define TX_PDM 0 + + +/*I2S_TX1_CTRL1*/ +#define TX1_CHSEL 0 + +/*I2S_TX1_CTRL2*/ +#define TX1_CH8_EN 7 +#define TX1_CH7_EN 6 +#define TX1_CH6_EN 5 +#define TX1_CH5_EN 4 +#define TX1_CH4_EN 3 +#define TX1_CH3_EN 2 +#define TX1_CH2_EN 1 +#define TX1_CH1_EN 0 + +/*I2S_TX1_CTRL3*/ +#define TX1_CH16_EN 7 +#define TX1_CH15_EN 6 +#define TX1_CH14_EN 5 +#define TX1_CH13_EN 4 +#define TX1_CH12_EN 3 +#define TX1_CH11_EN 2 +#define TX1_CH10_EN 1 +#define TX1_CH9_EN 0 + +/*I2S_TX1_CHMP_CTRL1*/ +#define TX1_CH4_MAP 6 +#define TX1_CH3_MAP 4 +#define TX1_CH2_MAP 2 +#define TX1_CH1_MAP 0 + +/*I2S_TX1_CHMP_CTRL2*/ +#define TX1_CH8_MAP 6 +#define TX1_CH7_MAP 4 +#define TX1_CH6_MAP 2 +#define TX1_CH5_MAP 0 + +/*I2S_TX1_CHMP_CTRL3*/ +#define TX1_CH12_MAP 6 +#define TX1_CH11_MAP 4 +#define TX1_CH10_MAP 2 +#define TX1_CH9_MAP 0 + +/*I2S_TX1_CHMP_CTRL4*/ +#define TX1_CH16_MAP 6 +#define TX1_CH15_MAP 4 +#define TX1_CH14_MAP 2 +#define TX1_CH13_MAP 0 + + +/*I2S_TX2_CTRL1*/ +#define TX2_CHSEL 0 + +/*I2S_TX2_CHMP_CTRL1*/ +#define TX2_CH4_MAP 6 +#define TX2_CH3_MAP 4 +#define TX2_CH2_MAP 2 +#define TX2_CH1_MAP 0 + +/*I2S_TX2_CHMP_CTRL2*/ +#define TX2_CH8_MAP 6 +#define TX2_CH7_MAP 4 +#define TX2_CH6_MAP 2 +#define TX2_CH5_MAP 0 + +/*I2S_TX2_CHMP_CTRL3*/ +#define TX2_CH12_MAP 6 +#define TX2_CH11_MAP 4 +#define TX2_CH10_MAP 2 +#define TX2_CH9_MAP 0 + +/*I2S_TX2_CHMP_CTRL4*/ +#define TX2_CH16_MAP 6 +#define TX2_CH15_MAP 4 +#define TX2_CH14_MAP 2 +#define TX2_CH13_MAP 0 + + +/*I2S_RX1_CTRL1*/ +#define RX1_CHSEL 0 + +/*I2S_RX1_CHMP_CTRL1*/ +#define RX1_CH4_MAP 6 +#define RX1_CH3_MAP 4 +#define RX1_CH2_MAP 2 +#define RX1_CH1_MAP 0 + +/*I2S_RX1_CHMP_CTRL2*/ +#define RX1_CH8_MAP 6 +#define RX1_CH7_MAP 4 +#define RX1_CH6_MAP 2 +#define RX1_CH5_MAP 0 + +/*I2S_RX1_CHMP_CTRL3*/ +#define RX1_CH12_MAP 6 +#define RX1_CH11_MAP 4 +#define RX1_CH10_MAP 2 +#define RX1_CH9_MAP 0 + +/*I2S_RX1_CHMP_CTRL4*/ +#define RX1_CH16_MAP 6 +#define RX1_CH15_MAP 4 +#define RX1_CH14_MAP 2 +#define RX1_CH13_MAP 0 + + +/*I2S_LPB_DEBUG*/ +#define I2S_LPB_DEBUG_EN 0 + + +/*ADC_SPRC*/ +#define ADC_FS_I2S1 0 + +/*ADC_DIG_EN*/ +#define DG_EN 4 +#define ENAD4 3 +#define ENAD3 2 +#define ENAD2 1 +#define ENAD1 0 + +/*DMIC_EN*/ +#define DMIC2_EN 1 +#define DMIC1_EN 0 + +/*ADC_DSR*/ +#define DIG_ADC4_SRS 6 +#define DIG_ADC3_SRS 4 +#define DIG_ADC2_SRS 2 +#define DIG_ADC1_SRS 0 + +/*ADC_DDT_CTRL*/ +#define ADOUT_DLY_EN 2 +#define ADOUT_DTS 0 + + +/*HPF_EN*/ +#define DIG_ADC4_HPF_EN 3 +#define DIG_ADC3_HPF_EN 2 +#define DIG_ADC2_HPF_EN 1 +#define DIG_ADC1_HPF_EN 0 + + +/*ADC1_DMIX_SRC*/ +#define ADC1_ADC4_DMXL_GC 7 +#define ADC1_ADC3_DMXL_GC 6 +#define ADC1_ADC2_DMXL_GC 5 +#define ADC1_ADC1_DMXL_GC 4 +#define ADC1_ADC4_DMXL_SRC 3 +#define ADC1_ADC3_DMXL_SRC 2 +#define ADC1_ADC2_DMXL_SRC 1 +#define ADC1_ADC1_DMXL_SRC 0 + +/*ADC2_DMIX_SRC*/ +#define ADC2_ADC4_DMXL_GC 7 +#define ADC2_ADC3_DMXL_GC 6 +#define ADC2_ADC2_DMXL_GC 5 +#define ADC2_ADC1_DMXL_GC 4 +#define ADC2_ADC4_DMXL_SRC 3 +#define ADC2_ADC3_DMXL_SRC 2 +#define ADC2_ADC2_DMXL_SRC 1 +#define ADC2_ADC1_DMXL_SRC 0 + +/*ADC3_DMIX_SRC*/ +#define ADC3_ADC4_DMXL_GC 7 +#define ADC3_ADC3_DMXL_GC 6 +#define ADC3_ADC2_DMXL_GC 5 +#define ADC3_ADC1_DMXL_GC 4 +#define ADC3_ADC4_DMXL_SRC 3 +#define ADC3_ADC3_DMXL_SRC 2 +#define ADC3_ADC2_DMXL_SRC 1 +#define ADC3_ADC1_DMXL_SRC 0 + +/*ADC4_DMIX_SRC*/ +#define ADC4_ADC4_DMXL_GC 7 +#define ADC4_ADC3_DMXL_GC 6 +#define ADC4_ADC2_DMXL_GC 5 +#define ADC4_ADC1_DMXL_GC 4 +#define ADC4_ADC4_DMXL_SRC 3 +#define ADC4_ADC3_DMXL_SRC 2 +#define ADC4_ADC2_DMXL_SRC 1 +#define ADC4_ADC1_DMXL_SRC 0 + + +/*ADC_DIG_DEBUG*/ +#define ADC_PTN_SEL 0 + + +/*I2S_DAT_PADDRV_CTRL*/ +#define TX2_DAT_DRV 4 +#define TX1_DAT_DRV 0 + +/*I2S_CLK_PADDRV_CTRL*/ +#define LRCK_DRV 4 +#define BCLK_DRV 0 + + +/*ANA_PGA1_CTRL*/ +#define ADC1_ANALOG_PGA 1 +#define ADC1_ANALOG_PGA_STEP 0 + +/*ANA_PGA2_CTRL*/ +#define ADC2_ANALOG_PGA 1 +#define ADC2_ANALOG_PGA_STEP 0 + +/*ANA_PGA3_CTRL*/ +#define ADC3_ANALOG_PGA 1 +#define ADC3_ANALOG_PGA_STEP 0 + +/*ANA_PGA4_CTRL*/ +#define ADC4_ANALOG_PGA 1 +#define ADC4_ANALOG_PGA_STEP 0 + + +/*MIC_OFFSET_CTRL1*/ +#define MIC_OFFSET_CAL_EN4 3 +#define MIC_OFFSET_CAL_EN3 2 +#define MIC_OFFSET_CAL_EN2 1 +#define MIC_OFFSET_CAL_EN1 0 + +/*MIC_OFFSET_CTRL2*/ +#define MIC_OFFSET_CAL_GAIN 3 +#define MIC_OFFSET_CAL_CHANNEL 1 +#define MIC_OFFSET_CAL_EN_ONCE 0 + +/*MIC1_OFFSET_STATU1*/ +#define MIC1_OFFSET_CAL_DONE 7 +#define MIC1_OFFSET_CAL_RUN_STA 6 +#define MIC1_OFFSET_MSB 0 + +/*MIC1_OFFSET_STATU2*/ +#define MIC1_OFFSET_LSB 0 + +/*MIC2_OFFSET_STATU1*/ +#define MIC2_OFFSET_CAL_DONE 7 +#define MIC2_OFFSET_CAL_RUN_STA 6 +#define MIC2_OFFSET_MSB 0 + +/*MIC2_OFFSET_STATU2*/ +#define MIC2_OFFSET_LSB 0 + +/*MIC3_OFFSET_STATU1*/ +#define MIC3_OFFSET_CAL_DONE 7 +#define MIC3_OFFSET_CAL_RUN_STA 6 +#define MIC3_OFFSET_MSB 0 + +/*MIC3_OFFSET_STATU2*/ +#define MIC3_OFFSET_LSB 0 + +/*MIC4_OFFSET_STATU1*/ +#define MIC4_OFFSET_CAL_DONE 7 +#define MIC4_OFFSET_CAL_RUN_STA 6 +#define MIC4_OFFSET_MSB 0 + +/*MIC4_OFFSET_STATU2*/ +#define MIC4_OFFSET_LSB 0 + + +/*ANA_ADC1_CTRL1*/ +#define ADC1_PGA_BYPASS 7 +#define ADC1_PGA_BYP_RCM 6 +#define ADC1_PGA_CTRL_RCM 4 +#define ADC1_PGA_MUTE 3 +#define ADC1_DSM_ENABLE 2 +#define ADC1_PGA_ENABLE 1 +#define ADC1_MICBIAS_EN 0 + +/*ANA_ADC1_CTRL3*/ +#define ADC1_ANA_CAL_EN 5 +#define ADC1_SEL_OUT_EDGE 3 +#define ADC1_DSM_DISABLE 2 +#define ADC1_VREFP_DISABLE 1 +#define ADC1_AAF_DISABLE 0 + +/*ANA_ADC1_CTRL6*/ +#define PGA_CTRL_TC 6 +#define PGA_CTRL_RC 4 +#define PGA_CTRL_I_LIN 2 +#define PGA_CTRL_I_IN 0 + +/*ANA_ADC1_CTRL7*/ +#define PGA_CTRL_HI_Z 7 +#define PGA_CTRL_SHORT_RF 6 +#define PGA_CTRL_VCM_VG 4 +#define PGA_CTRL_VCM_IN 0 + + +/*ANA_ADC2_CTRL1*/ +#define ADC2_PGA_BYPASS 7 +#define ADC2_PGA_BYP_RCM 6 +#define ADC2_PGA_CTRL_RCM 4 +#define ADC2_PGA_MUTE 3 +#define ADC2_DSM_ENABLE 2 +#define ADC2_PGA_ENABLE 1 +#define ADC2_MICBIAS_EN 0 + +/*ANA_ADC2_CTRL3*/ +#define ADC2_ANA_CAL_EN 5 +#define ADC2_SEL_OUT_EDGE 3 +#define ADC2_DSM_DISABLE 2 +#define ADC2_VREFP_DISABLE 1 +#define ADC2_AAF_DISABLE 0 + +/*ANA_ADC2_CTRL6*/ +#define PGA_CTRL_IBOOST 7 +#define PGA_CTRL_IQCTRL 6 +#define PGA_CTRL_OABIAS 4 +#define PGA_CTRL_CMLP_DIS 3 +#define PGA_CTRL_PDB_RIN 2 +#define PGA_CTRL_PEAKDET 0 + +/*ANA_ADC2_CTRL7*/ +#define AAF_LPMODE_EN 7 +#define AAF_STG2_IB_SEL 4 +#define AAFDSM_IB_DIV2 3 +#define AAF_STG1_IB_SEL 0 + + +/*ANA_ADC3_CTRL1*/ +#define ADC3_PGA_BYPASS 7 +#define ADC3_PGA_BYP_RCM 6 +#define ADC3_PGA_CTRL_RCM 4 +#define ADC3_PGA_MUTE 3 +#define ADC3_DSM_ENABLE 2 +#define ADC3_PGA_ENABLE 1 +#define ADC3_MICBIAS_EN 0 + +/*ANA_ADC3_CTRL3*/ +#define ADC3_ANA_CAL_EN 5 +#define ADC3_INVERT_CLK 4 +#define ADC3_SEL_OUT_EDGE 3 +#define ADC3_DSM_DISABLE 2 +#define ADC3_VREFP_DISABLE 1 +#define ADC3_AAF_DISABLE 0 + +/*ANA_ADC3_CTRL7*/ +#define DSM_COMP_IB_SEL 6 +#define DSM_OTA_CTRL 4 +#define DSM_LPMODE 3 +#define DSM_OTA_IB_SEL 0 + + +/*ANA_ADC4_CTRL1*/ +#define ADC4_PGA_BYPASS 7 +#define ADC4_PGA_BYP_RCM 6 +#define ADC4_PGA_CTRL_RCM 4 +#define ADC4_PGA_MUTE 3 +#define ADC4_DSM_ENABLE 2 +#define ADC4_PGA_ENABLE 1 +#define ADC4_MICBIAS_EN 0 + +/*ANA_ADC4_CTRL3*/ +#define ADC4_ANA_CAL_EN 5 +#define ADC4_SEL_OUT_EDGE 3 +#define ADC4_DSM_DISABLE 2 +#define ADC4_VREFP_DISABLE 1 +#define ADC4_AAF_DISABLE 0 + +/*ANA_ADC4_CTRL6*/ +#define DSM_DEMOFF 5 +#define DSM_EN_DITHER 4 +#define DSM_VREFP_LPMODE 2 +#define DSM_VREFP_OUTCTRL 0 + +/*ANA_ADC4_CTRL7*/ +#define CK8M_EN 5 +#define OSC_EN 4 +#define ADC4_CLK_GATING 3 +#define ADC3_CLK_GATING 2 +#define ADC2_CLK_GATING 1 +#define ADC1_CLK_GATING 0 + + +/*GPIO_CFG1*/ +#define GPIO2_SELECT 4 +#define GPIO1_SELECT 0 + +/*GPIO_CFG2*/ +#define GPIO4_SELECT 4 +#define GPIO3_SELECT 0 + +/*GPIO_DAT*///order??? +#define GPIO4_DAT 3 +#define GPIO3_DAT 2 +#define GPIO2_DAT 1 +#define GPIO1_DAT 0 + +/*GPIO_DRV*/ +#define GPIO4_DRV 6 +#define GPIO3_DRV 4 +#define GPIO2_DRV 2 +#define GPIO1_DRV 0 + +/*GPIO_PULL*/ +#define GPIO4_PULL 6 +#define GPIO3_PULL 4 +#define GPIO2_PULL 2 +#define GPIO1_PULL 0 + +/*GPIO_INT_CFG*/ +#define GPIO4_EINT_CFG 6 +#define GPIO3_EINT_CFG 4 +#define GPIO2_EINT_CFG 2 +#define GPIO1_EINT_CFG 0 + +/*GPIO_INT_EN*///order??? +#define GPIO4_EINT_EN 3 +#define GPIO3_EINT_EN 2 +#define GPIO2_EINT_EN 1 +#define GPIO1_EINT_EN 0 + +/*GPIO_INT_STATUS*///order??? +#define GPIO4_EINT_STA 3 +#define GPIO3_EINT_STA 2 +#define GPIO2_EINT_STA 1 +#define GPIO1_EINT_STA 0 + + +/*PRNG_CLK_CTRL*/ +#define PRNG_CLK_EN 1 +#define PRNG_CLK_POS 0 + + + +/*** Some Config Value ***/ + +//[SYSCLK_CTRL]: PLLCLK_SRC +#define PLLCLK_SRC_MCLK 0 +#define PLLCLK_SRC_BCLK 1 +#define PLLCLK_SRC_GPIO2 2 +#define PLLCLK_SRC_GPIO3 3 + +//[SYSCLK_CTRL]: SYSCLK_SRC +#define SYSCLK_SRC_MCLK 0 +#define SYSCLK_SRC_PLL 1 + +//I2S BCLK POLARITY Control +#define BCLK_NORMAL_DRIVE_N_SAMPLE_P 0 +#define BCLK_INVERT_DRIVE_P_SAMPLE_N 1 + +//I2S LRCK POLARITY Control +#define LRCK_LEFT_LOW_RIGHT_HIGH 0 +#define LRCK_LEFT_HIGH_RIGHT_LOW 1 + +//I2S Format Selection +#define PCM_FORMAT 0 +#define LEFT_JUSTIFIED_FORMAT 1 +#define RIGHT_JUSTIFIED_FORMAT 2 + + +//I2S data protocol types + +#define IS_ENCODING_MODE 0 + +#endif + diff --git a/sound/soc/codecs/ac10x.h b/sound/soc/codecs/ac10x.h new file mode 100644 index 0000000..d8ad442 --- /dev/null +++ b/sound/soc/codecs/ac10x.h @@ -0,0 +1,152 @@ +/* + * ac10x.h + * + * (C) Copyright 2017-2018 + * Seeed Technology Co., Ltd. + * + * PeterYang + * + * (C) Copyright 2010-2017 + * Reuuimlla Technology Co., Ltd. + * huangxin + * + * 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. + * + */ +#ifndef __AC10X_H__ +#define __AC10X_H__ + +#define AC101_I2C_ID 4 +#define _MASTER_AC108 0 +#define _MASTER_AC101 1 +#define _MASTER_MULTI_CODEC _MASTER_AC101 + +/* enable headset detecting & headset button pressing */ +#define CONFIG_AC101_SWITCH_DETECT + +/* obsolete */ +#define CONFIG_AC10X_TRIG_LOCK 0 + +#ifdef AC101_DEBG + #define AC101_DBG(format,args...) printk("[AC101] %s() L%d " format, __func__, __LINE__, ##args) +#else + #define AC101_DBG(...) +#endif + +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0) +#define __NO_SND_SOC_CODEC_DRV 1 +#else +#define __NO_SND_SOC_CODEC_DRV 0 +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) +#if __has_attribute(__fallthrough__) +# define fallthrough __attribute__((__fallthrough__)) +#else +# define fallthrough do {} while (0) /* fallthrough */ +#endif +#endif + +#if __NO_SND_SOC_CODEC_DRV +#define codec component +#define snd_soc_codec snd_soc_component +#define snd_soc_codec_driver snd_soc_component_driver +#define snd_soc_codec_get_drvdata snd_soc_component_get_drvdata +#define snd_soc_codec_get_dapm snd_soc_component_get_dapm +#define snd_soc_codec_get_bias_level snd_soc_component_get_bias_level +#define snd_soc_kcontrol_codec snd_soc_kcontrol_component +#define snd_soc_read snd_soc_component_read32 +#define snd_soc_register_codec snd_soc_register_component +#define snd_soc_unregister_codec snd_soc_unregister_component +#define snd_soc_update_bits snd_soc_component_update_bits +#define snd_soc_write snd_soc_component_write +#define snd_soc_add_codec_controls snd_soc_add_component_controls +#endif + + +#ifdef CONFIG_AC101_SWITCH_DETECT +enum headphone_mode_u { + HEADPHONE_IDLE, + FOUR_HEADPHONE_PLUGIN, + THREE_HEADPHONE_PLUGIN, +}; +#endif + +struct ac10x_priv { + struct i2c_client *i2c[4]; + struct regmap* i2cmap[4]; + int codec_cnt; + unsigned sysclk; +#define _FREQ_24_576K 24576000 +#define _FREQ_22_579K 22579200 + unsigned mclk; /* master clock or aif_clock/aclk */ + int clk_id; + unsigned char i2s_mode; + unsigned char data_protocol; + struct delayed_work dlywork; + int tdm_chips_cnt; + int sysclk_en; + + /* member for ac101 .begin */ + struct snd_soc_codec *codec; + struct i2c_client *i2c101; + struct regmap* regmap101; + + struct mutex dac_mutex; + u8 dac_enable; + spinlock_t lock; + u8 aif1_clken; + + struct work_struct codec_resume; + struct gpio_desc* gpiod_spk_amp_gate; + + #ifdef CONFIG_AC101_SWITCH_DETECT + struct gpio_desc* gpiod_irq; + long irq; + volatile int irq_cntr; + volatile int pullout_cntr; + volatile int state; + + enum headphone_mode_u mode; + struct work_struct work_switch; + struct work_struct work_clear_irq; + + struct input_dev* inpdev; + #endif + /* member for ac101 .end */ +}; + + +/* AC101 DAI operations */ +int ac101_audio_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai); +void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai); +int ac101_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt); +int ac101_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai); +int ac101_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai); +int ac101_aif_mute(struct snd_soc_dai *codec_dai, int mute); + +/* codec driver specific */ +int ac101_codec_probe(struct snd_soc_codec *codec); +int ac101_codec_remove(struct snd_soc_codec *codec); +int ac101_codec_suspend(struct snd_soc_codec *codec); +int ac101_codec_resume(struct snd_soc_codec *codec); +int ac101_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level); + +/* i2c device specific */ +int ac101_probe(struct i2c_client *i2c, const struct i2c_device_id *id); +void ac101_shutdown(struct i2c_client *i2c); +int ac101_remove(struct i2c_client *i2c); + +int ac10x_fill_regcache(struct device* dev, struct regmap* map); + +#endif//__AC10X_H__ diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 499604f..3c8e728 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -25,6 +25,7 @@ #include "wm8960.h" +#define WM8960_MCLK 4096000 /* R25 - Power 1 */ #define WM8960_VMID_MASK 0x180 #define WM8960_VREF 0x40 @@ -1287,6 +1288,8 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, { struct snd_soc_component *component = dai->component; struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + clk_id = WM8960_SYSCLK_MCLK; + switch (clk_id) { case WM8960_SYSCLK_MCLK: @@ -1302,7 +1305,7 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, default: return -EINVAL; } - + wm8960->freq_in = WM8960_MCLK; wm8960->sysclk = freq; wm8960->clk_id = clk_id; @@ -1354,6 +1357,22 @@ static int wm8960_probe(struct snd_soc_component *component) else wm8960->set_bias_level = wm8960_set_bias_level_out3; + #if 1 + snd_soc_component_update_bits(component, WM8960_LDAC, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8960_RDAC, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8960_LOUT1, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8960_ROUT1, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8960_LOUT2, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8960_ROUT2, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8960_POWER2, 0x1fB, 0x198); + snd_soc_component_update_bits(component, WM8960_LOUTMIX, 0x1F0, 0x100); + snd_soc_component_update_bits(component, WM8960_ROUTMIX, 0x1F0, 0x100); + snd_soc_component_update_bits(component, WM8960_LOUT1, 0x7f, 0x6f); + snd_soc_component_update_bits(component, WM8960_ROUT1, 0x7f, 0x6f); + snd_soc_component_update_bits(component, WM8960_LOUT2, 0x7f, 0x7f); + snd_soc_component_update_bits(component, WM8960_ROUT2, 0x7f, 0x7f); + #endif + snd_soc_add_component_controls(component, wm8960_snd_controls, ARRAY_SIZE(wm8960_snd_controls)); wm8960_add_widgets(component); @@ -1413,6 +1432,8 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, if (wm8960 == NULL) return -ENOMEM; + wm8960->clk_id = WM8960_SYSCLK_MCLK; + wm8960->mclk = devm_clk_get(&i2c->dev, "mclk"); if (IS_ERR(wm8960->mclk)) { if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER) @@ -1455,7 +1476,14 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100); regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100); regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100); - + + regmap_update_bits(wm8960->regmap, WM8960_LINPATH, 0x138, 0x138); + regmap_update_bits(wm8960->regmap, WM8960_RINPATH, 0x138, 0x138); + regmap_update_bits(wm8960->regmap, WM8960_POWER1, 0x7E, 0x7E); + regmap_update_bits(wm8960->regmap, WM8960_POWER3, 0x30, 0x30); + regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x19F, 0x197); + regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x19F, 0x197); + /* ADCLRC pin configured as GPIO. */ regmap_update_bits(wm8960->regmap, WM8960_IFACE2, 1 << 6, wm8960->pdata.gpio_cfg[0] << 6); diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 33ce257..5ab1718 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -735,7 +735,9 @@ static int dw_i2s_remove(struct platform_device *pdev) #ifdef CONFIG_OF static const struct of_device_id dw_i2s_of_match[] = { - { .compatible = "snps,designware-i2s", }, + { .compatible = "snps,designware-i2srx", }, + { .compatible = "snps,designware-i2stx-4ch0", }, + { .compatible = "snps,designware-i2stx-4ch1", }, {}, }; diff --git a/sound/soc/starfive/Kconfig b/sound/soc/starfive/Kconfig new file mode 100644 index 0000000..1811394 --- /dev/null +++ b/sound/soc/starfive/Kconfig @@ -0,0 +1,40 @@ +config SND_STARFIVE_SPDIF + tristate "starfive spdif" + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for codecs attached to the + I2S interface on VIC vic_starlight board. You will also need to select + the drivers for the rest of VIC audio subsystem. + +config SND_STARFIVE_SPDIF_PCM + bool "PCM PIO extension for spdif driver" + depends on SND_STARFIVE_SPDIF + help + Say Y or N if you want to add a custom ALSA extension that registers + a PCM and uses PIO to transfer data. + +config SND_STARFIVE_PWMDAC + tristate "starfive pwmdac Device Driver" + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for sf pwmdac driver. + +config SND_STARFIVE_PWMDAC_PCM + bool "PCM PIO extension for pwmdac driver" + depends on SND_STARFIVE_PWMDAC + help + Say Y or N if you want to add a custom ALSA extension that registers + a PCM and uses PIO to transfer data. + +config SND_STARFIVE_PDM + tristate "starfive pdm Device Driver" + select REGMAP_MMIO + help + Say Y or M if you want to add support for sf pdm driver. + +config SND_STARFIVE_TDM + tristate "starfive tdm Device Driver" + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for sf tdm driver. \ No newline at end of file diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile new file mode 100644 index 0000000..798208a --- /dev/null +++ b/sound/soc/starfive/Makefile @@ -0,0 +1,13 @@ +# starfive Platform Support +obj-$(CONFIG_SND_STARFIVE_SPDIF) += spdif.o + +spdif-y := starfive_spdif.o +spdif-$(CONFIG_SND_STARFIVE_SPDIF_PCM) += starfive_spdif_pcm.o + +obj-$(CONFIG_SND_STARFIVE_PWMDAC) += pwmdac.o + +pwmdac-y := starfive_pwmdac.o starfive_pwmdac_transmitter.o +pwmdac-$(CONFIG_SND_STARFIVE_PWMDAC_PCM) += starfive_pwmdac_pcm.o + +obj-$(CONFIG_SND_STARFIVE_PDM) += starfive_pdm.o +obj-$(CONFIG_SND_STARFIVE_TDM) += starfive_tdm.o diff --git a/sound/soc/starfive/pwmdac.h b/sound/soc/starfive/pwmdac.h new file mode 100644 index 0000000..75ec6b8 --- /dev/null +++ b/sound/soc/starfive/pwmdac.h @@ -0,0 +1,164 @@ +/** + ****************************************************************************** + * @file pwmdac.h + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#ifndef __STARFIVE_PWMDAC_LOCAL_H +#define __STARFIVE_PWMDAC_LOCAL_H + +#include +#include +#include +#include +#include + +#define PWMDAC_WDATA 0///PWMDAC_BASE_ADDR +#define PWMDAC_CTRL 0x04///PWMDAC_BASE_ADDR + 0x04 +#define PWMDAC_SATAE 0x08///PWMDAC_BASE_ADDR + 0x08 +#define PWMDAC_RESERVED 0x0C///PWMDAC_BASE_ADDR + 0x0C + +#define SFC_PWMDAC_SHIFT BIT(1) +#define SFC_PWMDAC_DUTY_CYCLE BIT(2) +#define SFC_PWMDAC_CNT_N BIT(4) + +#define SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE BIT(13) +#define SFC_PWMDAC_DATA_MODE BIT(14) + +#define FIFO_UN_FULL 0 +#define FIFO_FULL 1 + +#define PWMDAC_MCLK (4096000) + +enum pwmdac_lr_change{ + NO_CHANGE = 0, + CHANGE, +}; + +enum pwmdac_d_mode{ + UNSINGED_DATA = 0, + INVERTER_DATA_MSB, +}; + +enum pwmdac_shift_bit{ + PWMDAC_SHIFT_8 = 8, /*pwmdac shift 8 bit*/ + PWMDAC_SHIFT_10 = 10, /*pwmdac shift 10 bit*/ +}; + +enum pwmdac_duty_cycle{ + PWMDAC_CYCLE_LEFT = 0, /*pwmdac duty cycle left*/ + PWMDAC_CYCLE_RIGHT = 1, /*pwmdac duty cycle right*/ + PWMDAC_CYCLE_CENTER = 2, /*pwmdac duty cycle center*/ +}; + +/*sample count [12:4] <511*/ +enum pwmdac_sample_count{ + PWMDAC_SAMPLE_CNT_1 = 1, + PWMDAC_SAMPLE_CNT_2, + PWMDAC_SAMPLE_CNT_3, + PWMDAC_SAMPLE_CNT_4, + PWMDAC_SAMPLE_CNT_5, + PWMDAC_SAMPLE_CNT_6, + PWMDAC_SAMPLE_CNT_7, + PWMDAC_SAMPLE_CNT_8 = 1, //(32.468/8) == (12.288/3) == 4.096 + PWMDAC_SAMPLE_CNT_9, + PWMDAC_SAMPLE_CNT_10, + PWMDAC_SAMPLE_CNT_11, + PWMDAC_SAMPLE_CNT_12, + PWMDAC_SAMPLE_CNT_13, + PWMDAC_SAMPLE_CNT_14, + PWMDAC_SAMPLE_CNT_15, + PWMDAC_SAMPLE_CNT_16, + PWMDAC_SAMPLE_CNT_17, + PWMDAC_SAMPLE_CNT_18, + PWMDAC_SAMPLE_CNT_19, + PWMDAC_SAMPLE_CNT_20 = 20, + PWMDAC_SAMPLE_CNT_30 = 30, + PWMDAC_SAMPLE_CNT_511 = 511, +}; + + +enum data_shift{ + PWMDAC_DATA_LEFT_SHIFT_BIT_0 = 0, + PWMDAC_DATA_LEFT_SHIFT_BIT_1, + PWMDAC_DATA_LEFT_SHIFT_BIT_2, + PWMDAC_DATA_LEFT_SHIFT_BIT_3, + PWMDAC_DATA_LEFT_SHIFT_BIT_4, + PWMDAC_DATA_LEFT_SHIFT_BIT_5, + PWMDAC_DATA_LEFT_SHIFT_BIT_6, + PWMDAC_DATA_LEFT_SHIFT_BIT_7, + PWMDAC_DATA_LEFT_SHIFT_BIT_ALL, +}; + +enum pwmdac_config_list{ + shift_8Bit_unsigned = 0, + shift_8Bit_unsigned_dataShift, + shift_10Bit_unsigned, + shift_10Bit_unsigned_dataShift, + + shift_8Bit_inverter, + shift_8Bit_inverter_dataShift, + shift_10Bit_inverter, + shift_10Bit_inverter_dataShift, +}; + +struct sf_pwmdac_dev { + void __iomem *pwmdac_base; + resource_size_t mapbase; + u8 mode; + u8 shift_bit; + u8 duty_cycle; + u8 datan; + u8 data_mode; + u8 lr_change; + u8 shift; + u8 fifo_th; + bool use_pio; + spinlock_t lock; + int active; + + struct device *dev; + struct snd_dmaengine_dai_dma_data play_dma_data; + struct snd_pcm_substream __rcu *tx_substream; + unsigned int (*tx_fn)(struct sf_pwmdac_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed); + unsigned int tx_ptr; + struct task_struct *tx_thread; + bool tx_thread_exit; + + struct clk* audio_src; + struct clk* pwmdac_apb; + struct clk* pwmdac_mclk; +}; + + + +#if IS_ENABLED(CONFIG_SND_STARFIVE_PWMDAC_PCM) +void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev); +void sf_pwmdac_pcm_pop_rx(struct sf_pwmdac_dev *dev); +int sf_pwmdac_pcm_register(struct platform_device *pdev); +#else +void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev) { } +void sf_pwmdac_pcm_pop_rx(struct sf_pwmdac_dev *dev) { } +int sf_pwmdac_pcm_register(struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + +#endif diff --git a/sound/soc/starfive/starfive_pdm.c b/sound/soc/starfive/starfive_pdm.c new file mode 100644 index 0000000..32c72ae --- /dev/null +++ b/sound/soc/starfive/starfive_pdm.c @@ -0,0 +1,378 @@ +/** + ****************************************************************************** + * @file sf_pdm.c + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "starfive_pdm.h" + +#define AUDIOC_CLK (12288000) +#define PDM_MUL (128) + +struct sf_pdm { + struct regmap *pdm_map; + struct regmap *clk_map; + struct clk *clk; +}; + +static const DECLARE_TLV_DB_SCALE(volume_tlv, -9450, 150, 0); + +static const struct snd_kcontrol_new sf_pdm_snd_controls[] = { + SOC_SINGLE("DC compensation Control", PDM_DMIC_CTRL0, 30, 1, 0), + SOC_SINGLE("High Pass Filter Control", PDM_DMIC_CTRL0, 28, 1, 0), + SOC_SINGLE("Left Channel Volume Control", PDM_DMIC_CTRL0, 23, 1, 0), + SOC_SINGLE("Right Channel Volume Control", PDM_DMIC_CTRL0, 22, 1, 0), + SOC_SINGLE_TLV("Volume", PDM_DMIC_CTRL0, 16, 0x3F, 1, volume_tlv), + SOC_SINGLE("Data MSB Shift", PDM_DMIC_CTRL0, 1, 7, 0), + SOC_SINGLE("SCALE", PDM_DC_SCALE0, 0, 0x3F, 0), + SOC_SINGLE("DC offset", PDM_DC_SCALE0, 8, 0xFFFFF, 0), +}; + +static int sf_pdm_set_mclk(struct regmap *map, + unsigned int clk, unsigned int weight) +{ + int mclk_div,bclk_div,lrclk_div; + u32 pdm_div; + + /* + audio source clk:12288000, mclk_div:4, mclk:3M + support 8K/16K/32K/48K sample reate + suapport 16/24/32 bit weight + bit weight 32 + mclk bclk lrclk + 3M 1.5M 48K + 3M 1M 32K + 3M 0.5M 16K + 3M 0.25M 8K + + bit weight 24,set lrclk_div as 32 + mclk bclk lrclk + 3M 1.5M 48K + 3M 1M 32K + 3M 0.5M 16K + 3M 0.25M 8K + + bit weight 16 + mclk bclk lrclk + 3M 0.75M 48K + 3M 0.5M 32K + 3M 0.25M 16K + 3M 0.125M 8K + */ + + switch (clk) { + case 8000: + case 16000: + case 32000: + case 48000: + break; + default: + printk(KERN_ERR "sample rate:%d\n", clk); + return -EINVAL; + } + + switch (weight) { + case 16: + case 24: + case 32: + break; + default: + printk(KERN_ERR "bit weight:%d\n", weight); + return -EINVAL; + } + + if (24 == weight) { + weight = 32; + } + + mclk_div = 4; + bclk_div = AUDIOC_CLK/mclk_div/(clk*weight); + lrclk_div = weight; + + /* PDM MCLK = 128*LRCLK */ + pdm_div = AUDIOC_CLK/(PDM_MUL*clk); + + regmap_update_bits(map, AUDIO_CLK_ADC_MCLK, 0x0F, mclk_div); + regmap_update_bits(map, AUDIO_CLK_I2SADC_BCLK, 0x1F, bclk_div); + regmap_update_bits(map, AUDIO_CLK_ADC_LRCLK, 0x3F, lrclk_div); + regmap_update_bits(map, AUDIO_CLK_PDM_CLK, 0x0F, pdm_div); + + return 0; +} + +static void sf_pdm_enable(struct regmap *map) +{ + /* Enable PDM */ + regmap_update_bits(map, PDM_DMIC_CTRL0, 0x01<pdm_map); + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sf_pdm_disable(priv->pdm_map); + return 0; + + default: + return -EINVAL; + } +} + +static int sf_pdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai); + unsigned int rate = params_rate(params); + unsigned int width; + int ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + width = params_width(params); + switch (width) { + case 16: + case 24: + case 32: + break; + default: + dev_err(dai->dev, "unsupported sample width\n"); + return -EINVAL; + } + + ret = sf_pdm_set_mclk(priv->clk_map, rate, width); + if (ret < 0) { + dev_err(dai->dev, "unsupported sample rate\n"); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops sf_pdm_dai_ops = { + .trigger = sf_pdm_trigger, + .hw_params = sf_pdm_hw_params, +}; + +static int sf_pdm_dai_probe(struct snd_soc_dai *dai) +{ + struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai); + + /* Reset */ + regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map); + + /* MUTE */ + regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, + 0x3F<pdm_map, PDM_DMIC_CTRL0, + 0x3F<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x07<pdm_map, PDM_DC_SCALE0, 0x3F, 0x08); + + /* DC offset:0 */ + regmap_update_bits(priv->pdm_map, PDM_DC_SCALE0, + 0xFFFFF<pdm_map, PDM_DMIC_CTRL0, + 0x3F<pdm_map); + snd_soc_add_component_controls(component, sf_pdm_snd_controls, + ARRAY_SIZE(sf_pdm_snd_controls)); + + return 0; +} + +static const struct snd_soc_component_driver sf_pdm_component_drv = { + .name = "sf-pdm", + .probe = pdm_probe, +}; + +static const struct regmap_config sf_pdm_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x20, +}; + +static const struct regmap_config sf_audio_clk_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x100, +}; + +static int sf_pdm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sf_pdm *priv; + struct resource *res; + void __iomem *regs; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pdm"); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv->pdm_map = devm_regmap_init_mmio(dev, regs, &sf_pdm_regmap_cfg); + if (IS_ERR(priv->pdm_map)) { + dev_err(dev, "failed to init regmap: %ld\n", + PTR_ERR(priv->pdm_map)); + return PTR_ERR(priv->pdm_map); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-clk"); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv->clk_map = devm_regmap_init_mmio(dev, regs, &sf_audio_clk_regmap_cfg); + if (IS_ERR(priv->clk_map)) { + dev_err(dev, "failed to init regmap: %ld\n", + PTR_ERR(priv->clk_map)); + return PTR_ERR(priv->clk_map); + } + + return devm_snd_soc_register_component(dev, &sf_pdm_component_drv, + &sf_pdm_dai_drv, 1); +} + +static int sf_pdm_dev_remove(struct platform_device *pdev) +{ + return 0; +} +static const struct of_device_id sf_pdm_of_match[] = { + {.compatible = "starfive,sf-pdm",}, + {} +}; +MODULE_DEVICE_TABLE(of, sf_pdm_of_match); + +static struct platform_driver sf_pdm_driver = { + + .driver = { + .name = "sf-pdm", + .of_match_table = sf_pdm_of_match, + }, + .probe = sf_pdm_probe, + .remove = sf_pdm_dev_remove, +}; +module_platform_driver(sf_pdm_driver); + +MODULE_AUTHOR("michael.yan "); +MODULE_DESCRIPTION("starfive PDM Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/starfive/starfive_pdm.h b/sound/soc/starfive/starfive_pdm.h new file mode 100644 index 0000000..d8360cf --- /dev/null +++ b/sound/soc/starfive/starfive_pdm.h @@ -0,0 +1,59 @@ +/** + ****************************************************************************** + * @file sf_pdm.h + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#ifndef __SND_SOC_STARFIVE_SPDIF_H +#define __SND_SOC_STARFIVE_PDM_H + +#include +#include +#include +#include +#include +#include +#include + +#define PDM_DMIC_CTRL0 (0x00) +#define PDM_DC_SCALE0 (0x04) +#define PDM_DMIC_CTRL1 (0x10) +#define PDM_DC_SCALE1 (0x14) + +/* PDM CTRL OFFSET */ +#define PDM_DMIC_MSB_SHIFT_OFFSET (1) +#define PDM_DMIC_VOL_OFFSET (16) +#define PDM_DMIC_RVOL_OFFSET (22) +#define PDM_DMIC_LVOL_OFFSET (23) +#define PDM_DMIC_I2SMODE_OFFSET (24) +#define PDM_DMIC_ENHPF_OFFSET (28) +#define PDM_DMIC_FASTMODE_OFFSET (29) +#define PDM_DMIC_DCBPS_OFFSET (30) +#define PDM_DMIC_SW_RSTN_OFFSET (31) + +/* PDM SCALE OFFSET */ +#define PDM_DMIC_DCOFF3_OFFSET (24) +#define PDM_DMIC_DCOFF2_OFFSET (16) +#define PDM_DMIC_DCOFF1_OFFSET (8) +#define PDM_DMIC_SCALE_OFFSET (0) + +#define AUDIO_CLK_ADC_MCLK 0x0 +#define AUDIO_CLK_I2SADC_BCLK 0xC +#define AUDIO_CLK_ADC_LRCLK 0x14 +#define AUDIO_CLK_PDM_CLK 0x1C + +#endif /* __SND_SOC_STARFIVE_PDM_H */ diff --git a/sound/soc/starfive/starfive_pwmdac.c b/sound/soc/starfive/starfive_pwmdac.c new file mode 100644 index 0000000..191a165 --- /dev/null +++ b/sound/soc/starfive/starfive_pwmdac.c @@ -0,0 +1,797 @@ +/** + ****************************************************************************** + * @file sf_pwmdac.c + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pwmdac.h" +#include + +struct ct_pwmdac { + char *name; + unsigned int vals; +}; + +static const struct ct_pwmdac pwmdac_ct_shift_bit[] = { + { .name = "8bit", .vals = PWMDAC_SHIFT_8 }, + { .name = "10bit", .vals = PWMDAC_SHIFT_10 } +}; + +static const struct ct_pwmdac pwmdac_ct_duty_cycle[] = { + { .name = "left", .vals = PWMDAC_CYCLE_LEFT }, + { .name = "right", .vals = PWMDAC_CYCLE_RIGHT }, + { .name = "center", .vals = PWMDAC_CYCLE_CENTER } +}; + +static const struct ct_pwmdac pwmdac_ct_data_mode[] = { + { .name = "unsinged", .vals = UNSINGED_DATA }, + { .name = "inverter", .vals = INVERTER_DATA_MSB } +}; + +static const struct ct_pwmdac pwmdac_ct_lr_change[] = { + { .name = "no_change", .vals = NO_CHANGE }, + { .name = "change", .vals = CHANGE } +}; + +static const struct ct_pwmdac pwmdac_ct_shift[] = { + { .name = "left 0 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_0 }, + { .name = "left 1 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_1 }, + { .name = "left 2 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_2 }, + { .name = "left 3 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_3 }, + { .name = "left 4 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_4 }, + { .name = "left 5 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_5 }, + { .name = "left 6 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_6 } +}; + +static int pwmdac_shift_bit_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_shift_bit); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_shift_bit[uinfo->value.enumerated.item].name); + + return 0; +} +static int pwmdac_shift_bit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + unsigned int item; + + if (dev->shift_bit == pwmdac_ct_shift_bit[0].vals) + item = 0; + else + item = 1; + + ucontrol->value.enumerated.item[0] = item; + + return 0; +} + +static int pwmdac_shift_bit_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_shift_bit); + + if (sel > items) + return 0; + + switch (sel) { + case 1: + dev->shift_bit = pwmdac_ct_shift_bit[1].vals; + break; + default: + dev->shift_bit = pwmdac_ct_shift_bit[0].vals; + break; + } + + return 0; +} + +static int pwmdac_duty_cycle_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_duty_cycle); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_duty_cycle[uinfo->value.enumerated.item].name); + + return 0; +} + +static int pwmdac_duty_cycle_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = dev->duty_cycle; + return 0; +} + +static int pwmdac_duty_cycle_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_duty_cycle); + + if (sel > items) + return 0; + + dev->duty_cycle = pwmdac_ct_duty_cycle[sel].vals; + return 0; +} + +static int pwmdac_datan_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = PWMDAC_SAMPLE_CNT_511; + uinfo->value.integer.step = 1; + return 0; +} + +static int pwmdac_datan_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = dev->datan; + + return 0; +} + +static int pwmdac_datan_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.integer.value[0]; + + if (sel > PWMDAC_SAMPLE_CNT_511) + return 0; + + dev->datan = sel; + + return 0; +} + +static int pwmdac_data_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_data_mode); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_data_mode[uinfo->value.enumerated.item].name); + + return 0; +} + +static int pwmdac_data_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = dev->data_mode; + return 0; +} + +static int pwmdac_data_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_data_mode); + + if (sel > items) + return 0; + + dev->data_mode = pwmdac_ct_data_mode[sel].vals; + return 0; +} + +static int pwmdac_shift_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_shift); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_shift[uinfo->value.enumerated.item].name); + + return 0; +} + +static int pwmdac_shift_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + unsigned int item = dev->shift; + + ucontrol->value.enumerated.item[0] = pwmdac_ct_shift[item].vals; + return 0; +} + +static int pwmdac_shift_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_shift); + + if (sel > items) + return 0; + + dev->shift = pwmdac_ct_shift[sel].vals; + return 0; +} + +static int pwmdac_lr_change_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_lr_change); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_lr_change[uinfo->value.enumerated.item].name); + + return 0; +} + +static int pwmdac_lr_change_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = dev->lr_change; + return 0; +} + +static int pwmdac_lr_change_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_lr_change); + + if (sel > items) + return 0; + + dev->lr_change = pwmdac_ct_lr_change[sel].vals; + return 0; +} + +static inline void pwmdc_write_reg(void __iomem *io_base, int reg, u32 val) +{ + writel(val, io_base + reg); +} + +static inline u32 pwmdc_read_reg(void __iomem *io_base, int reg) +{ + return readl(io_base + reg); +} + +/* + * 32bit-4byte +*/ +static void pwmdac_set_ctrl_enable(struct sf_pwmdac_dev *dev) +{ + u32 date; + date = pwmdc_read_reg(dev->pwmdac_base, PWMDAC_CTRL); + pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, date | BIT(0) ); +} + +/* + * 32bit-4byte +*/ +static void pwmdac_set_ctrl_disable(struct sf_pwmdac_dev *dev) +{ + u32 date; + date = pwmdc_read_reg(dev->pwmdac_base, PWMDAC_CTRL); + pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, date & ~ BIT(0)); +} + +/* + * 8:8-bit + * 10:10-bit +*/ +static void pwmdac_set_ctrl_shift(struct sf_pwmdac_dev *dev, u8 data) +{ + u32 value = 0; + + if (data == 8) { + value = (~((~value) | 0x02)); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } + else if(data == 10){ + value |= 0x02; + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } +} + +/* + * 00:left + * 01:right + * 10:center +*/ +static void pwmdac_set_ctrl_dutyCycle(struct sf_pwmdac_dev *dev, u8 data) +{ + u32 value = 0; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + if (data == 0) { //left + value = (~((~value) | (0x03<<2))); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } + else if (data == 1) { //right + value = (~((~value) | (0x01<<3))) | (0x01<<2); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } + else if (data == 2) { //center + value = (~((~value) | (0x01<<2))) | (0x01<<3); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } +} + + +static void pwmdac_set_ctrl_N(struct sf_pwmdac_dev *dev, u16 data) +{ + u32 value = 0; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, (value & 0xF) | ((data - 1) << 4)); +} + + +static void pwmdac_LR_data_change(struct sf_pwmdac_dev *dev, u8 data) +{ + u32 value = 0; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + switch (data) { + case NO_CHANGE: + value &= (~SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE); + break; + case CHANGE: + value |= SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE; + break; + } + pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, value); +} + + +static void pwmdac_data_mode(struct sf_pwmdac_dev *dev, u8 data) +{ + u32 value = 0; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + if (data == UNSINGED_DATA) { + value &= (~SFC_PWMDAC_DATA_MODE); + } + else if (data == INVERTER_DATA_MSB) { + value |= SFC_PWMDAC_DATA_MODE; + } + pwmdc_write_reg(dev->pwmdac_base,PWMDAC_CTRL, value); +} + + +static int pwmdac_data_shift(struct sf_pwmdac_dev *dev,u8 data) +{ + u32 value = 0; + + if ((data < PWMDAC_DATA_LEFT_SHIFT_BIT_0) || (data > PWMDAC_DATA_LEFT_SHIFT_BIT_7)) { + return -1; + } + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + value &= ( ~ ( PWMDAC_DATA_LEFT_SHIFT_BIT_ALL << 15 ) ); + value |= (data<<15); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + return 0; +} + +static int get_pwmdac_fifo_state(struct sf_pwmdac_dev *dev) +{ + u32 value; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_SATAE); + if ((value & 0x02) == 0) + return FIFO_UN_FULL; + + return FIFO_FULL; +} + + +static void pwmdac_set(struct sf_pwmdac_dev *dev) +{ + ///8-bit + left + N=16 + pwmdac_set_ctrl_shift(dev, dev->shift_bit); + pwmdac_set_ctrl_dutyCycle(dev, dev->duty_cycle); + pwmdac_set_ctrl_N(dev, dev->datan); + pwmdac_set_ctrl_enable(dev); + + pwmdac_LR_data_change(dev, dev->lr_change); + pwmdac_data_mode(dev, dev->data_mode); + if (dev->shift) { + pwmdac_data_shift(dev, dev->shift); + } +} + +static void pwmdac_stop(struct sf_pwmdac_dev *dev) +{ + pwmdac_set_ctrl_disable(dev); +} + +static int pwmdac_config(struct sf_pwmdac_dev *dev) +{ + switch (dev->mode) { + case shift_8Bit_unsigned: + case shift_8Bit_unsigned_dataShift: + /* 8 bit, unsigned */ + dev->shift_bit = PWMDAC_SHIFT_8; + dev->duty_cycle = PWMDAC_CYCLE_CENTER; + dev->datan = PWMDAC_SAMPLE_CNT_8; + dev->data_mode = UNSINGED_DATA; + break; + + case shift_8Bit_inverter: + case shift_8Bit_inverter_dataShift: + /* 8 bit, invert */ + dev->shift_bit = PWMDAC_SHIFT_8; + dev->duty_cycle = PWMDAC_CYCLE_CENTER; + dev->datan = PWMDAC_SAMPLE_CNT_8; + dev->data_mode = INVERTER_DATA_MSB; + break; + + case shift_10Bit_unsigned: + case shift_10Bit_unsigned_dataShift: + /* 10 bit, unsigend */ + dev->shift_bit = PWMDAC_SHIFT_10; + dev->duty_cycle = PWMDAC_CYCLE_CENTER; + dev->datan = PWMDAC_SAMPLE_CNT_8; + dev->data_mode = UNSINGED_DATA; + break; + + case shift_10Bit_inverter: + case shift_10Bit_inverter_dataShift: + /* 10 bit, invert */ + dev->shift_bit = PWMDAC_SHIFT_10; + dev->duty_cycle = PWMDAC_CYCLE_CENTER; + dev->datan = PWMDAC_SAMPLE_CNT_8; + dev->data_mode = INVERTER_DATA_MSB; + break; + + default: + return -1; + } + + if ((dev->mode == shift_8Bit_unsigned_dataShift) || (dev->mode == shift_8Bit_inverter_dataShift) + || (dev->mode == shift_10Bit_unsigned_dataShift) || (dev->mode == shift_10Bit_inverter_dataShift)) { + dev->shift = 4; /*0~7*/ + } else { + dev->shift = 0; + } + dev->lr_change = NO_CHANGE; + return 0; +} + +static int sf_pwmdac_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sf_pwmdac_dev *dev = snd_soc_dai_get_drvdata(dai); + //pwmdac_set(dev); + return 0; +} +#if 0 +static int pwmdac_tx_thread(void *dev) +{ + struct sf_pwmdac_dev *pwmdac_dev = (struct sf_pwmdac_dev *)dev; + + set_current_state(TASK_INTERRUPTIBLE); + while (!schedule_timeout(usecs_to_jiffies(50))) { + if (pwmdac_dev->tx_thread_exit) + break; + if (get_pwmdac_fifo_state(pwmdac_dev)==0) { + sf_pwmdac_pcm_push_tx(pwmdac_dev); + } + + set_current_state(TASK_INTERRUPTIBLE); + } + + pwmdac_dev->tx_thread = NULL; + return 0; +} +#else + +int pwmdac_tx_thread(void *dev) +{ + struct sf_pwmdac_dev *pwmdac_dev = (struct sf_pwmdac_dev *)dev; + + if(!pwmdac_dev) + { + printk(KERN_ERR"%s L.%d dev is null.\n", __FILE__, __LINE__); + return -1; + } + + while (!pwmdac_dev->tx_thread_exit) { + if(get_pwmdac_fifo_state(pwmdac_dev)==0){ + sf_pwmdac_pcm_push_tx(pwmdac_dev); + } + else + { + udelay(100); + } + } + return 0; +} + + +#endif +static int sf_pwmdac_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct sf_pwmdac_dev *dev = 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: + dev->active++; + pwmdac_set(dev); + if (dev->use_pio) { + dev->tx_thread = kthread_create(pwmdac_tx_thread, (void *)dev, "pwmdac"); + if (IS_ERR(dev->tx_thread)) { + return PTR_ERR(dev->tx_thread); + } + wake_up_process(dev->tx_thread); + dev->tx_thread_exit = 0; + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + pwmdac_stop(dev); + if (dev->use_pio) { + if(dev->tx_thread){ + dev->tx_thread_exit = 1; + } + } + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int sf_pwmdac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sf_pwmdac_dev *dev = dev_get_drvdata(dai->dev); + + dev->play_dma_data.addr = dev->mapbase + PWMDAC_WDATA; + + switch (params_channels(params)) { + case 2: + dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + case 1: + dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + default: + dev_err(dai->dev, "%d channels not supported\n", + params_channels(params)); + return -EINVAL; + } + + dev->play_dma_data.fifo_size = 1; + dev->play_dma_data.maxburst = 16; + + snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, NULL); + snd_soc_dai_set_drvdata(dai, dev); + + return 0; +} + +static int sf_pwmdac_dai_probe(struct snd_soc_dai *dai) +{ + struct sf_pwmdac_dev *dev = dev_get_drvdata(dai->dev); + + dev->play_dma_data.addr = dev->mapbase + PWMDAC_WDATA; + dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dev->play_dma_data.fifo_size = 1; + dev->play_dma_data.maxburst = 16; + + snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, NULL); + snd_soc_dai_set_drvdata(dai, dev); + + return 0; +} + +#define SOC_PWMDAC_ENUM_DECL(xname, xinfo, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = xinfo, .get = xget, .put = xput,} + +static const struct snd_kcontrol_new pwmdac_snd_controls[] = { + SOC_PWMDAC_ENUM_DECL("shift_bit", pwmdac_shift_bit_info, + pwmdac_shift_bit_get, pwmdac_shift_bit_put), + SOC_PWMDAC_ENUM_DECL("duty_cycle", pwmdac_duty_cycle_info, + pwmdac_duty_cycle_get, pwmdac_duty_cycle_put), + SOC_PWMDAC_ENUM_DECL("data_mode", pwmdac_data_mode_info, + pwmdac_data_mode_get, pwmdac_data_mode_put), + SOC_PWMDAC_ENUM_DECL("shift", pwmdac_shift_info, + pwmdac_shift_get, pwmdac_shift_put), + SOC_PWMDAC_ENUM_DECL("lr_change", pwmdac_lr_change_info, + pwmdac_lr_change_get, pwmdac_lr_change_put), +}; + +static int pwmdac_probe(struct snd_soc_component *component) +{ + snd_soc_add_component_controls(component, pwmdac_snd_controls, + ARRAY_SIZE(pwmdac_snd_controls)); + return 0; +} + +static const struct snd_soc_dai_ops sf_pwmdac_dai_ops = { + .hw_params = sf_pwmdac_hw_params, + .prepare = sf_pwmdac_prepare, + .trigger = sf_pwmdac_trigger, +}; + +static const struct snd_soc_component_driver sf_pwmdac_component = { + .name = "sf-pwmdac", + .probe = pwmdac_probe, +}; + +static struct snd_soc_dai_driver pwmdac_dai = { + .name = "pwmdac", + .id = 0, + .probe = sf_pwmdac_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sf_pwmdac_dai_ops, +}; + +static int sf_pwmdac_probe(struct platform_device *pdev) +{ + struct sf_pwmdac_dev *dev; + struct resource *res; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->pwmdac_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(dev->pwmdac_base)) + return PTR_ERR(dev->pwmdac_base); + + dev->mapbase = res->start; + dev->dev = &pdev->dev; + dev->mode = shift_8Bit_inverter; + dev->fifo_th = 1;//8byte + pwmdac_config(dev); + + dev->use_pio = false; + //dev->use_pio = true; + dev_set_drvdata(&pdev->dev, dev); + ret = devm_snd_soc_register_component(&pdev->dev, &sf_pwmdac_component, + &pwmdac_dai, 1); + if (ret != 0) { + dev_err(&pdev->dev, "not able to register dai\n"); + return ret; + } + + if (dev->use_pio) { + ret = sf_pwmdac_pcm_register(pdev); + } else { + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + 0); + } + + return 0; +} + + +static int sf_pwmdac_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id sf_pwmdac_of_match[] = { + { .compatible = "sf,pwmdac", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, sf_pwmdac_of_match); +#endif + + +static struct platform_driver sf_pwmdac_driver = { + .probe = sf_pwmdac_probe, + .remove = sf_pwmdac_remove, + .driver = { + .name = "sf-pwmdac", + .of_match_table = of_match_ptr(sf_pwmdac_of_match), + }, +}; + +module_platform_driver(sf_pwmdac_driver); + +MODULE_AUTHOR("jenny.zhang "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("starfive pwmdac SoC Interface"); +MODULE_ALIAS("platform:starfive-pwmdac"); diff --git a/sound/soc/starfive/starfive_pwmdac_pcm.c b/sound/soc/starfive/starfive_pwmdac_pcm.c new file mode 100644 index 0000000..55141445 --- /dev/null +++ b/sound/soc/starfive/starfive_pwmdac_pcm.c @@ -0,0 +1,251 @@ +/** + ****************************************************************************** + * @file sf_pwmdac_pcm.c + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#include +#include +#include +#include +#include "pwmdac.h" + +#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) +#define PERIOD_BYTES_MIN 4096 +#define PERIODS_MIN 2 + +static unsigned int sf_pwmdac_pcm_tx_8(struct sf_pwmdac_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed) +{ + const u8 (*p)[2] = (void *)runtime->dma_area; + unsigned int period_pos = tx_ptr % runtime->period_size; + int i; + u32 basedat = 0; + + for (i = 0; i < dev->fifo_th; i++) { + basedat = (p[tx_ptr][0]<<8)|(p[tx_ptr][1] << 24); + iowrite32(basedat,dev->pwmdac_base + PWMDAC_WDATA); + period_pos++; + if (++tx_ptr >= runtime->buffer_size) + tx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + + return tx_ptr; +} + + +static unsigned int sf_pwmdac_pcm_tx_16(struct sf_pwmdac_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed) +{ + const u16 (*p)[2] = (void *)runtime->dma_area; + unsigned int period_pos = tx_ptr % runtime->period_size; + int i; + u32 basedat = 0; + + for (i = 0; i < dev->fifo_th; i++) { + basedat = (p[tx_ptr][0])|(p[tx_ptr][1] << 16); + iowrite32(basedat,dev->pwmdac_base + PWMDAC_WDATA); + period_pos++; + if (++tx_ptr >= runtime->buffer_size) + tx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + return tx_ptr; +} + + +static const struct snd_pcm_hardware sf_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_16000, + .rate_min = 16000, + .rate_max = 16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 2, +}; + +static void sf_pcm_transfer(struct sf_pwmdac_dev *dev, bool push) +{ + struct snd_pcm_substream *substream; + bool active, period_elapsed; + + rcu_read_lock(); + if (push) + substream = rcu_dereference(dev->tx_substream); + + active = substream && snd_pcm_running(substream); + if (active) { + unsigned int ptr; + unsigned int new_ptr; + + if (push) { + ptr = READ_ONCE(dev->tx_ptr); + new_ptr = dev->tx_fn(dev, substream->runtime, ptr, + &period_elapsed); + cmpxchg(&dev->tx_ptr, ptr, new_ptr); + } + + if (period_elapsed) + snd_pcm_period_elapsed(substream); + } + rcu_read_unlock(); +} + +void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev) +{ + sf_pcm_transfer(dev, true); +} + + +static int sf_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sf_pwmdac_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + + snd_soc_set_runtime_hwparams(substream, &sf_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = dev; + + return 0; +} + + +static int sf_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int sf_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_pwmdac_dev *dev = runtime->private_data; + int ret; + + + switch (params_channels(hw_params)) { + case 2: + break; + default: + dev_err(dev->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + dev->tx_fn = sf_pwmdac_pcm_tx_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + dev->tx_fn = sf_pwmdac_pcm_tx_16; + break; + default: + dev_err(dev->dev, "invalid format\n"); + return -EINVAL; + } + + return 0; +} + + +static int sf_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_pwmdac_dev *dev = runtime->private_data; + int ret = 0; + + 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) { + WRITE_ONCE(dev->tx_ptr, 0); + rcu_assign_pointer(dev->tx_substream, substream); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rcu_assign_pointer(dev->tx_substream, NULL); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t sf_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_pwmdac_dev *dev = runtime->private_data; + snd_pcm_uframes_t pos; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pos = READ_ONCE(dev->tx_ptr); + + return pos < runtime->buffer_size ? pos : 0; +} + +static int sf_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + size_t size = sf_pcm_hardware.buffer_bytes_max; + + snd_pcm_set_managed_buffer_all(rtd->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, size, size); + return 0; +} + +static const struct snd_soc_component_driver dw_pcm_component = { + .open = sf_pcm_open, + .close = sf_pcm_close, + .hw_params = sf_pcm_hw_params, + .trigger = sf_pcm_trigger, + .pointer = sf_pcm_pointer, + .pcm_construct = sf_pcm_new, +}; + +int sf_pwmdac_pcm_register(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &dw_pcm_component, + NULL, 0); +} diff --git a/sound/soc/starfive/starfive_pwmdac_transmitter.c b/sound/soc/starfive/starfive_pwmdac_transmitter.c new file mode 100644 index 0000000..c459878 --- /dev/null +++ b/sound/soc/starfive/starfive_pwmdac_transmitter.c @@ -0,0 +1,98 @@ +/** + ****************************************************************************** + * @file sf_pwmdac_transmitter.c + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "pwmdac-dit" + +#define STUB_RATES SNDRV_PCM_RATE_8000_192000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8|\ + SNDRV_PCM_FMTBIT_U8|\ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dapm_widget dit_widgets[] = { + SND_SOC_DAPM_OUTPUT("pwmdac-out"), +}; + +static const struct snd_soc_dapm_route dit_routes[] = { + { "pwmdac-out", NULL, "Playback" }, +}; + +static struct snd_soc_component_driver soc_codec_pwmdac_dit = { + .dapm_widgets = dit_widgets, + .num_dapm_widgets = ARRAY_SIZE(dit_widgets), + .dapm_routes = dit_routes, + .num_dapm_routes = ARRAY_SIZE(dit_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver dit_stub_dai = { + .name = "pwmdac-dit-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static int pwmdac_dit_probe(struct platform_device *pdev) +{ + + return devm_snd_soc_register_component(&pdev->dev, + &soc_codec_pwmdac_dit, + &dit_stub_dai, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id pwmdac_dit_dt_ids[] = { + { .compatible = "linux,pwmdac-dit", }, + { } +}; +MODULE_DEVICE_TABLE(of, pwmdac_dit_dt_ids); +#endif + +static struct platform_driver pwmdac_dit_driver = { + .probe = pwmdac_dit_probe, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(pwmdac_dit_dt_ids), + }, +}; + +module_platform_driver(pwmdac_dit_driver); + +MODULE_AUTHOR("jenny.zhang "); +MODULE_DESCRIPTION("pwmdac dummy codec driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform: starfive-pwmdac dummy codec"); diff --git a/sound/soc/starfive/starfive_spdif.c b/sound/soc/starfive/starfive_spdif.c new file mode 100644 index 0000000..74fdcf1 --- /dev/null +++ b/sound/soc/starfive/starfive_spdif.c @@ -0,0 +1,399 @@ +/** + ****************************************************************************** + * @file sf_spdif.c + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "starfive_spdif.h" + +static irqreturn_t spdif_irq_handler(int irq, void *dev_id) +{ + struct sf_spdif_dev *dev = dev_id; + bool irq_valid = false; + unsigned int intr; + unsigned int stat; + + regmap_read(dev->regmap, SPDIF_INT_REG, &intr); + regmap_read(dev->regmap, SPDIF_STAT_REG, &stat); + regmap_update_bits(dev->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, 0); + regmap_update_bits(dev->regmap, SPDIF_INT_REG, + SPDIF_INT_REG_BIT, 0); + + if ((stat & SPDIF_EMPTY_FLAG) || (stat & SPDIF_AEMPTY_FLAG)) { + sf_spdif_pcm_push_tx(dev); + irq_valid = true; + } + + if ((stat & SPDIF_FULL_FLAG) || (stat & SPDIF_AFULL_FLAG)) { + sf_spdif_pcm_pop_rx(dev); + irq_valid = true; + } + + if (stat & SPDIF_PARITY_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_UNDERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_OVRERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_SYNCERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_LOCK_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_BEGIN_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_RIGHT_LEFT) { + irq_valid = true; + } + + regmap_update_bits(dev->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE); + + if (irq_valid) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static int sf_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + bool tx; + + tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (tx) { + /* tx mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_TR_MODE, SPDIF_TR_MODE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_FIFO, SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK); + } else { + /* rx mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_TR_MODE, 0); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_FIFO, SPDIF_FULL_MASK | SPDIF_AFULL_MASK); + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* clock recovery form the SPDIF data stream 0:clk_enable */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, 0); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE, SPDIF_ENABLE); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* clock recovery form the SPDIF data stream 1:power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE, 0); + break; + default: + printk(KERN_ERR "%s L.%d cmd:%d\n", __func__, __LINE__, cmd); + return -EINVAL; + } + + return 0; +} + +static int sf_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int channels; + unsigned int rate; + unsigned int format; + unsigned int tsamplerate; + + channels = params_channels(params); + rate = params_rate(params); + format = params_format(params); + + switch (channels) { + case 2: + break; + default: + dev_err(dai->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + break; + default: + dev_err(spdif->dev, "invalid format\n"); + return -EINVAL; + } + + switch (rate) { + case 8000: + case 11025: + case 16000: + case 22050: + break; + default: + printk(KERN_ERR "channel:%d sample rate:%d\n", channels, rate); + return -EINVAL; + } + + /* 4096000/128=32000 */ + tsamplerate = (32000 + rate/2)/rate - 1; + + if (rate < 3) { + return -EINVAL; + } + + /* transmission sample rate */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, 0xFF, tsamplerate); + + return 0; +} + +static int sf_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + + #if 0 + spdif->play_dma_data.addr = (dma_addr_t)spdif->spdif_base + SPDIF_FIFO_ADDR; + spdif->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->play_dma_data.fifo_size = 16; + spdif->play_dma_data.maxburst = 16; + spdif->capture_dma_data.addr = (dma_addr_t)spdif->spdif_base + SPDIF_FIFO_ADDR; + spdif->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->capture_dma_data.fifo_size = 16; + spdif->capture_dma_data.maxburst = 16; + snd_soc_dai_init_dma_data(dai, &spdif->play_dma_data, &spdif->capture_dma_data); + snd_soc_dai_set_drvdata(dai, spdif); + #endif + + /* reset */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE | SPDIF_SFR_ENABLE | SPDIF_FIFO_ENABLE, 0); + + /* clear irq */ + regmap_update_bits(spdif->regmap, SPDIF_INT_REG, + SPDIF_INT_REG_BIT, 0); + + /* power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + /* power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE, + SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_SETPREAMBB, SPDIF_SETPREAMBB); + + regmap_update_bits(spdif->regmap, SPDIF_INT_REG, + 0x1FFF<regmap, SPDIF_FIFO_CTRL, + 0xFFFFFFFF, 0x20|(0x20<regmap, SPDIF_CTRL, + SPDIF_PARITYGEN, SPDIF_PARITYGEN); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE); + + /* APB access to FIFO enable, disable if use DMA/FIFO */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_USE_FIFO_IF, 0); + + /* two channel */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CHANNEL_MODE, 0); + + return 0; +} + +static const struct snd_soc_dai_ops sf_spdif_dai_ops = { + .trigger = sf_spdif_trigger, + .hw_params = sf_spdif_hw_params, +}; + +#define SF_PCM_RATE_44100_192000 (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) + +#define SF_PCM_RATE_8000_22050 (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050) + +static struct snd_soc_dai_driver sf_spdif_dai = { + .name = "spdif", + .id = 0, + .probe = sf_spdif_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SF_PCM_RATE_8000_22050, + .formats = SNDRV_PCM_FMTBIT_S16_LE \ + |SNDRV_PCM_FMTBIT_S24_LE \ + |SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SF_PCM_RATE_8000_22050, + .formats = SNDRV_PCM_FMTBIT_S16_LE \ + |SNDRV_PCM_FMTBIT_S24_LE \ + |SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &sf_spdif_dai_ops, + .symmetric_rate = 1, +}; + +static const struct snd_soc_component_driver sf_spdif_component = { + .name = "sf-spdif", +}; + +static const struct regmap_config sf_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x200, +}; + +static int sf_spdif_probe(struct platform_device *pdev) +{ + struct sf_spdif_dev *spdif; + struct resource *res; + void __iomem *base; + int ret; + int irq; + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + platform_set_drvdata(pdev, spdif); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + spdif->spdif_base = base; + spdif->regmap = devm_regmap_init_mmio(&pdev->dev, spdif->spdif_base, + &sf_spdif_regmap_config); + if (IS_ERR(spdif->regmap)) + return PTR_ERR(spdif->regmap); + + spdif->dev = &pdev->dev; + spdif->fifo_th = 16; + + irq = platform_get_irq(pdev, 0); + if (irq >= 0) { + ret = devm_request_irq(&pdev->dev, irq, spdif_irq_handler, 0, + pdev->name, spdif); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + } + + ret = devm_snd_soc_register_component(&pdev->dev, &sf_spdif_component, + &sf_spdif_dai, 1); + if (ret) + goto err_clk_disable; + + if (irq >= 0) { + ret = sf_spdif_pcm_register(pdev); + spdif->use_pio = true; + } else { + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + 0); + spdif->use_pio = false; + } + + if (ret) + goto err_clk_disable; + + return 0; + +err_clk_disable: + return ret; +} + +static const struct of_device_id sf_spdif_of_match[] = { + { .compatible = "starfive,sf-spdif", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sf_spdif_of_match); + +static struct platform_driver sf_spdif_driver = { + .driver = { + .name = "sf-spdif", + .of_match_table = sf_spdif_of_match, + }, + .probe = sf_spdif_probe, +}; +module_platform_driver(sf_spdif_driver); + +MODULE_AUTHOR("michael.yan "); +MODULE_DESCRIPTION("starfive SPDIF driver"); +MODULE_LICENSE("GPL v2"); \ No newline at end of file diff --git a/sound/soc/starfive/starfive_spdif.h b/sound/soc/starfive/starfive_spdif.h new file mode 100644 index 0000000..ff641c6 --- /dev/null +++ b/sound/soc/starfive/starfive_spdif.h @@ -0,0 +1,170 @@ +/** + ****************************************************************************** + * @file sf_spdif.h + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#ifndef __SND_SOC_STARFIVE_SPDIF_H +#define __SND_SOC_STARFIVE_SPDIF_H + +#include +#include +#include +#include +#include +#include +#include + +#define SPDIF_CTRL (0x0) +#define SPDIF_INT_REG (0x4) +#define SPDIF_FIFO_CTRL (0x8) +#define SPDIF_STAT_REG (0xC) + +#define SPDIF_FIFO_ADDR (0x100) +#define DMAC_SPDIF_POLLING_LEN (256) + +///ctrl: sampled on the rising clock edge +#define SPDIF_TSAMPLERATE 0///[SRATEW-1:0] +#define SPDIF_SFR_ENABLE (1<<8) ///0:SFR reg reset to defualt value; auto set back to '1' after reset +#define SPDIF_ENABLE (1<<9) ///0:reset of SPDIF block, SRF bits are unchanged; 1:enables SPDIF module +#define SPDIF_FIFO_ENABLE (1<<10) ///0:FIFO pointers are reset to zero,threshold levels for FIFO are unchaned; auto set back to '1' +#define SPDIF_CLK_ENABLE (1<<11) ///1:blocked and the modules are in power save mode; 0:block feeds the modules +#define SPDIF_TR_MODE (1<<12) ///0:rx; 1:tx +#define SPDIF_PARITCHECK (1<<13) ///0:party bit rx in a sub-frame is repeated on the parity; 1:check on a parity error +#define SPDIF_PARITYGEN (1<<14) ///0:parity bit from FIFO is transmitted in sub-frame;1:parity bit generated inside the core and added to a transmitted sub-frame +#define SPDIF_VALIDITYCHECK (1<<15) ///0:validity bit in frame isn't checked and all frame are written; 1:validity bit rx is checked +#define SPDIF_CHANNEL_MODE (1<<16) ///0:two-channel; 1:single-channel +#define SPDIF_DUPLICATE (1<<17) ///only tx -single-channel mode; 0:secondary channel; 1: left(primary) channel +#define SPDIF_SETPREAMBB (1<<18) ///only tx; 0:first preamble B after reset tx valid sub-frame; 1:first preamble B is tx after preambleddel(INT_REG) +#define SPDIF_USE_FIFO_IF (1<<19) ///0:FIFO disabled ,APB accese FIFO; 1:FIFO enable, APB access to FIFO disable; +///#define RESERVED (1<<20) +#define SPDIF_PARITY_MASK (1<<21) +#define SPDIF_UNDERR_MASK (1<<22) +#define SPDIF_OVRERR_MASK (1<<23) +#define SPDIF_EMPTY_MASK (1<<24) +#define SPDIF_AEMPTY_MASK (1<<25) +#define SPDIF_FULL_MASK (1<<26) +#define SPDIF_AFULL_MASK (1<<27) +#define SPDIF_SYNCERR_MASK (1<<28) +#define SPDIF_LOCK_MASK (1<<29) +#define SPDIF_BEGIN_MASK (1<<30) +#define SPDIF_INTEREQ_MAKS (1<<31) + +#define SPDIF_MASK_ENABLE (SPDIF_PARITY_MASK | SPDIF_UNDERR_MASK | SPDIF_OVRERR_MASK | SPDIF_EMPTY_MASK | \ + SPDIF_AEMPTY_MASK | SPDIF_FULL_MASK | SPDIF_AFULL_MASK | SPDIF_SYNCERR_MASK | \ + SPDIF_LOCK_MASK | SPDIF_BEGIN_MASK | SPDIF_INTEREQ_MAKS) + +#define SPDIF_MASK_FIFO (SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK | SPDIF_FULL_MASK | SPDIF_AFULL_MASK) + +////INT_REG +#define SPDIF_RSAMPLERATE 0 ///[SRATEW-1:0] +#define SPDIF_PREAMBLEDEL 8 ///[PDELAYW+7:8] first B delay +#define SPDIF_PARITYO (1<<21) ///0:clear parity error +#define SPDIF_TDATA_UNDERR (1<<22) ///tx data underrun error;0:clear +#define SPDIF_RDATA_OVRERR (1<<23) ///rx data overrun error; 0:clear +#define SPDIF_FIFO_EMPTY (1<<24) ///empty; 0:clear +#define SPDIF_FIOF_AEMPTY (1<<25) ///almost empty; 0:clear +#define SPDIF_FIFO_FULL (1<<26) ///FIFO full; 0:clear +#define SPDIF_FIFO_AFULL (1<<27) ///FIFO almost full; 0:clear +#define SPDIF_SYNCERR (1<<28) ///sync error; 0:clear +#define SPDIF_LOCK (1<<29) ///sync; 0:clear +#define SPDIF_BLOCK_BEGIN (1<<30) ///new start block rx data + +#define SPDIF_INT_REG_BIT (SPDIF_PARITYO | SPDIF_TDATA_UNDERR | SPDIF_RDATA_OVRERR | SPDIF_FIFO_EMPTY | \ + SPDIF_FIOF_AEMPTY | SPDIF_FIFO_FULL | SPDIF_FIFO_AFULL | SPDIF_SYNCERR | \ + SPDIF_LOCK | SPDIF_BLOCK_BEGIN) + +#define SPDIF_ERROR_INT_STATUS (SPDIF_PARITYO | SPDIF_TDATA_UNDERR | SPDIF_RDATA_OVRERR) +#define SPDIF_FIFO_INT_STATUS (SPDIF_FIFO_EMPTY | SPDIF_FIOF_AEMPTY | SPDIF_FIFO_FULL | SPDIF_FIFO_AFULL) + +#define SPDIF_INT_PARITY_ERROR (-1) +#define SPDIF_INT_TDATA_UNDERR (-2) +#define SPDIF_INT_RDATA_OVRERR (-3) +#define SPDIF_INT_FIFO_EMPTY 1 +#define SPDIF_INT_FIFO_AEMPTY 2 +#define SPDIF_INT_FIFO_FULL 3 +#define SPDIF_INT_FIFO_AFULL 4 +#define SPDIF_INT_SYNCERR (-4) +#define SPDIF_INT_LOCK 5 ///reciever has become synchronized with input data stream +#define SPDIF_INT_BLOCK_BEGIN 6 ///start a new block in recieve data, written into FIFO + +///FIFO_CTRL +#define SPDIF_AEMPTY_THRESHOLD 0///[depth-1:0] +#define SPDIF_AFULL_THRESHOLD 16///[depth+15:16] + +///STAT_REG +#define SPDIF_FIFO_LEVEL (1<<0) +#define SPDIF_PARITY_FLAG (1<<21) ///1:error; 0:repeated +#define SPDIF_UNDERR_FLAG (1<<22) ///1:error +#define SPDIF_OVRERR_FLAG (1<<23) ///1:error +#define SPDIF_EMPTY_FLAG (1<<24) ///1:fifo empty +#define SPDIF_AEMPTY_FLAG (1<<25) ///1:fifo almost empty +#define SPDIF_FULL_FLAG (1<<26) ///1:fifo full +#define SPDIF_AFULL_FLAG (1<<27) ///1:fifo almost full +#define SPDIF_SYNCERR_FLAG (1<<28) ///1:rx sync error +#define SPDIF_LOCK_FLAG (1<<29) ///1:RX sync +#define SPDIF_BEGIN_FLAG (1<<30) ///1:start a new block +#define SPDIF_RIGHT_LEFT (1<<31) ///1:left channel received and tx into FIFO; 0:right channel received and tx into FIFO + +#define SPDIF_STAT (SPDIF_PARITY_FLAG | SPDIF_UNDERR_FLAG | SPDIF_OVRERR_FLAG | SPDIF_EMPTY_FLAG | \ + SPDIF_AEMPTY_FLAG | SPDIF_FULL_FLAG | SPDIF_AFULL_FLAG | SPDIF_SYNCERR_FLAG | \ + SPDIF_LOCK_FLAG | SPDIF_BEGIN_FLAG | SPDIF_RIGHT_LEFT) +struct sf_spdif_dev { + void __iomem *spdif_base; + struct regmap *regmap; + struct device *dev; + u32 fifo_th; + int active; + + /* data related to DMA transfers b/w i2s and DMAC */ + struct snd_dmaengine_dai_dma_data play_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; + + bool use_pio; + struct snd_pcm_substream __rcu *tx_substream; + struct snd_pcm_substream __rcu *rx_substream; + + unsigned int (*tx_fn)(struct sf_spdif_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed, snd_pcm_format_t format); + unsigned int (*rx_fn)(struct sf_spdif_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int rx_ptr, + bool *period_elapsed, snd_pcm_format_t format); + + snd_pcm_format_t format; + //unsigned int sample_bits; + unsigned int tx_ptr; + unsigned int rx_ptr; + + struct snd_dmaengine_dai_dma_data dma_data; +}; + +#if IS_ENABLED(CONFIG_SND_STARFIVE_SPDIF_PCM) +void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev); +void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev); +int sf_spdif_pcm_register(struct platform_device *pdev); +#else +void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev) { } +void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev) { } +int sf_spdif_pcm_register(struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + + +#endif /* __SND_SOC_STARFIVE_SPDIF_H */ diff --git a/sound/soc/starfive/starfive_spdif_pcm.c b/sound/soc/starfive/starfive_spdif_pcm.c new file mode 100644 index 0000000..44ecc48 --- /dev/null +++ b/sound/soc/starfive/starfive_spdif_pcm.c @@ -0,0 +1,304 @@ +/** + ****************************************************************************** + * @file sf_spdif_pcm.c + * @author StarFive Technology + * @version V1.0 + * @date 05/27/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#include +#include +#include +#include +#include "starfive_spdif.h" + +#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) +#define PERIOD_BYTES_MIN 4096 +#define PERIODS_MIN 2 + +static unsigned int sf_spdif_pcm_tx(struct sf_spdif_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed, snd_pcm_format_t format) +{ + const u16 (*p16)[2] = (void *)runtime->dma_area; + const u32 (*p32)[2] = (void *)runtime->dma_area; + u32 data[2]; + unsigned int period_pos = tx_ptr % runtime->period_size; + int i; + + for (i = 0; i < dev->fifo_th; i++) { + if (SNDRV_PCM_FORMAT_S16_LE == format) { + data[0] = p16[tx_ptr][0]; + data[1] = p16[tx_ptr][1]; + data[0] = data[0]<<8; + data[1] = data[1]<<8; + } else if (SNDRV_PCM_FORMAT_S24_LE == format) { + data[0] = p32[tx_ptr][0]; + data[1] = p32[tx_ptr][1]; + } else if (SNDRV_PCM_FORMAT_S32_LE == format) { + data[0] = p32[tx_ptr][0]; + data[1] = p32[tx_ptr][1]; + data[0] = data[0]>>8; + data[1] = data[1]>>8; + } + + iowrite32(data[0], dev->spdif_base + SPDIF_FIFO_ADDR); + iowrite32(data[1], dev->spdif_base + SPDIF_FIFO_ADDR); + period_pos++; + if (++tx_ptr >= runtime->buffer_size) { + tx_ptr = 0; + } + } + + *period_elapsed = period_pos >= runtime->period_size; + return tx_ptr; +} + +static unsigned int sf_spdif_pcm_rx(struct sf_spdif_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int rx_ptr, + bool *period_elapsed, snd_pcm_format_t format) +{ + u16 (*p16)[2] = (void *)runtime->dma_area; + u32 (*p32)[2] = (void *)runtime->dma_area; + u32 data[2]; + unsigned int period_pos = rx_ptr % runtime->period_size; + int i; + + for (i = 0; i < dev->fifo_th; i++) { + data[0] = ioread32(dev->spdif_base + SPDIF_FIFO_ADDR); + data[1] = ioread32(dev->spdif_base + SPDIF_FIFO_ADDR); + if (SNDRV_PCM_FORMAT_S16_LE == format) { + p16[rx_ptr][0] = data[0]>>8; + p16[rx_ptr][1] = data[1]>>8; + } else if (SNDRV_PCM_FORMAT_S24_LE == format) { + p32[rx_ptr][0] = data[0]; + p32[rx_ptr][1] = data[1]; + } else if (SNDRV_PCM_FORMAT_S32_LE == format) { + p32[rx_ptr][0] = data[0]<<8; + p32[rx_ptr][1] = data[1]<<8; + } + + period_pos++; + if (++rx_ptr >= runtime->buffer_size) + rx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + return rx_ptr; +} + +static const struct snd_pcm_hardware sf_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 16, +}; + +static void sf_spdif_pcm_transfer(struct sf_spdif_dev *dev, bool push) +{ + struct snd_pcm_substream *substream; + bool active, period_elapsed; + + rcu_read_lock(); + if (push) + substream = rcu_dereference(dev->tx_substream); + else + substream = rcu_dereference(dev->rx_substream); + active = substream && snd_pcm_running(substream); + if (active) { + unsigned int ptr; + unsigned int new_ptr; + + if (push) { + ptr = READ_ONCE(dev->tx_ptr); + new_ptr = dev->tx_fn(dev, substream->runtime, ptr, + &period_elapsed, dev->format); + cmpxchg(&dev->tx_ptr, ptr, new_ptr); + } else { + ptr = READ_ONCE(dev->rx_ptr); + new_ptr = dev->rx_fn(dev, substream->runtime, ptr, + &period_elapsed, dev->format); + cmpxchg(&dev->rx_ptr, ptr, new_ptr); + } + + if (period_elapsed) + snd_pcm_period_elapsed(substream); + } + rcu_read_unlock(); +} + +void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev) +{ + sf_spdif_pcm_transfer(dev, true); +} + +void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev) +{ + sf_spdif_pcm_transfer(dev, false); +} + +static int sf_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sf_spdif_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + + snd_soc_set_runtime_hwparams(substream, &sf_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = dev; + + return 0; +} + +static int sf_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int sf_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_spdif_dev *dev = runtime->private_data; + int ret; + + switch (params_channels(hw_params)) { + case 2: + break; + default: + dev_err(dev->dev, "invalid channels number\n"); + return -EINVAL; + } + + dev->format = params_format(hw_params); + switch (dev->format) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + break; + default: + dev_err(dev->dev, "invalid format\n"); + return -EINVAL; + } + + dev->tx_fn = sf_spdif_pcm_tx; + dev->rx_fn = sf_spdif_pcm_rx; + + return 0; +} + +static int sf_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_spdif_dev *dev = runtime->private_data; + int ret = 0; + + 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) { + WRITE_ONCE(dev->tx_ptr, 0); + rcu_assign_pointer(dev->tx_substream, substream); + } else { + WRITE_ONCE(dev->rx_ptr, 0); + rcu_assign_pointer(dev->rx_substream, substream); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rcu_assign_pointer(dev->tx_substream, NULL); + else + rcu_assign_pointer(dev->rx_substream, NULL); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t sf_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_spdif_dev *dev = runtime->private_data; + snd_pcm_uframes_t pos; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pos = READ_ONCE(dev->tx_ptr); + } + else { + pos = READ_ONCE(dev->rx_ptr); + } + + return pos < runtime->buffer_size ? pos : 0; +} + +static int sf_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + size_t size = sf_pcm_hardware.buffer_bytes_max; + + snd_pcm_set_managed_buffer_all(rtd->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, size, size); + + return 0; +} + +static const struct snd_soc_component_driver sf_pcm_component = { + .open = sf_pcm_open, + .close = sf_pcm_close, + .hw_params = sf_pcm_hw_params, + .trigger = sf_pcm_trigger, + .pointer = sf_pcm_pointer, + .pcm_construct = sf_pcm_new, +}; + +int sf_spdif_pcm_register(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &sf_pcm_component, + NULL, 0); +} + diff --git a/sound/soc/starfive/starfive_tdm.c b/sound/soc/starfive/starfive_tdm.c new file mode 100644 index 0000000..a80d4d6 --- /dev/null +++ b/sound/soc/starfive/starfive_tdm.c @@ -0,0 +1,439 @@ +/** + ****************************************************************************** + * @file sf_tdm.c + * @author StarFive Technology + * @version V1.0 + * @date 08/2/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "starfive_tdm.h" + +#define AUDIOC_CLK (12288000) + + +static inline u32 sf_tdm_readl(struct sf_tdm_dev *tdm, u16 reg) +{ + return readl_relaxed(tdm->tdm_base + reg); +} + +static inline void sf_tdm_writel(struct sf_tdm_dev *tdm, u16 reg, u32 val) +{ + writel_relaxed(val, tdm->tdm_base + reg); +} + +static void sf_tdm_start(struct sf_tdm_dev *tdm, struct snd_pcm_substream *substream) +{ + u32 data; + + data = sf_tdm_readl(tdm, TDM_PCMGBCR); + sf_tdm_writel(tdm, TDM_PCMGBCR, data | 0x1 | (0x1<<4)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sf_tdm_writel(tdm, TDM_PCMTXCR, sf_tdm_readl(tdm, TDM_PCMTXCR) | 0x1); + }else{ + sf_tdm_writel(tdm, TDM_PCMRXCR, sf_tdm_readl(tdm, TDM_PCMRXCR) | 0x1); + } +} + +static void sf_tdm_stop(struct sf_tdm_dev*tdm, struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sf_tdm_writel(tdm, TDM_PCMTXCR, sf_tdm_readl(tdm, TDM_PCMTXCR) & 0xffe); + }else{ + sf_tdm_writel(tdm, TDM_PCMRXCR, sf_tdm_readl(tdm, TDM_PCMRXCR) & 0xffe); + } + sf_tdm_writel(tdm, TDM_PCMGBCR, sf_tdm_readl(tdm, TDM_PCMGBCR) & 0x1e); +} + +static int sf_tdm_syncdiv(struct sf_tdm_dev *tdm) +{ + u32 sl, sscale, syncdiv; + + sl = (tdm->rx.sl >= tdm->tx.sl)? tdm->rx.sl:tdm->tx.sl; + sscale = (tdm->rx.sscale >= tdm->tx.sscale)? tdm->rx.sscale:tdm->tx.sscale; + syncdiv = tdm->pcmclk/tdm->samplerate - 1; + + if((syncdiv + 1) < (sl * sscale)){ + printk("set syncdiv failed !\n"); + return -1; + } + + if((tdm->syncm == TDM_SYNCM_LONG) && ((tdm->rx.sscale <= 1) || (tdm->tx.sscale <= 1))){ + if((syncdiv+1) <= (sl)){ + printk("set syncdiv failed ! it must be (syncdiv+1) > max[txsl, rxsl]\n"); + return -1; + } + } + + sf_tdm_writel(tdm, TDM_PCMDIV, syncdiv); + return 0; +} + +static void sf_tdm_contrl(struct sf_tdm_dev *tdm) +{ + u32 data; + + data = (tdm->clkpolity << 5) | (tdm->elm << 3) | (tdm->syncm << 2) | (tdm->mode << 1); + sf_tdm_writel(tdm, TDM_PCMGBCR, data); +} + +static void sf_tdm_config(struct sf_tdm_dev *tdm, struct snd_pcm_substream *substream) +{ + u32 datarx, datatx; + + sf_tdm_stop(tdm, substream); + sf_tdm_contrl(tdm); + sf_tdm_syncdiv(tdm); + + datarx = (tdm->rx.ifl << 11) | (tdm->rx.wl << 8) | (tdm->rx.sscale <<4) + | (tdm->rx.sl <<2) | (tdm->rx.lrj <<1); + + datatx = (tdm->tx.ifl << 11) | (tdm->tx.wl << 8) | (tdm->tx.sscale <<4) + | (tdm->tx.sl <<2) | (tdm->tx.lrj <<1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sf_tdm_writel(tdm, TDM_PCMTXCR, datatx); + } + else { + sf_tdm_writel(tdm, TDM_PCMRXCR, datarx); + } + + sf_tdm_start(tdm, substream); +} + + +#define sf_tdm_suspend NULL +#define sf_tdm_resume NULL + +static const struct snd_soc_component_driver sf_tdm_component = { + .name = "sf-tdm", + .suspend = sf_tdm_suspend, + .resume = sf_tdm_resume, +}; + +static int sf_tdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct sf_tdm_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + struct snd_dmaengine_dai_dma_data *dma_data = NULL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &dev->play_dma_data; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dma_data = &dev->capture_dma_data; + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data); + + return 0; +} + +static void sf_tdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + + +static int sf_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sf_tdm_dev *dev = snd_soc_dai_get_drvdata(dai); + u32 value; + int ret; + int chan_wl, chan_sl, chan_nr; + u32 sample_rate; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + chan_wl = TDM_8BIT_WORD_LEN; + chan_sl = TDM_8BIT_SLOT_LEN; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + chan_wl = TDM_16BIT_WORD_LEN; + chan_sl = TDM_16BIT_SLOT_LEN; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + chan_wl = TDM_24BIT_WORD_LEN; + chan_sl = TDM_32BIT_SLOT_LEN; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + chan_wl = TDM_32BIT_WORD_LEN; + chan_sl = TDM_32BIT_SLOT_LEN; + break; + + default: + dev_err(dev->dev, "tdm: unsupported PCM fmt"); + return -EINVAL; + } + + chan_nr = params_channels(params); + switch (chan_nr) { + case TWO_CHANNEL_SUPPORT: + case FOUR_CHANNEL_SUPPORT: + case SIX_CHANNEL_SUPPORT: + case EIGHT_CHANNEL_SUPPORT: + break; + + default: + dev_err(dev->dev, "channel not supported\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev->tx.wl = chan_wl; + dev->tx.sl = chan_sl; + dev->tx.sscale = chan_nr; + } + else + { + dev->rx.wl = chan_wl; + dev->rx.sl = chan_sl; + dev->rx.sscale = chan_nr; + } + + dev->samplerate = params_rate(params); + if (!dev->mode) { + sf_tdm_syncdiv(dev); + } + + sf_tdm_config(dev, substream); + + return 0; +} + + +static int sf_tdm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sf_tdm_dev *dev = snd_soc_dai_get_drvdata(dai); + + return 0; +} + + +static int sf_tdm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct sf_tdm_dev *dev = 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: + dev->active++; + sf_tdm_start(dev, substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + sf_tdm_stop(dev, substream); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int sf_tdm_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct sf_tdm_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + dev->mode = TDM_AS_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + dev->mode = TDM_AS_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + ret = -EINVAL; + break; + default: + dev_dbg(dev->dev, "tdm : Invalid master/slave format\n"); + ret = -EINVAL; + break; + } + return ret; +} + + +static const struct snd_soc_dai_ops sf_tdm_dai_ops = { + .startup = sf_tdm_startup, + .shutdown = sf_tdm_shutdown, + .hw_params = sf_tdm_hw_params, + .prepare = sf_tdm_prepare, + .trigger = sf_tdm_trigger, + .set_fmt = sf_tdm_set_fmt, +}; + +static int tdm_configure_dai(struct sf_tdm_dev *dev, + struct snd_soc_dai_driver *sf_tdm_dai, + unsigned int rates) +{ + sf_tdm_dai->playback.channels_min = TDM_MIN_CHANNEL_NUM; + sf_tdm_dai->playback.channels_max = TDM_MAX_CHANNEL_NUM; + sf_tdm_dai->playback.formats = SNDRV_PCM_FMTBIT_S8|SNDRV_PCM_FMTBIT_S16_LE|SNDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE; + sf_tdm_dai->playback.rates = rates; + + sf_tdm_dai->capture.channels_min = TDM_MIN_CHANNEL_NUM; + sf_tdm_dai->capture.channels_max = TDM_MAX_CHANNEL_NUM; + sf_tdm_dai->capture.formats = SNDRV_PCM_FMTBIT_S8|SNDRV_PCM_FMTBIT_S16_LE|SNDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE; + sf_tdm_dai->capture.rates = rates; + + return 0; +} + +static int sf_tdm_dai_probe(struct snd_soc_dai *dai) +{ + struct sf_tdm_dev *dev = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, &dev->capture_dma_data); + return 0; +} + +static int sf_tdm_probe(struct platform_device *pdev) +{ + struct sf_tdm_dev *dev; + struct resource *res; + int ret, irq; + struct snd_soc_dai_driver *sf_tdm_dai; + const char *clk_id; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + sf_tdm_dai = devm_kzalloc(&pdev->dev, sizeof(*sf_tdm_dai), GFP_KERNEL); + if (!sf_tdm_dai) + return -ENOMEM; + + sf_tdm_dai->ops = &sf_tdm_dai_ops; + sf_tdm_dai->probe = sf_tdm_dai_probe; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->tdm_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->tdm_base)) + return PTR_ERR(dev->tdm_base); + + dev->dev = &pdev->dev; + + ret = tdm_configure_dai(dev, sf_tdm_dai, SNDRV_PCM_RATE_8000_192000); + if (ret < 0) + return ret; + + int osc_clk = 12288000; + u32 pcmclk = 4096000; + + dev->clkpolity = TDM_TX_RASING_RX_FALLING; + dev->tritxen = 1; + dev->elm = TDM_ELM_LATE; + dev->syncm = TDM_SYNCM_SHORT; + dev->mode = TDM_AS_MASTER; + dev->rx.ifl = TDM_FIFO_HALF; + dev->tx.ifl = TDM_FIFO_HALF; + dev->rx.sscale = 1; + dev->tx.sscale = 1; + dev->rx.lrj = TDM_LEFT_JUSTIFT; + dev->tx.lrj = TDM_LEFT_JUSTIFT; + + dev->samplerate = 16000; + dev->pcmclk = pcmclk; + + dev->play_dma_data.addr = res->start + TDM_TXDMA; + dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dev->play_dma_data.fifo_size = TDM_FIFO_DEPTH/2; + dev->play_dma_data.maxburst = 16; + + dev->capture_dma_data.addr = res->start + TDM_RXDMA; + dev->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dev->capture_dma_data.fifo_size = TDM_FIFO_DEPTH/2; + dev->capture_dma_data.maxburst = 16; + +#if 0 + if (!dev->mode) { + dev->clk = devm_clk_get(&pdev->dev, clk_id); + + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + ret = clk_prepare_enable(dev->clk); + if (ret < 0) + return ret; + } +#endif + + dev_set_drvdata(&pdev->dev, dev); + ret = devm_snd_soc_register_component(&pdev->dev, &sf_tdm_component, + sf_tdm_dai, 1); + if (ret != 0) { + dev_err(&pdev->dev, "not able to register dai\n"); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + 0); + if (ret) { + dev_err(&pdev->dev, "could not register pcm: %d\n", + ret); + return ret; + } + + return 0; +} + + +static int sf_tdm_dev_remove(struct platform_device *pdev) +{ + return 0; +} +static const struct of_device_id sf_tdm_of_match[] = { + {.compatible = "starfive,sf-tdm",}, + {} +}; +MODULE_DEVICE_TABLE(of, sf_tdm_of_match); + +static struct platform_driver sf_tdm_driver = { + + .driver = { + .name = "sf-tdm", + .of_match_table = sf_tdm_of_match, + }, + .probe = sf_tdm_probe, + .remove = sf_tdm_dev_remove, +}; +module_platform_driver(sf_tdm_driver); + +MODULE_AUTHOR("jenny.zhang "); +MODULE_DESCRIPTION("starfive TDM Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/starfive/starfive_tdm.h b/sound/soc/starfive/starfive_tdm.h new file mode 100644 index 0000000..ff88953 --- /dev/null +++ b/sound/soc/starfive/starfive_tdm.h @@ -0,0 +1,138 @@ +/** + ****************************************************************************** + * @file sf_tdm.h + * @author StarFive Technology + * @version V1.0 + * @date 08/2/2021 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + *

© COPYRIGHT 20120 Shanghai StarFive Technology Co., Ltd.

+ */ + +#ifndef __SND_SOC_STARFIVE_TDM_H +#define __SND_SOC_STARFIVE_TDM_H + +#include +#include +#include +#include +#include +#include +#include + +#define TDM_PCMGBCR (0x00) +#define TDM_PCMTXCR (0x04) +#define TDM_PCMRXCR (0x08) +#define TDM_PCMDIV (0x0c) + +/* DMA registers */ +#define TDM_RXDMA (0xc0) +#define TDM_TXDMA (0xd0) + +#define TDM_FIFO_DEPTH (16) + +#define TDM_MAX_CHANNEL_NUM 8 +#define TDM_MIN_CHANNEL_NUM 2 + +#define TWO_CHANNEL_SUPPORT 2 +#define FOUR_CHANNEL_SUPPORT 4 +#define SIX_CHANNEL_SUPPORT 6 +#define EIGHT_CHANNEL_SUPPORT 8 + +enum TDM_MODE +{ + TDM_AS_MASTER = 0, + TDM_AS_SLAVE, +}; + +enum TDM_CLKPOL { + /* tx raising and rx falling */ + TDM_TX_RASING_RX_FALLING = 0, + /* tx raising and rx falling */ + TDM_TX_FALLING_RX_RASING, +}; + +enum TDM_ELM { + /* only work while SYNCM=0 */ + TDM_ELM_LATE = 0, + TDM_ELM_EARLY, +}; + +enum TDM_SYNCM { + /* short frame sync */ + TDM_SYNCM_SHORT = 0, + /* long frame sync */ + TDM_SYNCM_LONG, +}; + +enum TDM_IFL { + /* FIFO to send or recieve : half-1/2, Quarter-1/4*/ + TDM_FIFO_HALF = 0, + TDM_FIFO_QUARTER, +}; + +enum TDM_WL { + /* send or recieve word length */ + TDM_8BIT_WORD_LEN = 0, + TDM_16BIT_WORD_LEN, + TDM_20BIT_WORD_LEN, + TDM_24BIT_WORD_LEN, + TDM_32BIT_WORD_LEN, +}; + +enum TDM_SL { + /* send or recieve slot length */ + TDM_8BIT_SLOT_LEN = 0, + TDM_16BIT_SLOT_LEN, + TDM_32BIT_SLOT_LEN, +}; + +enum TDM_LRJ { + /* left-justify or right-justify */ + TDM_RIGHT_JUSTIFY = 0, + TDM_LEFT_JUSTIFT, +}; + +typedef struct tdm_chan_cfg { + enum TDM_IFL ifl; + enum TDM_WL wl; + unsigned char sscale; + enum TDM_SL sl; + enum TDM_LRJ lrj; + unsigned char enable; +}tdm_chan_cfg_t; + +struct sf_tdm_dev { + void __iomem *tdm_base; + struct device *dev; + struct clk *clk; + int active; + + enum TDM_CLKPOL clkpolity; + enum TDM_ELM elm; + enum TDM_SYNCM syncm; + enum TDM_MODE mode; + unsigned char tritxen; + + tdm_chan_cfg_t tx; + tdm_chan_cfg_t rx; + + u16 syncdiv; + u32 samplerate; + u32 pcmclk; + + /* data related to DMA transfers b/w tdm and DMAC */ + struct snd_dmaengine_dai_dma_data play_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; +}; + +#endif /* __SND_SOC_STARFIVE_TDM_H */ -- 2.7.4