blob: d3f70807c8d667e7c34d47d8b239d62e161d742e [file] [log] [blame]
/*
* drivers/amlogic/amaudio2/amaudio2.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#define pr_fmt(fmt) "amaudio2: " fmt
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/of_device.h>
#ifdef CONFIG_COMPAT
#include <linux/compat.h>
#endif
/* Amlogic headers */
#include <linux/amlogic/major.h>
#include <linux/amlogic/iomap.h>
#include <linux/amlogic/media/sound/aiu_regs.h>
#include <linux/amlogic/media/sound/audin_regs.h>
#include <linux/amlogic/media/sound/audio_iomap.h>
/*#define AMAUDIO2_DEBUG*/
/*#define AMAUDIO2_USE_IRQ*/
#include "amaudio2.h"
#define BASE_IRQ (32)
#define AM_IRQ(reg) (reg + BASE_IRQ)
#define INT_I2S_DDR AM_IRQ(48)
#define INT_AUDIO_IN AM_IRQ(7)
#define IRQ_OUT INT_I2S_DDR
#define AMAUDIO2_DEVICE_COUNT ARRAY_SIZE(amaudio_ports)
#define SOFT_BUFFER_SIZE (PAGE_SIZE * 4 * 8)
#define MAX_LATENCY (64 * 32 * 3)
#define MIN_LATENCY (64 * 32)
/*max size is SOFT_BUFFER_SIZE*/
#define BUFFER_THRESHOLD_DEFAULT (4096 * 3)
/*irq size: min 2, max 32*/
#define INT_NUM (16)
#define I2S_BLOCK (64)
#define INT_BLOCK ((INT_NUM)*(I2S_BLOCK))
static unsigned char *amaudio_start_addr;
static dma_addr_t amaudio_phy_start_addr;
static struct device *amaudio_dev;
static struct class *amaudio_classp;
int direct_left_gain = 256;
int direct_right_gain = 256;
int music_gain;
int audio_out_mode = 2;
int amaudio2_enable;
EXPORT_SYMBOL(amaudio2_enable);
int amaudio2_read_enable;
EXPORT_SYMBOL(amaudio2_read_enable);
static unsigned long playback_data_cache_start;
static unsigned long playback_data_cache_buf_size;
static unsigned long playback_data_cache_rp;
static unsigned long playback_data_cache_wp;
static void *playback_tmp_start;
static unsigned int amaudio_delay;
struct mutex amaudio2_utils_lock;
static int external_mute_flag;
static int external_mute_enable = 1;
static int int_num = INT_NUM;
static int i2s_num = I2S_BLOCK;
static int int_block = INT_BLOCK;
static int soft_buffer_threshold = BUFFER_THRESHOLD_DEFAULT;
static unsigned int latency = MIN_LATENCY * 2;
void (*aml_cover_memcpy)(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count) = cover_memcpy;
void (*aml_direct_mix_memcpy)(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count) = direct_mix_memcpy;
void (*aml_inter_mix_memcpy)(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count) = inter_mix_memcpy;
static const struct file_operations amaudio_fops = {
.owner = THIS_MODULE,
.open = amaudio_open,
.release = amaudio_release,
.unlocked_ioctl = amaudio_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = amaudio_compat_ioctl,
#endif
};
static const struct file_operations amaudio_out_fops = {
.owner = THIS_MODULE,
.open = amaudio_open,
.release = amaudio_release,
.mmap = amaudio_mmap,
.unlocked_ioctl = amaudio_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = amaudio_compat_ioctl,
#endif
};
static const struct file_operations amaudio_in_fops = {
.owner = THIS_MODULE,
.open = amaudio_open,
.release = amaudio_release,
.mmap = amaudio_mmap,
.unlocked_ioctl = amaudio_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = amaudio_compat_ioctl,
#endif
};
static const struct file_operations amaudio_ctl_fops = {
.owner = THIS_MODULE,
.open = amaudio_open,
.release = amaudio_release,
.unlocked_ioctl = amaudio_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = amaudio_compat_ioctl,
#endif
};
static const struct file_operations amaudio_utils_fops = {
.owner = THIS_MODULE,
.open = amaudio_open,
.release = amaudio_release,
.read = amaudio_read,
.unlocked_ioctl = amaudio_utils_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = amaudio_compat_utils_ioctl,
#endif
};
static struct amaudio_port_t amaudio_ports[] = {
{
.name = "amaudio2_out",
.fops = &amaudio_out_fops,
},
{
.name = "amaudio2_in",
.fops = &amaudio_in_fops,
},
{
.name = "amaudio2_ctl",
.fops = &amaudio_ctl_fops,
},
{
.name = "amaudio2_utils",
.fops = &amaudio_utils_fops,
},
};
static inline int16_t clip16(int x)
{
if (x < -32768)
return -32768;
else if (x > 32767)
return 32767;
return (int16_t)x;
}
static inline int32_t clip24(int x)
{
if (x < -8388608)
return -8388608;
else if (x > 8388607)
return 8388607;
return (int32_t)x;
}
static inline int32_t clip32(long x)
{
if (x < -2147483648LL)
return (int32_t)(-2147483648LL);
else if (x > 2147483647LL)
return (int32_t)(2147483647LL);
return (int32_t)x;
}
int External_Mute(int mute_flag)
{
if (mute_flag == 1) {
external_mute_flag = 1;
pr_info("amaudio2_out external mute!\n");
} else {
external_mute_flag = 0;
pr_info("amaudio2_out external unmute!\n");
}
return 0;
}
EXPORT_SYMBOL(External_Mute);
static int amaudio_open(struct inode *inode, struct file *file)
{
struct amaudio_port_t *this = &amaudio_ports[iminor(inode)];
struct amaudio_t *amaudio =
kzalloc(sizeof(struct amaudio_t), GFP_KERNEL);
int res = 0;
if (iminor(inode) == 0) {
pr_debug("amaudio2_out open!\n");
of_dma_configure(amaudio_dev, amaudio_dev->of_node);
int_num = INT_NUM;
i2s_num = I2S_BLOCK;
int_block = INT_BLOCK;
soft_buffer_threshold = BUFFER_THRESHOLD_DEFAULT;
latency = MIN_LATENCY * 2;
aml_cover_memcpy = cover_memcpy;
aml_direct_mix_memcpy = direct_mix_memcpy;
aml_inter_mix_memcpy = inter_mix_memcpy;
if (aml_i2s_playback_channel == 8) {
int_num *= 2;
i2s_num *= 4;
int_block = int_num * i2s_num;
soft_buffer_threshold *= 8;
latency *= 8;
aml_cover_memcpy = cover_memcpy_8_channel;
aml_direct_mix_memcpy = direct_mix_memcpy_8_channel;
aml_inter_mix_memcpy = inter_mix_memcpy_8_channel;
}
amaudio->sw.addr = amaudio_start_addr;
amaudio->sw.paddr = amaudio_phy_start_addr;
amaudio->sw.size = SOFT_BUFFER_SIZE;
amaudio->hw.addr = (unsigned char *)aml_i2s_playback_start_addr;
amaudio->hw.paddr = aml_i2s_playback_phy_start_addr;
amaudio->hw.size = get_i2s_out_size();
amaudio->hw.rd = get_i2s_out_ptr();
#ifdef AMAUDIO2_USE_IRQ
spin_lock_init(&amaudio->sw.lock);
spin_lock_init(&amaudio->hw.lock);
if (request_irq(IRQ_OUT, i2s_out_callback, IRQF_SHARED,
"i2s_out", amaudio)) {
res = -EINVAL;
pr_err("amaudio2 request irq error!\n");
goto error;
}
#else
mutex_init(&amaudio->sw.lock);
mutex_init(&amaudio->hw.lock);
if (request_threaded_irq(IRQ_OUT, NULL, i2s_out_callback,
IRQF_SHARED | IRQF_ONESHOT,
"i2s_out", (void *)amaudio)) {
res = -EINVAL;
pr_err("amaudio2 request irq error!\n");
goto error;
}
#endif
aml_aiu_update_bits(AIU_MEM_I2S_MASKS, 0xffff << 16,
int_num << 16);
amaudio2_enable = 1;
// pr_info("channel: %d, int_num = %d,"
// "int_block = %d, amaudio->hw.size = %d\n",
// aml_i2s_playback_channel, int_num,
// int_block, amaudio->hw.size);
} else if (iminor(inode) == 1) {
pr_debug("amaudio2_in opened\n");
of_dma_configure(amaudio_dev, amaudio_dev->of_node);
} else if (iminor(inode) == 2) {
pr_info("amaudio2_ctl opened\n");
} else if (iminor(inode) == 3) {
pr_info("amaudio2_utils opened\n");
playback_data_cache_buf_size = SOFT_BUFFER_SIZE;
playback_data_cache_rp = playback_data_cache_start;
playback_data_cache_wp = playback_data_cache_start;
mutex_init(&amaudio2_utils_lock);
} else {
pr_err("BUG:%s,%d, please check\n", __FILE__, __LINE__);
res = -EINVAL;
goto error;
}
amaudio->type = iminor(inode);
amaudio->dev = this->dev;
file->private_data = amaudio;
file->f_op = this->fops;
this->runtime = amaudio;
return res;
error: kfree(amaudio);
return res;
}
static int amaudio_release(struct inode *inode, struct file *file)
{
struct amaudio_t *amaudio = (struct amaudio_t *)file->private_data;
if (iminor(inode) == 0) {
free_irq(IRQ_OUT, amaudio);
amaudio->sw.addr = 0;
amaudio2_enable = 0;
} else if (iminor(inode) == 3)
amaudio2_read_enable = 0;
kfree(amaudio);
return 0;
}
static int amaudio_mmap(struct file *file, struct vm_area_struct *vma)
{
struct amaudio_t *amaudio = (struct amaudio_t *)file->private_data;
if (amaudio->type == 0) {
int mmap_flag = dma_mmap_coherent(amaudio->dev, vma,
(void *)amaudio->sw.addr,
amaudio->sw.paddr,
amaudio->sw.size);
return mmap_flag;
} else if (amaudio->type == 1) {
pr_debug("audio in mmap!\n");
} else {
return -ENODEV;
}
return 0;
}
int remap_8ch_to_2ch(int32_t *in_buf, int16_t *out_buf, size_t frames)
{
uint i;
if (in_buf == NULL || out_buf == NULL || frames == 0)
return -EINVAL;
for (i = 0; i < frames; i++) {
out_buf[2*i + 0] = (int16_t)((in_buf[8*i + 2]) >> 16);
out_buf[2*i + 1] = (int16_t)((in_buf[8*i + 3]) >> 16);
}
return 0;
}
int get_pcm_cache_space(void)
{
int space;
if (playback_data_cache_wp >= playback_data_cache_rp) {
space = playback_data_cache_buf_size - (playback_data_cache_wp
- playback_data_cache_rp);
} else {
space = playback_data_cache_rp - playback_data_cache_wp;
}
return space;
}
EXPORT_SYMBOL(get_pcm_cache_space);
static int get_pcm_cache_content(void)
{
return playback_data_cache_buf_size - get_pcm_cache_space();
}
static unsigned int get_hw_delay_size(void)
{
unsigned int size;
if (amaudio2_enable == 0) {
unsigned int i2s_out_ptr = get_i2s_out_ptr();
unsigned int buffer_size = get_i2s_out_size();
size = (aml_i2s_alsa_write_addr + buffer_size - i2s_out_ptr)
% buffer_size;
} else
size = amaudio_delay;
if (aml_i2s_playback_channel == 8)
size = size >> 3;
return size;
}
static void buffer_copy(int size)
{
int tail;
mutex_lock(&amaudio2_utils_lock);
if ((playback_data_cache_wp + size) > (playback_data_cache_buf_size
+ playback_data_cache_start)) {
tail = playback_data_cache_buf_size + playback_data_cache_start
- playback_data_cache_wp;
memcpy((char *)playback_data_cache_wp,
playback_tmp_start, tail);
playback_data_cache_wp = playback_data_cache_start;
memcpy((char *)playback_data_cache_wp,
playback_tmp_start + tail, size - tail);
playback_data_cache_wp = playback_data_cache_start
+ size - tail;
} else {
memcpy((char *)playback_data_cache_wp,
playback_tmp_start, size);
playback_data_cache_wp += size;
}
mutex_unlock(&amaudio2_utils_lock);
}
int cache_pcm_write(char __user *buf, int size)
{
if (playback_data_cache_wp == 0 || playback_data_cache_rp == 0
|| playback_data_cache_buf_size == 0) {
pr_info("write ! check the audio cached buffer ptr\n");
return -1;
}
if (copy_from_user((char *)playback_tmp_start, buf, size)) {
pr_err("copy audio pcm from user failed\n");
return -EFAULT;
}
if (aml_i2s_playback_channel == 8) {
remap_8ch_to_2ch(playback_tmp_start,
playback_tmp_start, size >> 5);
size = size >> 3;
}
buffer_copy(size);
return size;
}
EXPORT_SYMBOL(cache_pcm_write);
#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
static int amaudio2_cache_pcm_write(char *buf, int size)
{
if (playback_data_cache_wp == 0 || playback_data_cache_rp == 0
|| playback_data_cache_buf_size == 0) {
pr_info("write ! check the audio cached buffer ptr\n");
return -1;
}
memcpy((char *)playback_tmp_start, buf, size);
if (aml_i2s_playback_channel == 8) {
remap_8ch_to_2ch(playback_tmp_start,
playback_tmp_start, size >> 5);
size = size >> 3;
}
buffer_copy(size);
return size;
}
#endif
static int cache_pcm_read(char __user *buf, int size)
{
int tail;
int data_left = get_pcm_cache_content();
if (data_left <= 0)
return 0;
if (data_left < size)
size = data_left;
if (playback_data_cache_wp == 0 || playback_data_cache_rp == 0
|| playback_data_cache_buf_size == 0) {
pr_info("read!check the audio cached buffer ptr\n");
return -1;
}
mutex_lock(&amaudio2_utils_lock);
if ((playback_data_cache_rp + size) > (playback_data_cache_buf_size
+ playback_data_cache_start)) {
tail = playback_data_cache_buf_size + playback_data_cache_start
- playback_data_cache_rp;
if (copy_to_user(buf, (char *)playback_data_cache_rp, tail)) {
pr_err("copy audio pcm to user failed\n");
return -EFAULT;
}
playback_data_cache_rp = playback_data_cache_start;
if (copy_to_user(buf + tail, (char *)playback_data_cache_rp,
size - tail)) {
pr_err("copy audio pcm to user failed\n");
return -EFAULT;
}
playback_data_cache_rp = playback_data_cache_start
+ size - tail;
} else {
if (copy_to_user(buf, (char *)playback_data_cache_rp, size)) {
pr_err("copy audio pcm to user failed\n");
return -EFAULT;
}
playback_data_cache_rp += size;
}
mutex_unlock(&amaudio2_utils_lock);
return size;
}
static ssize_t amaudio_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct amaudio_t *amaudio = (struct amaudio_t *)file->private_data;
amaudio2_read_enable = 1;
/* just use the software buffer to temp store the output sample data */
if (amaudio->type == 3) {
if (if_audio_out_enable() == 0 || count == 0 ||
amaudio2_read_enable == 0)
return 0;
return cache_pcm_read(buf, count);
}
return 0;
}
static unsigned int get_i2s_out_size(void)
{
return aml_aiu_read(AIU_MEM_I2S_END_PTR)
- aml_aiu_read(AIU_MEM_I2S_START_PTR) + i2s_num;
}
static unsigned int get_i2s_out_ptr(void)
{
return aml_aiu_read(AIU_MEM_I2S_RD_PTR)
- aml_aiu_read(AIU_MEM_I2S_START_PTR);
}
void cover_memcpy(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count)
{
int i;
int samp;
int16_t *des_left = (int16_t *)(des->addr + a);
int16_t *des_right = des_left + 16;
int16_t *src_buf = (int16_t *)(src->addr + b);
#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
for (i = 0; i < count; i += 4) {
samp = ((*src_buf++) * direct_left_gain) >> 8;
*des_left++ = (int16_t)samp;
samp = ((*src_buf++) * direct_right_gain) >> 8;
*des_right++ = (int16_t)samp;
}
#else
int j;
for (i = 0; i < count; i += 64) {
for (j = 0; j < 16; j++) {
samp = ((*src_buf++) * direct_left_gain) >> 8;
*des_left++ = (int16_t)samp;
samp = ((*src_buf++) * direct_right_gain) >> 8;
*des_right++ = (int16_t)samp;
}
des_left += 16;
des_right += 16;
}
#endif
}
void direct_mix_memcpy(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count)
{
int i;
int samp;
int16_t *des_left = (int16_t *)(des->addr + a);
int16_t *des_right = des_left + 16;
int16_t *src_buf = (int16_t *)(src->addr + b);
#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
for (i = 0; i < count; i += 4) {
samp = ((*des_left) * music_gain +
(*src_buf++) * direct_left_gain) >> 8;
*des_left++ = clip16(samp);
samp = ((*des_right) * music_gain +
(*src_buf++) * direct_right_gain) >> 8;
*des_right++ = clip16(samp);
}
#else
int j;
for (i = 0; i < count; i += 64) {
for (j = 0; j < 16; j++) {
samp = ((*des_left) * music_gain +
(*src_buf++) * direct_left_gain) >> 8;
*des_left++ = clip16(samp);
samp = ((*des_right) * music_gain +
(*src_buf++) * direct_right_gain) >> 8;
*des_right++ = clip16(samp);
}
des_left += 16;
des_right += 16;
}
#endif
}
void inter_mix_memcpy(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count)
{
int i;
int16_t sampL, sampR;
int samp, sampLR;
int16_t *des_left = (int16_t *)(des->addr + a);
int16_t *des_right = des_left + 16;
int16_t *src_buf = (int16_t *)(src->addr + b);
#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
for (i = 0; i < count; i += 4) {
sampL = *src_buf++;
sampR = *src_buf++;
/* Here has risk to distortion.
* Linein signals are always weak,
* so add them direct.
*/
sampLR = (sampL * direct_left_gain +
sampR * direct_right_gain) >> 1;
samp = ((*des_left) * music_gain + sampLR) >> 8;
*des_left++ = clip16(samp);
samp = ((*des_right) * music_gain + sampLR) >> 8;
*des_right++ = clip16(samp);
}
#else
int j;
for (i = 0; i < count; i += 64) {
for (j = 0; j < 16; j++) {
sampL = *src_buf++;
sampR = *src_buf++;
/* Here has risk to distortion.
* Linein signals are always weak,
* so add them direct.
*/
sampLR = (sampL * direct_left_gain +
sampR * direct_right_gain) >> 1;
samp = ((*des_left) * music_gain + sampLR) >> 8;
*des_left++ = clip16(samp);
samp = ((*des_right) * music_gain + sampLR) >> 8;
*des_right++ = clip16(samp);
}
des_left += 16;
des_right += 16;
}
#endif
}
void cover_memcpy_8_channel(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count)
{
int i;
#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
if (aml_i2s_playback_format == 32 || aml_i2s_playback_format == 24) {
int32_t *to = (int32_t *)(des->addr + a);
int32_t *tfrom = (int32_t *)(src->addr + b);
for (i = 0; i < count; i += 8) {
*to++ = (int32_t)
(((long)(*tfrom++) * direct_left_gain) >> 8);
*to++ = (int32_t)
(((long)(*tfrom++) * direct_right_gain) >> 8);
}
} else {
int16_t *to = (int16_t *)(des->addr + a);
int16_t *tfrom = (int16_t *)(src->addr + b);
for (i = 0; i < count; i += 4) {
*to++ = (int16_t)
(((*tfrom++) * direct_left_gain) >> 8);
*to++ = (int16_t)
(((*tfrom++) * direct_right_gain) >> 8);
}
}
#else
int j;
if (aml_i2s_playback_format == 32 || aml_i2s_playback_format == 24) {
int32_t *to = (int32_t *)(des->addr + a);
int32_t *tfrom = (int32_t *)(src->addr + b);
int32_t *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
lf = to;
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++) {
*lf++ = (((*tfrom++) >> 8) * direct_left_gain)
>> 8;
*cf++ = (((*tfrom++) >> 8) * direct_right_gain)
>> 8;
*rf++ = (((*tfrom++) >> 8) * direct_left_gain)
>> 8;
*ls++ = (((*tfrom++) >> 8) * direct_right_gain)
>> 8;
*rs++ = (((*tfrom++) >> 8) * direct_left_gain)
>> 8;
*lef++ = (((*tfrom++) >> 8) * direct_right_gain)
>> 8;
*sbl++ = (((*tfrom++) >> 8) * direct_left_gain)
>> 8;
*sbr++ = (((*tfrom++) >> 8) * direct_right_gain)
>> 8;
}
lf += 56;
cf += 56;
rf += 56;
ls += 56;
rs += 56;
lef += 56;
sbl += 56;
sbr += 56;
}
} else {
int16_t *to = (int16_t *)(des->addr + a);
int16_t *tfrom = (int16_t *)(src->addr + b);
int16_t *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
lf = to;
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++) {
*lf++ = ((*tfrom++) * direct_left_gain) >> 8;
*cf++ = ((*tfrom++) * direct_right_gain) >> 8;
*rf++ = ((*tfrom++) * direct_left_gain) >> 8;
*ls++ = ((*tfrom++) * direct_right_gain) >> 8;
*rs++ = ((*tfrom++) * direct_left_gain) >> 8;
*lef++ = ((*tfrom++) * direct_right_gain) >> 8;
*sbl++ = ((*tfrom++) * direct_left_gain) >> 8;
*sbr++ = ((*tfrom++) * direct_right_gain) >> 8;
}
lf += 7 * 16;
cf += 7 * 16;
rf += 7 * 16;
ls += 7 * 16;
rs += 7 * 16;
lef += 7 * 16;
sbl += 7 * 16;
sbr += 7 * 16;
}
}
#endif
}
void direct_mix_memcpy_8_channel(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count)
{
int i;
int samp;
#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
if (aml_i2s_playback_format == 32 || aml_i2s_playback_format == 24) {
int32_t *to = (int32_t *)(des->addr + a);
int32_t *tfrom = (int32_t *)(src->addr + b);
for (i = 0; i < count; i += 8) {
samp = *to;
*to++ = clip32(((long)(samp) * music_gain +
(long)(*tfrom++) * direct_left_gain) >> 8);
samp = *to;
*to++ = clip32(((long)(samp) * music_gain +
(long)(*tfrom++) * direct_right_gain) >> 8);
}
} else {
int16_t *to = (int16_t *)(des->addr + a);
int16_t *tfrom = (int16_t *)(src->addr + b);
for (i = 0; i < count; i += 4) {
samp = *to;
*to++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_left_gain) >> 8);
samp = *to;
*to++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_right_gain) >> 8);
}
}
#else
int j;
if (aml_i2s_playback_format == 32 || aml_i2s_playback_format == 24) {
int32_t *to = (int32_t *)(des->addr + a);
int32_t *tfrom = (int32_t *)(src->addr + b);
int32_t *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
lf = to;
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++) {
samp = *lf;
*lf++ = clip24(((samp) * music_gain +
((*tfrom++) >> 8) * direct_left_gain)
>> 8);
samp = *cf;
*cf++ = clip24(((samp) * music_gain +
((*tfrom++) >> 8) * direct_right_gain)
>> 8);
samp = *rf;
*rf++ = clip24(((samp) * music_gain +
((*tfrom++) >> 8) * direct_left_gain)
>> 8);
samp = *ls;
*ls++ = clip24(((samp) * music_gain +
((*tfrom++) >> 8) * direct_right_gain)
>> 8);
samp = *rs;
*rs++ = clip24(((samp) * music_gain +
((*tfrom++) >> 8) * direct_left_gain)
>> 8);
samp = *lef;
*lef++ = clip24(((samp) * music_gain +
((*tfrom++) >> 8) * direct_right_gain)
>> 8);
samp = *sbl;
*sbl++ = clip24(((samp) * music_gain +
((*tfrom++) >> 8) * direct_left_gain)
>> 8);
samp = *sbr;
*sbr++ = clip24(((samp) * music_gain +
((*tfrom++) >> 8) * direct_right_gain)
>> 8);
}
lf += 56;
cf += 56;
rf += 56;
ls += 56;
rs += 56;
lef += 56;
sbl += 56;
sbr += 56;
}
} else {
int16_t *to = (int16_t *)(des->addr + a);
int16_t *tfrom = (int16_t *)(src->addr + b);
int16_t *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
lf = to;
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++) {
samp = *lf;
*lf++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_left_gain) >> 8);
samp = *cf;
*cf++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_right_gain) >> 8);
samp = *rf;
*rf++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_left_gain) >> 8);
samp = *ls;
*ls++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_right_gain) >> 8);
samp = *rs;
*rs++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_left_gain) >> 8);
samp = *lef;
*lef++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_right_gain) >> 8);
samp = *sbl;
*sbl++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_left_gain) >> 8);
samp = *sbr;
*sbr++ = clip16(((samp) * music_gain +
(*tfrom++) * direct_right_gain) >> 8);
}
lf += 7 * 16;
cf += 7 * 16;
rf += 7 * 16;
ls += 7 * 16;
rs += 7 * 16;
lef += 7 * 16;
sbl += 7 * 16;
sbr += 7 * 16;
}
}
#endif
}
void inter_mix_memcpy_8_channel(struct BUF *des, int a, struct BUF *src, int b,
unsigned int count)
{
int i;
int samp, sampLR, sampL, sampR;
#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
if (aml_i2s_playback_format == 32 || aml_i2s_playback_format == 24) {
int32_t *to = (int32_t *)(des->addr + a);
int32_t *tfrom = (int32_t *)(src->addr + b);
for (i = 0; i < count; i += 8) {
sampL = (int)
(((long)(*tfrom++) * direct_left_gain) >> 8);
sampR = (int)
(((long)(*tfrom++) * direct_right_gain) >> 8);
sampLR = (sampL + sampR) >> 1;
samp = *to;
*to++ = clip32(
(((long)samp * music_gain) >> 8) + sampLR);
samp = *to;
*to++ = clip32(
(((long)samp * music_gain) >> 8) + sampLR);
}
} else {
int16_t *to = (int16_t *)(des->addr + a);
int16_t *tfrom = (int16_t *)(src->addr + b);
for (i = 0; i < count; i += 4) {
sampL = (int)(((*tfrom++) * direct_left_gain) >> 8);
sampR = (int)(((*tfrom++) * direct_right_gain) >> 8);
sampLR = (sampL + sampR) >> 1;
samp = *to;
*to++ = clip16(
(((long)samp * music_gain) >> 8) + sampLR);
samp = *to;
*to++ = clip16(
(((long)samp * music_gain) >> 8) + sampLR);
}
}
#else
int j;
if (aml_i2s_playback_format == 32 || aml_i2s_playback_format == 24) {
int32_t *to = (int32_t *)(des->addr + a);
int32_t *tfrom = (int32_t *)(src->addr + b);
int32_t *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
lf = to;
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++) {
sampL = (((*tfrom++) >> 8) * direct_left_gain)
>> 8;
sampR = (((*tfrom++) >> 8) * direct_right_gain)
>> 8;
sampLR = (sampL + sampR) >> 1;
samp = *lf;
*lf++ = clip24(
((samp * music_gain) >> 8) + sampLR);
samp = *cf;
*cf++ = clip24(
((samp * music_gain) >> 8) + sampLR);
sampL = (((*tfrom++) >> 8) * direct_left_gain)
>> 8;
sampR = (((*tfrom++) >> 8) * direct_right_gain)
>> 8;
sampLR = (sampL + sampR) >> 1;
samp = *rf;
*rf++ = clip24(
((samp * music_gain) >> 8) + sampLR);
samp = *ls;
*ls++ = clip24(
((samp * music_gain) >> 8) + sampLR);
sampL = (((*tfrom++) >> 8) * direct_left_gain)
>> 8;
sampR = (((*tfrom++) >> 8) * direct_right_gain)
>> 8;
sampLR = (sampL + sampR) >> 1;
samp = *rs;
*rs++ = clip24(
((samp * music_gain) >> 8) + sampLR);
samp = *lef;
*lef++ = clip24(
((samp * music_gain) >> 8) + sampLR);
sampL = (((*tfrom++) >> 8) * direct_left_gain)
>> 8;
sampR = (((*tfrom++) >> 8) * direct_right_gain)
>> 8;
sampLR = (sampL + sampR) >> 1;
samp = *sbl;
*sbl++ = clip24(
((samp * music_gain) >> 8) + sampLR);
samp = *sbr;
*sbr++ = clip24(
((samp * music_gain) >> 8) + sampLR);
}
lf += 56;
cf += 56;
rf += 56;
ls += 56;
rs += 56;
lef += 56;
sbl += 56;
sbr += 56;
}
} else {
int16_t *to = (int16_t *)(des->addr + a);
int16_t *tfrom = (int16_t *)(src->addr + b);
int16_t *lf, *cf, *rf, *ls, *rs, *lef, *sbl, *sbr;
lf = to;
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++) {
sampL = ((*tfrom++) * direct_left_gain) >> 8;
sampR = ((*tfrom++) * direct_right_gain) >> 8;
sampLR = (sampL + sampR) >> 1;
samp = *lf;
*lf++ = clip16(
((samp * music_gain) >> 8) + sampLR);
samp = *cf;
*cf++ = clip16(
((samp * music_gain) >> 8) + sampLR);
sampL = ((*tfrom++) * direct_left_gain) >> 8;
sampR = ((*tfrom++) * direct_right_gain) >> 8;
sampLR = (sampL + sampR) >> 1;
samp = *rf;
*rf++ = clip16(
((samp * music_gain) >> 8) + sampLR);
samp = *ls;
*ls++ = clip16(
((samp * music_gain) >> 8) + sampLR);
sampL = ((*tfrom++) * direct_left_gain) >> 8;
sampR = ((*tfrom++) * direct_right_gain) >> 8;
sampLR = (sampL + sampR) >> 1;
samp = *rs;
*rs++ = clip16(
((samp * music_gain) >> 8) + sampLR);
samp = *lef;
*lef++ = clip16(
((samp * music_gain) >> 8) + sampLR);
sampL = ((*tfrom++) * direct_left_gain) >> 8;
sampR = ((*tfrom++) * direct_right_gain) >> 8;
sampLR = (sampL + sampR) >> 1;
samp = *sbl;
*sbl++ = clip16(
((samp * music_gain) >> 8) + sampLR);
samp = *sbr;
*sbr++ = clip16(
((samp * music_gain) >> 8) + sampLR);
}
lf += 7 * 16;
cf += 7 * 16;
rf += 7 * 16;
ls += 7 * 16;
rs += 7 * 16;
lef += 7 * 16;
sbl += 7 * 16;
sbr += 7 * 16;
}
}
#endif
}
static void i2s_copy(struct amaudio_t *amaudio)
{
struct BUF *hw = &amaudio->hw;
struct BUF *sw = &amaudio->sw;
unsigned int i2s_out_ptr = get_i2s_out_ptr();
unsigned int alsa_delay =
(aml_i2s_alsa_write_addr + hw->size - i2s_out_ptr) % hw->size;
#ifdef AMAUDIO2_USE_IRQ
unsigned long swirqflags, hwirqflags;
#endif
amaudio_delay = (hw->wr + hw->size - i2s_out_ptr) % hw->size;
#ifdef AMAUDIO2_USE_IRQ
spin_lock_irqsave(&hw->lock, hwirqflags);
#else
mutex_lock(&hw->lock);
#endif
hw->rd = i2s_out_ptr;
hw->level = amaudio_delay;
if (hw->level <= int_block ||
(alsa_delay - amaudio_delay) < int_block) {
hw->wr = (hw->rd + latency) % hw->size;
hw->wr /= int_block;
hw->wr *= int_block;
hw->level = latency;
goto EXIT;
}
if (sw->level > soft_buffer_threshold) {
/*
* pr_debug(
* "Reset sw: hw->wr = %x,hw->rd = %x, hw->level = %x,"
* "alsa_delay:%x,sw->wr = %x, sw->rd = %x,sw->level = %x\n",
* hw->wr, hw->rd, hw->level, alsa_delay,
* sw->wr, sw->rd, sw->level);
*/
sw->rd = (sw->wr + int_block) % sw->size;
sw->level = sw->size - int_block;
goto EXIT;
}
if (sw->level < int_block)
goto EXIT;
WARN_ON((hw->wr + int_block > hw->size) ||
(sw->rd + int_block > sw->size));
if (external_mute_enable == 1 && external_mute_flag == 1) {
memset((hw->addr + hw->wr), 0, int_block);
} else {
if (audio_out_mode == 0)
(*aml_cover_memcpy)
(hw, hw->wr, sw, sw->rd, int_block);
else if (audio_out_mode == 1)
(*aml_inter_mix_memcpy)
(hw, hw->wr, sw, sw->rd, int_block);
else if (audio_out_mode == 2)
(*aml_direct_mix_memcpy)
(hw, hw->wr, sw, sw->rd, int_block);
#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE
if (amaudio2_enable == 1 && amaudio2_read_enable == 1) {
char *read_buf = (char *)(hw->addr + hw->wr);
amaudio2_cache_pcm_write(read_buf, int_block);
}
#endif
}
hw->wr = (hw->wr + int_block) % hw->size;
hw->level = (hw->wr + hw->size - i2s_out_ptr) % hw->size;
#ifdef AMAUDIO2_USE_IRQ
spin_unlock_irqrestore(&hw->lock, hwirqflags);
spin_lock_irqsave(&sw->lock, swirqflags);
#else
mutex_unlock(&hw->lock);
mutex_lock(&sw->lock);
#endif
sw->rd = (sw->rd + int_block) % sw->size;
sw->level = (sw->size + sw->wr - sw->rd) % sw->size;
#ifdef AMAUDIO2_USE_IRQ
spin_unlock_irqrestore(&sw->lock, swirqflags);
#else
mutex_unlock(&sw->lock);
#endif
return;
EXIT:
#ifdef AMAUDIO2_USE_IRQ
spin_unlock_irqrestore(&hw->lock, hwirqflags);
#else
mutex_unlock(&hw->lock);
#endif
}
#ifdef AMAUDIO2_DEBUG
#define period_reset (1000*1000)
#define CCCNT_WARN 2000
static unsigned long start_time;
static unsigned long isr_time;
static unsigned long total_time;
static unsigned long total_cnt;
#endif
static irqreturn_t i2s_out_callback(int irq, void *data)
{
struct amaudio_t *amaudio = (struct amaudio_t *)data;
#ifdef AMAUDIO2_DEBUG
unsigned long clk1, clk2;
unsigned int ratio = 0;
clk1 = sched_clock()/1000;
#endif
i2s_copy(amaudio);
#ifdef AMAUDIO2_DEBUG
clk2 = sched_clock()/1000;
isr_time += (clk2-clk1);
total_time = (clk2-start_time);
total_cnt++;
if (total_time >= period_reset) {
ratio = isr_time * 100 / total_time;
if ((ratio >= 20) || (total_cnt > CCCNT_WARN)) {
pr_err("Warning:isrtime:%ld totalTime:%ld ratio:%d, cnt:%d\n",
isr_time, total_time, ratio, (int)total_cnt);
}
start_time = sched_clock()/1000;
isr_time = 0;
total_time = 0;
total_cnt = 0;
}
#endif
return IRQ_HANDLED;
}
/* -----------------control interface---------------- */
#ifdef CONFIG_COMPAT
static long amaudio_compat_ioctl(struct file *file, unsigned int cmd, ulong arg)
{
return amaudio_ioctl(file, cmd, (ulong)compat_ptr(arg));
}
static long amaudio_compat_utils_ioctl(struct file *file,
unsigned int cmd, ulong arg)
{
return amaudio_utils_ioctl(file, cmd, (ulong)compat_ptr(arg));
}
#endif
static long amaudio_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct amaudio_t *amaudio = (struct amaudio_t *)file->private_data;
s32 r = 0;
#ifdef AMAUDIO2_USE_IRQ
unsigned long swirqflags, hwirqflags;
#endif
switch (cmd) {
case AMAUDIO_IOC_GET_SIZE:
/* total size of internal buffer */
r = amaudio->sw.size;
break;
case AMAUDIO_IOC_GET_PTR:
/* the read pointer of internal buffer */
#ifdef AMAUDIO2_USE_IRQ
spin_lock_irqsave(&amaudio->sw.lock, swirqflags);
#else
mutex_lock(&amaudio->sw.lock);
#endif
r = amaudio->sw.rd;
#ifdef AMAUDIO2_USE_IRQ
spin_unlock_irqrestore(&amaudio->sw.lock, swirqflags);
#else
mutex_unlock(&amaudio->sw.lock);
#endif
break;
case AMAUDIO_IOC_UPDATE_APP_PTR:
/*
* the user space write pointer
* of the internal buffer
*/
#ifdef AMAUDIO2_USE_IRQ
spin_lock_irqsave(&amaudio->sw.lock, swirqflags);
#else
mutex_lock(&amaudio->sw.lock);
#endif
amaudio->sw.wr = arg;
amaudio->sw.level = (amaudio->sw.size + amaudio->sw.wr
- amaudio->sw.rd) % amaudio->sw.size;
#ifdef AMAUDIO2_USE_IRQ
spin_unlock_irqrestore(&amaudio->sw.lock, swirqflags);
#else
mutex_unlock(&amaudio->sw.lock);
#endif
if (amaudio->sw.wr % i2s_num)
pr_err("wr:%x, not %d Bytes align\n",
amaudio->sw.wr, i2s_num);
break;
case AMAUDIO_IOC_RESET:
/*
* reset the internal write buffer
* pointer to get a given latency
* this api should be called before fill datas
*/
#ifdef AMAUDIO2_USE_IRQ
spin_lock_irqsave(&amaudio->hw.lock, hwirqflags);
#else
mutex_lock(&amaudio->hw.lock);
#endif
amaudio->hw.rd = get_i2s_out_ptr();
amaudio->hw.wr = (amaudio->hw.rd + latency) % amaudio->hw.size;
amaudio->hw.wr /= int_block;
amaudio->hw.wr *= int_block;
amaudio->hw.level = latency;
#ifdef AMAUDIO2_USE_IRQ
spin_unlock_irqrestore(&amaudio->hw.lock, hwirqflags);
/* empty the buffer */
spin_lock_irqsave(&amaudio->sw.lock, swirqflags);
#else
mutex_unlock(&amaudio->hw.lock);
mutex_lock(&amaudio->sw.lock);
#endif
amaudio->sw.wr = 0;
amaudio->sw.rd = 0;
amaudio->sw.level = 0;
#ifdef AMAUDIO2_USE_IRQ
spin_unlock_irqrestore(&amaudio->sw.lock, swirqflags);
#else
mutex_unlock(&amaudio->sw.lock);
#endif
/*pr_info("Reset amaudio2: latency=%d bytes\n", latency);*/
break;
case AMAUDIO_IOC_AUDIO_OUT_MODE:
/*
* audio_out_mode = 0, covered alsa audio mode;
* audio_out_mode = 1, karaOK mode, Linein left and
* right channel inter mixed with android alsa audio;
* audio_out_mode = 2,
* TV in direct mix with android audio;
*/
if (arg > 2)
return -EINVAL;
audio_out_mode = arg;
break;
case AMAUDIO_IOC_MIC_LEFT_GAIN:
/* in karaOK mode, mic volume can be set from 0-256 */
if (arg > 256)
return -EINVAL;
direct_left_gain = arg;
break;
case AMAUDIO_IOC_MIC_RIGHT_GAIN:
if (arg > 256)
return -EINVAL;
direct_right_gain = arg;
break;
case AMAUDIO_IOC_MUSIC_GAIN:
/* music volume can be set from 0-256 */
if (arg > 256)
return -EINVAL;
music_gain = arg;
break;
default:
break;
};
return r;
}
static long amaudio_utils_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
int r = 0;
switch (cmd) {
case AMAUDIO_IOC_GET_READY_SIZE:
r = get_pcm_cache_content();
break;
case AMAUDIO_IOC_GET_HW_DELAY_SIZE:
r = get_hw_delay_size();
break;
case AMAUDIO_IOC_RESET_BUFFER:
mutex_lock(&amaudio2_utils_lock);
playback_data_cache_rp = playback_data_cache_start;
playback_data_cache_wp = playback_data_cache_start;
mutex_unlock(&amaudio2_utils_lock);
pr_info("amaudio_utils_ioctl: reset buffer!\n");
break;
case AMAUDIO_IOC_RUNNING_FLAG:
r = aml_i2s_playback_running_flag;
break;
default:
break;
}
return r;
}
/* -----------------class interface---------------- */
static ssize_t show_audio_out_mode(struct class *class,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", audio_out_mode);
}
static ssize_t store_audio_out_mode(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
if (buf[0] == '0') {
pr_info("Audio_in data covered the android local data as output!\n");
audio_out_mode = 0;
} else if (buf[0] == '1') {
pr_info("Audio_in left and right channel mix first, then mix with android!\n");
audio_out_mode = 1;
} else if (buf[0] == '2') {
pr_info("Audio_in data direct mixed with the android local data as output!\n");
audio_out_mode = 2;
}
return count;
}
static ssize_t show_direct_left_gain(struct class *class,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", direct_left_gain);
}
static ssize_t store_direct_left_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;
direct_left_gain = val;
pr_debug("direct_left_gain set to %d\n", direct_left_gain);
return count;
}
static ssize_t show_direct_right_gain(struct class *class,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", direct_right_gain);
}
static ssize_t store_direct_right_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;
direct_right_gain = val;
pr_debug("direct_right_gain set to %d\n", direct_right_gain);
return count;
}
static ssize_t show_music_gain(struct class *class,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", music_gain);
}
static ssize_t store_music_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;
music_gain = val;
pr_debug("music_gain set to %d\n", music_gain);
return count;
}
static ssize_t show_aml_external_mute_enable(struct class *class,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", external_mute_enable);
}
static ssize_t store__aml_external_mute_enable(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
if (buf[0] == '0') {
pr_info("amaudio2_out external mute enable!\n");
external_mute_enable = 0;
} else if (buf[0] == '1') {
pr_info("amaudio2_out external mute disable!\n");
external_mute_enable = 1;
}
return count;
}
static struct class_attribute amaudio_attrs[] = {
__ATTR(aml_audio_out_mode,
0644,
show_audio_out_mode,
store_audio_out_mode),
__ATTR(aml_direct_left_gain,
0644,
show_direct_left_gain,
store_direct_left_gain),
__ATTR(aml_direct_right_gain,
0644,
show_direct_right_gain,
store_direct_right_gain),
__ATTR(aml_music_gain,
0644,
show_music_gain,
store_music_gain),
__ATTR(aml_external_mute_enable,
0644,
show_aml_external_mute_enable,
store__aml_external_mute_enable),
__ATTR_NULL
};
static struct class amaudio_class = {
.name = AMAUDIO2_CLASS_NAME,
.class_attrs = amaudio_attrs,
};
static int amaudio2_init(struct platform_device *pdev)
{
int ret = 0;
int i = 0;
struct amaudio_port_t *ap;
/*pr_info("amaudio2: driver %s init!\n", AMAUDIO2_DRIVER_NAME);*/
ret = register_chrdev(AMAUDIO2_MAJOR, AMAUDIO2_DRIVER_NAME,
&amaudio_fops);
if (ret < 0) {
pr_err("Can't register %s divece ", AMAUDIO2_DRIVER_NAME);
ret = -ENODEV;
goto err;
}
amaudio_classp = class_create(THIS_MODULE, AMAUDIO2_DEVICE_NAME);
if (IS_ERR(amaudio_classp)) {
ret = PTR_ERR(amaudio_classp);
goto err1;
}
ap = &amaudio_ports[0];
for (i = 0; i < AMAUDIO2_DEVICE_COUNT; ap++, i++) {
ap->dev = device_create(amaudio_classp, NULL,
MKDEV(AMAUDIO2_MAJOR, i), NULL,
amaudio_ports[i].name);
if (IS_ERR(ap->dev)) {
pr_err("amaudio2: failed to create amaudio device node\n");
goto err2;
}
}
ret = class_register(&amaudio_class);
if (ret) {
pr_err("amaudio2 class create fail.\n");
goto err3;
}
amaudio_dev =
device_create(&amaudio_class, NULL, MKDEV(AMAUDIO2_MAJOR, 15),
NULL, AMAUDIO2_CLASS_NAME);
if (IS_ERR(amaudio_dev)) {
pr_err("amaudio2 creat device fail.\n");
goto err4;
}
of_dma_configure(amaudio_dev, amaudio_dev->of_node);
amaudio_start_addr =
(unsigned char *)dma_alloc_coherent(amaudio_dev,
SOFT_BUFFER_SIZE,
&amaudio_phy_start_addr,
GFP_KERNEL);
if (!amaudio_start_addr) {
pr_err("amaudio2 out soft DMA buffer alloc failed\n");
goto err4;
}
playback_data_cache_start = (unsigned long)kmalloc(
(SOFT_BUFFER_SIZE), GFP_KERNEL);
if (!playback_data_cache_start) {
pr_err("malloc for the audio output cache buffer fail\n");
goto err5;
}
playback_tmp_start = kmalloc(
(SOFT_BUFFER_SIZE), GFP_KERNEL);
if (!playback_tmp_start)
goto err6;
pr_info("amaudio2: driver %s succuess!\n", AMAUDIO2_DRIVER_NAME);
return 0;
err6:
kfree((void *)playback_data_cache_start);
playback_data_cache_start = 0;
err5:
dma_free_coherent(amaudio_dev, SOFT_BUFFER_SIZE,
(void *)amaudio_start_addr,
amaudio_phy_start_addr);
amaudio_start_addr = 0;
err4:
device_destroy(&amaudio_class, MKDEV(AMAUDIO2_MAJOR, 10));
err3:
class_unregister(&amaudio_class);
err2:
for (ap = &amaudio_ports[0], i = 0; i < AMAUDIO2_DEVICE_COUNT;
ap++, i++)
device_destroy(amaudio_classp, MKDEV(AMAUDIO2_MAJOR, i));
err1:
class_destroy(amaudio_classp);
err:
unregister_chrdev(AMAUDIO2_MAJOR, AMAUDIO2_DRIVER_NAME);
return ret;
}
static int amaudio2_exit(struct platform_device *pdev)
{
int i = 0;
struct amaudio_port_t *ap;
if (playback_data_cache_start) {
kfree((void *)playback_data_cache_start);
playback_data_cache_start = 0;
}
kfree(playback_tmp_start);
if (amaudio_start_addr) {
dma_free_coherent(amaudio_dev, SOFT_BUFFER_SIZE,
(void *)amaudio_start_addr,
amaudio_phy_start_addr);
amaudio_start_addr = 0;
}
device_destroy(&amaudio_class, MKDEV(AMAUDIO2_MAJOR, 10));
class_unregister(&amaudio_class);
for (ap = &amaudio_ports[0], i = 0; i < AMAUDIO2_DEVICE_COUNT;
ap++, i++)
device_destroy(amaudio_classp, MKDEV(AMAUDIO2_MAJOR, i));
class_destroy(amaudio_classp);
unregister_chrdev(AMAUDIO2_MAJOR, AMAUDIO2_DRIVER_NAME);
return 0;
}
static const struct of_device_id amlogic_match[] = {
{.compatible = "amlogic, aml_amaudio2",},
{},
};
static struct platform_driver aml_amaudio2_driver = {
.driver = {
.name = "aml_amaudio2_driver",
.owner = THIS_MODULE,
.of_match_table = amlogic_match,
},
.probe = amaudio2_init,
.remove = amaudio2_exit,
};
static int __init aml_amaudio2_modinit(void)
{
return platform_driver_register(&aml_amaudio2_driver);
}
static void __exit aml_amaudio2_modexit(void)
{
platform_driver_unregister(&aml_amaudio2_driver);
}
module_init(aml_amaudio2_modinit);
module_exit(aml_amaudio2_modexit);
MODULE_DESCRIPTION("AMLOGIC TV Audio driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Amlogic Inc.");
MODULE_VERSION("2.0.0");