summaryrefslogtreecommitdiff
authorfahui.feng <fahui.feng@amlogic.com>2019-04-26 02:14:59 (GMT)
committer Jianxin Pan <jianxin.pan@amlogic.com>2019-10-16 02:58:23 (GMT)
commit8ca3108b53f71f4628fee6fb702c8958d3335a41 (patch)
tree4d79744891f129834bbe9341808f0f0829cf2ea7
parent712bd039942fb67d4801b41045b224f8a0605201 (diff)
downloadcommon-8ca3108b53f71f4628fee6fb702c8958d3335a41.zip
common-8ca3108b53f71f4628fee6fb702c8958d3335a41.tar.gz
common-8ca3108b53f71f4628fee6fb702c8958d3335a41.tar.bz2
audio: add usb karaoke driver [1/1]
PD#OTT-1624 Problem: doesn't support Karaoke Solution: 1) add usb karaoke driver 2) Default karaoke is disable. if enable it, add "CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA=y" in defconfig file. Verify: p212 Change-Id: I9f00873da930f9d5924fc752133ed51a4ae93636 Signed-off-by: fahui.feng <fahui.feng@amlogic.com> Signed-off-by: Zhe Wang <Zhe.Wang@amlogic.com>
Diffstat
-rw-r--r--MAINTAINERS1
-rw-r--r--arch/arm/boot/dts/amlogic/gxl_p212_1g.dts9
-rw-r--r--arch/arm/boot/dts/amlogic/gxl_p212_2g.dts9
-rw-r--r--arch/arm/boot/dts/amlogic/gxl_p241_1g.dts9
-rw-r--r--arch/arm/boot/dts/amlogic/gxl_p241_v2-1g.dts9
-rw-r--r--arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts9
-rw-r--r--arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts9
-rw-r--r--drivers/amlogic/Kconfig2
-rw-r--r--drivers/amlogic/Makefile2
-rw-r--r--drivers/amlogic/amlkaraoke/Kconfig21
-rw-r--r--drivers/amlogic/amlkaraoke/Makefile17
-rw-r--r--drivers/amlogic/amlkaraoke/aml_audio_resampler.c116
-rw-r--r--drivers/amlogic/amlkaraoke/aml_audio_resampler.h37
-rw-r--r--drivers/amlogic/amlkaraoke/aml_i2s_out_mix.c677
-rw-r--r--drivers/amlogic/amlkaraoke/aml_i2s_out_mix.h52
-rw-r--r--drivers/amlogic/amlkaraoke/aml_karaoke.c305
-rw-r--r--drivers/amlogic/amlkaraoke/aml_karaoke.h30
-rw-r--r--drivers/amlogic/amlkaraoke/aml_reverb.c235
-rw-r--r--drivers/amlogic/amlkaraoke/aml_reverb.h84
-rw-r--r--drivers/amlogic/amlkaraoke/aml_usb_capture.c552
-rw-r--r--drivers/amlogic/amlkaraoke/aml_usb_capture.h59
-rw-r--r--include/linux/amlogic/media/sound/usb_karaoke.h27
-rw-r--r--sound/soc/amlogic/meson/i2s_dai.c32
-rw-r--r--sound/soc/amlogic/meson/i2s_dai.h1
-rw-r--r--sound/usb/pcm.c42
-rw-r--r--sound/usb/pcm.h9
26 files changed, 2350 insertions, 5 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index fa98d78..b6c4814 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13820,6 +13820,7 @@ F: arch/arm64/configs/meson64_defconfig
F: drivers/amlogic/clk/*
F: drivers/amlogic/media/vout/hdmitx/hdmi_tx_20/hdmi_tx_main.c
F: drivers/amlogic/pinctrl/*
+F: drivers/amlogic/*
F: include/dt-bindings/clock/*
F: include/linux/amlogic/media/sound/*
F: sound/soc/Kconfig
diff --git a/arch/arm/boot/dts/amlogic/gxl_p212_1g.dts b/arch/arm/boot/dts/amlogic/gxl_p212_1g.dts
index 20b6811..1188fb3 100644
--- a/arch/arm/boot/dts/amlogic/gxl_p212_1g.dts
+++ b/arch/arm/boot/dts/amlogic/gxl_p212_1g.dts
@@ -941,6 +941,15 @@
sound-dai = <&pcm_codec>;
};
};
+
+ amlkaraoke {
+ compatible = "amlogic, aml_karaoke";
+ dev_name = "aml_karaoke";
+ status = "okay";
+ interrupts = <0 48 1>;
+ interrupt-names = "aml_karaoke";
+ };
+
/* END OF AUDIO board specific */
rdma{
compatible = "amlogic, meson, rdma";
diff --git a/arch/arm/boot/dts/amlogic/gxl_p212_2g.dts b/arch/arm/boot/dts/amlogic/gxl_p212_2g.dts
index 5d0e52d..92a19c3 100644
--- a/arch/arm/boot/dts/amlogic/gxl_p212_2g.dts
+++ b/arch/arm/boot/dts/amlogic/gxl_p212_2g.dts
@@ -941,6 +941,15 @@
sound-dai = <&pcm_codec>;
};
};
+
+ amlkaraoke {
+ compatible = "amlogic, aml_karaoke";
+ dev_name = "aml_karaoke";
+ status = "okay";
+ interrupts = <0 48 1>;
+ interrupt-names = "aml_karaoke";
+ };
+
/* END OF AUDIO board specific */
rdma{
compatible = "amlogic, meson, rdma";
diff --git a/arch/arm/boot/dts/amlogic/gxl_p241_1g.dts b/arch/arm/boot/dts/amlogic/gxl_p241_1g.dts
index 7164209..e875138 100644
--- a/arch/arm/boot/dts/amlogic/gxl_p241_1g.dts
+++ b/arch/arm/boot/dts/amlogic/gxl_p241_1g.dts
@@ -997,6 +997,15 @@
sound-dai = <&pcm_codec>;
};
};
+
+ amlkaraoke {
+ compatible = "amlogic, aml_karaoke";
+ dev_name = "aml_karaoke";
+ status = "okay";
+ interrupts = <0 48 1>;
+ interrupt-names = "aml_karaoke";
+ };
+
/* END OF AUDIO board specific */
rdma{
compatible = "amlogic, meson, rdma";
diff --git a/arch/arm/boot/dts/amlogic/gxl_p241_v2-1g.dts b/arch/arm/boot/dts/amlogic/gxl_p241_v2-1g.dts
index 245c1dc..d6d01b5 100644
--- a/arch/arm/boot/dts/amlogic/gxl_p241_v2-1g.dts
+++ b/arch/arm/boot/dts/amlogic/gxl_p241_v2-1g.dts
@@ -997,6 +997,15 @@
sound-dai = <&pcm_codec>;
};
};
+
+ amlkaraoke {
+ compatible = "amlogic, aml_karaoke";
+ dev_name = "aml_karaoke";
+ status = "okay";
+ interrupts = <0 48 1>;
+ interrupt-names = "aml_karaoke";
+ };
+
/* END OF AUDIO board specific */
rdma{
compatible = "amlogic, meson, rdma";
diff --git a/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts b/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts
index 8cca125..b92989e 100644
--- a/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts
+++ b/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts
@@ -937,6 +937,15 @@
sound-dai = <&pcm_codec>;
};
};
+
+ amlkaraoke {
+ compatible = "amlogic, aml_karaoke";
+ dev_name = "aml_karaoke";
+ status = "okay";
+ interrupts = <0 48 1>;
+ interrupt-names = "aml_karaoke";
+ };
+
/* END OF AUDIO board specific */
rdma{
compatible = "amlogic, meson, rdma";
diff --git a/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts b/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts
index e7354f7..d421ba5 100644
--- a/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts
+++ b/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts
@@ -938,6 +938,15 @@
sound-dai = <&pcm_codec>;
};
};
+
+ amlkaraoke {
+ compatible = "amlogic, aml_karaoke";
+ dev_name = "aml_karaoke";
+ status = "okay";
+ interrupts = <0 48 1>;
+ interrupt-names = "aml_karaoke";
+ };
+
/* END OF AUDIO board specific */
rdma{
compatible = "amlogic, meson, rdma";
diff --git a/drivers/amlogic/Kconfig b/drivers/amlogic/Kconfig
index 93ee0eb..914146f 100644
--- a/drivers/amlogic/Kconfig
+++ b/drivers/amlogic/Kconfig
@@ -84,6 +84,8 @@ source "drivers/amlogic/amaudio/Kconfig"
source "drivers/amlogic/amaudio2/Kconfig"
+source "drivers/amlogic/amlkaraoke/Kconfig"
+
source "drivers/amlogic/audiodsp/Kconfig"
source "drivers/amlogic/audioinfo/Kconfig"
diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile
index 5ed5331..d2686b8 100644
--- a/drivers/amlogic/Makefile
+++ b/drivers/amlogic/Makefile
@@ -90,6 +90,8 @@ obj-$(CONFIG_AMLOGIC_AMAUDIO2) += amaudio2/
obj-$(CONFIG_AMLOGIC_AUDIO_INFO) += audioinfo/
+obj-$(CONFIG_AMLKARAOKE) += amlkaraoke/
+
obj-$(CONFIG_AMLOGIC_SUSPEND) += pm/
obj-$(CONFIG_AMLOGIC_LED) += led/
diff --git a/drivers/amlogic/amlkaraoke/Kconfig b/drivers/amlogic/amlkaraoke/Kconfig
new file mode 100644
index 0000000..f3c2654
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/Kconfig
@@ -0,0 +1,21 @@
+# AML Karaoke control drivers
+
+menuconfig AMLKARAOKE
+ bool "Amlogic karaoke Interface V1"
+ default y
+ help
+ Amlogic karaoke Interface V1, usb capture audio data, mix with i2s out directly.
+ ---
+ ---
+
+if AMLKARAOKE
+
+config AMLOGIC_SND_USB_CAPTURE_DATA
+ tristate "USB Capture Audio Data In, for aml karaoke"
+ depends on AMLOGIC_SND_SOC_MESON
+ help
+ capture usb audio data into a ring buffer. The ring buffer data would be mixed with i2s out data.
+ Say 'Y' for aml karaoker driver.
+
+
+endif
diff --git a/drivers/amlogic/amlkaraoke/Makefile b/drivers/amlogic/amlkaraoke/Makefile
new file mode 100644
index 0000000..74eec80
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/Makefile
@@ -0,0 +1,17 @@
+#
+# Makefile for sound control interface
+#
+
+# Toplevel Module Dependency
+#AML usb audio in to i2s out mixed
+
+
+# AML USB capture
+snd-usb-capture-objs := aml_usb_capture.o aml_audio_resampler.o aml_reverb.o
+obj-$(CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA) += snd-usb-capture.o
+
+aml-i2s-out-mix-objs := aml_i2s_out_mix.o
+obj-$(CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA) += aml-i2s-out-mix.o
+
+amlogic_karaoke-objs := aml_karaoke.o
+obj-$(CONFIG_AMLKARAOKE) += amlogic_karaoke.o
diff --git a/drivers/amlogic/amlkaraoke/aml_audio_resampler.c b/drivers/amlogic/amlkaraoke/aml_audio_resampler.c
new file mode 100644
index 0000000..bc1a1e6
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_audio_resampler.c
@@ -0,0 +1,116 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_audio_resampler.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.
+ *
+ */
+
+#include <trace/events/printk.h>
+#include "aml_audio_resampler.h"
+
+#include <linux/types.h>
+
+/*Clip from 16.16 fixed-point to 0.15 fixed-point*/
+static inline short clip(int x)
+{
+ if (x < -32768)
+ return -32768;
+ else if (x > 32767)
+ return 32767;
+ else
+ return x;
+}
+
+int resampler_init(struct resample_para *resample)
+{
+ /* 64bit:long long */
+ static const long int k_phase_multiplier = 1L << 28;
+
+ resample->fraction_step = (unsigned int)
+ (resample->input_sr * k_phase_multiplier
+ / resample->output_sr);
+ resample->sample_fraction = 0;
+ resample->lastsample_left = 0;
+ resample->lastsample_right = 0;
+ return 0;
+}
+
+int resample_process(
+ struct resample_para *resample,
+ unsigned int in_frame,
+ short *input, short *output) {
+ unsigned int input_index = 0;
+ unsigned int output_index = 0;
+ unsigned int fraction_step = resample->fraction_step;
+ static const unsigned int k_phase_mask = (1LU << 28) - 1;
+ unsigned int frac = resample->sample_fraction;
+ short lastsample_left = resample->lastsample_left;
+ short lastsample_right = resample->lastsample_right;
+
+ if (resample->channels == 2) {
+ while (input_index == 0) {
+ *output++ = clip((int)lastsample_left +
+ ((((int)input[0] - (int)lastsample_left)
+ * ((int)frac >> 13)) >> 15));
+ *output++ = clip((int)lastsample_right +
+ ((((int)input[1] - (int)lastsample_right)
+ * ((int)frac >> 13)) >> 15));
+ frac += fraction_step;
+ input_index += (frac >> 28);
+ frac = (frac & k_phase_mask);
+ output_index++;
+ }
+ while (input_index < in_frame) {
+ *output++ = clip((int)input[2 * input_index - 2]
+ + ((((int)input[2 * input_index]
+ - (int)input[2 * input_index - 2])
+ * ((int)frac >> 13)) >> 15));
+ *output++ = clip((int)input[2 * input_index - 1]
+ + ((((int)input[2 * input_index + 1]
+ - (int)input[2 * input_index - 1])
+ * ((int)frac >> 13)) >> 15));
+
+ frac += fraction_step;
+ input_index += (frac >> 28);
+ frac = (frac & k_phase_mask);
+ output_index++;
+ }
+ resample->lastsample_left = input[2 * in_frame - 2];
+ resample->lastsample_right = input[2 * in_frame - 1];
+ resample->sample_fraction = frac;
+ } else {
+ /*left channel as output*/
+ while (input_index == 0) {
+ *output++ = clip((int)lastsample_left +
+ ((((int)input[0] - (int)lastsample_left)
+ * ((int)frac >> 13)) >> 15));
+ frac += fraction_step;
+ input_index += (frac >> 28);
+ frac = (frac & k_phase_mask);
+ output_index++;
+ }
+ while (input_index < in_frame) {
+ *output++ = clip((int)input[2 * input_index - 2]
+ + ((((int)input[2 * input_index]
+ - (int)input[2 * input_index - 2])
+ * ((int)frac >> 13)) >> 15));
+ frac += fraction_step;
+ input_index += (frac >> 28);
+ frac = (frac & k_phase_mask);
+ output_index++;
+ }
+ resample->lastsample_left = input[2 * in_frame - 2];
+ resample->sample_fraction = frac;
+ }
+ return output_index;
+}
diff --git a/drivers/amlogic/amlkaraoke/aml_audio_resampler.h b/drivers/amlogic/amlkaraoke/aml_audio_resampler.h
new file mode 100644
index 0000000..37a0ea4
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_audio_resampler.h
@@ -0,0 +1,37 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_audio_resampler.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __AUDIO_RESAMPLER_H__
+#define __AUDIO_RESAMPLER_H__
+
+struct resample_para {
+ unsigned int fraction_step;
+ unsigned int sample_fraction;
+ short lastsample_left;
+ short lastsample_right;
+ unsigned int input_sr;
+ unsigned int output_sr;
+ unsigned int channels;
+};
+
+int resampler_init(struct resample_para *resample);
+int resample_process(
+ struct resample_para *resample,
+ unsigned int in_frame,
+ short *input, short *output);
+
+#endif
diff --git a/drivers/amlogic/amlkaraoke/aml_i2s_out_mix.c b/drivers/amlogic/amlkaraoke/aml_i2s_out_mix.c
new file mode 100644
index 0000000..62b528e
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_i2s_out_mix.c
@@ -0,0 +1,677 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_i2s_out_mix.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.
+ *
+ */
+
+/*
+ * A virtual path to get usb audio capture data to i2s mixed out.
+ */
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/amlogic/iomap.h>
+#include <linux/amlogic/media/sound/aiu_regs.h>
+#include <linux/amlogic/media/sound/usb_karaoke.h>
+
+#include "aml_i2s_out_mix.h"
+
+#define BASE_IRQ (32)
+#define AM_IRQ(reg) ((reg) + BASE_IRQ)
+#define INT_I2S_DDR AM_IRQ(48)
+#define IRQ_OUT INT_I2S_DDR
+#define I2S_INT_NUM (16) /* min 2, max 32 */
+#define I2S_BLOCK_SIZE (64) /* block_size=32byte*channel_num, normal is 2*/
+#define I2S_INT_BLOCK ((I2S_INT_NUM) * (I2S_BLOCK_SIZE))
+#define MIN_LATENCY (64 * 32)
+
+static int i2s_block_size;
+static int speaker_channel_mask = 1;
+
+struct i2s_info {
+ /* hw info */
+ unsigned int channels;
+ unsigned int format;
+
+ /* for mixer */
+ unsigned int int_num;
+ unsigned int i2s_block;
+ unsigned int int_block;
+ unsigned int latency;
+};
+
+/*Ultimate output from i2s */
+struct i2s_output {
+ /* audio info */
+ i2s_audio_buffer i2s_buf;
+
+ /* work queue */
+ struct work_struct work;
+
+ /* is irq registered ? */
+ bool isInit;
+
+ /*i2s info for mixer*/
+ struct i2s_info i2sinfo;
+};
+
+typedef int (*mixer_f)(char *dst, char *src, unsigned int count,
+ unsigned int channels, unsigned int format);
+
+static struct i2s_output s_i2s_output;
+
+static inline s16 clip16(int x)
+{
+ if (x < -32768)
+ return -32768;
+ else if (x > 32767)
+ return 32767;
+
+ return (s16)x;
+}
+
+static inline s32 clip32(long x)
+{
+ if (x < -2147483647)
+ return -2147483647; //default:-2147483648
+ else if (x > 2147483647)
+ return 2147483647;
+
+ return (s32)x;
+}
+
+/* Average weight to mix */
+static inline s16 aweight_mix16(s16 s1, s16 s2)
+{
+ return clip16(((s32)s1 + (s32)s2) >> 1);
+}
+
+static inline s32 aweight_mix32(s32 s1, s32 s2)
+{
+ return clip32(((int64_t)s1 + (int64_t)s2) >> 1);
+}
+
+/* Can expand to support more mixer method */
+static inline s16 mix16(s16 s1, s16 s2)
+{
+ return aweight_mix16(s1, s2);
+}
+
+static inline s32 mix32(s32 s1, s32 s2)
+{
+ return aweight_mix32(s1, s2);
+}
+
+/* Get usb audio info. */
+struct usb_audio_buffer *usb_get_audio_info(void)
+{
+ return (struct usb_audio_buffer *)snd_usb_pcm_capture_buffer;
+}
+
+/* Get i2s audio info. */
+i2s_audio_buffer *i2s_get_audio_info(void)
+{
+ return (i2s_audio_buffer *)&s_i2s_output.i2s_buf;
+}
+
+/* Get i2s output. */
+static struct i2s_output *i2s_get_output(void)
+{
+ return (struct i2s_output *)&s_i2s_output;
+}
+
+/* i2s get memory size */
+static unsigned int i2s_get_out_size(void)
+{
+ return aml_read_cbus(AIU_MEM_I2S_END_PTR) -
+ aml_read_cbus(AIU_MEM_I2S_START_PTR) + i2s_block_size;
+}
+
+/* i2s get read pointer */
+static unsigned int i2s_get_out_read_ptr(void)
+{
+ return aml_read_cbus(AIU_MEM_I2S_RD_PTR) -
+ aml_read_cbus(AIU_MEM_I2S_START_PTR);
+}
+
+static int mixer_transfer(
+ char *dst, char *src,
+ unsigned int count,
+ unsigned int i2s_channels,
+ unsigned int i2s_format)
+{
+ int i;
+
+#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
+ int i2s_frame, i2s_frames_count;
+
+ /* bytes in one frame */
+ i2s_frame = i2s_channels * (i2s_format / 8);
+ /* frames in on block */
+ i2s_frames_count = count / i2s_frame;
+
+ if (i2s_channels == 2) {
+ if (i2s_format == 16) {
+ s16 *to = (s16 *)dst;
+ s16 *tfrom = (s16 *)src;
+
+ for (i = 0; i < i2s_frames_count * i2s_channels;
+ i++, to++)
+ *to = mix16(*to, *tfrom++);
+ } else if (i2s_format == 32 ||
+ i2s_format == 24) {
+ s32 *to = (s32 *)dst;
+ s16 *tfrom = (s16 *)src;
+
+ for (i = 0; i < i2s_frames_count * i2s_channels;
+ i++, to++)
+ *to = mix32(*to, (s32)(*tfrom++) << 16);
+ } else {
+ pr_err("Error: Unsupport format:%d\n", i2s_format);
+ }
+ } else if (i2s_channels == 8) {
+ if (i2s_format == 16) {
+ s16 *to = (s16 *)dst;
+ s16 *tfrom = (s16 *)src;
+ s16 *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
+
+ /* yet only cpu txhd support split 8ch, 16bit */
+ lf = to + 0;
+ cf = to + 1;
+ rf = to + 2;
+ ls = to + 3;
+ rs = to + 4;
+ lef = to + 5;
+ sbl = to + 6;
+ sbr = to + 7;
+
+ for (i = 0; i < i2s_frames_count; i++) {
+ if (speaker_channel_mask == 0) {
+ (*lf) = mix16(*lf, (*tfrom++));
+ (*cf) = mix16(*cf, (*tfrom++));
+ }
+ if (speaker_channel_mask == 1) {
+ (*rf) = mix16(*rf, (*tfrom++));
+ (*ls) = mix16(*ls, (*tfrom++));
+ }
+ if (speaker_channel_mask == 2) {
+ (*rs) = mix16(*rs, (*tfrom++));
+ (*lef) = mix16(*lef, (*tfrom++));
+ }
+ if (speaker_channel_mask == 3) {
+ (*sbl) = mix16(*sbl, (*tfrom++));
+ (*sbr) = mix16(*sbr, (*tfrom++));
+ }
+ lf += 8;
+ cf += 8;
+ rf += 8;
+ ls += 8;
+ rs += 8;
+ lef += 8;
+ sbl += 8;
+ sbr += 8;
+ }
+ } else if (i2s_format == 32 ||
+ i2s_format == 24) {
+ s32 *to = (s32 *)dst;
+ s16 *tfrom = (s16 *)src;
+ s32 *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
+
+ lf = to + 0;
+ cf = to + 1;
+ rf = to + 2;
+ ls = to + 3;
+ rs = to + 4;
+ lef = to + 5;
+ sbl = to + 6;
+ sbr = to + 7;
+
+ for (i = 0; i < i2s_frames_count; i++) {
+ if (speaker_channel_mask == 0) {
+ (*lf) = mix32(*lf, (*tfrom++) << 16);
+ (*cf) = mix32(*cf, (*tfrom++) << 16);
+ }
+ if (speaker_channel_mask == 1) {
+ (*rf) = mix32(*rf, (*tfrom++) << 16);
+ (*ls) = mix32(*ls, (*tfrom++) << 16);
+ }
+ if (speaker_channel_mask == 2) {
+ (*rs) = mix32(*rs, (*tfrom++) << 16);
+ (*lef) = mix32(*lef, (*tfrom++) << 16);
+ }
+
+ if (speaker_channel_mask == 3) {
+ (*sbl) = mix32(*sbl, (*tfrom++) << 16);
+ (*sbr) = mix32(*sbr, (*tfrom++) << 16);
+ }
+ lf += 8;
+ cf += 8;
+ rf += 8;
+ ls += 8;
+ rs += 8;
+ lef += 8;
+ sbl += 8;
+ sbr += 8;
+ }
+ } else {
+ pr_err("Error: Unsupport format:%d\n", i2s_format);
+ }
+ } else {
+ pr_err("Error: Unsupport channels:%d\n", i2s_channels);
+ }
+#else
+ int j;
+
+ if (i2s_channels == 2) {
+ if (i2s_format == 16) {
+ s16 *to = (s16 *)(dst);
+ s16 *tfrom = (s16 *)(src);
+ s16 *lf, *rf;
+
+ lf = to;
+ rf = lf + 16;
+ for (i = 0; i < count; i += 64) {
+ for (j = 0; j < 16; j++) {
+ (*lf++) = mix16(*lf, *tfrom++);
+ (*rf++) = mix16(*rf, *tfrom++);
+ }
+ lf += 16;
+ rf += 16;
+ }
+ } else if (i2s_format == 32 ||
+ i2s_format == 24) {
+ s32 *to = (s32 *)dst;
+ s16 *tfrom = (s16 *)src;
+ s32 *lf, *rf;
+ s32 sample;
+
+ lf = to;
+ rf = to + 8;
+ for (i = 0; i < count; i += 64) {
+ for (j = 0; j < 8; j++) {
+ (*lf++) = mix32(
+ *lf, ((s32)(*tfrom++)) << 8);
+ (*rf++) = mix32(
+ *rf, ((s32)(*tfrom++)) << 8);
+ }
+ lf += 8;
+ rf += 8;
+ }
+ } else {
+ pr_err("Error: Unsupport format:%d\n", i2s_format);
+ }
+ } else if (i2s_channels == 8) {
+ if (i2s_format == 16) {
+ s16 *to = (s16 *)(dst);
+ s16 *tfrom = (s16 *)(src);
+ s16 *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
+
+ lf = to + 0 * 16;
+ cf = to + 1 * 16;
+ rf = to + 2 * 16;
+ ls = to + 3 * 16;
+ rs = to + 4 * 16;
+ lef = to + 5 * 16;
+ sbl = to + 6 * 16;
+ sbr = to + 7 * 16;
+ for (j = 0; j < count; j += 256) {
+ for (i = 0; i < 16; i++) {
+ if (speaker_channel_mask == 0) {
+ (*lf++) = mix16(
+ *lf, (*tfrom++));
+ (*cf++) = mix16(
+ *cf, (*tfrom++));
+ } else {
+ lf++;
+ cf++;
+ }
+ if (speaker_channel_mask == 1) {
+ (*rf++) = mix16(
+ *rf, (*tfrom++));
+ (*ls++) = mix16(
+ *ls, (*tfrom++));
+ } else {
+ rf++;
+ ls++;
+ }
+ if (speaker_channel_mask == 2) {
+ (*rs++) = mix16(
+ *rs, (*tfrom++));
+ (*lef++) = mix16(
+ *lef, (*tfrom++));
+ } else {
+ rs++;
+ lef++;
+ }
+ if (speaker_channel_mask == 3) {
+ (*sbl++) = mix16(
+ *sbl, (*tfrom++));
+ (*sbr++) = mix16(
+ *sbr, (*tfrom++));
+ } else {
+ sbl++;
+ sbr++;
+ }
+ }
+ lf += 7 * 16;
+ cf += 7 * 16;
+ rf += 7 * 16;
+ ls += 7 * 16;
+ rs += 7 * 16;
+ lef += 7 * 16;
+ sbl += 7 * 16;
+ sbr += 7 * 16;
+ }
+
+ } else if (i2s_format == 32 ||
+ i2s_format == 24) {
+ s32 *to = (s32 *)(dst);
+ s16 *tfrom = (s16 *)(src);
+ s32 *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
+
+ lf = to + 0 * 8;
+ cf = to + 1 * 8;
+ rf = to + 2 * 8;
+ ls = to + 3 * 8;
+ rs = to + 4 * 8;
+ lef = to + 5 * 8;
+ sbl = to + 6 * 8;
+ sbr = to + 7 * 8;
+ for (j = 0; j < count; j += 256) {
+ for (i = 0; i < 8; i++) {
+ if (speaker_channel_mask == 0) {
+ (*lf++) = mix32(
+ *lf,
+ ((s32)(*tfrom++)) << 8);
+ (*cf++) = mix32(
+ *cf,
+ ((s32)(*tfrom++)) << 8);
+ } else {
+ lf++;
+ cf++;
+ }
+ if (speaker_channel_mask == 1) {
+ (*rf++) = mix32(
+ *rf,
+ ((s32)(*tfrom++)) << 8);
+ (*ls++) = mix32(
+ *ls,
+ ((s32)(*tfrom++)) << 8);
+ } else {
+ rf++;
+ ls++;
+ }
+ if (speaker_channel_mask == 2) {
+ (*rs++) = mix32(
+ *rs,
+ ((s32)(*tfrom++)) << 8);
+ (*lef++) = mix32(
+ *lef,
+ ((s32)(*tfrom++)) << 8);
+ } else {
+ rs++;
+ lef++;
+ }
+ if (speaker_channel_mask == 3) {
+ (*sbl++) = mix32(
+ *sbl,
+ ((s32)(*tfrom++)) << 8);
+ (*sbr++) = mix32(
+ *sbr,
+ ((s32)(*tfrom++)) << 8);
+ } else {
+ sbl++;
+ sbr++;
+ }
+ }
+ lf += 7 * 8;
+ cf += 7 * 8;
+ rf += 7 * 8;
+ ls += 7 * 8;
+ rs += 7 * 8;
+ lef += 7 * 8;
+ sbl += 7 * 8;
+ sbr += 7 * 8;
+ }
+ } else {
+ pr_err("Error: Unsupport format:%d\n", i2s_format);
+ }
+ } else {
+ pr_err("Error: Unsupport channels:%d\n", i2s_channels);
+ }
+#endif
+
+ return 0;
+}
+
+/*mix i2s memory audio data with usb audio record in,output stereo to i2s*/
+static void i2s_out_mix(
+ i2s_audio_buffer *i2s_audio,
+ struct usb_audio_buffer *usb_audio,
+ struct i2s_info *p_i2sinfo,
+ mixer_f mixer)
+{
+ i2s_audio_buffer *i2sbuf = i2s_audio;
+ struct usb_audio_buffer *usbbuf = usb_audio;
+ unsigned int i2s_out_ptr = i2s_get_out_read_ptr();
+ unsigned int alsa_delay = (aml_i2s_alsa_write_addr +
+ i2sbuf->size - i2s_out_ptr) % i2sbuf->size;
+ unsigned int i2s_mix_delay = (i2sbuf->wr +
+ i2sbuf->size - i2s_out_ptr) % i2sbuf->size;
+ unsigned long i2sirqflags, usbirqflags;
+ unsigned int mix_count = 0, mix_usb_count;
+ int avail = 0;
+ int i2s_frame, usb_frame, i2s_frames_count, usb_frames_count;
+
+ i2s_frame = p_i2sinfo->channels * (p_i2sinfo->format / 8);
+ usb_frame = 4; /* usb in: 2ch, 16bit */
+
+ spin_lock_irqsave(&i2sbuf->lock, i2sirqflags);
+
+ i2sbuf->rd = i2s_out_ptr;
+ /*(i2sbuf->size + i2sbuf->wr - i2s_out_ptr) % i2sbuf->size;*/
+ i2sbuf->level = i2s_mix_delay;
+
+ mix_count = p_i2sinfo->int_block;
+ i2s_frames_count = mix_count / i2s_frame;
+
+ /*update*/
+ usb_frames_count = i2s_frames_count;
+ mix_usb_count = usb_frames_count * usb_frame;
+
+ if (i2sbuf->level <= p_i2sinfo->int_block ||
+ (alsa_delay - i2s_mix_delay) < p_i2sinfo->int_block) {
+ i2sbuf->wr = (i2sbuf->rd + p_i2sinfo->latency) % i2sbuf->size;
+ i2sbuf->wr /= p_i2sinfo->int_block;
+ i2sbuf->wr *= p_i2sinfo->int_block;
+ i2sbuf->level = p_i2sinfo->latency;
+ goto EXIT;
+ }
+
+ if (i2sbuf->wr % p_i2sinfo->int_block) {
+ i2sbuf->wr /= p_i2sinfo->int_block;
+ i2sbuf->wr *= p_i2sinfo->int_block;
+ }
+
+ if (usbbuf->wr >= usbbuf->rd)
+ avail = usbbuf->wr - usbbuf->rd;
+ else
+ avail = usbbuf->wr + usbbuf->size - usbbuf->rd;
+
+ if (avail < mix_usb_count) {
+ /*
+ * pr_info("i2sOUT buffer underrun\n");
+ * goto EXIT;
+ */
+
+ /*fill zero data*/
+ memset(usbbuf->addr + (usbbuf->rd + avail) % usbbuf->size,
+ 0,
+ mix_usb_count - avail);
+ }
+
+ spin_lock_irqsave(&usbbuf->lock, usbirqflags);
+
+ mixer(i2sbuf->addr + i2sbuf->wr,
+ usbbuf->addr + usbbuf->rd,
+ mix_count,
+ p_i2sinfo->channels,
+ p_i2sinfo->format);
+
+ i2sbuf->wr = (i2sbuf->wr + mix_count) % i2sbuf->size;
+ i2sbuf->level = (i2sbuf->size + i2sbuf->wr - i2sbuf->rd) % i2sbuf->size;
+
+ usbbuf->rd = (usbbuf->rd + mix_usb_count) % usbbuf->size;
+
+ spin_unlock_irqrestore(&usbbuf->lock, usbirqflags);
+
+EXIT:
+ spin_unlock_irqrestore(&i2sbuf->lock, i2sirqflags);
+}
+
+/* IRQ handler */
+static irqreturn_t i2s_out_mix_callback(int irq, void *data)
+{
+ struct i2s_output *p_i2s_out = (struct i2s_output *)data;
+ i2s_audio_buffer *i2s_buf = (i2s_audio_buffer *)&p_i2s_out->i2s_buf;
+ unsigned int i2s_size = i2s_get_out_size();
+ struct usb_audio_buffer *usbbuf =
+ (struct usb_audio_buffer *)usb_get_audio_info();
+ //struct i2s_info *p_i2sinfo = &p_i2s_out->i2sinfo;
+
+ /*check whether usb audio record start*/
+ if (!usbbuf || !usbbuf->addr || !usbbuf->running)
+ return IRQ_HANDLED;
+
+ /*update i2s buffer informaiton if needed.*/
+ if (i2s_size != i2s_buf->size) {
+ i2s_buf->size = i2s_size;
+ i2s_buf->addr = (unsigned char *)aml_i2s_playback_start_addr;
+ i2s_buf->paddr = aml_i2s_playback_phy_start_addr;
+ i2s_buf->rd = i2s_get_out_read_ptr();
+ }
+
+ schedule_work(&p_i2s_out->work);
+ //i2s_out_mix(i2s_buf, usbbuf, p_i2sinfo, mixer_transfer);
+
+ return IRQ_HANDLED;
+}
+
+/* Work Queue handler */
+static void i2s_out_mix_work_handler(struct work_struct *data)
+{
+ struct i2s_output *p_i2s_out = i2s_get_output();
+ i2s_audio_buffer *i2sbuf =
+ (i2s_audio_buffer *)i2s_get_audio_info();
+ struct usb_audio_buffer *usbbuf =
+ (struct usb_audio_buffer *)usb_get_audio_info();
+ struct i2s_info *p_i2sinfo = &p_i2s_out->i2sinfo;
+
+ if (!i2sbuf || !usbbuf || !p_i2sinfo->channels)
+ return;
+
+ /* mixer: usb info, 2 channels, 16 bits;
+ * i2s info, 2/8 channels, 16/32 bits
+ * case 1: 2 channel mixer with 2 channel i2s
+ * case 2: 2 channel mixer with 8 channel i2s,
+ * to special channels according to speaker_channel_mask
+ */
+ i2s_out_mix(i2sbuf, usbbuf, p_i2sinfo, mixer_transfer);
+}
+
+int i2s_out_mix_init(void)
+{
+ int ret = 0;
+
+ if (!builtin_mixer) {
+ pr_info("Not to mix usb in and i2s out\n");
+ return 0;
+ }
+
+ memset((void *)&s_i2s_output, 0, sizeof(struct i2s_output));
+ /* init i2s audio buffer */
+ spin_lock_init(&s_i2s_output.i2s_buf.lock);
+ s_i2s_output.i2s_buf.addr =
+ (unsigned char *)aml_i2s_playback_start_addr;
+ s_i2s_output.i2s_buf.paddr =
+ aml_i2s_playback_phy_start_addr;
+ s_i2s_output.i2s_buf.size =
+ i2s_get_out_size();
+ s_i2s_output.i2s_buf.rd =
+ i2s_get_out_read_ptr();
+
+ /*defalut for 2ch, 16bit,*/
+ i2s_block_size = I2S_BLOCK_SIZE;
+ s_i2s_output.i2sinfo.int_num = I2S_INT_NUM;
+ s_i2s_output.i2sinfo.i2s_block = i2s_block_size;
+ s_i2s_output.i2sinfo.int_block = I2S_INT_BLOCK;
+ s_i2s_output.i2sinfo.latency = MIN_LATENCY * 2;
+ s_i2s_output.i2sinfo.channels = aml_i2s_playback_channel;
+ s_i2s_output.i2sinfo.format = aml_i2s_playback_format;
+ if (s_i2s_output.i2sinfo.channels == 8) {
+ i2s_block_size *= 4;
+ s_i2s_output.i2sinfo.int_num *= 2;
+ s_i2s_output.i2sinfo.i2s_block = i2s_block_size;
+ s_i2s_output.i2sinfo.int_block = s_i2s_output.i2sinfo.int_num
+ * s_i2s_output.i2sinfo.i2s_block;
+ s_i2s_output.i2sinfo.latency *= 8;
+ }
+
+ pr_info("%s:%d, channels:%d, format:%d, i2s_block_size:%d\n",
+ __func__, __LINE__,
+ s_i2s_output.i2sinfo.channels,
+ s_i2s_output.i2sinfo.format,
+ i2s_block_size);
+
+ if (!s_i2s_output.isInit) {
+ s_i2s_output.isInit = true;
+
+ /*register irq*/
+ if (request_irq(
+ irq_karaoke, i2s_out_mix_callback,
+ IRQF_SHARED, "i2s_out_mix",
+ &s_i2s_output)) {
+ ret = -EINVAL;
+ }
+ pr_info("register irq\n");
+ }
+ /*irq block*/
+#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
+ /* TODO: split mode aiu_mem_i2s_mask[15:0] must set 8'hffff_ffff. */
+ aml_cbus_update_bits(AIU_MEM_I2S_MASKS, 0xffff << 16, 4 << 16);
+#else
+ aml_cbus_update_bits(
+ AIU_MEM_I2S_MASKS,
+ 0xffff << 16,
+ s_i2s_output.i2sinfo.int_num << 16);
+#endif
+
+ /*work queue*/
+ INIT_WORK(&s_i2s_output.work, i2s_out_mix_work_handler);
+
+ return ret;
+}
+EXPORT_SYMBOL(i2s_out_mix_init);
+
+/* Deinit */
+int i2s_out_mix_deinit(void)
+{
+ if (!s_i2s_output.isInit)
+ return -1;
+ free_irq(IRQ_OUT, &s_i2s_output);
+ memset((void *)&s_i2s_output, 0, sizeof(struct i2s_output));
+ return 0;
+}
+EXPORT_SYMBOL(i2s_out_mix_deinit);
diff --git a/drivers/amlogic/amlkaraoke/aml_i2s_out_mix.h b/drivers/amlogic/amlkaraoke/aml_i2s_out_mix.h
new file mode 100644
index 0000000..a65938d
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_i2s_out_mix.h
@@ -0,0 +1,52 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_i2s_out_mix.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __AML_I2S_OUT_MIX_H
+#define __AML_I2S_OUT_MIX_H
+
+/*
+ * A virtual path to get usb audio capture data to i2s mixed out.
+ *
+ */
+
+extern unsigned long aml_i2s_playback_start_addr;
+extern unsigned long aml_i2s_playback_phy_start_addr;
+extern unsigned long aml_i2s_alsa_write_addr;
+
+extern int builtin_mixer;
+extern struct usb_audio_buffer *snd_usb_pcm_capture_buffer;
+
+extern unsigned int aml_i2s_playback_channel;
+extern unsigned int aml_i2s_playback_format;
+extern int irq_karaoke;
+
+/*Keep same struct with usb_capture.h */
+typedef
+struct usb_audio_buffer {
+ dma_addr_t paddr;
+ unsigned char *addr;
+ unsigned int size;
+ unsigned int wr;
+ unsigned int rd;
+ unsigned int level;
+ unsigned int channels;
+ unsigned int rate; /* rate in Hz */
+ unsigned int running;
+ spinlock_t lock; /* lock the ringbuffer */
+} i2s_audio_buffer;
+
+#endif
diff --git a/drivers/amlogic/amlkaraoke/aml_karaoke.c b/drivers/amlogic/amlkaraoke/aml_karaoke.c
new file mode 100644
index 0000000..cd044ee
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_karaoke.c
@@ -0,0 +1,305 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_karaoke.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.
+ *
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+
+#include <linux/amlogic/major.h>
+
+#include "aml_karaoke.h"
+
+int builtin_mixer = 1;
+EXPORT_SYMBOL(builtin_mixer);
+
+static ssize_t show_builtin_mixer(struct class *class,
+ struct class_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", builtin_mixer);
+}
+
+static ssize_t store_builtin_mixer(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+
+ if (buf[0] && kstrtoint(buf, 10, &val))
+ return -EINVAL;
+
+ if (val < 0)
+ val = 0;
+
+ builtin_mixer = val;
+ pr_info("builtin_mixer set to %d\n", builtin_mixer);
+ return count;
+}
+
+int reverb_time;
+EXPORT_SYMBOL(reverb_time);
+
+static ssize_t show_reverb_time(struct class *class,
+ struct class_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", reverb_time);
+}
+
+static ssize_t store_reverb_time(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+
+ if (buf[0] && kstrtoint(buf, 10, &val))
+ return -EINVAL;
+
+ if (val < 0)
+ val = 0;
+ if (val > 6)
+ val = 6;
+
+ reverb_time = val;
+ pr_info("reverb_time set to %d\n", reverb_time);
+ return count;
+}
+
+int usb_mic_digital_gain = 256;
+EXPORT_SYMBOL(usb_mic_digital_gain);
+
+static ssize_t show_usb_mic_digital_gain(struct class *class,
+ struct class_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", usb_mic_digital_gain);
+}
+
+static ssize_t store_usb_mic_digital_gain(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+
+ if (buf[0] && kstrtoint(buf, 10, &val))
+ return -EINVAL;
+
+ if (val < 0)
+ val = 0;
+
+ if (val > 256)
+ val = 256;
+
+ usb_mic_digital_gain = val;
+ pr_info("usb_mic_digital_gain set to %d\n", usb_mic_digital_gain);
+ return count;
+}
+
+int reverb_enable;
+EXPORT_SYMBOL(reverb_enable);
+static ssize_t show_reverb_enable(struct class *class,
+ struct class_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", reverb_enable);
+}
+
+static ssize_t store_reverb_enable(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+
+ if (buf[0] && kstrtoint(buf, 10, &val))
+ return -EINVAL;
+
+ reverb_enable = val;
+ pr_info("reverb_enable set to %d\n", reverb_enable);
+ return count;
+}
+
+int reverb_highpass;
+EXPORT_SYMBOL(reverb_highpass);
+
+static ssize_t show_reverb_highpass(struct class *class,
+ struct class_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", reverb_highpass);
+}
+
+static ssize_t store_reverb_highpass(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+
+ if (buf[0] && kstrtoint(buf, 10, &val))
+ return -EINVAL;
+
+ reverb_highpass = val;
+ pr_info("reverb_highpass set to %d\n", reverb_highpass);
+ return count;
+}
+
+/* reverb in gain [0%, 100%]*/
+int reverb_in_gain = 100;
+EXPORT_SYMBOL(reverb_in_gain);
+static ssize_t show_reverb_in_gain(struct class *class,
+ struct class_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", reverb_in_gain);
+}
+
+static ssize_t store_reverb_in_gain(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+
+ if (buf[0] && kstrtoint(buf, 10, &val))
+ return -EINVAL;
+
+ if (val < 0)
+ val = 0;
+ if (val > 100)
+ val = 100;
+
+ reverb_in_gain = val;
+ pr_info("reverb_in_gain set to %d\n", reverb_in_gain);
+ return count;
+}
+
+/* reverb out gain [0%, 100%]*/
+int reverb_out_gain = 100;
+EXPORT_SYMBOL(reverb_out_gain);
+static ssize_t show_reverb_out_gain(struct class *class,
+ struct class_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", reverb_out_gain);
+}
+
+static ssize_t store_reverb_out_gain(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+
+ if (buf[0] && kstrtoint(buf, 10, &val))
+ return -EINVAL;
+
+ if (val < 0)
+ val = 0;
+ if (val > 100)
+ val = 100;
+
+ reverb_out_gain = val;
+ pr_info("reverb_out_gain set to %d\n", reverb_out_gain);
+ return count;
+}
+
+static struct class_attribute amlkaraoke_attrs[] = {
+ __ATTR(builtin_mixer, 0664,
+ show_builtin_mixer, store_builtin_mixer),
+ __ATTR(reverb_time, 0644,
+ show_reverb_time, store_reverb_time),
+ __ATTR(usb_mic_digital_gain, 0644,
+ show_usb_mic_digital_gain, store_usb_mic_digital_gain),
+ __ATTR(reverb_enable, 0644,
+ show_reverb_enable, store_reverb_enable),
+ __ATTR(reverb_highpass, 0644,
+ show_reverb_highpass, store_reverb_highpass),
+ __ATTR(reverb_in_gain, 0644,
+ show_reverb_in_gain, store_reverb_in_gain),
+ __ATTR(reverb_out_gain, 0644,
+ show_reverb_out_gain, store_reverb_out_gain),
+ __ATTR_NULL,
+};
+
+static struct class amlkaraoke_class = {
+ .name = AMAUDIO_CLASS_NAME,
+ .class_attrs = amlkaraoke_attrs,
+};
+
+int irq_karaoke;
+static int amlkaraoke_probe(struct platform_device *pdev)
+{
+ int int_karaoke = platform_get_irq_byname(pdev, "aml_karaoke");
+
+ pr_info("%s irq %d num", __func__, int_karaoke);
+ irq_karaoke = int_karaoke;
+
+ return 0;
+}
+
+static int amlkaraoke_init(void)
+{
+ int ret = class_register(&amlkaraoke_class);
+
+ if (ret) {
+ pr_err("amlkaraoke class create fail.\n");
+ goto err;
+ }
+
+ pr_info("amlkaraoke init success!\n");
+
+err:
+ return ret;
+}
+
+static int amlkaraoke_exit(void)
+{
+ class_unregister(&amlkaraoke_class);
+
+ pr_info("amlkaraoke_exit!\n");
+ return 0;
+}
+
+static const struct of_device_id amlogic_match[] = {
+ {.compatible = "amlogic, aml_karaoke",},
+ {}
+};
+
+static struct platform_driver aml_karaoke_driver = {
+ .driver = {
+ .name = "aml_karaoke_driver",
+ .owner = THIS_MODULE,
+ .of_match_table = amlogic_match,
+ },
+
+ .probe = amlkaraoke_probe,
+ .remove = NULL,
+};
+
+static int __init aml_karaoke_modinit(void)
+{
+ amlkaraoke_init();
+
+ return platform_driver_register(&aml_karaoke_driver);
+}
+
+static void __exit aml_karaoke_modexit(void)
+{
+ amlkaraoke_exit();
+ platform_driver_unregister(&aml_karaoke_driver);
+}
+
+module_init(aml_karaoke_modinit);
+module_exit(aml_karaoke_modexit);
+
+MODULE_DESCRIPTION("AMLOGIC Karaoke Interface driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Amlogic Inc.");
+MODULE_VERSION("1.0.0");
diff --git a/drivers/amlogic/amlkaraoke/aml_karaoke.h b/drivers/amlogic/amlkaraoke/aml_karaoke.h
new file mode 100644
index 0000000..0fcc2ed
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_karaoke.h
@@ -0,0 +1,30 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_karaoke.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef _AML_KARAOKE_H_
+#define _AML_KARAOKE_H_
+
+/*
+ * A virtual path to get usb audio capture data to i2s mixed out.
+ */
+
+#define AMLKARAOKE_DRIVER_NAME "amlkaraoke"
+#define AMLKARAOKE_DRIVER_NAME "amlkaraoke"
+#define AMLKARAOKE_DEVICE_NAME "amlkaraoke-dev"
+#define AMAUDIO_CLASS_NAME "amlkaraoke"
+
+#endif
diff --git a/drivers/amlogic/amlkaraoke/aml_reverb.c b/drivers/amlogic/amlkaraoke/aml_reverb.c
new file mode 100644
index 0000000..5a8cadc
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_reverb.c
@@ -0,0 +1,235 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_reverb.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.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+
+#include "aml_reverb.h"
+
+struct af_equalizer filer;
+
+/* Compute a realistic decay */
+
+static int decay_table[][8] = {
+ {0, 0, 0, 0, 0, 0, 0, 0}, /*decay 0ms*/
+ {2068, 0, 0, 0, 0, 0, 0, 0}, /*decay time 20ms*/
+ {13045, 130, 1, 0, 0, 0, 0, 0}, /*decay time 60ms*/
+ {20675, 2068, 207, 21, 2, 0, 0, 0}, /*decay time 120ms*/
+ {19284, 5193, 1119, 241, 52, 11, 2, 1}, /*decay time 180ms*/
+ {20615, 7751, 2331, 701, 211, 63, 19, 6}, /*decay time 230ms*/
+ {27255, 10851, 4320, 1720, 685, 273, 109, 43},
+ /*decay time 300ms, max value*/
+};
+
+static inline int clip16(int x)
+{
+ if (x < -32768)
+ return -32768;
+ if (x > 32767)
+ return 32767;
+
+ return x;
+}
+
+int reverb_start(struct reverb_t *aml_reverb)
+{
+ struct reverb_t *reverb = aml_reverb;
+ int i;
+
+ reverb->counter = 0;
+ reverb->in_gain = ((long long)(32767 * reverb_in_gain + 16384))
+ >> 7;
+ reverb->out_gain = ((long long)(32767 * reverb_out_gain + 16384))
+ >> 7;
+ reverb->time = 0;
+ reverb->numdelays = 8;
+ reverb->maxsamples = 0;
+ reverb->reverbbuf = NULL;
+
+ for (i = 0; i < MAXREVERBS; i++) {
+ reverb->delay[i] = 40 * i + 8;
+ reverb->decay[i] = decay_table[reverb->time][i];
+ }
+
+ for (i = 0; i < reverb->numdelays; i++) {
+ /* stereo channel */
+ reverb->samples[i] = reverb->delay[i] * MAXRATE * 2;
+ if (reverb->samples[i] > reverb->maxsamples)
+ reverb->maxsamples = reverb->samples[i];
+ }
+
+ i = sizeof(s16) * reverb->maxsamples;
+ reverb->reverbbuf = kzalloc(i, GFP_KERNEL);
+ if (!reverb->reverbbuf)
+ return -1;
+
+ for (i = 0; i < reverb->numdelays; i++)
+ reverb->in_gain = (reverb->in_gain *
+ (32768 - ((reverb->decay[i] * reverb->decay[i]
+ + 16384) >> 15)) + 16384) >> 15;
+ pr_info("reverb: reverb->in_gain = %d, reverb->maxsamples = %d\n",
+ reverb->in_gain, reverb->maxsamples);
+
+ if (reverb_highpass)
+ eq_init(&filer);
+
+ return 0;
+}
+
+/*
+ * Processed signed long samples from ibuf to obuf.
+ * Return number of samples processed.
+ */
+int reverb_process(struct reverb_t *aml_reverb,
+ s16 *ibuf, s16 *obuf, int isamp,
+ int channels, int channel)
+{
+ struct reverb_t *reverb = aml_reverb;
+ int len, done, offset;
+ int i, j, k;
+ int d_in, d_out;
+ long long tmp;
+
+ /* check whether reverb is enabled */
+ if (!reverb_enable)
+ return -1;
+
+ if (reverb->time != reverb_time) {
+ for (i = 0; i < MAXREVERBS; i++)
+ reverb->decay[i] = decay_table[reverb_time][i];
+
+ memset(reverb->reverbbuf, 0, reverb->maxsamples);
+ pr_info("reverb: reset reverb parameters!\n");
+ }
+
+ i = reverb->counter;
+ len = isamp >> (channels >> 1);
+ offset = channels;
+ for (done = 0; done < len; done++) {
+ d_in = (int)*ibuf;
+ ibuf += offset;
+ tmp = 0;
+
+ if (reverb_highpass)
+ d_in = fourth_order_IIR(d_in, &filer, channel);
+
+ tmp = ((long long)d_in * reverb->in_gain)
+ >> (15 - DATA_FRACTION_BIT);
+ /* Mix decay of delay and input as output */
+ for (j = 0; j < reverb->numdelays; j++) {
+ k = (i + reverb->maxsamples - reverb->samples[j])
+ % reverb->maxsamples;
+ tmp += ((long long)reverb->reverbbuf[k]
+ * reverb->decay[j]) >> 15;
+ }
+ d_in = clip16((tmp + HALF_ERROR) >> DATA_FRACTION_BIT);
+ tmp = (tmp * reverb->out_gain) >> 15;
+ d_out = clip16((tmp + HALF_ERROR) >> DATA_FRACTION_BIT);
+
+ *obuf = (s16)d_out;
+ obuf += offset;
+ reverb->reverbbuf[i] = (s16)d_in;
+ i++;
+ i += (offset >> 1);
+ i %= reverb->maxsamples;
+ }
+ reverb->counter = i;
+ reverb->time = reverb_time;
+
+ return 0;
+}
+
+/*
+ * Clean up reverb effect.
+ */
+int reverb_stop(struct reverb_t *aml_reverb)
+{
+ struct reverb_t *reverb = aml_reverb;
+
+ kfree(reverb->reverbbuf);
+ reverb->reverbbuf = NULL;
+ return 0;
+}
+
+static int eq_coefficients[2][SECTION][COEFF_COUNT] = {
+ { /* B coefficients */
+ {16777216, -33554433, 16777216}, /*B1*/
+ {16777216, -32950274, 16179410}, /*B2*/
+ },
+ { /* A coefficients*/
+ {16777216, -33554431, 16777216}, /*A1*/
+ {16777216, -33297782, 16526985}, /*A2*/
+ },
+};
+
+void eq_init(struct af_equalizer *eq)
+{
+ int i, j;
+
+ for (i = 0; i < SECTION; i++) {
+ for (j = 0; j < COEFF_COUNT; j++) {
+ eq->b[i][j] = eq_coefficients[0][i][j];
+ eq->a[i][j] = eq_coefficients[1][i][j];
+ }
+ }
+
+ memset(eq->cx, 0, sizeof(int) * CF * SECTION * 2);
+ memset(eq->cy, 0, sizeof(int) * CF * SECTION * 2);
+}
+
+int fourth_order_IIR(int input, struct af_equalizer *eq_ptr, int channel)
+{
+ int sample = input;
+ int y = 0, i = 0;
+ long long temp = 0;
+ int cx[2][2], cy[2][2];
+
+ cx[0][0] = eq_ptr->cx[channel][0][0];
+ cx[0][1] = eq_ptr->cx[channel][0][1];
+ cx[1][0] = eq_ptr->cx[channel][1][0];
+ cx[1][1] = eq_ptr->cx[channel][1][1];
+ cy[0][0] = eq_ptr->cy[channel][0][0];
+ cy[0][1] = eq_ptr->cy[channel][0][1];
+ cy[1][0] = eq_ptr->cy[channel][1][0];
+ cy[1][1] = eq_ptr->cy[channel][1][1];
+
+ sample <<= DATA_FRACTION_BIT;
+
+ for (i = 0; i < SECTION; i++) {
+ temp = (long long)sample * (eq_ptr->b[i][0]);
+ temp += (long long)cx[i][0] * (eq_ptr->b[i][1]);
+ temp += (long long)cx[i][1] * (eq_ptr->b[i][2]);
+ temp -= (long long)cy[i][0] * (eq_ptr->a[i][1]);
+ temp -= (long long)cy[i][1] * (eq_ptr->a[i][2]);
+ y = (int)(temp >> COEFF_FRACTION_BIT);
+ cx[i][1] = cx[i][0];
+ cx[i][0] = sample;
+ cy[i][1] = cy[i][0];
+ cy[i][0] = y;
+ sample = y;
+ }
+ eq_ptr->cx[channel][0][0] = cx[0][0];
+ eq_ptr->cx[channel][0][1] = cx[0][1];
+ eq_ptr->cx[channel][1][0] = cx[1][0];
+ eq_ptr->cx[channel][1][1] = cx[1][1];
+ eq_ptr->cy[channel][0][0] = cy[0][0];
+ eq_ptr->cy[channel][0][1] = cy[0][1];
+ eq_ptr->cy[channel][1][0] = cy[1][0];
+ eq_ptr->cy[channel][1][1] = cy[1][1];
+
+ return (y + HALF_ERROR) >> DATA_FRACTION_BIT;
+}
diff --git a/drivers/amlogic/amlkaraoke/aml_reverb.h b/drivers/amlogic/amlkaraoke/aml_reverb.h
new file mode 100644
index 0000000..3a5f085
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_reverb.h
@@ -0,0 +1,84 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_reverb.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef AML_REVERB_H
+#define AML_REVERB_H
+
+#define MAXRATE 48 /*sample num in ms */
+#define MAXREVERBS 8
+
+extern int reverb_time;
+extern int reverb_enable;
+extern int reverb_highpass;
+extern int reverb_in_gain;
+extern int reverb_out_gain;
+
+struct reverb_t {
+ int counter;
+ int numdelays;
+ int in_gain;
+ int out_gain;
+ int time;
+ int delay[MAXREVERBS];
+ int decay[MAXREVERBS];
+ int samples[MAXREVERBS];
+ int maxsamples;
+ s16 *reverbbuf;
+};
+
+#define DATA_FRACTION_BIT 0
+#define HALF_ERROR 0 /*((0x1) << (DATA_FRACTION_BIT - 1))*/
+
+/* Count if coefficients */
+#define COEFF_COUNT 3
+/*Section of cascaded two order IIR filter*/
+#define SECTION 2
+/*Channel Count*/
+#define CF 2
+#define COEFF_FRACTION_BIT 24
+
+struct af_equalizer {
+ /*B coefficient array*/
+ int b[SECTION][COEFF_COUNT];
+ /*A coefficient array*/
+ int a[SECTION][COEFF_COUNT];
+ /*Circular buffer for channel input data*/
+ int cx[CF][SECTION][2];
+ /*Circular buffer for channel output data*/
+ int cy[CF][SECTION][2];
+};
+
+struct audio_format {
+ short left;
+ short right;
+};
+
+int reverb_start(struct reverb_t *aml_reverb);
+int reverb_process(struct reverb_t *aml_reverb,
+ s16 *ibuf, s16 *obuf, int isamp,
+ int channels, int channel);
+int reverb_drain(struct reverb_t *aml_reverb, s16 *obuf, int osamp);
+int reverb_stop(struct reverb_t *aml_reverb);
+
+void eq_init(struct af_equalizer *eq);
+
+int fourth_order_IIR(int input,
+ struct af_equalizer *eq_ptr,
+ int channel);
+
+#endif
+
diff --git a/drivers/amlogic/amlkaraoke/aml_usb_capture.c b/drivers/amlogic/amlkaraoke/aml_usb_capture.c
new file mode 100644
index 0000000..e014c74
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_usb_capture.c
@@ -0,0 +1,552 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_usb_capture.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.
+ *
+ */
+
+/*
+ * A virtual path to get usb audio capture data.
+ *
+ */
+#include <linux/slab.h>
+#include <linux/bug.h>
+#include <sound/core.h>
+
+#include <linux/amlogic/media/sound/usb_karaoke.h>
+
+#include "aml_usb_capture.h"
+#include "aml_reverb.h"
+
+/*void *snd_usb_pcm_capture_buffer = NULL;*/
+struct usb_audio_buffer *snd_usb_pcm_capture_buffer;
+EXPORT_SYMBOL(snd_usb_pcm_capture_buffer);
+
+/* delay time for no audio effect to avoid noise. */
+int usb_ignore_effect_time;
+
+struct usb_input *s_usb_capture;
+struct reverb_t aml_reverb_l;
+struct reverb_t aml_reverb_r;
+
+/* i2s info */
+static unsigned int aml_i2s_playback_channels;
+static unsigned int aml_i2s_playback_sample;
+
+void aml_i2s_set_ch_r_info(unsigned int channels, unsigned int samplerate)
+{
+ aml_i2s_playback_sample = samplerate;
+ aml_i2s_playback_channels = channels;
+
+ pr_info("[%s]:[%d] samplerate:%d, channels:%d\n",
+ __func__, __LINE__, samplerate, channels);
+
+ /* limit resample usb in to 2 channels */
+ if (aml_i2s_playback_channels > 2)
+ aml_i2s_playback_channels = 2;
+}
+
+static inline short clip(int x)
+{
+ if (x < -32768)
+ x = -32768;
+ else if (x > 32767)
+ x = 32767;
+ return x & 0xFFFF;
+}
+
+struct usb_input *usb_audio_get_capture(void)
+{
+ return s_usb_capture;
+}
+
+static void usb_audio_data_tuning_mic_gain(unsigned char *audiobuf,
+ int frames)
+{
+ s16 *tuningbuf = (s16 *)audiobuf;
+ int i;
+
+ WARN_ON(!audiobuf);
+ for (i = 0; i < frames; i++) {
+ *tuningbuf = ((*tuningbuf) * (usb_mic_digital_gain)) >> 8;
+ *tuningbuf = clip(*tuningbuf);
+ tuningbuf += 1;
+ }
+}
+
+static int usb_audio_mono2stereo(unsigned char *dst,
+ unsigned char *src, int frames)
+{
+ int j;
+ s16 *transfer_src = (s16 *)src;
+ s16 *transfer_dst = (s16 *)dst;
+
+ for (j = 0; j < frames; j++) {
+ transfer_dst[2 * j] = transfer_src[j];
+ transfer_dst[2 * j + 1] = transfer_src[j];
+ }
+
+ return 0;
+}
+
+static int usb_resample_and_mono2stereo_malloc_buffer(
+ struct usb_input *usbinput,
+ unsigned int out_rate)
+{
+ if (!usbinput->out_buffer &&
+ !usbinput->usb_buf &&
+ !usbinput->usb_buf->rate) {
+ pr_info("usb resample buffer alloc failed\n");
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int usb_resample_and_mono2stereo_free_buffer(
+ struct usb_input *usbinput)
+{
+ if (usbinput &&
+ (usbinput->out_buffer || usbinput->mono2stereo)) {
+ kfree(usbinput->out_buffer);
+ kfree(usbinput->mono2stereo);
+ }
+
+ return 0;
+}
+
+static int usb_audio_capture_malloc_buffer(
+ struct usb_audio_buffer *usb_audio, int size)
+{
+ usb_audio->addr = (unsigned char *)
+ kzalloc(size, GFP_KERNEL);
+ if (!usb_audio->addr) {
+ pr_info("USB audio capture buffer alloc failed\n");
+ return -ENOMEM;
+ }
+ usb_audio->size = size;
+ return 0;
+}
+
+static int usb_audio_capture_free_buffer(
+ struct usb_audio_buffer *usb_audio)
+{
+ if (usb_audio && usb_audio->addr) {
+ kfree(usb_audio->addr);
+ usb_audio->addr = NULL;
+ }
+ return 0;
+}
+
+static int usb_check_resample_and_mono2stereo_buffer(
+ struct usb_input *usbinput, unsigned int rate)
+{
+ int ret = -1;
+
+ if (rate &&
+ ((!usbinput->out_buffer) || (!usbinput->mono2stereo))) {
+ usb_resample_and_mono2stereo_malloc_buffer(
+ usbinput,
+ rate);
+
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int usb_check_and_do_mono2stereo(struct usb_input *usbinput,
+ unsigned int channels,
+ unsigned char *cp,
+ unsigned int frames)
+
+{
+ int ret = -1;
+ struct usb_audio_buffer *usb_buf = usbinput->usb_buf;
+
+ if (!usb_buf) {
+ pr_info("check and do mono2stereo, invalid usb buffer\n");
+ return ret;
+ }
+
+ if (channels && channels != usb_buf->channels &&
+ usbinput->mono2stereo) {
+ usb_audio_mono2stereo(
+ usbinput->mono2stereo,
+ cp,
+ frames);
+ ret = 0;
+ }
+ return ret;
+}
+
+static void usb_check_resample_init(struct usb_input *usbinput,
+ unsigned int rate,
+ unsigned int channels)
+{
+ struct usb_audio_buffer *usb_buf = usbinput->usb_buf;
+
+ if (!usb_buf) {
+ pr_info("check mono2stereo init, invalid usb buffer\n");
+ return;
+ }
+
+ if (!usbinput->resample_request &&
+ usb_buf->rate && rate && channels &&
+ rate != usb_buf->rate) {
+ usbinput->resample_request = true;
+
+ usbinput->resampler.input_sr = usb_buf->rate;
+ usbinput->resampler.output_sr = rate;
+ usbinput->resampler.channels = channels;
+ resampler_init(&usbinput->resampler);
+ pr_info("check mono2stereo init, resample from %d ch, %d to %d ch, %d",
+ usb_buf->channels,
+ usb_buf->rate,
+ channels,
+ rate);
+ }
+}
+
+static int usb_check_and_do_resample(struct usb_input *usbinput,
+ unsigned int channels,
+ unsigned char *cp,
+ unsigned int in_frames)
+{
+ struct usb_audio_buffer *usb_buf = usbinput->usb_buf;
+ void *in_buffer;
+ unsigned int out_frames;
+
+ if (!usb_buf) {
+ pr_info("check and do resample, invalid usb buffer\n");
+ return -1;
+ }
+
+ if ((usb_buf->channels == 1) &&
+ (channels != usb_buf->channels) &&
+ usbinput->mono2stereo) {
+ in_buffer = usbinput->mono2stereo;
+ /* in_frames <<= 1; */
+ } else {
+ /* in_frames = bytes >> subs->
+ * usb_input->resampler.channels;
+ */
+ in_buffer = cp;
+ }
+
+ out_frames = resample_process(
+ &usbinput->resampler,
+ in_frames,
+ (short *)(in_buffer),
+ (short *)usbinput->out_buffer);
+
+ return out_frames;
+}
+
+static void usb_check_and_do_reverb(struct reverb_t *reverb_L,
+ struct reverb_t *reverb_R,
+ unsigned int channels,
+ unsigned char *cp,
+ unsigned int frames)
+{
+ if (channels == 1) {
+ reverb_process(reverb_L, (s16 *)cp, (s16 *)cp,
+ frames, channels, 0);
+ } else {
+ reverb_process(reverb_L, (s16 *)cp, (s16 *)cp,
+ frames, channels, 0);
+ reverb_process(reverb_R, (s16 *)cp + 1,
+ (s16 *)cp + 1, frames,
+ channels, 1);
+ }
+}
+
+static void usb_audio_copy_ringbuffer(struct usb_audio_buffer *usb_buf,
+ unsigned char *cp_src,
+ unsigned int cp_bytes)
+{
+ uint avail;
+ unsigned long usbirqflags;
+
+ spin_lock_irqsave(&usb_buf->lock, usbirqflags);
+
+ if (usb_buf->wr >= usb_buf->rd)
+ avail = usb_buf->rd + usb_buf->size - usb_buf->wr;
+ else
+ avail = usb_buf->rd - usb_buf->wr;
+
+ if (avail >= cp_bytes) {
+ if (usb_buf->wr + cp_bytes > usb_buf->size) {
+ memcpy(usb_buf->addr + usb_buf->wr,
+ cp_src, usb_buf->size - usb_buf->wr);
+ memcpy(usb_buf->addr,
+ cp_src + usb_buf->size - usb_buf->wr,
+ cp_bytes + usb_buf->wr - usb_buf->size);
+ } else {
+ memcpy(usb_buf->addr + usb_buf->wr,
+ cp_src, cp_bytes);
+ }
+
+ usb_buf->wr = (usb_buf->wr + cp_bytes) %
+ usb_buf->size;
+ usb_buf->level = (usb_buf->size + usb_buf->wr
+ - usb_buf->rd) % usb_buf->size;
+ } else {
+ /*reset buffer ptr*/
+ usb_buf->wr = (usb_buf->rd + usb_buf->size / 2) % usb_buf->size;
+ }
+ spin_unlock_irqrestore(&usb_buf->lock, usbirqflags);
+}
+
+int usb_set_capture_status(bool isrunning)
+{
+ struct usb_input *usbinput = usb_audio_get_capture();
+
+ if (!usbinput)
+ return -1;
+ if (!usbinput->usb_buf)
+ return -2;
+
+ usbinput->usb_buf->running = isrunning;
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_set_capture_status);
+
+int usb_audio_capture_init(void)
+{
+ struct usb_audio_buffer *usb_buffer = NULL;
+ struct usb_input *s_usb_input;
+ int ret = 0;
+ unsigned int buffer_size = 0;
+
+ s_usb_input = kzalloc(sizeof(*s_usb_input), GFP_KERNEL);
+ if (!s_usb_input) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ usb_buffer = kzalloc(sizeof(*usb_buffer), GFP_KERNEL);
+ if (!usb_buffer) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ s_usb_input->usb_buf = usb_buffer;
+
+ snd_usb_pcm_capture_buffer = usb_buffer;
+
+ if (usb_audio_capture_malloc_buffer(
+ usb_buffer,
+ USB_AUDIO_CAPTURE_BUFFER_SIZE)) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ buffer_size = (USB_AUDIO_CAPTURE_PACKAGE_SIZE * (48000
+ / 8000) + 1) * 4;
+
+ if (!s_usb_input->out_buffer) {
+ s_usb_input->out_buffer = (unsigned char *)
+ kzalloc(buffer_size, GFP_KERNEL);
+ if (!s_usb_input->out_buffer) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ }
+
+ s_usb_input->mono2stereo = (unsigned char *)
+ kzalloc(buffer_size * 2, GFP_KERNEL);
+ if (!s_usb_input->mono2stereo) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /*snd_usb_pcm_capture_buffer->addr = usb_buffer->addr;*/
+ spin_lock_init(&usb_buffer->lock);
+ s_usb_capture = s_usb_input;
+
+ reverb_start(&aml_reverb_l);
+ reverb_start(&aml_reverb_r);
+
+ /* ignore some usb data */
+ usb_ignore_effect_time = 3;
+
+err:
+ if (!s_usb_input) {
+ if (!s_usb_input->out_buffer)
+ kfree(s_usb_input->out_buffer);
+ kfree(s_usb_input);
+ }
+ if (!usb_buffer) {
+ usb_audio_capture_free_buffer(usb_buffer);
+ kfree(usb_buffer);
+ }
+ return ret;
+}
+EXPORT_SYMBOL(usb_audio_capture_init);
+
+int usb_audio_capture_deinit(void)
+{
+ struct usb_input *usbinput = usb_audio_get_capture();
+
+ if (!usbinput)
+ return 0;
+
+ if (snd_usb_pcm_capture_buffer) {
+ usb_audio_capture_free_buffer(usbinput->usb_buf);
+ kfree(usbinput->usb_buf);
+ usbinput->usb_buf = NULL;
+ snd_usb_pcm_capture_buffer = NULL;
+ }
+
+ usb_resample_and_mono2stereo_free_buffer(usbinput);
+ kfree(usbinput);
+
+ reverb_stop(&aml_reverb_l);
+ reverb_stop(&aml_reverb_r);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_audio_capture_deinit);
+
+int retire_capture_usb(struct snd_pcm_runtime *runtime,
+ unsigned char *cp, unsigned int bytes,
+ unsigned int oldptr, unsigned int stride)
+{
+ struct usb_input *usbinput = usb_audio_get_capture();
+ struct usb_audio_buffer *usb_buf = NULL;
+ unsigned char *cp_src = NULL;
+ unsigned char *effect_cp = NULL;
+ unsigned int effect_bytes = 0;
+ unsigned int frame_count = 0;
+ unsigned int cp_bytes = 0;
+ unsigned int in_frames = 0, out_frames = 0;
+ int ret = 0;
+
+ if ((builtin_mixer && usb_ignore_effect_time) || (!builtin_mixer)) {
+ /* At the beginning when usb capture start,
+ * noise is captured in the first audio data,
+ * so deley 3ms for no audio effect.
+ */
+ if (usb_ignore_effect_time)
+ usb_ignore_effect_time--;
+
+ /* copy a data chunk */
+ if (oldptr + bytes > runtime->buffer_size * stride) {
+ unsigned int bytes1 =
+ runtime->buffer_size * stride - oldptr;
+ memcpy(runtime->dma_area + oldptr, cp, bytes1);
+ memcpy(runtime->dma_area,
+ cp + bytes1, bytes - bytes1);
+ } else {
+ memcpy(runtime->dma_area + oldptr, cp, bytes);
+ }
+
+ goto exit;
+ }
+
+ effect_cp = cp;
+ effect_bytes = bytes;
+ frame_count = bytes_to_frames(runtime, effect_bytes);
+
+ /* check data is valid */
+ if (NULL == effect_cp || 0 == effect_bytes) {
+ /* pr_info("retire usb, invalid data,cp:%p, bytes:%d\n",
+ * cp, bytes);
+ */
+ ret = -1;
+ goto exit;
+ }
+
+ /* Audio effect reverb */
+ if (reverb_enable) {
+ usb_check_and_do_reverb(
+ &aml_reverb_l, &aml_reverb_r,
+ runtime->channels,
+ effect_cp, frame_count);
+ }
+
+ /* Tuning usb mic gain */
+ usb_audio_data_tuning_mic_gain(effect_cp,
+ frame_count);
+
+ /* copy a data chunk */
+ if (oldptr + effect_bytes >
+ runtime->buffer_size * stride) {
+ unsigned int bytes1 =
+ runtime->buffer_size * stride - oldptr;
+ memcpy(runtime->dma_area + oldptr,
+ effect_cp, bytes1);
+ memcpy(runtime->dma_area,
+ effect_cp + bytes1, effect_bytes - bytes1);
+ } else {
+ memcpy(runtime->dma_area + oldptr,
+ effect_cp, effect_bytes);
+ }
+
+ /* Audio reverb effect is added to the source,
+ * countine to do resample/mono2stereo, then copy
+ * audio data to ring buffer.
+ */
+ usb_buf = usbinput->usb_buf;
+ usb_buf->channels = runtime->channels;
+ usb_buf->rate = runtime->rate;
+
+ /*usb resample and mono2stereo buffer prepared*/
+ usb_check_resample_and_mono2stereo_buffer(
+ usbinput, aml_i2s_playback_sample);
+
+ /*mono to stereo, default that i2s channel is stereo.*/
+ in_frames = frame_count;
+ usb_check_and_do_mono2stereo(
+ usbinput, aml_i2s_playback_channels,
+ cp, in_frames);
+
+ /* Resample Init */
+ usb_check_resample_init(usbinput, aml_i2s_playback_sample,
+ aml_i2s_playback_channels);
+
+ if ((bytes) && (usbinput->resample_request) &&
+ (usbinput->out_buffer)) {
+ /* bytes to frame, bytes / (channel * bytes_per_frame),
+ * default:16bit
+ */
+ out_frames = usb_check_and_do_resample(
+ usbinput,
+ aml_i2s_playback_channels,
+ cp, in_frames);
+ if (out_frames < 0) {
+ pr_info("do reample failed\n");
+ goto exit;
+ }
+ cp_src = usbinput->out_buffer;
+ cp_bytes = out_frames * 4;
+ } else {
+ /*snd_printdd(KERN_ERR "usb record not resample\n");*/
+ if (usb_buf->channels == 1 && usbinput->mono2stereo &&
+ aml_i2s_playback_channels &&
+ aml_i2s_playback_channels != usb_buf->channels) {
+ cp_src = usbinput->mono2stereo;
+ cp_bytes = in_frames * 4;
+ } else {
+ cp_src = cp;
+ cp_bytes = bytes;
+ }
+ }
+
+ /* copy audio audio to ring buffer.
+ * Default, ring buffer, stereo channel, 16bit
+ */
+ usb_audio_copy_ringbuffer(usb_buf, cp_src, cp_bytes);
+
+exit:
+ return ret;
+}
+EXPORT_SYMBOL(retire_capture_usb);
diff --git a/drivers/amlogic/amlkaraoke/aml_usb_capture.h b/drivers/amlogic/amlkaraoke/aml_usb_capture.h
new file mode 100644
index 0000000..1a763c7
--- a/dev/null
+++ b/drivers/amlogic/amlkaraoke/aml_usb_capture.h
@@ -0,0 +1,59 @@
+/*
+ * drivers/amlogic/amlkaraoke/aml_usb_capture.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __AML_USB_CAPTURE_H
+#define __AML_USB_CAPTURE_H
+
+/*
+ * A virtual path to get usb audio capture data.
+ *
+ */
+#include <sound/pcm.h>
+
+#include "aml_audio_resampler.h"
+
+/* Keep same with aml_i2s_out_mix.h */
+struct usb_audio_buffer {
+ dma_addr_t paddr;
+ unsigned char *addr;
+ unsigned int size;
+ unsigned int wr;
+ unsigned int rd;
+ unsigned int level;
+ unsigned int channels; /* channels */
+ unsigned int rate; /* rate in Hz */
+ unsigned int running;
+ spinlock_t lock; /*lock the ringbuffer */
+};
+
+struct usb_input {
+ struct usb_audio_buffer *usb_buf;
+ /* Resample if needed */
+ bool resample_request;
+ struct resample_para resampler;
+ unsigned char *out_buffer;
+ unsigned char *mono2stereo;
+};
+
+#define USB_AUDIO_CAPTURE_BUFFER_SIZE (1024 * 4)
+#define USB_AUDIO_CAPTURE_PACKAGE_SIZE (512)
+
+extern int usb_mic_digital_gain;
+extern int builtin_mixer;
+extern int reverb_enable;
+
+#endif
diff --git a/include/linux/amlogic/media/sound/usb_karaoke.h b/include/linux/amlogic/media/sound/usb_karaoke.h
new file mode 100644
index 0000000..305c165
--- a/dev/null
+++ b/include/linux/amlogic/media/sound/usb_karaoke.h
@@ -0,0 +1,27 @@
+/*
+ * include/linux/amlogic/media/sound/usb_karaoke.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __USB_KARAOKE_H
+#define __USB_KARAOKE_H
+
+int i2s_out_mix_init(void);
+int i2s_out_mix_deinit(void);
+void aml_i2s_set_ch_r_info(
+ unsigned int channels,
+ unsigned int samplerate);
+
+#endif
diff --git a/sound/soc/amlogic/meson/i2s_dai.c b/sound/soc/amlogic/meson/i2s_dai.c
index 381f389..ea55b37 100644
--- a/sound/soc/amlogic/meson/i2s_dai.c
+++ b/sound/soc/amlogic/meson/i2s_dai.c
@@ -48,6 +48,9 @@
#include <linux/amlogic/media/sound/aout_notify.h>
#include "spdif_dai.h"
#include "dmic.h"
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+#include <linux/amlogic/media/sound/usb_karaoke.h>
+#endif
static int i2s_clk_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
@@ -279,12 +282,34 @@ static int aml_dai_i2s_prepare(struct snd_pcm_substream *substream,
/* use the hw same sync for i2s/958 */
pr_debug("i2s/958 same source\n");
}
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+ aml_i2s_set_ch_r_info(runtime->channels, runtime->rate);
+ i2s_out_mix_init();
+#endif
if (runtime->channels == 8) {
- pr_debug("8ch PCM output->notify HDMI\n");
- aout_notifier_call_chain(AOUT_EVENT_IEC_60958_PCM,
- substream);
+ dev_info(substream->pcm->card->dev, "8ch PCM output->notify HDMI\n");
+ aout_notifier_call_chain(
+ AOUT_EVENT_IEC_60958_PCM,
+ substream);
}
}
+
+ return 0;
+}
+
+static int aml_dai_i2s_hw_free(
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+ struct aml_runtime_data *prtd = substream->runtime->private_data;
+ struct audio_stream *s = &prtd->s;
+
+ if (s && s->device_type == AML_AUDIO_I2SOUT) {
+ i2s_out_mix_deinit();
+ aml_i2s_set_ch_r_info(0, 0);
+ }
+#endif
return 0;
}
@@ -431,6 +456,7 @@ static struct snd_soc_dai_ops aml_dai_i2s_ops = {
.prepare = aml_dai_i2s_prepare,
.trigger = aml_dai_i2s_trigger,
.hw_params = aml_dai_i2s_hw_params,
+ .hw_free = aml_dai_i2s_hw_free,
.set_fmt = aml_dai_set_i2s_fmt,
.set_sysclk = aml_dai_set_i2s_sysclk,
};
diff --git a/sound/soc/amlogic/meson/i2s_dai.h b/sound/soc/amlogic/meson/i2s_dai.h
index 202b6f5..88dea68 100644
--- a/sound/soc/amlogic/meson/i2s_dai.h
+++ b/sound/soc/amlogic/meson/i2s_dai.h
@@ -28,4 +28,5 @@ struct aml_i2s {
int clk_data_pos;
unsigned long mclk;
};
+
#endif
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index 497bad9..c29b37a 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -1247,6 +1247,7 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction)
struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usb_substream *subs = &as->substream[direction];
+ int ret = 0;
subs->interface = -1;
subs->altset_idx = 0;
@@ -1260,7 +1261,28 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction)
subs->dsd_dop.channel = 0;
subs->dsd_dop.marker = 1;
- return setup_hw_info(runtime, subs);
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+ if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+ ret = usb_audio_capture_init();
+ if (ret)
+ goto err;
+ }
+
+ ret = setup_hw_info(runtime, subs);
+
+ return ret;
+
+err:
+ snd_printdd(KERN_ERR "built-in mixer alloc usb buffer failed.\n");
+ if (direction == SNDRV_PCM_STREAM_CAPTURE)
+ usb_audio_capture_deinit();
+
+ ret = setup_hw_info(runtime, subs);
+#else
+ ret = setup_hw_info(runtime, subs);
+#endif
+
+ return ret;
}
static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction)
@@ -1280,6 +1302,11 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction)
subs->pcm_substream = NULL;
snd_usb_autosuspend(subs->stream->chip);
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+ if (direction == SNDRV_PCM_STREAM_CAPTURE)
+ usb_audio_capture_deinit();
+#endif
+
return 0;
}
@@ -1343,6 +1370,9 @@ static void retire_capture_urb(struct snd_usb_substream *subs,
subs->last_frame_number &= 0xFF; /* keep 8 LSBs */
spin_unlock_irqrestore(&subs->lock, flags);
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+ retire_capture_usb(runtime, cp, bytes, oldptr, stride);
+#else
/* copy a data chunk */
if (oldptr + bytes > runtime->buffer_size * stride) {
unsigned int bytes1 =
@@ -1352,6 +1382,7 @@ static void retire_capture_urb(struct snd_usb_substream *subs,
} else {
memcpy(runtime->dma_area + oldptr, cp, bytes);
}
+#endif
}
if (period_elapsed)
@@ -1684,13 +1715,20 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream
err = start_endpoints(subs);
if (err < 0)
return err;
-
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+ err = usb_set_capture_status(true);
+#endif
subs->data_endpoint->retire_data_urb = retire_capture_urb;
subs->running = 1;
return 0;
case SNDRV_PCM_TRIGGER_STOP:
stop_endpoints(subs, false);
subs->running = 0;
+
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+ usb_set_capture_status(false);
+#endif
+
return 0;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
subs->data_endpoint->retire_data_urb = NULL;
diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h
index df7a003..671ae0a 100644
--- a/sound/usb/pcm.h
+++ b/sound/usb/pcm.h
@@ -10,5 +10,14 @@ int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
struct usb_host_interface *alts,
struct audioformat *fmt);
+#ifdef CONFIG_AMLOGIC_SND_USB_CAPTURE_DATA
+int usb_set_capture_status(bool isrunning);
+int usb_audio_capture_init(void);
+int usb_audio_capture_deinit(void);
+int retire_capture_usb(
+ struct snd_pcm_runtime *runtime,
+ unsigned char *cp, unsigned int bytes,
+ unsigned int oldptr, unsigned int stride);
+#endif
#endif /* __USBAUDIO_PCM_H */