audio: add usb karaoke driver [1/1]
authorfahui.feng <fahui.feng@amlogic.com>
Fri, 26 Apr 2019 02:14:59 +0000 (10:14 +0800)
committerJianxin Pan <jianxin.pan@amlogic.com>
Wed, 16 Oct 2019 02:58:23 +0000 (19:58 -0700)
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>
26 files changed:
MAINTAINERS
arch/arm/boot/dts/amlogic/gxl_p212_1g.dts
arch/arm/boot/dts/amlogic/gxl_p212_2g.dts
arch/arm/boot/dts/amlogic/gxl_p241_1g.dts
arch/arm/boot/dts/amlogic/gxl_p241_v2-1g.dts
arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts
arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts
drivers/amlogic/Kconfig
drivers/amlogic/Makefile
drivers/amlogic/amlkaraoke/Kconfig [new file with mode: 0644]
drivers/amlogic/amlkaraoke/Makefile [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_audio_resampler.c [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_audio_resampler.h [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_i2s_out_mix.c [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_i2s_out_mix.h [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_karaoke.c [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_karaoke.h [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_reverb.c [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_reverb.h [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_usb_capture.c [new file with mode: 0644]
drivers/amlogic/amlkaraoke/aml_usb_capture.h [new file with mode: 0644]
include/linux/amlogic/media/sound/usb_karaoke.h [new file with mode: 0644]
sound/soc/amlogic/meson/i2s_dai.c
sound/soc/amlogic/meson/i2s_dai.h
sound/usb/pcm.c
sound/usb/pcm.h

index fa98d7885da5ae4a3d9441d3034e5b6c94e3ed66..b6c48148d216f7d75df9a693e019a105a85bb56b 100644 (file)
@@ -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
index 20b6811a21d6fa5faf176efe9debb51db5b863a0..1188fb3415806a121000300e45331db2ba0fb887 100644 (file)
                        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";
index 5d0e52d4c76f3a27f43c576db57e619d59270679..92a19c38afa0861424b2abcdc9cdd9f3436099dd 100644 (file)
                        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";
index 7164209425c3610a739396e54130c170871df71b..e8751381aa5fcf444554f7fff334249bd7510d58 100644 (file)
                        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";
index 245c1dcb1405e44ef98aacbdce3ab27d94a38e1d..d6d01b5da977f627ae216ce5bc4d0143a553f0de 100644 (file)
                        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";
index 8cca125aceee065e03210853982f923f92d3ef7b..b92989e01c38fcc37f36cc5a7d66d7c097e9ea8e 100644 (file)
                        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";
index e7354f7ccde19832e30037243bf4f213f61605ac..d421ba575468e3558d56b25714050e22f4f8d6b9 100644 (file)
                        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";
index 93ee0eb080ee205b9e7bb8958ab5f949570a3e53..914146f80a1eb1e35dbc5e44dc119039bfc1c91f 100644 (file)
@@ -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"
index 5ed5331595cfc56e9febbc43b32e0548ed43fc23..d2686b8bcf59be1c0ed3ec0d44f4e411b59cc56f 100644 (file)
@@ -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 (file)
index 0000000..f3c2654
--- /dev/null
@@ -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 (file)
index 0000000..74eec80
--- /dev/null
@@ -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 (file)
index 0000000..bc1a1e6
--- /dev/null
@@ -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 (file)
index 0000000..37a0ea4
--- /dev/null
@@ -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 (file)
index 0000000..62b528e
--- /dev/null
@@ -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 (file)
index 0000000..a65938d
--- /dev/null
@@ -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 (file)
index 0000000..cd044ee
--- /dev/null
@@ -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 (file)
index 0000000..0fcc2ed
--- /dev/null
@@ -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 (file)
index 0000000..5a8cadc
--- /dev/null
@@ -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 (file)
index 0000000..3a5f085
--- /dev/null
@@ -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 (file)
index 0000000..e014c74
--- /dev/null
@@ -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 (file)
index 0000000..1a763c7
--- /dev/null
@@ -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 (file)
index 0000000..305c165
--- /dev/null
@@ -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
index 381f3898a25bf14194fc98322bcfbc56e2f6a1d5..ea55b3753698b1258ef85c6d6c2295e1f4b083cc 100644 (file)
@@ -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,
 };
index 202b6f5975bb46117c32e81a930ccb5b6c7306ee..88dea68aeb9555aed5bb9ce2cb21c49bcf62b872 100644 (file)
@@ -28,4 +28,5 @@ struct aml_i2s {
        int clk_data_pos;
        unsigned long mclk;
 };
+
 #endif
index 497bad9f27898a1d4ff0ee70b4b64621dd5d1030..c29b37a1749a193c8cd54bb20df70ed24c0486da 100644 (file)
@@ -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;
index df7a003682ad6426a3e50532cd815e8bf00a4af6..671ae0a9e6dca58235a5ca15580dca4a7eebe0b9 100644 (file)
@@ -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 */