diff --git a/Documentation/devicetree/bindings/sound/rockchip,vad.txt b/Documentation/devicetree/bindings/sound/rockchip,vad.txt new file mode 100644 index 000000000000..3ebe3c0218ad --- /dev/null +++ b/Documentation/devicetree/bindings/sound/rockchip,vad.txt @@ -0,0 +1,31 @@ +* Rockchip VAD controller + +Required properties: + +- compatible: "rockchip,vad" +- reg: physical base address of the controller and length of memory mapped + region. +- clocks: a list of phandle + clock-specifer pairs, one for each entry in clock-names. +- clock-names: should contain following: +- interrupts: should contain the vad interrupt. +- rockchip,audio-src: the audio src for vad. +- rockchip,det-channel: the channel(0~7) is used for detection. +- rockchip,mode: vad work mode. + - 0: begin to store the data after voice detected. + - 1: begin to store the data after vad is enabled. + - 2: don't store the data. + +Example for rk3308 VAD controller: + +vad: vad@ff3c0000 { + compatible = "rockchip,rk3308-vad", "rockchip,vad"; + reg = <0x0 0xff3c0000 0x0 0x10000>, <0x0 0xfff88000 0x0 0x38000>; + reg-names = "vad", "vad-memory"; + clocks = <&cru HCLK_VAD>; + clock-names = "hclk"; + interrupts = ; + rockchip,audio-src = <0>; + rockchip,det-channel = <0>; + rockchip,mode = <0>; + status = "disabled"; +}; diff --git a/include/sound/pcm.h b/include/sound/pcm.h index b0be09279943..e6dc30362a03 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -1399,6 +1399,25 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) return 1ULL << (__force int) pcm_format; } +#ifdef CONFIG_SND_SOC_ROCKCHIP_VAD +snd_pcm_sframes_t snd_pcm_vad_read(struct snd_pcm_substream *substream, + void __user *buf, snd_pcm_uframes_t frames); +/** + * snd_pcm_vad_avail - Get the available (readable) space for vad + * @runtime: PCM substream instance + * + * Result is between 0 ... (boundary - 1) + */ +snd_pcm_uframes_t snd_pcm_vad_avail(struct snd_pcm_substream *substream); +/** + * snd_pcm_vad_attached - Check whether vad is attached to substream or not + * @substream: PCM substream instance + * + * Result is true for attached or false for detached + */ +bool snd_pcm_vad_attached(struct snd_pcm_substream *substream); +#endif + /* printk helpers */ #define pcm_err(pcm, fmt, args...) \ dev_err((pcm)->card->dev, fmt, ##args) diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig index bbeb8ff009b5..3d4ba424734a 100644 --- a/sound/soc/rockchip/Kconfig +++ b/sound/soc/rockchip/Kconfig @@ -48,6 +48,13 @@ config SND_SOC_ROCKCHIP_SPDIF Say Y or M if you want to add support for SPDIF driver for Rockchip SPDIF transceiver device. +config SND_SOC_ROCKCHIP_VAD + tristate "Rockchip Voice Activity Detection Driver" + depends on CLKDEV_LOOKUP && SND_SOC_ROCKCHIP + help + Say Y or M if you want to add support for VAD driver for + Rockchip VAD device. + config SND_SOC_ROCKCHIP_DA7219 tristate "ASoC support for Rockchip boards using a DA7219 codec" depends on SND_SOC_ROCKCHIP && I2C && GPIOLIB diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile index dddff3eae374..d7e5a59b6dd8 100644 --- a/sound/soc/rockchip/Makefile +++ b/sound/soc/rockchip/Makefile @@ -3,11 +3,13 @@ snd-soc-rockchip-i2s-objs := rockchip_i2s.o snd-soc-rockchip-i2s-tdm-objs := rockchip_i2s_tdm.o snd-soc-rockchip-pdm-objs := rockchip_pdm.o snd-soc-rockchip-spdif-objs := rockchip_spdif.o +snd-soc-rockchip-vad-objs := rockchip_vad.o obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S) += snd-soc-rockchip-i2s.o obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S_TDM) += snd-soc-rockchip-i2s-tdm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_PDM) += snd-soc-rockchip-pdm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_SPDIF) += snd-soc-rockchip-spdif.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_VAD) += snd-soc-rockchip-vad.o snd-soc-rockchip-da7219-objs := rockchip_da7219.o snd-soc-rockchip-hdmi-analog-objs := rockchip_hdmi_analog.o diff --git a/sound/soc/rockchip/rockchip_vad.c b/sound/soc/rockchip/rockchip_vad.c new file mode 100644 index 000000000000..32a2759ff24d --- /dev/null +++ b/sound/soc/rockchip/rockchip_vad.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip VAD driver + * + * Copyright (C) 2018 Fuzhou Rockchip Electronics Co., Ltd + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rockchip_vad.h" + +#define DRV_NAME "rockchip-vad" + +#define VAD_RATES SNDRV_PCM_RATE_8000_192000 +#define VAD_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +#define ACODEC_REG_NUM 28 + +struct vad_buf { + void __iomem *begin; + void __iomem *end; + void __iomem *cur; + void __iomem *pos; + int size; + bool loop; +}; + +struct rockchip_vad { + struct device *dev; + struct clk *hclk; + struct regmap *regmap; + unsigned int memphy; + void __iomem *membase; + struct vad_buf vbuf; + int mode; + u32 audio_src; + u32 audio_src_addr; + u32 audio_chnl; + struct dentry *debugfs_dir; +}; + +struct audio_src_addr_map { + u32 id; + u32 addr; +}; + +static int rockchip_vad_stop(struct rockchip_vad *vad) +{ + unsigned int val; + struct vad_buf *vbuf = &vad->vbuf; + + regmap_update_bits(vad->regmap, VAD_CTRL, VAD_EN_MASK, VAD_DISABLE); + regmap_read(vad->regmap, VAD_RAM_END_ADDR, &val); + vbuf->end = vbuf->begin + (val - vad->memphy) + 0x8; + regmap_read(vad->regmap, VAD_INT, &val); + val &= BIT(8); + vbuf->loop = val; + regmap_read(vad->regmap, VAD_RAM_CUR_ADDR, &val); + if (!val) { + vbuf->size = 0; + vbuf->cur = vbuf->begin; + return 0; + } + vbuf->cur = vbuf->begin + (val - vad->memphy); + + if (vbuf->loop) { + vbuf->size = vbuf->end - vbuf->begin; + vbuf->pos = vbuf->cur; + } else { + vbuf->size = vbuf->cur - vbuf->begin; + vbuf->end = vbuf->cur; + vbuf->pos = vbuf->begin; + } + + dev_info(vad->dev, + "begin: %p, cur: %p, end: %p, size: %d, loop: %d\n", + vbuf->begin, vbuf->cur, vbuf->end, vbuf->size, vbuf->loop); + + return 0; +} + +static int rockchip_vad_setup(struct rockchip_vad *vad) +{ + struct regmap *regmap = vad->regmap; + u32 val, mask; + + regmap_write(regmap, VAD_IS_ADDR, vad->audio_src_addr); + + val = NOISE_ABS(200); + mask = NOISE_ABS_MASK; + /* regmap_update_bits(regmap, VAD_DET_CON5, mask, val); */ + val = VAD_DET_CHNL(vad->audio_chnl); + val |= vad->audio_src; + val |= vad->mode << VAD_MODE_SHIFT; + val |= SRC_ADDR_MODE_INC | SRC_BURST_INCR8; + val |= VAD_EN; + + mask = VAD_DET_CHNL_MASK | AUDIO_CHNL_NUM_MASK | + AUDIO_SRC_SEL_MASK | VAD_MODE_MASK | + SRC_ADDR_MODE_MASK | SRC_BURST_MASK | + VAD_EN_MASK; + + regmap_update_bits(regmap, VAD_CTRL, mask, val); + + val = ERR_INT_EN | VAD_DET_INT_EN; + mask = ERR_INT_EN_MASK | VAD_DET_INT_EN_MASK; + + regmap_update_bits(regmap, VAD_INT, mask, val); + + return 0; +} + +static struct rockchip_vad *substream_get_drvdata(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rockchip_vad *vad = NULL; + int i; + + if (PCM_RUNTIME_CHECK(substream)) + return NULL; + if (!rtd) + return NULL; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + if (strstr(codec_dai->name, "vad")) + vad = snd_soc_codec_get_drvdata(codec_dai->codec); + } + + return vad; +} + +snd_pcm_sframes_t snd_pcm_vad_read(struct snd_pcm_substream *substream, + void __user *buf, snd_pcm_uframes_t frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rockchip_vad *vad = NULL; + struct vad_buf *vbuf; + snd_pcm_uframes_t avail; + int bytes; + + vad = substream_get_drvdata(substream); + + if (!vad) + return -EFAULT; + + vbuf = &vad->vbuf; + + avail = snd_pcm_vad_avail(substream); + avail = avail > frames ? frames : avail; + bytes = frames_to_bytes(runtime, avail); + + if (bytes <= 0) + return -EFAULT; + + dev_dbg(vad->dev, + "begin: %p, pos: %p, end: %p, size: %d, bytes: %d\n", + vbuf->begin, vbuf->pos, vbuf->end, vbuf->size, bytes); + if (!vbuf->loop) { + if (copy_to_user_fromio(buf, vbuf->pos, bytes)) + return -EFAULT; + vbuf->pos += bytes; + } else { + if ((vbuf->pos + bytes) <= vbuf->end) { + if (copy_to_user_fromio(buf, vbuf->pos, bytes)) + return -EFAULT; + vbuf->pos += bytes; + } else { + int part1 = vbuf->end - vbuf->pos; + int part2 = bytes - part1; + + copy_to_user_fromio(buf, vbuf->pos, part1); + copy_to_user_fromio(buf + part1, vbuf->begin, part2); + vbuf->pos = vbuf->begin + part2; + } + } + + vbuf->size -= bytes; + dev_dbg(vad->dev, + "begin: %p, pos: %p, end: %p, size: %d, bytes: %d\n", + vbuf->begin, vbuf->pos, vbuf->end, vbuf->size, bytes); + + return avail; +} +EXPORT_SYMBOL(snd_pcm_vad_read); + +/** + * snd_pcm_vad_avail - Get the available (readable) space for vad + * @runtime: PCM substream instance + * + * Result is between 0 ... (boundary - 1) + */ +snd_pcm_uframes_t snd_pcm_vad_avail(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rockchip_vad *vad = NULL; + struct vad_buf *vbuf; + + vad = substream_get_drvdata(substream); + + if (!vad) + return 0; + + vbuf = &vad->vbuf; + + if (vbuf->size <= 0) + return 0; + + dev_info(vad->dev, "%s, %d frames\n", + __func__, (int)bytes_to_frames(runtime, vbuf->size)); + return bytes_to_frames(runtime, vbuf->size); +} +EXPORT_SYMBOL(snd_pcm_vad_avail); + +/** + * snd_pcm_vad_attached - Check whether vad is attached to substream or not + * @substream: PCM substream instance + * + * Result is true for attached or false for detached + */ +bool snd_pcm_vad_attached(struct snd_pcm_substream *substream) +{ + struct rockchip_vad *vad = NULL; + + vad = substream_get_drvdata(substream); + + return vad ? true : false; +} +EXPORT_SYMBOL(snd_pcm_vad_attached); + +static bool rockchip_vad_writeable_reg(struct device *dev, unsigned int reg) +{ + return true; +} + +static bool rockchip_vad_readable_reg(struct device *dev, unsigned int reg) +{ + return true; +} + +static bool rockchip_vad_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case VAD_INT: + case VAD_RAM_CUR_ADDR: + case VAD_DET_CON5: + return true; + default: + return false; + } +} + +static const struct reg_default rockchip_vad_reg_defaults[] = { + {VAD_CTRL, 0x03000000}, + {VAD_DET_CON0, 0x00024020}, + {VAD_DET_CON1, 0x00ff0064}, + {VAD_DET_CON2, 0x3bf5e663}, + {VAD_DET_CON3, 0x3bf58817}, + {VAD_DET_CON4, 0x382b8858}, + {VAD_RAM_BEGIN_ADDR, 0xfff88000}, + {VAD_RAM_END_ADDR, 0xfffbfff8}, +}; + +static const struct regmap_config rockchip_vad_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = VAD_INT, + .reg_defaults = rockchip_vad_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(rockchip_vad_reg_defaults), + .writeable_reg = rockchip_vad_writeable_reg, + .readable_reg = rockchip_vad_readable_reg, + .volatile_reg = rockchip_vad_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static const struct audio_src_addr_map addr_maps[] = { + {0, 0xff300800}, + {1, 0xff310800}, + {2, 0xff320800}, + {3, 0xff330800}, + {4, 0xff380400} +}; + +static int rockchip_vad_get_audio_src_address(struct rockchip_vad *vad) +{ + const struct audio_src_addr_map *map; + int i; + + for (i = 0; i < ARRAY_SIZE(addr_maps); i++) { + map = &addr_maps[i]; + if (vad->audio_src == map->id) { + vad->audio_src_addr = map->addr; + return 0; + } + } + + return -ENODEV; +} + +static irqreturn_t rockchip_vad_irq(int irqno, void *dev_id) +{ + struct rockchip_vad *vad = dev_id; + unsigned int val; + + regmap_read(vad->regmap, VAD_INT, &val); + regmap_write(vad->regmap, VAD_INT, val); + + dev_dbg(vad->dev, "irq 0x%08x\n", val); + + return IRQ_HANDLED; +} + +static const struct reg_sequence rockchip_vad_acodec_adc_enable[] = { + { VAD_OD_ADDR0, 0x36261606 }, + { VAD_D_DATA0, 0x51515151 }, + { VAD_OD_ADDR1, 0x30201000 }, + { VAD_D_DATA1, 0xbbbbbbbb }, + { VAD_OD_ADDR2, 0x32221202 }, + { VAD_D_DATA2, 0x11111111 }, + { VAD_OD_ADDR3, 0x35251505 }, + { VAD_D_DATA3, 0x77777777 }, + { VAD_OD_ADDR4, 0x32221202 }, + { VAD_D_DATA4, 0x33333333 }, + { VAD_OD_ADDR5, 0x30201000 }, + { VAD_D_DATA5, 0xffffffff }, + { VAD_OD_ADDR6, 0x32221202 }, + { VAD_D_DATA6, 0x77777777 }, +}; + +static int rockchip_vad_config_acodec(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct rockchip_vad *vad = snd_soc_codec_get_drvdata(codec); + unsigned int val = 0; + + val = ACODEC_BASE + ACODEC_ADC_ANA_CON0; + regmap_write(vad->regmap, VAD_ID_ADDR, val); + + regmap_multi_reg_write(vad->regmap, rockchip_vad_acodec_adc_enable, + ARRAY_SIZE(rockchip_vad_acodec_adc_enable)); + + regmap_update_bits(vad->regmap, VAD_CTRL, ACODE_CFG_REG_NUM_MASK, + ACODE_CFG_REG_NUM(ACODEC_REG_NUM)); + regmap_update_bits(vad->regmap, VAD_CTRL, CFG_ACODE_AFTER_DET_EN_MASK, + CFG_ACODE_AFTER_DET_EN); + + return 0; +} + +static int rockchip_vad_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct rockchip_vad *vad = snd_soc_codec_get_drvdata(codec); + unsigned int val = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = AUDIO_CHNL_16B; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + val = AUDIO_CHNL_24B; + break; + default: + return -EINVAL; + } + + regmap_update_bits(vad->regmap, VAD_CTRL, AUDIO_CHNL_BW_MASK, val); + regmap_update_bits(vad->regmap, VAD_CTRL, AUDIO_CHNL_NUM_MASK, + AUDIO_CHNL_NUM(params_channels(params))); + + /* + * config acodec + * audio_src 2/3 is connected to acodec + */ + val = vad->audio_src >> AUDIO_SRC_SEL_SHIFT; + if (val == 2 || val == 3) + rockchip_vad_config_acodec(params, dai); + + return 0; +} + +static int rockchip_vad_enable_cpudai(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai; + int ret = 0; + + if (PCM_RUNTIME_CHECK(substream)) + return -EFAULT; + if (!rtd) + return -EFAULT; + + cpu_dai = rtd->cpu_dai; + + pm_runtime_get_sync(cpu_dai->dev); + + if (cpu_dai->driver->ops && cpu_dai->driver->ops->trigger) + ret = cpu_dai->driver->ops->trigger(substream, + SNDRV_PCM_TRIGGER_START, + cpu_dai); + + return ret; +} + +static void rockchip_vad_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct rockchip_vad *vad = snd_soc_codec_get_drvdata(codec); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return; + + rockchip_vad_enable_cpudai(substream); + rockchip_vad_setup(vad); +} + +static int rockchip_vad_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct rockchip_vad *vad = snd_soc_codec_get_drvdata(codec); + + 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) + return 0; + rockchip_vad_stop(vad); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + break; + } + + return 0; +} + +static struct snd_soc_dai_ops rockchip_vad_dai_ops = { + .hw_params = rockchip_vad_hw_params, + .shutdown = rockchip_vad_pcm_shutdown, + .trigger = rockchip_vad_trigger, +}; + +static struct snd_soc_dai_driver vad_dai = { + .name = "vad", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = VAD_RATES, + .formats = VAD_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = VAD_RATES, + .formats = VAD_FORMATS, + }, + .ops = &rockchip_vad_dai_ops, +}; + +static struct snd_soc_codec_driver soc_vad_codec; + +#if defined(CONFIG_DEBUG_FS) +#include +#include +#include + +static int rockchip_vad_debugfs_reg_show(struct seq_file *s, void *v) +{ + struct rockchip_vad *vad = s->private; + unsigned int i; + unsigned int val; + + for (i = VAD_CTRL; i <= VAD_INT; i += 4) { + regmap_read(vad->regmap, i, &val); + if (!(i % 16)) + seq_printf(s, "\n%08x: ", i); + seq_printf(s, "%08x ", val); + } + + return 0; +} + +static ssize_t rockchip_vad_debugfs_reg_write(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rockchip_vad *vad = ((struct seq_file *)file->private_data)->private; + unsigned int reg, val; + char kbuf[24]; + + if (count >= sizeof(kbuf)) + return -EINVAL; + + if (copy_from_user(kbuf, buf, count)) + return -EFAULT; + kbuf[count] = '\0'; + if (sscanf(kbuf, "%x %x", ®, &val) != 2) + return -EFAULT; + + regmap_write(vad->regmap, reg, val); + + return count; +} + +static int rockchip_vad_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, rockchip_vad_debugfs_reg_show, inode->i_private); +} + +static const struct file_operations rockchip_vad_reg_debugfs_fops = { + .owner = THIS_MODULE, + .open = rockchip_vad_debugfs_open, + .read = seq_read, + .write = rockchip_vad_debugfs_reg_write, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +static int rockchip_vad_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct rockchip_vad *vad; + struct resource *res; + void __iomem *regbase; + int irq; + int ret; + + vad = devm_kzalloc(&pdev->dev, sizeof(*vad), GFP_KERNEL); + if (!vad) + return -ENOMEM; + + vad->dev = &pdev->dev; + + of_property_read_u32(np, "rockchip,mode", &vad->mode); + of_property_read_u32(np, "rockchip,audio-src", &vad->audio_src); + ret = rockchip_vad_get_audio_src_address(vad); + if (ret) + return ret; + + vad->audio_src = vad->audio_src << AUDIO_SRC_SEL_SHIFT; + of_property_read_u32(np, "rockchip,det-channel", &vad->audio_chnl); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vad"); + regbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regbase)) + return PTR_ERR(regbase); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "vad-memory"); + vad->memphy = res->start; + vad->membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(vad->membase)) + return PTR_ERR(vad->membase); + + vad->hclk = devm_clk_get(&pdev->dev, "hclk"); + if (IS_ERR(vad->hclk)) + return PTR_ERR(vad->hclk); + + ret = clk_prepare_enable(vad->hclk); + if (ret) + return ret; + + vad->regmap = devm_regmap_init_mmio(&pdev->dev, regbase, + &rockchip_vad_regmap_config); + if (IS_ERR(vad->regmap)) { + ret = PTR_ERR(vad->regmap); + goto err; + } + + regmap_write(vad->regmap, VAD_RAM_BEGIN_ADDR, vad->memphy); + vad->vbuf.begin = vad->membase; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err; + } + + ret = devm_request_irq(&pdev->dev, irq, rockchip_vad_irq, + 0, dev_name(&pdev->dev), vad); + if (ret < 0) + goto err; + +#if defined(CONFIG_DEBUG_FS) + vad->debugfs_dir = debugfs_create_dir("vad", NULL); + if (IS_ERR(vad->debugfs_dir)) + dev_err(&pdev->dev, "failed to create debugfs dir for vad!\n"); + else + debugfs_create_file("reg", 0644, vad->debugfs_dir, vad, + &rockchip_vad_reg_debugfs_fops); +#endif + + platform_set_drvdata(pdev, vad); + return snd_soc_register_codec(&pdev->dev, &soc_vad_codec, + &vad_dai, 1); +err: + clk_disable_unprepare(vad->hclk); + return ret; +} + +static int rockchip_vad_remove(struct platform_device *pdev) +{ + struct rockchip_vad *vad = dev_get_drvdata(&pdev->dev); + + if (!IS_ERR(vad->hclk)) + clk_disable_unprepare(vad->hclk); + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static const struct of_device_id rockchip_vad_match[] = { + { .compatible = "rockchip,vad", }, + {}, +}; + +static struct platform_driver rockchip_vad_driver = { + .probe = rockchip_vad_probe, + .remove = rockchip_vad_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(rockchip_vad_match), + }, +}; +module_platform_driver(rockchip_vad_driver); + +MODULE_DESCRIPTION("Rockchip VAD Controller"); +MODULE_AUTHOR("Andy Yan "); +MODULE_AUTHOR("Sugar Zhang "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, rockchip_vad_match); diff --git a/sound/soc/rockchip/rockchip_vad.h b/sound/soc/rockchip/rockchip_vad.h new file mode 100644 index 000000000000..8cd51e34ae9f --- /dev/null +++ b/sound/soc/rockchip/rockchip_vad.h @@ -0,0 +1,182 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip VAD driver + * + * Copyright (C) 2018 Fuzhou Rockchip Electronics Co., Ltd + * + */ + +#ifndef _ROCKCHIP_VAD_H +#define _ROCKCHIP_VAD_H + +#define VAD_CTRL 0x00 +#define VAD_DET_CHNL_SHIFT 29 +#define VAD_DET_CHNL_MASK GENMASK(31, 29) +#define VAD_DET_CHNL(x) ((x) << VAD_DET_CHNL_SHIFT) +#define AUDIO_24BIT_SAT_SHIFT 28 +#define AUDIO_24BIT_SAT_MASK BIT(28) +#define AUDIO_H16B 0 +#define AUDIO_SAT_24TO16 BIT(28) +#define AUDIO_24BIT_ALIGN_MODE_SHIFT 27 +#define AUDIO_24BIT_ALIGN_MODE_MASK BIT(27) +#define AUDIO_24BIT_ALIGN_8_31B 0 +#define AUDIO_24BIT_ALIGN_0_23B BIT(27) +#define AUDIO_CHNL_BW_SHIFT 26 +#define AUDIO_CHNL_BW_MASK BIT(26) +#define AUDIO_CHNL_16B 0 +#define AUDIO_CHNL_24B BIT(26) +#define AUDIO_CHNL_NUM_SHIFT 23 +#define AUDIO_CHNL_NUM_MASK GENMASK(25, 23) +#define AUDIO_CHNL_NUM(x) ((x - 1) << AUDIO_CHNL_NUM_SHIFT) +#define CFG_ACODE_AFTER_DET_EN_SHIFT 22 +#define CFG_ACODE_AFTER_DET_EN_MASK BIT(22) +#define CFG_ACODE_AFTER_DET_EN BIT(22) +#define VAD_MODE_SHIFT 20 +#define VAD_MODE_MASK GENMASK(21, 20) +#define STORE_DATA_VAD_DET_ONLY 0 +#define STORE_DATA_ALL (1 << VAD_MODE_SHIFT) +#define NO_STORE_DATA (2 << VAD_MODE_SHIFT) +#define ACODE_CFG_REG_NUM_SHIFT 15 +#define ACODE_CFG_REG_NUM_MASK GENMASK(19, 15) +#define ACODE_CFG_REG_NUM(x) ((x - 1) << ACODE_CFG_REG_NUM_SHIFT) +#define SRC_ADDR_MODE_SHIFT 14 +#define SRC_ADDR_MODE_MASK BIT(14) +#define SRC_ADDR_MODE_INC 0 +#define SRC_ADDR_MODE_FIXED BIT(14) +#define INCR_BURST_LEN_SHIFT 10 +#define INCR_BURST_LEN_MASK GENMASK(13, 10) +#define INCR_BURST_LEN(x) ((x) << INCR_BURST_LEN_SHIFT) +#define SRC_BURST_NUM_SHIFT 7 +#define SRC_BURST_NUM_MASK GENMASK(9, 7) +#define SRC_BURST_NUM(x) ((x - 1) << SRC_BURST_NUM_SHIFT) +#define SRC_BURST_SHIFT 4 +#define SRC_BURST_MASK GENMASK(6, 4) +#define SRC_BURST_SIGNLE 0 +#define SRC_BURST_INCR (1 << SRC_BURST_SHIFT) +#define SRC_BURST_INCR4 (3 << SRC_BURST_SHIFT) +#define SRC_BURST_INCR8 (5 << SRC_BURST_SHIFT) +#define SRC_BURST_INCR16 (7 << SRC_BURST_SHIFT) +#define AUDIO_SRC_SEL_SHIFT 1 +#define AUDIO_SRC_SEL_MASK GENMASK(3, 1) +#define AUDIO_SRC_SEL_I2S0 0 +#define AUDIO_SRC_SEL_I2S1 (1 << AUDIO_SRC_SEL_MASK) +#define AUDIO_SRC_SEL_I2S2 (2 << AUDIO_SRC_SEL_MASK) +#define AUDIO_SRC_SEL_I2S3 (3 << AUDIO_SRC_SEL_MASK) +#define AUDIO_SRC_SEL_PDM (4 << AUDIO_SRC_SEL_MASK) +#define VAD_EN_SHIFT 0 +#define VAD_EN_MASK BIT(0) +#define VAD_EN BIT(0) +#define VAD_DISABLE 0 +#define VAD_IS_ADDR 4 +#define VAD_ID_ADDR 8 +#define VAD_OD_ADDR0 0x0c +#define VAD_OD_ADDR1 0x10 +#define VAD_OD_ADDR2 0x14 +#define VAD_OD_ADDR3 0x18 +#define VAD_OD_ADDR4 0x1c +#define VAD_OD_ADDR5 0x20 +#define VAD_OD_ADDR6 0x24 +#define VAD_OD_ADDR7 0x28 +#define VAD_D_DATA0 0x2c +#define VAD_D_DATA1 0x30 +#define VAD_D_DATA2 0x34 +#define VAD_D_DATA3 0x38 +#define VAD_D_DATA4 0x3c +#define VAD_D_DATA5 0x40 +#define VAD_D_DATA6 0x44 +#define VAD_D_DATA7 0x48 + +#define VAD_TIMEOUT 0x4c +#define WORK_TIMEOUT_EN_MASK BIT(31) +#define WORK_TIMEOUT_EN BIT(31) +#define WORK_TIMEOUT_DISABLE 0 +#define IDLE_TIMEOUT_EN_MASK BIT(30) +#define IDLE_TIMEOUT_EN BIT(30) +#define IDLE_TIMEOUT_DISABLE 0 +#define WORK_TIMEOUT_THD_SHIFT 20 +#define WORK_TIMEOUT_THD_MASK GENMASK(29, 20) +#define WORK_TIMEOUT_THD(x) ((x) << WORK_TIMEOUT_THD_SHIFT) +#define IDLE_TIMEOUT_THD_SHIFT 0 +#define IDLE_TIMEOUT_THD_MASK GENMASK(19, 0) +#define IDLE_TIMEOUT_THD(x) ((x) << IDLE_TIMEOUT_THD_SHIFT) + +#define VAD_RAM_BEGIN_ADDR 0x50 +#define VAD_RAM_END_ADDR 0x54 +#define VAD_RAM_CUR_ADDR 0x58 +#define VAD_DET_CON0 0x5c +#define VAD_CON_THD_SHIFT 16 +#define VAD_CON_THD_MASK GENMASK(23, 16) +#define VAD_CON_THD(x) ((x) << VAD_CON_THD_SHIFT) +#define NOISE_LEVEL_SHIFT 12 +#define NOISE_LEVEL_MASK GENMASK(14, 12) +#define NOISE_LEVEL(x) ((x) << NOISE_LEVEL_SHIFT) +#define GAIN_MASK GENMASK(9, 0) +#define GAIN(x) (x) + +#define VAD_DET_CON1 0x60 +#define MIN_NOISE_FIND_MODE_SHIFT 30 +#define MIN_NOISE_FIN_MODE_MASK BIT(30) +#define MIN_NOISE_FIND_MODE0 0 +#define MIN_NOISE_FIND_MODE1 BIT(30) +#define NOISE_CLEAN_MODE_SHIFT 29 +#define NOISE_CLEAN_MODE_MASK BIT(29) +#define NOISE_CLEAN_MODE0 0 +#define NOISE_CLEAN_MODE1 BIT(29) +#define NOISE_CLK_FORCE_EN_MASK BIT(28) +#define NOISE_CLK_AUTO_GATING 0 +#define NOISE_CLK_FORCE_EN BIT(28) +#define NOISE_SAMPLE_NUM_SHIFT 16 +#define NOISE_SAMPLE_NUM_MASK GENMASK(25, 16) +#define NOISE_SAMPLE_NUM ((x) << NOISE_SAMPLE_NUM_SHIFT) +#define SOUND_THD_MASK GENMASK(15, 0) +#define SOUND_THD(x) (x) + +#define VAD_DET_CON2 0x64 +#define IIR_B0_SHIFT 16 +#define IIR_B0_MASK GENMASK(31, 16) +#define IIR_B0(x) ((x) << IIR_B0_SHIFT) +#define NOISE_ALPHA_SHIFT 8 +#define NOISE_ALPHA_MASK GENMASK(15, 8) +#define NOISE_ALPHA(x) ((x) << NOISE_ALPHA_SHIFT) +#define NOISE_FRM_NUM_MASK GENMASK(6, 0) +#define NOISE_FRM_NUM(x) (x) + +#define VAD_DET_CON3 0x68 +#define IIR_B2_MASK GENMASK(31, 16) +#define IIR_B2(x) ((x) << 16) +#define IIR_B1_MASK GENMASK(15, 0) +#define IIR_B1(x) (x) + +#define VAD_DET_CON4 0x6c +#define IIR_A2_MASK GENMASK(31, 16) +#define IIR_A2(x) ((x) << 16) +#define IIR_A1_MASK GENMASK(15, 0) +#define IIR_A1(x) (x) + +#define VAD_DET_CON5 0x70 +#define IIR_RESULT_SHIFT 16 +#define IIR_RESULT_MASK GENMASK(31, 16) +#define NOISE_ABS_MASK GENMASK(15, 0) +#define NOISE_ABS(x) (x) + +#define VAD_INT 0x74 +#define VAD_IDLE_MASK BIT(9) +#define RAM_LOOP_FLGA_MASK BIT(8) +#define WORK_TIMEOUT_FLAG_MASK BIT(7) +#define IDLE_TIMEOUT_FLAG_MASK BIT(6) +#define ERR_INT_FLAG_MASK BIT(5) +#define VAD_DET_INT_FLAG_MASK BIT(4) +#define WORK_TIMEOUT_INT_EN_MASK BIT(3) +#define WORK_TIMEOUT_INT_EN BIT(3) +#define IDLE_TIMEOUT_INT_EN_MASK BIT(2) +#define IDLE_TIMEOUT_INT_EN BIT(2) +#define ERR_INT_EN_MASK BIT(1) +#define ERR_INT_EN BIT(1) +#define VAD_DET_INT_EN_MASK BIT(0) +#define VAD_DET_INT_EN BIT(0) + +/* acodec */ +#define ACODEC_BASE 0xff560000 +#define ACODEC_ADC_ANA_CON0 0X340 + +#endif