mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-09 04:10:18 +09:00
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>
This commit is contained in:
@@ -13821,9 +13821,10 @@ F: drivers/amlogic/clk/clk_misc.c
|
||||
F: drivers/amlogic/clk/clkc.h
|
||||
F: drivers/amlogic/clk/gxl.c
|
||||
F: drivers/amlogic/media/vout/hdmitx/hdmi_tx_20/hdmi_tx_main.c
|
||||
F: drivers/amlogic/pinctrl/pinctrl_gxl.c
|
||||
F: include/dt-bindings/clock/amlogic,gxl-clkc.h
|
||||
F: include/linux/amlogic/media/sound/audin_regs.h
|
||||
F: drivers/amlogic/pinctrl/*
|
||||
F: drivers/amlogic/*
|
||||
F: include/dt-bindings/clock/*
|
||||
F: include/linux/amlogic/media/sound/*
|
||||
F: sound/soc/Kconfig
|
||||
F: sound/soc/Makefile
|
||||
F: sound/soc/amlogic/auge/*
|
||||
|
||||
@@ -944,6 +944,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";
|
||||
|
||||
@@ -944,6 +944,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";
|
||||
|
||||
@@ -1001,6 +1001,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";
|
||||
|
||||
@@ -1001,6 +1001,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";
|
||||
|
||||
@@ -940,6 +940,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";
|
||||
|
||||
@@ -942,6 +942,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";
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/
|
||||
|
||||
21
drivers/amlogic/amlkaraoke/Kconfig
Normal file
21
drivers/amlogic/amlkaraoke/Kconfig
Normal file
@@ -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
|
||||
17
drivers/amlogic/amlkaraoke/Makefile
Normal file
17
drivers/amlogic/amlkaraoke/Makefile
Normal file
@@ -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
|
||||
116
drivers/amlogic/amlkaraoke/aml_audio_resampler.c
Normal file
116
drivers/amlogic/amlkaraoke/aml_audio_resampler.c
Normal file
@@ -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;
|
||||
}
|
||||
37
drivers/amlogic/amlkaraoke/aml_audio_resampler.h
Normal file
37
drivers/amlogic/amlkaraoke/aml_audio_resampler.h
Normal file
@@ -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
|
||||
677
drivers/amlogic/amlkaraoke/aml_i2s_out_mix.c
Normal file
677
drivers/amlogic/amlkaraoke/aml_i2s_out_mix.c
Normal file
@@ -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);
|
||||
52
drivers/amlogic/amlkaraoke/aml_i2s_out_mix.h
Normal file
52
drivers/amlogic/amlkaraoke/aml_i2s_out_mix.h
Normal file
@@ -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
|
||||
305
drivers/amlogic/amlkaraoke/aml_karaoke.c
Normal file
305
drivers/amlogic/amlkaraoke/aml_karaoke.c
Normal file
@@ -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");
|
||||
30
drivers/amlogic/amlkaraoke/aml_karaoke.h
Normal file
30
drivers/amlogic/amlkaraoke/aml_karaoke.h
Normal file
@@ -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
|
||||
235
drivers/amlogic/amlkaraoke/aml_reverb.c
Normal file
235
drivers/amlogic/amlkaraoke/aml_reverb.c
Normal file
@@ -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;
|
||||
}
|
||||
84
drivers/amlogic/amlkaraoke/aml_reverb.h
Normal file
84
drivers/amlogic/amlkaraoke/aml_reverb.h
Normal file
@@ -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
|
||||
|
||||
552
drivers/amlogic/amlkaraoke/aml_usb_capture.c
Normal file
552
drivers/amlogic/amlkaraoke/aml_usb_capture.c
Normal file
@@ -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);
|
||||
59
drivers/amlogic/amlkaraoke/aml_usb_capture.h
Normal file
59
drivers/amlogic/amlkaraoke/aml_usb_capture.h
Normal file
@@ -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
|
||||
27
include/linux/amlogic/media/sound/usb_karaoke.h
Normal file
27
include/linux/amlogic/media/sound/usb_karaoke.h
Normal file
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -28,4 +28,5 @@ struct aml_i2s {
|
||||
int clk_data_pos;
|
||||
unsigned long mclk;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1240,6 +1240,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;
|
||||
@@ -1253,7 +1254,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)
|
||||
@@ -1273,6 +1295,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;
|
||||
}
|
||||
|
||||
@@ -1336,6 +1363,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 =
|
||||
@@ -1345,6 +1375,7 @@ static void retire_capture_urb(struct snd_usb_substream *subs,
|
||||
} else {
|
||||
memcpy(runtime->dma_area + oldptr, cp, bytes);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (period_elapsed)
|
||||
@@ -1677,13 +1708,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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user