author | fahui.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) |
commit | 8ca3108b53f71f4628fee6fb702c8958d3335a41 (patch) | |
tree | 4d79744891f129834bbe9341808f0f0829cf2ea7 | |
parent | 712bd039942fb67d4801b41045b224f8a0605201 (diff) | |
download | common-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>
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 */ |