| /* |
| * sound/soc/amlogic/auge/vad.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| */ |
| #define DEBUG |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/platform_device.h> |
| #include <linux/init.h> |
| #include <linux/errno.h> |
| #include <linux/ioctl.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_platform.h> |
| #include <linux/clk.h> |
| #include <sound/core.h> |
| #include <sound/pcm.h> |
| #include <sound/initval.h> |
| #include <sound/control.h> |
| #include <sound/soc.h> |
| #include <sound/pcm_params.h> |
| |
| #include <linux/sched.h> |
| #include <linux/kthread.h> |
| #include <linux/freezer.h> |
| |
| #include <linux/amlogic/pm.h> |
| #include <linux/pm_wakeirq.h> |
| #include <linux/pm_wakeup.h> |
| |
| #include <linux/input.h> |
| #include <linux/amlogic/vad_api.h> |
| |
| #include "vad_hw_coeff.c" |
| #include "vad_hw.h" |
| #include "vad.h" |
| |
| #define DRV_NAME "VAD" |
| |
| #define DMA_BUFFER_BYTES_MAX (2 * 1024 * 1024) |
| |
| |
| #define VAD_READ_FRAME_COUNT (1024 / 2) |
| |
| /*#define __VAD_DUMP_DATA__*/ |
| #define VAD_DUMP_FILE_NAME "/mnt/vaddump.pcm" |
| |
| enum vad_level { |
| LEVEL_USER, |
| LEVEL_KERNEL, |
| }; |
| |
| struct vad { |
| struct aml_audio_controller *actrl; |
| struct device *dev; |
| struct input_dev *input_device; |
| |
| struct clk *gate; |
| struct clk *pll; |
| struct clk *clk; |
| |
| struct toddr *tddr; |
| |
| char *buf; |
| struct task_struct *thread; |
| |
| struct snd_dma_buffer dma_buffer; |
| unsigned int start_last; |
| unsigned int end_last; |
| unsigned int addr; |
| int switch_buffer; |
| |
| /* vad flag interrupt */ |
| int irq_wakeup; |
| /* frame sync interrupt */ |
| int irq_fs; |
| /* data source select |
| * Data src sel: |
| * 0: tdmin_a; |
| * 1: tdmin_b; |
| * 2: tdmin_c; |
| * 3: spdifin; |
| * 4: pdmin; |
| * 5: loopback_b; |
| * 6: tdmin_lb; |
| * 7: loopback_a; |
| */ |
| int src; |
| /* Enable */ |
| int en; |
| |
| /* user space or kernel space to check hot word |
| * 1: check hot word in kernel |
| * 0: check hot word in user space |
| */ |
| enum vad_level level; |
| |
| int (*callback)(char *buf, int frames, int rate, |
| int channels, int bitdepth); |
| |
| #ifdef __VAD_DUMP_DATA__ |
| struct file *fp; |
| mm_segment_t fs; |
| loff_t pos; |
| #endif |
| }; |
| |
| static struct vad *s_vad; |
| |
| static struct vad *get_vad(void) |
| { |
| struct vad *p_vad; |
| |
| p_vad = s_vad; |
| |
| if (!p_vad) { |
| pr_debug("Not init vad\n"); |
| return NULL; |
| } |
| |
| return p_vad; |
| } |
| |
| static void vad_key_report(void) |
| { |
| struct vad *p_vad = get_vad(); |
| |
| if (!p_vad) |
| return; |
| pr_info("%s\n", __func__); |
| |
| input_event(p_vad->input_device, EV_KEY, KEY_POWER, 1); |
| input_sync(p_vad->input_device); |
| input_event(p_vad->input_device, EV_KEY, KEY_POWER, 0); |
| input_sync(p_vad->input_device); |
| } |
| |
| static void vad_input_device_init(struct vad *pvad) |
| { |
| pvad->input_device->name = "vad_keypad"; |
| pvad->input_device->phys = "vad_keypad/input3"; |
| pvad->input_device->dev.parent = pvad->dev; |
| pvad->input_device->id.bustype = BUS_ISA; |
| pvad->input_device->id.vendor = 0x0001; |
| pvad->input_device->id.product = 0x0001; |
| pvad->input_device->id.version = 0x0100; |
| pvad->input_device->evbit[0] = BIT_MASK(EV_KEY); |
| pvad->input_device->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); |
| set_bit(KEY_POWER, pvad->input_device->keybit); |
| } |
| |
| static bool vad_is_enable(void) |
| { |
| struct vad *p_vad = get_vad(); |
| |
| if (!p_vad) |
| return false; |
| |
| return p_vad->en; |
| } |
| |
| static bool vad_src_check(enum vad_src src) |
| { |
| struct vad *p_vad = get_vad(); |
| |
| if (!p_vad) |
| return false; |
| |
| if (p_vad->src == src) |
| return true; |
| |
| return false; |
| } |
| |
| bool vad_tdm_is_running(int tdm_idx) |
| { |
| enum vad_src src; |
| |
| if (tdm_idx > 2) |
| return false; |
| |
| src = (enum vad_src)tdm_idx; |
| |
| if (vad_is_enable() && vad_src_check(src)) |
| return true; |
| |
| return false; |
| } |
| |
| bool vad_pdm_is_running(void) |
| { |
| if (vad_is_enable() && vad_src_check(VAD_SRC_PDMIN)) |
| return true; |
| |
| return false; |
| } |
| |
| static void vad_notify_user_space(struct vad *p_vad) |
| { |
| pr_info("Notify to wake up user space\n"); |
| |
| pm_wakeup_event(p_vad->dev, 2000); |
| p_vad->addr = 0; |
| vad_key_report(); |
| } |
| |
| /* For test */ |
| static int vad_in_kernel_test; |
| static int vad_wakeup_count; |
| |
| int register_vad_callback(int (*callback)(char *, int, int, int, int)) |
| { |
| struct vad *p_vad = get_vad(); |
| |
| if (!p_vad) |
| return -1; |
| |
| p_vad->callback = callback; |
| return 0; |
| } |
| EXPORT_SYMBOL(register_vad_callback); |
| |
| void unregister_vad_callback(void) |
| { |
| struct vad *p_vad = get_vad(); |
| |
| if (!p_vad) |
| return; |
| |
| p_vad->callback = NULL; |
| } |
| EXPORT_SYMBOL(unregister_vad_callback); |
| |
| /* transfer audio data to algorithm |
| * 0: no wake word found |
| * 1: contained wake word, should wake up system |
| */ |
| static int vad_transfer_data_to_algorithm( |
| struct vad *p_vad, char *buf, int frames, |
| int rate, int channels, int bitdepth) |
| { |
| int ret = 0; |
| /* TODO: for test */ |
| if (vad_in_kernel_test) { |
| |
| if (vad_wakeup_count < 50) |
| return 0; |
| |
| vad_wakeup_count = 0; |
| return 1; |
| } |
| |
| /* Do check by algorithm */ |
| if (p_vad->callback != NULL) |
| ret = p_vad->callback(buf, frames, rate, channels, bitdepth); |
| |
| return ret; |
| } |
| |
| /* Check buffer in kernel for VAD */ |
| static int vad_engine_check(struct vad *p_vad) |
| { |
| unsigned char *hwbuf; |
| int bytes, read_bytes; |
| int start, end, size, last_addr, curr_addr; |
| int chnum, bytes_per_sample; |
| int frame_count = VAD_READ_FRAME_COUNT; |
| |
| if (!p_vad->tddr || !p_vad->tddr->actrl || |
| !p_vad->tddr->in_use) |
| return 0; |
| |
| hwbuf = p_vad->dma_buffer.area; |
| size = p_vad->dma_buffer.bytes - 8; |
| start = p_vad->dma_buffer.addr; |
| end = start + size; |
| last_addr = p_vad->addr; |
| curr_addr = aml_toddr_get_position(p_vad->tddr); |
| |
| if (curr_addr < start || curr_addr > end || |
| last_addr < start || last_addr > end) { |
| pr_info("%s line:%d, start:%x,end:%x, addr:%x, curr_addr=%x\n", |
| __func__, __LINE__, |
| start, |
| end, |
| p_vad->addr, |
| curr_addr); |
| p_vad->addr = curr_addr; |
| return 0; |
| } |
| |
| bytes = (curr_addr - last_addr + size) % size; |
| chnum = p_vad->tddr->channels; |
| /* bytes for each sample */ |
| bytes_per_sample = p_vad->tddr->bitdepth >> 3; |
| |
| read_bytes = frame_count * chnum * bytes_per_sample; |
| if (bytes < read_bytes) { |
| pr_debug("%s line:%d, %d bytes, need more data\n", |
| __func__, __LINE__, bytes); |
| return 0; |
| } |
| |
| if (!p_vad->buf) { |
| p_vad->buf = kzalloc(read_bytes, GFP_KERNEL); |
| if (!p_vad->buf) { |
| pr_err("%s failed to allocate buffer for vad\n", |
| __func__); |
| return -ENOMEM; |
| } |
| } |
| memset(p_vad->buf, 0x0, read_bytes); |
| |
| pr_debug("start:%x,end:%x, curr_addr:%x, last_addr:%x, offset:%d, read_bytes:%d\n", |
| start, |
| end, |
| curr_addr, |
| last_addr, |
| bytes, |
| read_bytes); |
| |
| |
| if (last_addr + read_bytes <= end) { |
| memcpy(p_vad->buf, |
| hwbuf + last_addr - start, |
| read_bytes); |
| p_vad->addr = last_addr + read_bytes; |
| } else { |
| int tmp_bytes = end - last_addr; |
| |
| memcpy(p_vad->buf, |
| hwbuf + last_addr - start, |
| tmp_bytes); |
| memcpy(p_vad->buf + tmp_bytes, |
| hwbuf, |
| read_bytes - tmp_bytes); |
| p_vad->addr = start + read_bytes - tmp_bytes; |
| } |
| |
| #ifdef __VAD_DUMP_DATA__ |
| vfs_write(p_vad->fp, p_vad->buf, read_bytes, &p_vad->pos); |
| #endif |
| |
| return vad_transfer_data_to_algorithm(p_vad, |
| p_vad->buf, frame_count, |
| p_vad->tddr->rate, |
| p_vad->tddr->channels, |
| p_vad->tddr->bitdepth); |
| } |
| |
| static int vad_freeze_thread(void *data) |
| { |
| struct vad *p_vad = data; |
| |
| if (!p_vad) |
| return 0; |
| |
| current->flags |= PF_NOFREEZE; |
| |
| for (;;) { |
| msleep_interruptible(10); |
| if (kthread_should_stop()) { |
| pr_info("%s line:%d\n", __func__, __LINE__); |
| |
| break; |
| } |
| |
| if (!p_vad || !p_vad->en || !p_vad->callback || |
| !p_vad->tddr || !p_vad->switch_buffer) { |
| msleep_interruptible(10); |
| continue; |
| } |
| |
| if (vad_engine_check(p_vad) > 0) |
| vad_notify_user_space(p_vad); |
| } |
| |
| dev_info(p_vad->dev, "vad: freeze thread exit\n"); |
| return 0; |
| } |
| |
| |
| static irqreturn_t vad_wakeup_isr(int irq, void *data) |
| { |
| struct vad *p_vad = (struct vad *)data; |
| |
| if (vad_in_kernel_test) { |
| pr_info("%s vad_wakeup_count:%d\n", __func__, vad_wakeup_count); |
| if ((p_vad->level == LEVEL_KERNEL) && p_vad->switch_buffer) |
| vad_wakeup_count++; |
| } |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t vad_fs_isr(int irq, void *data) |
| { |
| return IRQ_HANDLED; |
| } |
| |
| static int vad_set_clks(struct vad *p_vad, bool enable) |
| { |
| if (enable) { |
| int ret = 0; |
| |
| /* enable clock gate */ |
| ret = clk_prepare_enable(p_vad->gate); |
| |
| /* enable clock */ |
| ret = clk_prepare_enable(p_vad->pll); |
| if (ret) { |
| pr_err("Can't enable vad pll: %d\n", ret); |
| return -EINVAL; |
| } |
| |
| clk_set_rate(p_vad->clk, 25000000); |
| ret = clk_prepare_enable(p_vad->clk); |
| if (ret) { |
| pr_err("Can't enable vad clk: %d\n", ret); |
| return -EINVAL; |
| } |
| } else { |
| /* disable clock and gate */ |
| clk_disable_unprepare(p_vad->clk); |
| clk_disable_unprepare(p_vad->pll); |
| clk_disable_unprepare(p_vad->gate); |
| } |
| |
| return 0; |
| } |
| |
| static int vad_init(struct vad *p_vad) |
| { |
| int ret = 0, flag = 0; |
| |
| /* malloc buffer */ |
| ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, |
| p_vad->dev, |
| DMA_BUFFER_BYTES_MAX, |
| &p_vad->dma_buffer); |
| if (ret) { |
| dev_err(p_vad->dev, "Cannot allocate buffer(s)\n"); |
| return ret; |
| } |
| |
| /* register irq */ |
| if (p_vad->level == LEVEL_KERNEL) { |
| flag = IRQF_SHARED | IRQF_NO_SUSPEND; |
| |
| /* Start Micro/Audio Dock firmware loader thread */ |
| if (!p_vad->thread) { |
| p_vad->thread = |
| kthread_create(vad_freeze_thread, p_vad, |
| "vad_freeze_thread"); |
| if (IS_ERR(p_vad->thread)) { |
| int err = PTR_ERR(p_vad->thread); |
| |
| p_vad->thread = NULL; |
| dev_info(p_vad->dev, |
| "vad freeze: Creating thread failed\n"); |
| return err; |
| } |
| wake_up_process(p_vad->thread); |
| vad_wakeup_count = 0; |
| } |
| |
| #ifdef __VAD_DUMP_DATA__ |
| p_vad->fp = filp_open(VAD_DUMP_FILE_NAME, O_RDWR | O_CREAT, 0666); |
| if (IS_ERR(p_vad->fp)) { |
| pr_err("create file %s error/n", VAD_DUMP_FILE_NAME); |
| return -1; |
| } |
| p_vad->fs = get_fs(); |
| p_vad->pos = 0; |
| set_fs(KERNEL_DS); |
| #endif |
| |
| } else if (p_vad->level == LEVEL_USER) |
| flag = IRQF_SHARED; |
| |
| ret = request_irq(p_vad->irq_wakeup, |
| vad_wakeup_isr, flag, "vad_wakeup", |
| p_vad); |
| if (ret) { |
| dev_err(p_vad->dev, "failed to claim irq_wakeup %u\n", |
| p_vad->irq_wakeup); |
| return -ENXIO; |
| } |
| |
| ret = request_irq(p_vad->irq_fs, |
| vad_fs_isr, 0, "vad_fs", |
| p_vad); |
| if (ret) { |
| dev_err(p_vad->dev, "failed to claim irq_fs %u\n", |
| p_vad->irq_fs); |
| return -ENXIO; |
| } |
| |
| /* clock ready */ |
| vad_set_clks(p_vad, true); |
| |
| return ret; |
| } |
| |
| static void vad_deinit(struct vad *p_vad) |
| { |
| if (p_vad->level == LEVEL_KERNEL) { |
| #ifdef __VAD_DUMP_DATA__ |
| if (p_vad->fp) { |
| set_fs(p_vad->fs); |
| filp_close(p_vad->fp, NULL); |
| } |
| #endif |
| if (p_vad->thread) { |
| kthread_stop(p_vad->thread); |
| p_vad->thread = NULL; |
| } |
| kfree(p_vad->buf); |
| p_vad->buf = NULL; |
| } |
| |
| /* free irq */ |
| free_irq(p_vad->irq_wakeup, p_vad); |
| free_irq(p_vad->irq_fs, p_vad); |
| |
| /* free buffer */ |
| snd_dma_free_pages(&p_vad->dma_buffer); |
| |
| /* clock disabled */ |
| vad_set_clks(p_vad, false); |
| } |
| |
| void vad_update_buffer(int isvad) |
| { |
| struct vad *p_vad = get_vad(); |
| unsigned int start, end; |
| unsigned int rd_th; |
| |
| if (!p_vad || !p_vad->en || !p_vad->tddr || |
| !p_vad->tddr->in_use || !p_vad->tddr->actrl) |
| return; |
| |
| if (p_vad->switch_buffer == isvad) |
| return; |
| p_vad->switch_buffer = isvad; |
| |
| if (isvad) { /* switch to vad buffer */ |
| struct toddr *tddr = p_vad->tddr; |
| |
| p_vad->start_last = tddr->start_addr; |
| p_vad->end_last = tddr->end_addr; |
| |
| rd_th = 0x100; |
| |
| pr_debug("Switch to VAD buffer\n"); |
| pr_debug("\t ASAL start:%x, end:%x, bytes:%d\n", |
| tddr->start_addr, tddr->end_addr, |
| tddr->end_addr - tddr->start_addr); |
| |
| start = p_vad->dma_buffer.addr; |
| end = start + p_vad->dma_buffer.bytes - 8; |
| |
| pr_debug("\t VAD start:%x, end:%x, bytes:%d\n", |
| start, end, |
| end - start); |
| } else { |
| pr_debug("Switch to ALSA buffer\n"); |
| start = p_vad->start_last; |
| end = p_vad->end_last; |
| rd_th = 0x40; |
| vad_set_trunk_data_readable(true); |
| } |
| aml_toddr_set_buf(p_vad->tddr, start, end); |
| aml_toddr_force_finish(p_vad->tddr); |
| aml_toddr_update_fifos_rd_th(p_vad->tddr, rd_th); |
| p_vad->addr = 0; |
| } |
| |
| int vad_transfer_chunk_data(unsigned long data, int frames) |
| { |
| struct vad *p_vad = get_vad(); |
| char __user *buf = (char __user *)data; |
| unsigned char *hwbuf; |
| int bytes; |
| int start, end, addr, size; |
| int chnum, bytes_per_sample; |
| |
| if (!buf || !p_vad || !p_vad->en || !p_vad->tddr) |
| return 0; |
| |
| size = p_vad->dma_buffer.bytes; |
| start = p_vad->dma_buffer.addr; |
| end = start + size - 8; |
| addr = p_vad->addr; |
| hwbuf = p_vad->dma_buffer.area; |
| |
| if (addr < start || addr > end) |
| return 0; |
| |
| chnum = p_vad->tddr->channels; |
| /* bytes for each sample */ |
| bytes_per_sample = p_vad->tddr->bitdepth >> 3; |
| |
| bytes = frames * chnum * bytes_per_sample < size ? |
| frames * chnum * bytes_per_sample : size; |
| |
| pr_debug("%s dma bytes:%d, wanted bytes:%d, actual bytes:%d\n", |
| __func__, |
| size, |
| frames * chnum * bytes_per_sample, |
| bytes); |
| |
| pr_debug("%s dma bytes:%d, start:%d, end:%d, current:%d\n", |
| __func__, |
| size, |
| start, |
| end, |
| addr); |
| |
| if (addr - start >= bytes) { |
| if (copy_to_user(buf, |
| hwbuf + addr - bytes - start, |
| bytes)) |
| return 0; |
| } else { |
| int tmp_bytes = bytes - (addr - start); |
| int tmp_offset = (end - tmp_bytes) - start; |
| |
| if (copy_to_user(buf, |
| hwbuf + tmp_offset, |
| tmp_bytes)) |
| return 0; |
| |
| if (copy_to_user(buf + tmp_bytes, |
| hwbuf, |
| addr - start)) |
| return 0; |
| } |
| |
| /* After data copied, reset dma buffer */ |
| memset(hwbuf, 0x0, size); |
| |
| return bytes / (chnum * bytes_per_sample); |
| } |
| |
| void vad_set_toddr_info(struct toddr *to) |
| { |
| struct vad *p_vad = get_vad(); |
| |
| if (!p_vad || !p_vad->en) |
| return; |
| |
| pr_debug("%s update vad toddr:%p\n", __func__, to); |
| |
| p_vad->tddr = to; |
| } |
| |
| void vad_enable(bool enable) |
| { |
| struct vad *p_vad = get_vad(); |
| |
| if (!p_vad || !p_vad->en) |
| return; |
| |
| /* Force VAD enable to set parameters */ |
| if (enable) { |
| int *p_de_coeff = vad_de_coeff; |
| int len_de = ARRAY_SIZE(vad_de_coeff); |
| int *p_win_coeff = vad_ram_coeff; |
| int len_ram = ARRAY_SIZE(vad_ram_coeff); |
| |
| vad_set_enable(true); |
| vad_set_ram_coeff(len_ram, p_win_coeff); |
| vad_set_de_params(len_de, p_de_coeff); |
| vad_set_pwd(); |
| vad_set_cep(); |
| vad_set_src(p_vad->src); |
| vad_set_in(); |
| |
| /* reset then enable VAD */ |
| vad_set_enable(false); |
| } |
| vad_set_enable(enable); |
| } |
| |
| static int vad_get_enable_enum( |
| struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct vad *p_vad = snd_kcontrol_chip(kcontrol); |
| |
| if (!p_vad) { |
| pr_debug("VAD is not inited\n"); |
| return 0; |
| } |
| |
| ucontrol->value.integer.value[0] = p_vad->en; |
| |
| return 0; |
| } |
| |
| static int vad_set_enable_enum( |
| struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct vad *p_vad = snd_kcontrol_chip(kcontrol); |
| |
| if (!p_vad) { |
| pr_debug("VAD is not inited\n"); |
| return 0; |
| } |
| |
| if (p_vad->en == ucontrol->value.integer.value[0]) |
| return 0; |
| |
| p_vad->en = ucontrol->value.integer.value[0]; |
| |
| if (p_vad->en) { |
| vad_init(p_vad); |
| |
| aml_set_vad(p_vad->en, p_vad->src); |
| } else { |
| aml_set_vad(p_vad->en, p_vad->src); |
| |
| vad_deinit(p_vad); |
| } |
| |
| return 0; |
| } |
| |
| |
| static const char *const vad_src_txt[] = { |
| "TDMIN_A", |
| "TDMIN_B", |
| "TDMIN_C", |
| "SPDIFIN", |
| "PDMIN", |
| "LOOPBACK_B", |
| "TDMIN_LB", |
| "LOOPBACK_A", |
| }; |
| |
| const struct soc_enum vad_src_enum = |
| SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(vad_src_txt), |
| vad_src_txt); |
| |
| static int vad_get_src_enum( |
| struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct vad *p_vad = snd_kcontrol_chip(kcontrol); |
| |
| if (!p_vad) { |
| pr_debug("VAD is not inited\n"); |
| return 0; |
| } |
| |
| ucontrol->value.integer.value[0] = p_vad->src; |
| |
| return 0; |
| } |
| |
| static int vad_set_src_enum( |
| struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct vad *p_vad = snd_kcontrol_chip(kcontrol); |
| |
| if (!p_vad) { |
| pr_debug("VAD is not inited\n"); |
| return 0; |
| } |
| |
| p_vad->src = ucontrol->value.integer.value[0]; |
| |
| if (p_vad->en) |
| aml_set_vad(p_vad->en, p_vad->src); |
| |
| return 0; |
| } |
| |
| static int vad_get_switch_enum( |
| struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct vad *p_vad = snd_kcontrol_chip(kcontrol); |
| |
| if (!p_vad) { |
| pr_debug("VAD is not inited\n"); |
| return 0; |
| } |
| |
| ucontrol->value.integer.value[0] = p_vad->switch_buffer; |
| |
| return 0; |
| } |
| |
| static int vad_set_switch_enum( |
| struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct vad *p_vad = snd_kcontrol_chip(kcontrol); |
| |
| if (!p_vad) { |
| pr_debug("VAD is not inited\n"); |
| return 0; |
| } |
| |
| if (p_vad->en) |
| vad_update_buffer(ucontrol->value.integer.value[0]); |
| |
| return 0; |
| } |
| |
| static int vad_test_get_enum( |
| struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.integer.value[0] = vad_in_kernel_test; |
| |
| return 0; |
| } |
| |
| static int vad_test_set_enum( |
| struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| vad_in_kernel_test = ucontrol->value.integer.value[0]; |
| |
| return 0; |
| } |
| |
| |
| static const struct snd_kcontrol_new vad_controls[] = { |
| SOC_SINGLE_BOOL_EXT("VAD enable", |
| 0, |
| vad_get_enable_enum, |
| vad_set_enable_enum), |
| |
| SOC_ENUM_EXT("VAD Source sel", |
| vad_src_enum, |
| vad_get_src_enum, |
| vad_set_src_enum), |
| |
| SOC_SINGLE_BOOL_EXT("VAD Switch", |
| 0, |
| vad_get_switch_enum, |
| vad_set_switch_enum), |
| |
| SOC_SINGLE_BOOL_EXT("VAD Test", |
| 0, |
| vad_test_get_enum, |
| vad_test_set_enum), |
| }; |
| |
| int card_add_vad_kcontrols(struct snd_soc_card *card) |
| { |
| unsigned int idx; |
| int err; |
| |
| struct vad *p_vad = get_vad(); |
| |
| if (!p_vad) |
| return -ENODEV; |
| |
| for (idx = 0; idx < ARRAY_SIZE(vad_controls); idx++) { |
| err = snd_ctl_add(card->snd_card, |
| snd_ctl_new1(&vad_controls[idx], |
| p_vad)); |
| if (err < 0) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static const struct of_device_id vad_device_id[] = { |
| { |
| .compatible = "amlogic, snd-vad", |
| }, |
| {}, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, vad_device_id); |
| |
| static int vad_platform_probe(struct platform_device *pdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| struct device_node *node_prt = NULL; |
| struct platform_device *pdev_parent; |
| struct device *dev = &pdev->dev; |
| struct aml_audio_controller *actrl = NULL; |
| struct vad *p_vad = NULL; |
| int ret = 0; |
| |
| p_vad = devm_kzalloc(&pdev->dev, sizeof(struct vad), GFP_KERNEL); |
| if (!p_vad) |
| return -ENOMEM; |
| |
| p_vad->dev = dev; |
| dev_set_drvdata(dev, p_vad); |
| |
| /* get audio controller */ |
| node_prt = of_get_parent(node); |
| if (node_prt == NULL) |
| return -ENXIO; |
| |
| pdev_parent = of_find_device_by_node(node_prt); |
| of_node_put(node_prt); |
| actrl = (struct aml_audio_controller *) |
| platform_get_drvdata(pdev_parent); |
| p_vad->actrl = actrl; |
| |
| /* clock */ |
| p_vad->gate = devm_clk_get(&pdev->dev, "gate"); |
| if (IS_ERR(p_vad->gate)) { |
| dev_err(&pdev->dev, |
| "Can't get vad clock gate\n"); |
| return PTR_ERR(p_vad->gate); |
| } |
| p_vad->pll = devm_clk_get(&pdev->dev, "pll"); |
| if (IS_ERR(p_vad->pll)) { |
| dev_err(&pdev->dev, |
| "Can't retrieve vad pll clock\n"); |
| return PTR_ERR(p_vad->pll); |
| } |
| p_vad->clk = devm_clk_get(&pdev->dev, "clk"); |
| if (IS_ERR(p_vad->clk)) { |
| dev_err(&pdev->dev, |
| "Can't retrieve vad clock\n"); |
| return PTR_ERR(p_vad->clk); |
| } |
| ret = clk_set_parent(p_vad->clk, p_vad->pll); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "Can't set p_vad->clk parent clock\n"); |
| return PTR_ERR(p_vad->clk); |
| } |
| |
| /* irqs */ |
| p_vad->irq_wakeup = platform_get_irq_byname(pdev, "irq_wakeup"); |
| if (p_vad->irq_wakeup < 0) { |
| dev_err(dev, "Failed to get irq_wakeup:%d\n", |
| p_vad->irq_wakeup); |
| return -ENXIO; |
| } |
| p_vad->irq_fs = platform_get_irq_byname(pdev, "irq_frame_sync"); |
| if (p_vad->irq_fs < 0) { |
| dev_err(dev, "Failed to get irq_frame_sync:%d\n", |
| p_vad->irq_fs); |
| return -ENXIO; |
| } |
| |
| /* data source select */ |
| ret = of_property_read_u32(node, "src", |
| &p_vad->src); |
| if (ret < 0) { |
| dev_err(dev, "Failed to get vad data src select:%d\n", |
| p_vad->src); |
| return -EINVAL; |
| } |
| /* to deal with hot word in user space or kernel space */ |
| ret = of_property_read_u32(node, "level", |
| &p_vad->level); |
| if (ret < 0) { |
| dev_info(dev, |
| "Failed to get vad level, default in user space\n"); |
| p_vad->level = 0; |
| } |
| |
| pr_info("%s vad data source sel:%d, level:%d\n", |
| __func__, |
| p_vad->src, |
| p_vad->level); |
| |
| p_vad->input_device = input_allocate_device(); |
| if (!p_vad->input_device) { |
| kfree(p_vad); |
| return -ENOMEM; |
| } |
| vad_input_device_init(p_vad); |
| if (input_register_device(p_vad->input_device)) { |
| input_free_device(p_vad->input_device); |
| kfree(p_vad); |
| return -EINVAL; |
| } |
| |
| s_vad = p_vad; |
| |
| device_init_wakeup(dev, 1); |
| |
| return 0; |
| } |
| |
| int vad_platform_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct device *dev = &pdev->dev; |
| struct vad *p_vad = dev_get_drvdata(dev); |
| |
| pr_info("%s\n", __func__); |
| |
| /* whether in freeze */ |
| if (is_pm_freeze_mode() |
| && vad_is_enable()) { |
| pr_info("%s, Entry in freeze\n", __func__); |
| |
| if (p_vad->level == LEVEL_USER) |
| dev_pm_set_wake_irq(dev, p_vad->irq_wakeup); |
| } |
| |
| return 0; |
| } |
| |
| int vad_platform_resume(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct vad *p_vad = dev_get_drvdata(dev); |
| |
| pr_info("%s\n", __func__); |
| |
| /* whether in freeze mode */ |
| if (is_pm_freeze_mode() |
| && vad_is_enable()) { |
| pr_info("%s, Exist from freeze\n", __func__); |
| |
| if (p_vad->level == LEVEL_USER) |
| dev_pm_clear_wake_irq(dev); |
| } |
| |
| return 0; |
| } |
| |
| struct platform_driver vad_driver = { |
| .driver = { |
| .name = DRV_NAME, |
| .of_match_table = vad_device_id, |
| }, |
| .probe = vad_platform_probe, |
| .suspend = vad_platform_suspend, |
| .resume = vad_platform_resume, |
| }; |
| |
| module_platform_driver(vad_driver); |
| |
| MODULE_AUTHOR("Amlogic, Inc."); |
| MODULE_DESCRIPTION("Amlogic Voice Activity Detection ASoc driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("Platform:" DRV_NAME); |
| MODULE_DEVICE_TABLE(of, vad_device_id); |