| /* Copyright (c) 2009-2011, The Linux Foundation. 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 version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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/wait.h> |
| #include <linux/sched.h> |
| #include <linux/jiffies.h> |
| #include <linux/debugfs.h> |
| #include <asm/uaccess.h> |
| #include <mach/qdsp5v2/qdsp5afecmdi.h> |
| #include <mach/qdsp5v2/qdsp5afemsg.h> |
| #include <mach/qdsp5v2/afe.h> |
| #include <mach/msm_adsp.h> |
| #include <mach/debug_mm.h> |
| |
| #define AFE_MAX_TIMEOUT 500 /* 500 ms */ |
| #define AFE_MAX_CLNT 6 /* 6 HW path defined so far */ |
| #define GETDEVICEID(x) ((x) - 1) |
| |
| struct msm_afe_state { |
| struct msm_adsp_module *mod; |
| struct msm_adsp_ops adsp_ops; |
| struct mutex lock; |
| u8 in_use; |
| u8 codec_config[AFE_MAX_CLNT]; |
| wait_queue_head_t wait; |
| u8 aux_conf_flag; |
| }; |
| |
| #ifdef CONFIG_DEBUG_FS |
| static struct dentry *debugfs_afelb; |
| #endif |
| |
| |
| static struct msm_afe_state the_afe_state; |
| |
| #define afe_send_queue(afe, cmd, len) \ |
| msm_adsp_write(afe->mod, QDSP_apuAfeQueue, \ |
| cmd, len) |
| |
| static void afe_dsp_event(void *data, unsigned id, size_t len, |
| void (*getevent)(void *ptr, size_t len)) |
| { |
| struct msm_afe_state *afe = data; |
| |
| MM_DBG("msg_id %d \n", id); |
| |
| switch (id) { |
| case AFE_APU_MSG_CODEC_CONFIG_ACK: { |
| struct afe_msg_codec_config_ack afe_ack; |
| getevent(&afe_ack, AFE_APU_MSG_CODEC_CONFIG_ACK_LEN); |
| MM_DBG("%s: device_id: %d device activity: %d\n", __func__, |
| afe_ack.device_id, afe_ack.device_activity); |
| if (afe_ack.device_activity == AFE_MSG_CODEC_CONFIG_DISABLED) |
| afe->codec_config[GETDEVICEID(afe_ack.device_id)] = 0; |
| else |
| afe->codec_config[GETDEVICEID(afe_ack.device_id)] = |
| afe_ack.device_activity; |
| |
| wake_up(&afe->wait); |
| break; |
| } |
| case AFE_APU_MSG_VOC_TIMING_SUCCESS: |
| MM_INFO("Received VOC_TIMING_SUCCESS message from AFETASK\n"); |
| break; |
| case ADSP_MESSAGE_ID: |
| MM_DBG("Received ADSP event: module enable/disable(audpptask)"); |
| break; |
| default: |
| MM_ERR("unexpected message from afe \n"); |
| } |
| |
| return; |
| } |
| |
| static void afe_dsp_codec_config(struct msm_afe_state *afe, |
| u8 path_id, u8 enable, struct msm_afe_config *config) |
| { |
| struct afe_cmd_codec_config cmd; |
| |
| MM_DBG("%s() %p\n", __func__, config); |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_CODEC_CONFIG_CMD; |
| cmd.device_id = path_id; |
| cmd.activity = enable; |
| if (config) { |
| MM_DBG("%s: sample_rate %x ch mode %x vol %x\n", |
| __func__, config->sample_rate, |
| config->channel_mode, config->volume); |
| cmd.sample_rate = config->sample_rate; |
| cmd.channel_mode = config->channel_mode; |
| cmd.volume = config->volume; |
| } |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| } |
| /* Function is called after afe module been enabled */ |
| void afe_loopback(int enable) |
| { |
| struct afe_cmd_loopback cmd; |
| struct msm_afe_state *afe; |
| |
| afe = &the_afe_state; |
| MM_DBG("enable %d\n", enable); |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_LOOPBACK; |
| if (enable) |
| cmd.enable_flag = AFE_LOOPBACK_ENABLE_COMMAND; |
| |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| } |
| EXPORT_SYMBOL(afe_loopback); |
| |
| void afe_ext_loopback(int enable, int rx_copp_id, int tx_copp_id) |
| { |
| struct afe_cmd_ext_loopback cmd; |
| struct msm_afe_state *afe; |
| |
| afe = &the_afe_state; |
| MM_DBG("enable %d\n", enable); |
| if ((rx_copp_id == 0) && (tx_copp_id == 0)) { |
| afe_loopback(enable); |
| } else { |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_EXT_LOOPBACK; |
| cmd.source_id = tx_copp_id; |
| cmd.dst_id = rx_copp_id; |
| if (enable) |
| cmd.enable_flag = AFE_LOOPBACK_ENABLE_COMMAND; |
| |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| } |
| } |
| EXPORT_SYMBOL(afe_ext_loopback); |
| |
| void afe_device_volume_ctrl(u16 device_id, u16 device_volume) |
| { |
| struct afe_cmd_device_volume_ctrl cmd; |
| struct msm_afe_state *afe; |
| |
| afe = &the_afe_state; |
| MM_DBG("device 0x%4x volume 0x%4x\n", device_id, device_volume); |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_DEVICE_VOLUME_CTRL; |
| cmd.device_id = device_id; |
| cmd.device_volume = device_volume; |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| } |
| EXPORT_SYMBOL(afe_device_volume_ctrl); |
| |
| int afe_enable(u8 path_id, struct msm_afe_config *config) |
| { |
| struct msm_afe_state *afe = &the_afe_state; |
| int rc; |
| |
| MM_DBG("%s: path %d\n", __func__, path_id); |
| if ((GETDEVICEID(path_id) < 0) || (GETDEVICEID(path_id) > 5)) { |
| MM_ERR("Invalid path_id: %d\n", path_id); |
| return -EINVAL; |
| } |
| mutex_lock(&afe->lock); |
| if (!afe->in_use && !afe->aux_conf_flag) { |
| /* enable afe */ |
| rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); |
| if (rc < 0) { |
| MM_ERR("%s: failed to get AFETASK module\n", __func__); |
| goto error_adsp_get; |
| } |
| rc = msm_adsp_enable(afe->mod); |
| if (rc < 0) |
| goto error_adsp_enable; |
| } |
| /* Issue codec config command */ |
| afe_dsp_codec_config(afe, path_id, 1, config); |
| rc = wait_event_timeout(afe->wait, |
| afe->codec_config[GETDEVICEID(path_id)], |
| msecs_to_jiffies(AFE_MAX_TIMEOUT)); |
| if (!rc) { |
| MM_ERR("AFE failed to respond within %d ms\n", AFE_MAX_TIMEOUT); |
| rc = -ENODEV; |
| if (!afe->in_use) { |
| if (!afe->aux_conf_flag || |
| (afe->aux_conf_flag && |
| (path_id == AFE_HW_PATH_AUXPCM_RX || |
| path_id == AFE_HW_PATH_AUXPCM_TX))) { |
| /* clean up if there is no client */ |
| msm_adsp_disable(afe->mod); |
| msm_adsp_put(afe->mod); |
| afe->aux_conf_flag = 0; |
| afe->mod = NULL; |
| } |
| } |
| |
| } else { |
| rc = 0; |
| afe->in_use++; |
| } |
| |
| mutex_unlock(&afe->lock); |
| return rc; |
| |
| error_adsp_enable: |
| msm_adsp_put(afe->mod); |
| afe->mod = NULL; |
| error_adsp_get: |
| mutex_unlock(&afe->lock); |
| return rc; |
| } |
| EXPORT_SYMBOL(afe_enable); |
| |
| int afe_config_fm_codec(int fm_enable, uint16_t source) |
| { |
| struct afe_cmd_fm_codec_config cmd; |
| struct msm_afe_state *afe = &the_afe_state; |
| int rc = 0; |
| int i = 0; |
| unsigned short *ptrmem = (unsigned short *)&cmd; |
| |
| MM_INFO(" configure fm codec\n"); |
| mutex_lock(&afe->lock); |
| if (!afe->in_use) { |
| /* enable afe */ |
| rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); |
| if (rc < 0) { |
| MM_ERR("%s: failed to get AFETASK module\n", __func__); |
| goto error_adsp_get; |
| } |
| rc = msm_adsp_enable(afe->mod); |
| if (rc < 0) |
| goto error_adsp_enable; |
| } |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_FM_RX_ROUTING_CMD; |
| cmd.enable = fm_enable; |
| cmd.device_id = source; |
| |
| for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) |
| MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| |
| mutex_unlock(&afe->lock); |
| return rc; |
| error_adsp_enable: |
| msm_adsp_put(afe->mod); |
| afe->mod = NULL; |
| error_adsp_get: |
| mutex_unlock(&afe->lock); |
| return rc; |
| } |
| EXPORT_SYMBOL(afe_config_fm_codec); |
| |
| int afe_config_fm_volume(uint16_t volume) |
| { |
| struct afe_cmd_fm_volume_config cmd; |
| struct msm_afe_state *afe = &the_afe_state; |
| int rc = 0; |
| |
| MM_INFO(" configure fm volume\n"); |
| mutex_lock(&afe->lock); |
| if (!afe->in_use) { |
| /* enable afe */ |
| rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); |
| if (rc < 0) { |
| MM_ERR("%s: failed to get AFETASK module\n", __func__); |
| goto error_adsp_get; |
| } |
| rc = msm_adsp_enable(afe->mod); |
| if (rc < 0) |
| goto error_adsp_enable; |
| } |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_FM_PLAYBACK_VOLUME_CMD; |
| cmd.volume = volume; |
| |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| |
| mutex_unlock(&afe->lock); |
| return rc; |
| error_adsp_enable: |
| msm_adsp_put(afe->mod); |
| afe->mod = NULL; |
| error_adsp_get: |
| mutex_unlock(&afe->lock); |
| return rc; |
| } |
| EXPORT_SYMBOL(afe_config_fm_volume); |
| |
| int afe_config_fm_calibration_gain(uint16_t device_id, |
| uint16_t calibration_gain) |
| { |
| struct afe_cmd_fm_calibgain_config cmd; |
| struct msm_afe_state *afe = &the_afe_state; |
| int rc = 0; |
| |
| MM_INFO("Configure for rx device = 0x%4x, gain = 0x%4x\n", device_id, |
| calibration_gain); |
| mutex_lock(&afe->lock); |
| if (!afe->in_use) { |
| /* enable afe */ |
| rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); |
| if (rc < 0) { |
| MM_ERR("%s: failed to get AFETASK module\n", __func__); |
| goto error_adsp_get; |
| } |
| rc = msm_adsp_enable(afe->mod); |
| if (rc < 0) |
| goto error_adsp_enable; |
| } |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_FM_CALIBRATION_GAIN_CMD; |
| cmd.device_id = device_id; |
| cmd.calibration_gain = calibration_gain; |
| |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| |
| mutex_unlock(&afe->lock); |
| return rc; |
| error_adsp_enable: |
| msm_adsp_put(afe->mod); |
| afe->mod = NULL; |
| error_adsp_get: |
| mutex_unlock(&afe->lock); |
| return rc; |
| } |
| EXPORT_SYMBOL(afe_config_fm_calibration_gain); |
| |
| int afe_config_aux_codec(int pcm_ctl_value, int aux_codec_intf_value, |
| int data_format_pad) |
| { |
| struct afe_cmd_aux_codec_config cmd; |
| struct msm_afe_state *afe = &the_afe_state; |
| int rc = 0; |
| |
| MM_DBG(" configure aux codec \n"); |
| mutex_lock(&afe->lock); |
| if (!afe->in_use && !afe->aux_conf_flag) { |
| /* enable afe */ |
| rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); |
| if (rc < 0) { |
| MM_ERR("%s: failed to get AFETASK module\n", __func__); |
| goto error_adsp_get; |
| } |
| rc = msm_adsp_enable(afe->mod); |
| if (rc < 0) |
| goto error_adsp_enable; |
| } |
| afe->aux_conf_flag = 1; |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_AUX_CODEC_CONFIG_CMD; |
| cmd.dma_path_ctl = 0; |
| cmd.pcm_ctl = pcm_ctl_value; |
| cmd.eight_khz_int_mode = 0; |
| cmd.aux_codec_intf_ctl = aux_codec_intf_value; |
| cmd.data_format_padding_info = data_format_pad; |
| |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| |
| mutex_unlock(&afe->lock); |
| return rc; |
| error_adsp_enable: |
| msm_adsp_put(afe->mod); |
| afe->mod = NULL; |
| error_adsp_get: |
| mutex_unlock(&afe->lock); |
| return rc; |
| } |
| EXPORT_SYMBOL(afe_config_aux_codec); |
| |
| int afe_config_rmc_block(struct acdb_rmc_block *acdb_rmc) |
| { |
| struct afe_cmd_cfg_rmc cmd; |
| struct msm_afe_state *afe = &the_afe_state; |
| int rc = 0; |
| int i = 0; |
| unsigned short *ptrmem = (unsigned short *)&cmd; |
| |
| MM_DBG(" configure rmc block\n"); |
| mutex_lock(&afe->lock); |
| if (!afe->in_use && !afe->mod) { |
| /* enable afe */ |
| rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); |
| if (rc < 0) { |
| MM_DBG("%s: failed to get AFETASK module\n", __func__); |
| goto error_adsp_get; |
| } |
| rc = msm_adsp_enable(afe->mod); |
| if (rc < 0) |
| goto error_adsp_enable; |
| } |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd_id = AFE_CMD_CFG_RMC_PARAMS; |
| |
| cmd.rmc_mode = acdb_rmc->rmc_enable; |
| cmd.rmc_ipw_length_ms = acdb_rmc->rmc_ipw_length_ms; |
| cmd.rmc_peak_length_ms = acdb_rmc->rmc_peak_length_ms; |
| cmd.rmc_init_pulse_length_ms = acdb_rmc->rmc_init_pulse_length_ms; |
| cmd.rmc_total_int_length_ms = acdb_rmc->rmc_total_int_length_ms; |
| cmd.rmc_rampupdn_length_ms = acdb_rmc->rmc_rampupdn_length_ms; |
| cmd.rmc_delay_length_ms = acdb_rmc->rmc_delay_length_ms; |
| cmd.rmc_detect_start_threshdb = acdb_rmc->rmc_detect_start_threshdb; |
| cmd.rmc_init_pulse_threshdb = acdb_rmc->rmc_init_pulse_threshdb; |
| |
| for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) |
| MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); |
| afe_send_queue(afe, &cmd, sizeof(cmd)); |
| |
| mutex_unlock(&afe->lock); |
| return rc; |
| error_adsp_enable: |
| msm_adsp_put(afe->mod); |
| afe->mod = NULL; |
| error_adsp_get: |
| mutex_unlock(&afe->lock); |
| return rc; |
| } |
| EXPORT_SYMBOL(afe_config_rmc_block); |
| |
| int afe_disable(u8 path_id) |
| { |
| struct msm_afe_state *afe = &the_afe_state; |
| int rc; |
| |
| mutex_lock(&afe->lock); |
| |
| BUG_ON(!afe->in_use); |
| MM_DBG("%s() path_id:%d codec state:%d\n", __func__, path_id, |
| afe->codec_config[GETDEVICEID(path_id)]); |
| afe_dsp_codec_config(afe, path_id, 0, NULL); |
| rc = wait_event_timeout(afe->wait, |
| !afe->codec_config[GETDEVICEID(path_id)], |
| msecs_to_jiffies(AFE_MAX_TIMEOUT)); |
| if (!rc) { |
| MM_ERR("AFE failed to respond within %d ms\n", AFE_MAX_TIMEOUT); |
| rc = -1; |
| } else |
| rc = 0; |
| afe->in_use--; |
| MM_DBG("%s() in_use:%d \n", __func__, afe->in_use); |
| if (!afe->in_use) { |
| msm_adsp_disable(afe->mod); |
| msm_adsp_put(afe->mod); |
| afe->aux_conf_flag = 0; |
| afe->mod = NULL; |
| } |
| mutex_unlock(&afe->lock); |
| return rc; |
| } |
| EXPORT_SYMBOL(afe_disable); |
| |
| |
| #ifdef CONFIG_DEBUG_FS |
| static int afe_debug_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| MM_INFO("debug intf %s\n", (char *) file->private_data); |
| return 0; |
| } |
| |
| static ssize_t afe_debug_write(struct file *filp, |
| const char __user *ubuf, size_t cnt, loff_t *ppos) |
| { |
| char *lb_str = filp->private_data; |
| char cmd; |
| |
| if (get_user(cmd, ubuf)) |
| return -EFAULT; |
| |
| MM_INFO("%s %c\n", lb_str, cmd); |
| |
| if (!strcmp(lb_str, "afe_loopback")) { |
| switch (cmd) { |
| case '1': |
| afe_loopback(1); |
| break; |
| case '0': |
| afe_loopback(0); |
| break; |
| } |
| } |
| |
| return cnt; |
| } |
| |
| static const struct file_operations afe_debug_fops = { |
| .open = afe_debug_open, |
| .write = afe_debug_write |
| }; |
| #endif |
| |
| static int __init afe_init(void) |
| { |
| struct msm_afe_state *afe = &the_afe_state; |
| |
| MM_INFO("AFE driver init\n"); |
| |
| memset(afe, 0, sizeof(struct msm_afe_state)); |
| afe->adsp_ops.event = afe_dsp_event; |
| mutex_init(&afe->lock); |
| init_waitqueue_head(&afe->wait); |
| |
| #ifdef CONFIG_DEBUG_FS |
| debugfs_afelb = debugfs_create_file("afe_loopback", |
| S_IFREG | S_IWUGO, NULL, (void *) "afe_loopback", |
| &afe_debug_fops); |
| #endif |
| |
| return 0; |
| } |
| |
| static void __exit afe_exit(void) |
| { |
| MM_INFO("AFE driver exit\n"); |
| #ifdef CONFIG_DEBUG_FS |
| if (debugfs_afelb) |
| debugfs_remove(debugfs_afelb); |
| #endif |
| if (the_afe_state.mod) |
| msm_adsp_put(the_afe_state.mod); |
| return; |
| } |
| |
| module_init(afe_init); |
| module_exit(afe_exit); |
| |
| MODULE_DESCRIPTION("MSM AFE driver"); |
| MODULE_LICENSE("GPL v2"); |