![Tejun Heo](/assets/img/avatar_default.png)
percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
849 lines
20 KiB
C
849 lines
20 KiB
C
/* arch/arm/mach-msm/qdsp5/audio_out.c
|
|
*
|
|
* pcm audio output device
|
|
*
|
|
* Copyright (C) 2008 Google, Inc.
|
|
* Copyright (C) 2008 HTC Corporation
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/wakelock.h>
|
|
#include <linux/gfp.h>
|
|
|
|
#include <linux/msm_audio.h>
|
|
|
|
#include <asm/atomic.h>
|
|
#include <asm/ioctls.h>
|
|
#include <mach/msm_adsp.h>
|
|
|
|
#include "audmgr.h"
|
|
|
|
#include <mach/qdsp5/qdsp5audppcmdi.h>
|
|
#include <mach/qdsp5/qdsp5audppmsg.h>
|
|
|
|
#include "evlog.h"
|
|
|
|
#define LOG_AUDIO_EVENTS 1
|
|
#define LOG_AUDIO_FAULTS 0
|
|
|
|
enum {
|
|
EV_NULL,
|
|
EV_OPEN,
|
|
EV_WRITE,
|
|
EV_RETURN,
|
|
EV_IOCTL,
|
|
EV_WRITE_WAIT,
|
|
EV_WAIT_EVENT,
|
|
EV_FILL_BUFFER,
|
|
EV_SEND_BUFFER,
|
|
EV_DSP_EVENT,
|
|
EV_ENABLE,
|
|
};
|
|
|
|
#if (LOG_AUDIO_EVENTS != 1)
|
|
static inline void LOG(unsigned id, unsigned arg) {}
|
|
#else
|
|
static const char *pcm_log_strings[] = {
|
|
"NULL",
|
|
"OPEN",
|
|
"WRITE",
|
|
"RETURN",
|
|
"IOCTL",
|
|
"WRITE_WAIT",
|
|
"WAIT_EVENT",
|
|
"FILL_BUFFER",
|
|
"SEND_BUFFER",
|
|
"DSP_EVENT",
|
|
"ENABLE",
|
|
};
|
|
|
|
DECLARE_LOG(pcm_log, 64, pcm_log_strings);
|
|
|
|
static int __init _pcm_log_init(void)
|
|
{
|
|
return ev_log_init(&pcm_log);
|
|
}
|
|
module_init(_pcm_log_init);
|
|
|
|
#define LOG(id,arg) ev_log_write(&pcm_log, id, arg)
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#define BUFSZ (960 * 5)
|
|
#define DMASZ (BUFSZ * 2)
|
|
|
|
#define AUDPP_CMD_CFG_OBJ_UPDATE 0x8000
|
|
#define AUDPP_CMD_EQ_FLAG_DIS 0x0000
|
|
#define AUDPP_CMD_EQ_FLAG_ENA -1
|
|
#define AUDPP_CMD_IIR_FLAG_DIS 0x0000
|
|
#define AUDPP_CMD_IIR_FLAG_ENA -1
|
|
|
|
#define AUDPP_CMD_IIR_TUNING_FILTER 1
|
|
#define AUDPP_CMD_EQUALIZER 2
|
|
#define AUDPP_CMD_ADRC 3
|
|
|
|
#define ADRC_ENABLE 0x0001
|
|
#define EQ_ENABLE 0x0002
|
|
#define IIR_ENABLE 0x0004
|
|
|
|
struct adrc_filter {
|
|
uint16_t compression_th;
|
|
uint16_t compression_slope;
|
|
uint16_t rms_time;
|
|
uint16_t attack_const_lsw;
|
|
uint16_t attack_const_msw;
|
|
uint16_t release_const_lsw;
|
|
uint16_t release_const_msw;
|
|
uint16_t adrc_system_delay;
|
|
};
|
|
|
|
struct eqalizer {
|
|
uint16_t num_bands;
|
|
uint16_t eq_params[132];
|
|
};
|
|
|
|
struct rx_iir_filter {
|
|
uint16_t num_bands;
|
|
uint16_t iir_params[48];
|
|
};
|
|
|
|
typedef struct {
|
|
audpp_cmd_cfg_object_params_common common;
|
|
uint16_t eq_flag;
|
|
uint16_t num_bands;
|
|
uint16_t eq_params[132];
|
|
} audpp_cmd_cfg_object_params_eq;
|
|
|
|
typedef struct {
|
|
audpp_cmd_cfg_object_params_common common;
|
|
uint16_t active_flag;
|
|
uint16_t num_bands;
|
|
uint16_t iir_params[48];
|
|
} audpp_cmd_cfg_object_params_rx_iir;
|
|
|
|
struct buffer {
|
|
void *data;
|
|
unsigned size;
|
|
unsigned used;
|
|
unsigned addr;
|
|
};
|
|
|
|
struct audio {
|
|
struct buffer out[2];
|
|
|
|
spinlock_t dsp_lock;
|
|
|
|
uint8_t out_head;
|
|
uint8_t out_tail;
|
|
uint8_t out_needed; /* number of buffers the dsp is waiting for */
|
|
|
|
atomic_t out_bytes;
|
|
|
|
struct mutex lock;
|
|
struct mutex write_lock;
|
|
wait_queue_head_t wait;
|
|
|
|
/* configuration to use on next enable */
|
|
uint32_t out_sample_rate;
|
|
uint32_t out_channel_mode;
|
|
uint32_t out_weight;
|
|
uint32_t out_buffer_size;
|
|
|
|
struct audmgr audmgr;
|
|
|
|
/* data allocated for various buffers */
|
|
char *data;
|
|
dma_addr_t phys;
|
|
|
|
int opened;
|
|
int enabled;
|
|
int running;
|
|
int stopped; /* set when stopped, cleared on flush */
|
|
unsigned volume;
|
|
|
|
struct wake_lock wakelock;
|
|
struct wake_lock idlelock;
|
|
|
|
int adrc_enable;
|
|
struct adrc_filter adrc;
|
|
|
|
int eq_enable;
|
|
struct eqalizer eq;
|
|
|
|
int rx_iir_enable;
|
|
struct rx_iir_filter iir;
|
|
};
|
|
|
|
static void audio_prevent_sleep(struct audio *audio)
|
|
{
|
|
printk(KERN_INFO "++++++++++++++++++++++++++++++\n");
|
|
wake_lock(&audio->wakelock);
|
|
wake_lock(&audio->idlelock);
|
|
}
|
|
|
|
static void audio_allow_sleep(struct audio *audio)
|
|
{
|
|
wake_unlock(&audio->wakelock);
|
|
wake_unlock(&audio->idlelock);
|
|
printk(KERN_INFO "------------------------------\n");
|
|
}
|
|
|
|
static int audio_dsp_out_enable(struct audio *audio, int yes);
|
|
static int audio_dsp_send_buffer(struct audio *audio, unsigned id, unsigned len);
|
|
static int audio_dsp_set_adrc(struct audio *audio);
|
|
static int audio_dsp_set_eq(struct audio *audio);
|
|
static int audio_dsp_set_rx_iir(struct audio *audio);
|
|
|
|
static void audio_dsp_event(void *private, unsigned id, uint16_t *msg);
|
|
|
|
/* must be called with audio->lock held */
|
|
static int audio_enable(struct audio *audio)
|
|
{
|
|
struct audmgr_config cfg;
|
|
int rc;
|
|
|
|
pr_info("audio_enable()\n");
|
|
|
|
if (audio->enabled)
|
|
return 0;
|
|
|
|
/* refuse to start if we're not ready */
|
|
if (!audio->out[0].used || !audio->out[1].used)
|
|
return -EIO;
|
|
|
|
/* we start buffers 0 and 1, so buffer 0 will be the
|
|
* next one the dsp will want
|
|
*/
|
|
audio->out_tail = 0;
|
|
audio->out_needed = 0;
|
|
|
|
cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE;
|
|
cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000;
|
|
cfg.def_method = RPC_AUD_DEF_METHOD_HOST_PCM;
|
|
cfg.codec = RPC_AUD_DEF_CODEC_PCM;
|
|
cfg.snd_method = RPC_SND_METHOD_MIDI;
|
|
|
|
audio_prevent_sleep(audio);
|
|
rc = audmgr_enable(&audio->audmgr, &cfg);
|
|
if (rc < 0) {
|
|
audio_allow_sleep(audio);
|
|
return rc;
|
|
}
|
|
|
|
if (audpp_enable(-1, audio_dsp_event, audio)) {
|
|
pr_err("audio: audpp_enable() failed\n");
|
|
audmgr_disable(&audio->audmgr);
|
|
audio_allow_sleep(audio);
|
|
return -ENODEV;
|
|
}
|
|
|
|
audio->enabled = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* must be called with audio->lock held */
|
|
static int audio_disable(struct audio *audio)
|
|
{
|
|
pr_info("audio_disable()\n");
|
|
if (audio->enabled) {
|
|
audio->enabled = 0;
|
|
audio_dsp_out_enable(audio, 0);
|
|
|
|
audpp_disable(-1, audio);
|
|
|
|
wake_up(&audio->wait);
|
|
audmgr_disable(&audio->audmgr);
|
|
audio->out_needed = 0;
|
|
audio_allow_sleep(audio);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------- dsp --------------------- */
|
|
static void audio_dsp_event(void *private, unsigned id, uint16_t *msg)
|
|
{
|
|
struct audio *audio = private;
|
|
struct buffer *frame;
|
|
unsigned long flags;
|
|
|
|
LOG(EV_DSP_EVENT, id);
|
|
switch (id) {
|
|
case AUDPP_MSG_HOST_PCM_INTF_MSG: {
|
|
unsigned id = msg[2];
|
|
unsigned idx = msg[3] - 1;
|
|
|
|
/* pr_info("audio_dsp_event: HOST_PCM id %d idx %d\n", id, idx); */
|
|
if (id != AUDPP_MSG_HOSTPCM_ID_ARM_RX) {
|
|
pr_err("bogus id\n");
|
|
break;
|
|
}
|
|
if (idx > 1) {
|
|
pr_err("bogus buffer idx\n");
|
|
break;
|
|
}
|
|
|
|
spin_lock_irqsave(&audio->dsp_lock, flags);
|
|
if (audio->running) {
|
|
atomic_add(audio->out[idx].used, &audio->out_bytes);
|
|
audio->out[idx].used = 0;
|
|
|
|
frame = audio->out + audio->out_tail;
|
|
if (frame->used) {
|
|
audio_dsp_send_buffer(
|
|
audio, audio->out_tail, frame->used);
|
|
audio->out_tail ^= 1;
|
|
} else {
|
|
audio->out_needed++;
|
|
}
|
|
wake_up(&audio->wait);
|
|
}
|
|
spin_unlock_irqrestore(&audio->dsp_lock, flags);
|
|
break;
|
|
}
|
|
case AUDPP_MSG_PCMDMAMISSED:
|
|
pr_info("audio_dsp_event: PCMDMAMISSED %d\n", msg[0]);
|
|
break;
|
|
case AUDPP_MSG_CFG_MSG:
|
|
if (msg[0] == AUDPP_MSG_ENA_ENA) {
|
|
LOG(EV_ENABLE, 1);
|
|
pr_info("audio_dsp_event: CFG_MSG ENABLE\n");
|
|
audio->out_needed = 0;
|
|
audio->running = 1;
|
|
audpp_set_volume_and_pan(5, audio->volume, 0);
|
|
audio_dsp_set_adrc(audio);
|
|
audio_dsp_set_eq(audio);
|
|
audio_dsp_set_rx_iir(audio);
|
|
audio_dsp_out_enable(audio, 1);
|
|
} else if (msg[0] == AUDPP_MSG_ENA_DIS) {
|
|
LOG(EV_ENABLE, 0);
|
|
pr_info("audio_dsp_event: CFG_MSG DISABLE\n");
|
|
audio->running = 0;
|
|
} else {
|
|
pr_err("audio_dsp_event: CFG_MSG %d?\n", msg[0]);
|
|
}
|
|
break;
|
|
default:
|
|
pr_err("audio_dsp_event: UNKNOWN (%d)\n", id);
|
|
}
|
|
}
|
|
|
|
static int audio_dsp_out_enable(struct audio *audio, int yes)
|
|
{
|
|
audpp_cmd_pcm_intf cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.cmd_id = AUDPP_CMD_PCM_INTF_2;
|
|
cmd.object_num = AUDPP_CMD_PCM_INTF_OBJECT_NUM;
|
|
cmd.config = AUDPP_CMD_PCM_INTF_CONFIG_CMD_V;
|
|
cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V;
|
|
|
|
if (yes) {
|
|
cmd.write_buf1LSW = audio->out[0].addr;
|
|
cmd.write_buf1MSW = audio->out[0].addr >> 16;
|
|
cmd.write_buf1_len = audio->out[0].size;
|
|
cmd.write_buf2LSW = audio->out[1].addr;
|
|
cmd.write_buf2MSW = audio->out[1].addr >> 16;
|
|
cmd.write_buf2_len = audio->out[1].size;
|
|
cmd.arm_to_rx_flag = AUDPP_CMD_PCM_INTF_ENA_V;
|
|
cmd.weight_decoder_to_rx = audio->out_weight;
|
|
cmd.weight_arm_to_rx = 1;
|
|
cmd.partition_number_arm_to_dsp = 0;
|
|
cmd.sample_rate = audio->out_sample_rate;
|
|
cmd.channel_mode = audio->out_channel_mode;
|
|
}
|
|
|
|
return audpp_send_queue2(&cmd, sizeof(cmd));
|
|
}
|
|
|
|
static int audio_dsp_send_buffer(struct audio *audio, unsigned idx, unsigned len)
|
|
{
|
|
audpp_cmd_pcm_intf_send_buffer cmd;
|
|
|
|
cmd.cmd_id = AUDPP_CMD_PCM_INTF_2;
|
|
cmd.host_pcm_object = AUDPP_CMD_PCM_INTF_OBJECT_NUM;
|
|
cmd.config = AUDPP_CMD_PCM_INTF_BUFFER_CMD_V;
|
|
cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V;
|
|
cmd.dsp_to_arm_buf_id = 0;
|
|
cmd.arm_to_dsp_buf_id = idx + 1;
|
|
cmd.arm_to_dsp_buf_len = len;
|
|
|
|
LOG(EV_SEND_BUFFER, idx);
|
|
return audpp_send_queue2(&cmd, sizeof(cmd));
|
|
}
|
|
|
|
static int audio_dsp_set_adrc(struct audio *audio)
|
|
{
|
|
audpp_cmd_cfg_object_params_adrc cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE;
|
|
cmd.common.command_type = AUDPP_CMD_ADRC;
|
|
|
|
if (audio->adrc_enable) {
|
|
cmd.adrc_flag = AUDPP_CMD_ADRC_FLAG_ENA;
|
|
cmd.compression_th = audio->adrc.compression_th;
|
|
cmd.compression_slope = audio->adrc.compression_slope;
|
|
cmd.rms_time = audio->adrc.rms_time;
|
|
cmd.attack_const_lsw = audio->adrc.attack_const_lsw;
|
|
cmd.attack_const_msw = audio->adrc.attack_const_msw;
|
|
cmd.release_const_lsw = audio->adrc.release_const_lsw;
|
|
cmd.release_const_msw = audio->adrc.release_const_msw;
|
|
cmd.adrc_system_delay = audio->adrc.adrc_system_delay;
|
|
} else {
|
|
cmd.adrc_flag = AUDPP_CMD_ADRC_FLAG_DIS;
|
|
}
|
|
return audpp_send_queue3(&cmd, sizeof(cmd));
|
|
}
|
|
|
|
static int audio_dsp_set_eq(struct audio *audio)
|
|
{
|
|
audpp_cmd_cfg_object_params_eq cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE;
|
|
cmd.common.command_type = AUDPP_CMD_EQUALIZER;
|
|
|
|
if (audio->eq_enable) {
|
|
cmd.eq_flag = AUDPP_CMD_EQ_FLAG_ENA;
|
|
cmd.num_bands = audio->eq.num_bands;
|
|
memcpy(&cmd.eq_params, audio->eq.eq_params,
|
|
sizeof(audio->eq.eq_params));
|
|
} else {
|
|
cmd.eq_flag = AUDPP_CMD_EQ_FLAG_DIS;
|
|
}
|
|
return audpp_send_queue3(&cmd, sizeof(cmd));
|
|
}
|
|
|
|
static int audio_dsp_set_rx_iir(struct audio *audio)
|
|
{
|
|
audpp_cmd_cfg_object_params_rx_iir cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE;
|
|
cmd.common.command_type = AUDPP_CMD_IIR_TUNING_FILTER;
|
|
|
|
if (audio->rx_iir_enable) {
|
|
cmd.active_flag = AUDPP_CMD_IIR_FLAG_ENA;
|
|
cmd.num_bands = audio->iir.num_bands;
|
|
memcpy(&cmd.iir_params, audio->iir.iir_params,
|
|
sizeof(audio->iir.iir_params));
|
|
} else {
|
|
cmd.active_flag = AUDPP_CMD_IIR_FLAG_DIS;
|
|
}
|
|
|
|
return audpp_send_queue3(&cmd, sizeof(cmd));
|
|
}
|
|
|
|
/* ------------------- device --------------------- */
|
|
|
|
static int audio_enable_adrc(struct audio *audio, int enable)
|
|
{
|
|
if (audio->adrc_enable != enable) {
|
|
audio->adrc_enable = enable;
|
|
if (audio->running)
|
|
audio_dsp_set_adrc(audio);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int audio_enable_eq(struct audio *audio, int enable)
|
|
{
|
|
if (audio->eq_enable != enable) {
|
|
audio->eq_enable = enable;
|
|
if (audio->running)
|
|
audio_dsp_set_eq(audio);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int audio_enable_rx_iir(struct audio *audio, int enable)
|
|
{
|
|
if (audio->rx_iir_enable != enable) {
|
|
audio->rx_iir_enable = enable;
|
|
if (audio->running)
|
|
audio_dsp_set_rx_iir(audio);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void audio_flush(struct audio *audio)
|
|
{
|
|
audio->out[0].used = 0;
|
|
audio->out[1].used = 0;
|
|
audio->out_head = 0;
|
|
audio->out_tail = 0;
|
|
audio->stopped = 0;
|
|
}
|
|
|
|
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct audio *audio = file->private_data;
|
|
int rc;
|
|
|
|
if (cmd == AUDIO_GET_STATS) {
|
|
struct msm_audio_stats stats;
|
|
stats.byte_count = atomic_read(&audio->out_bytes);
|
|
if (copy_to_user((void*) arg, &stats, sizeof(stats)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
if (cmd == AUDIO_SET_VOLUME) {
|
|
unsigned long flags;
|
|
spin_lock_irqsave(&audio->dsp_lock, flags);
|
|
audio->volume = arg;
|
|
if (audio->running)
|
|
audpp_set_volume_and_pan(6, arg, 0);
|
|
spin_unlock_irqrestore(&audio->dsp_lock, flags);
|
|
}
|
|
|
|
LOG(EV_IOCTL, cmd);
|
|
mutex_lock(&audio->lock);
|
|
switch (cmd) {
|
|
case AUDIO_START:
|
|
rc = audio_enable(audio);
|
|
break;
|
|
case AUDIO_STOP:
|
|
rc = audio_disable(audio);
|
|
audio->stopped = 1;
|
|
break;
|
|
case AUDIO_FLUSH:
|
|
if (audio->stopped) {
|
|
/* Make sure we're stopped and we wake any threads
|
|
* that might be blocked holding the write_lock.
|
|
* While audio->stopped write threads will always
|
|
* exit immediately.
|
|
*/
|
|
wake_up(&audio->wait);
|
|
mutex_lock(&audio->write_lock);
|
|
audio_flush(audio);
|
|
mutex_unlock(&audio->write_lock);
|
|
}
|
|
case AUDIO_SET_CONFIG: {
|
|
struct msm_audio_config config;
|
|
if (copy_from_user(&config, (void*) arg, sizeof(config))) {
|
|
rc = -EFAULT;
|
|
break;
|
|
}
|
|
if (config.channel_count == 1) {
|
|
config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V;
|
|
} else if (config.channel_count == 2) {
|
|
config.channel_count= AUDPP_CMD_PCM_INTF_STEREO_V;
|
|
} else {
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
audio->out_sample_rate = config.sample_rate;
|
|
audio->out_channel_mode = config.channel_count;
|
|
rc = 0;
|
|
break;
|
|
}
|
|
case AUDIO_GET_CONFIG: {
|
|
struct msm_audio_config config;
|
|
config.buffer_size = BUFSZ;
|
|
config.buffer_count = 2;
|
|
config.sample_rate = audio->out_sample_rate;
|
|
if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) {
|
|
config.channel_count = 1;
|
|
} else {
|
|
config.channel_count = 2;
|
|
}
|
|
config.unused[0] = 0;
|
|
config.unused[1] = 0;
|
|
config.unused[2] = 0;
|
|
config.unused[3] = 0;
|
|
if (copy_to_user((void*) arg, &config, sizeof(config))) {
|
|
rc = -EFAULT;
|
|
} else {
|
|
rc = 0;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
rc = -EINVAL;
|
|
}
|
|
mutex_unlock(&audio->lock);
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t audio_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static inline int rt_policy(int policy)
|
|
{
|
|
if (unlikely(policy == SCHED_FIFO) || unlikely(policy == SCHED_RR))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static inline int task_has_rt_policy(struct task_struct *p)
|
|
{
|
|
return rt_policy(p->policy);
|
|
}
|
|
|
|
static ssize_t audio_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *pos)
|
|
{
|
|
struct sched_param s = { .sched_priority = 1 };
|
|
struct audio *audio = file->private_data;
|
|
unsigned long flags;
|
|
const char __user *start = buf;
|
|
struct buffer *frame;
|
|
size_t xfer;
|
|
int old_prio = current->rt_priority;
|
|
int old_policy = current->policy;
|
|
int cap_nice = cap_raised(current_cap(), CAP_SYS_NICE);
|
|
int rc = 0;
|
|
|
|
LOG(EV_WRITE, count | (audio->running << 28) | (audio->stopped << 24));
|
|
|
|
/* just for this write, set us real-time */
|
|
if (!task_has_rt_policy(current)) {
|
|
struct cred *new = prepare_creds();
|
|
cap_raise(new->cap_effective, CAP_SYS_NICE);
|
|
commit_creds(new);
|
|
sched_setscheduler(current, SCHED_RR, &s);
|
|
}
|
|
|
|
mutex_lock(&audio->write_lock);
|
|
while (count > 0) {
|
|
frame = audio->out + audio->out_head;
|
|
|
|
LOG(EV_WAIT_EVENT, 0);
|
|
rc = wait_event_interruptible(audio->wait,
|
|
(frame->used == 0) || (audio->stopped));
|
|
LOG(EV_WAIT_EVENT, 1);
|
|
|
|
if (rc < 0)
|
|
break;
|
|
if (audio->stopped) {
|
|
rc = -EBUSY;
|
|
break;
|
|
}
|
|
xfer = count > frame->size ? frame->size : count;
|
|
if (copy_from_user(frame->data, buf, xfer)) {
|
|
rc = -EFAULT;
|
|
break;
|
|
}
|
|
frame->used = xfer;
|
|
audio->out_head ^= 1;
|
|
count -= xfer;
|
|
buf += xfer;
|
|
|
|
spin_lock_irqsave(&audio->dsp_lock, flags);
|
|
LOG(EV_FILL_BUFFER, audio->out_head ^ 1);
|
|
frame = audio->out + audio->out_tail;
|
|
if (frame->used && audio->out_needed) {
|
|
audio_dsp_send_buffer(audio, audio->out_tail, frame->used);
|
|
audio->out_tail ^= 1;
|
|
audio->out_needed--;
|
|
}
|
|
spin_unlock_irqrestore(&audio->dsp_lock, flags);
|
|
}
|
|
|
|
mutex_unlock(&audio->write_lock);
|
|
|
|
/* restore scheduling policy and priority */
|
|
if (!rt_policy(old_policy)) {
|
|
struct sched_param v = { .sched_priority = old_prio };
|
|
sched_setscheduler(current, old_policy, &v);
|
|
if (likely(!cap_nice)) {
|
|
struct cred *new = prepare_creds();
|
|
cap_lower(new->cap_effective, CAP_SYS_NICE);
|
|
commit_creds(new);
|
|
sched_setscheduler(current, SCHED_RR, &s);
|
|
}
|
|
}
|
|
|
|
LOG(EV_RETURN,(buf > start) ? (buf - start) : rc);
|
|
if (buf > start)
|
|
return buf - start;
|
|
return rc;
|
|
}
|
|
|
|
static int audio_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct audio *audio = file->private_data;
|
|
|
|
LOG(EV_OPEN, 0);
|
|
mutex_lock(&audio->lock);
|
|
audio_disable(audio);
|
|
audio_flush(audio);
|
|
audio->opened = 0;
|
|
mutex_unlock(&audio->lock);
|
|
return 0;
|
|
}
|
|
|
|
static struct audio the_audio;
|
|
|
|
static int audio_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct audio *audio = &the_audio;
|
|
int rc;
|
|
|
|
mutex_lock(&audio->lock);
|
|
|
|
if (audio->opened) {
|
|
pr_err("audio: busy\n");
|
|
rc = -EBUSY;
|
|
goto done;
|
|
}
|
|
|
|
if (!audio->data) {
|
|
audio->data = dma_alloc_coherent(NULL, DMASZ,
|
|
&audio->phys, GFP_KERNEL);
|
|
if (!audio->data) {
|
|
pr_err("audio: could not allocate DMA buffers\n");
|
|
rc = -ENOMEM;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
rc = audmgr_open(&audio->audmgr);
|
|
if (rc)
|
|
goto done;
|
|
|
|
audio->out_buffer_size = BUFSZ;
|
|
audio->out_sample_rate = 44100;
|
|
audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V;
|
|
audio->out_weight = 100;
|
|
|
|
audio->out[0].data = audio->data + 0;
|
|
audio->out[0].addr = audio->phys + 0;
|
|
audio->out[0].size = BUFSZ;
|
|
|
|
audio->out[1].data = audio->data + BUFSZ;
|
|
audio->out[1].addr = audio->phys + BUFSZ;
|
|
audio->out[1].size = BUFSZ;
|
|
|
|
audio->volume = 0x2000;
|
|
|
|
audio_flush(audio);
|
|
|
|
file->private_data = audio;
|
|
audio->opened = 1;
|
|
rc = 0;
|
|
LOG(EV_OPEN, 1);
|
|
done:
|
|
mutex_unlock(&audio->lock);
|
|
return rc;
|
|
}
|
|
|
|
static long audpp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct audio *audio = file->private_data;
|
|
int rc = 0, enable;
|
|
uint16_t enable_mask;
|
|
|
|
mutex_lock(&audio->lock);
|
|
switch (cmd) {
|
|
case AUDIO_ENABLE_AUDPP:
|
|
if (copy_from_user(&enable_mask, (void *) arg, sizeof(enable_mask)))
|
|
goto out_fault;
|
|
|
|
enable = (enable_mask & ADRC_ENABLE)? 1 : 0;
|
|
audio_enable_adrc(audio, enable);
|
|
enable = (enable_mask & EQ_ENABLE)? 1 : 0;
|
|
audio_enable_eq(audio, enable);
|
|
enable = (enable_mask & IIR_ENABLE)? 1 : 0;
|
|
audio_enable_rx_iir(audio, enable);
|
|
break;
|
|
|
|
case AUDIO_SET_ADRC:
|
|
if (copy_from_user(&audio->adrc, (void*) arg, sizeof(audio->adrc)))
|
|
goto out_fault;
|
|
break;
|
|
|
|
case AUDIO_SET_EQ:
|
|
if (copy_from_user(&audio->eq, (void*) arg, sizeof(audio->eq)))
|
|
goto out_fault;
|
|
break;
|
|
|
|
case AUDIO_SET_RX_IIR:
|
|
if (copy_from_user(&audio->iir, (void*) arg, sizeof(audio->iir)))
|
|
goto out_fault;
|
|
break;
|
|
|
|
default:
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
goto out;
|
|
|
|
out_fault:
|
|
rc = -EFAULT;
|
|
out:
|
|
mutex_unlock(&audio->lock);
|
|
return rc;
|
|
}
|
|
|
|
static int audpp_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct audio *audio = &the_audio;
|
|
|
|
file->private_data = audio;
|
|
return 0;
|
|
}
|
|
|
|
static struct file_operations audio_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = audio_open,
|
|
.release = audio_release,
|
|
.read = audio_read,
|
|
.write = audio_write,
|
|
.unlocked_ioctl = audio_ioctl,
|
|
};
|
|
|
|
static struct file_operations audpp_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = audpp_open,
|
|
.unlocked_ioctl = audpp_ioctl,
|
|
};
|
|
|
|
struct miscdevice audio_misc = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "msm_pcm_out",
|
|
.fops = &audio_fops,
|
|
};
|
|
|
|
struct miscdevice audpp_misc = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "msm_pcm_ctl",
|
|
.fops = &audpp_fops,
|
|
};
|
|
|
|
static int __init audio_init(void)
|
|
{
|
|
mutex_init(&the_audio.lock);
|
|
mutex_init(&the_audio.write_lock);
|
|
spin_lock_init(&the_audio.dsp_lock);
|
|
init_waitqueue_head(&the_audio.wait);
|
|
wake_lock_init(&the_audio.wakelock, WAKE_LOCK_SUSPEND, "audio_pcm");
|
|
wake_lock_init(&the_audio.idlelock, WAKE_LOCK_IDLE, "audio_pcm_idle");
|
|
return (misc_register(&audio_misc) || misc_register(&audpp_misc));
|
|
}
|
|
|
|
device_initcall(audio_init);
|