From dd777b0b101d7f0989ada2f80feeb723e503a899 Mon Sep 17 00:00:00 2001 From: Markinus Date: Mon, 30 Aug 2010 13:36:24 +0200 Subject: [PATCH] htcleo: add 1550 audio All credits to Cotulla --- arch/arm/configs/htcleo_defconfig | 4 + arch/arm/mach-msm/Kconfig | 2 +- arch/arm/mach-msm/Makefile | 6 +- arch/arm/mach-msm/board-htcleo-acoustic.c | 247 ++ arch/arm/mach-msm/board-htcleo-audio.c | 330 +++ arch/arm/mach-msm/board-htcleo.c | 2 + .../mach-msm/include/mach/msm_qdsp6_audio.h | 5 +- arch/arm/mach-msm/qdsp6_1550/Makefile | 11 + arch/arm/mach-msm/qdsp6_1550/aac_in.c | 217 ++ arch/arm/mach-msm/qdsp6_1550/audio_ctl.c | 252 ++ arch/arm/mach-msm/qdsp6_1550/dal.c | 746 +++++ arch/arm/mach-msm/qdsp6_1550/dal.h | 65 + arch/arm/mach-msm/qdsp6_1550/dal_acdb.h | 90 + arch/arm/mach-msm/qdsp6_1550/dal_adie.h | 110 + arch/arm/mach-msm/qdsp6_1550/dal_audio.h | 690 +++++ .../mach-msm/qdsp6_1550/dal_audio_format.h | 285 ++ arch/arm/mach-msm/qdsp6_1550/mp3.c | 229 ++ arch/arm/mach-msm/qdsp6_1550/msm_q6vdec.c | 940 ++++++ arch/arm/mach-msm/qdsp6_1550/msm_q6venc.c | 637 ++++ arch/arm/mach-msm/qdsp6_1550/pcm_in.c | 207 ++ arch/arm/mach-msm/qdsp6_1550/pcm_out.c | 242 ++ arch/arm/mach-msm/qdsp6_1550/q6audio.c | 2634 +++++++++++++++++ .../arm/mach-msm/qdsp6_1550/q6audio_devices.h | 268 ++ arch/arm/mach-msm/qdsp6_1550/qcelp_in.c | 212 ++ arch/arm/mach-msm/qdsp6_1550/routing.c | 71 + include/linux/msm_q6venc_1550.h | 125 + 26 files changed, 8624 insertions(+), 3 deletions(-) create mode 100644 arch/arm/mach-msm/board-htcleo-acoustic.c create mode 100644 arch/arm/mach-msm/board-htcleo-audio.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/Makefile create mode 100644 arch/arm/mach-msm/qdsp6_1550/aac_in.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/audio_ctl.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/dal.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/dal.h create mode 100644 arch/arm/mach-msm/qdsp6_1550/dal_acdb.h create mode 100644 arch/arm/mach-msm/qdsp6_1550/dal_adie.h create mode 100644 arch/arm/mach-msm/qdsp6_1550/dal_audio.h create mode 100644 arch/arm/mach-msm/qdsp6_1550/dal_audio_format.h create mode 100644 arch/arm/mach-msm/qdsp6_1550/mp3.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/msm_q6vdec.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/msm_q6venc.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/pcm_in.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/pcm_out.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/q6audio.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/q6audio_devices.h create mode 100644 arch/arm/mach-msm/qdsp6_1550/qcelp_in.c create mode 100644 arch/arm/mach-msm/qdsp6_1550/routing.c create mode 100755 include/linux/msm_q6venc_1550.h diff --git a/arch/arm/configs/htcleo_defconfig b/arch/arm/configs/htcleo_defconfig index b805ec89..314cbe67 100644 --- a/arch/arm/configs/htcleo_defconfig +++ b/arch/arm/configs/htcleo_defconfig @@ -212,6 +212,7 @@ CONFIG_MSM_DEBUG_UART_NONE=y # # MSM Board Type # +CONFIG_AAT1271_FLASHLIGHT=y CONFIG_MACH_HTCLEO=y # CONFIG_MACH_SWORDFISH is not set # CONFIG_MACH_MAHIMAHI is not set @@ -251,6 +252,9 @@ CONFIG_MSM_RPCSERVERS=y # CONFIG_MSM_CPU_FREQ_SCREEN is not set # CONFIG_MSM_HW3D is not set CONFIG_MSM_QDSP6=y +# CONFIG_HTC_ACOUSTIC is not set +CONFIG_QSD_AUDIO=y +CONFIG_ARCH_MSM_FLASHLIGHT=y # CONFIG_MSM_CLOCK_CTRL_DEBUG is not set CONFIG_WIFI_CONTROL_FUNC=y CONFIG_WIFI_MEM_PREALLOC=y diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig index c62acb9c..d60dc6d1 100644 --- a/arch/arm/mach-msm/Kconfig +++ b/arch/arm/mach-msm/Kconfig @@ -894,7 +894,7 @@ config VIRTUAL_KPANIC_PARTITION config QSD_AUDIO bool "QSD audio" - depends on (ARCH_QSD8X50 && MSM_DALRPC) + depends on (ARCH_QSD8X50) default y help Provides PCM, MP3, and AAC audio playback. diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile index 4ea17310..4e82190f 100644 --- a/arch/arm/mach-msm/Makefile +++ b/arch/arm/mach-msm/Makefile @@ -58,7 +58,11 @@ obj-$(CONFIG_ARCH_MSM7X30) += dal_axi.o obj-$(CONFIG_MSM_ADSP) += qdsp5/ obj-$(CONFIG_MSM_ADSP_COMP) += qdsp5_comp/ obj-$(CONFIG_MSM7KV2_AUDIO) += qdsp5v2/ +ifdef CONFIG_MSM_AMSS_VERSION_1550 +obj-$(CONFIG_QSD_AUDIO) += qdsp6_1550/ +else obj-$(CONFIG_QSD_AUDIO) += qdsp6/ +endif obj-$(CONFIG_MSM_HW3D) += hw3d.o obj-$(CONFIG_PM) += pm.o obj-$(CONFIG_CPU_FREQ) += cpufreq.o @@ -113,7 +117,7 @@ obj-$(CONFIG_MACH_BRAVOC) += board-bravoc-microp.o clock.o obj-$(CONFIG_MACH_HTCLEO) += board-htcleo.o board-htcleo-spi.o board-htcleo-panel.o board-htcleo-keypad.o obj-$(CONFIG_MACH_HTCLEO) += board-htcleo-ts.o board-htcleo-mmc.o ieee754-df.o board-htcleo-power.o -obj-$(CONFIG_MACH_HTCLEO) += board-htcleo-battery.o board-htcleo-log.o +obj-$(CONFIG_MACH_HTCLEO) += board-htcleo-battery.o board-htcleo-log.o board-htcleo-audio.o board-htcleo-acoustic.o obj-$(CONFIG_MACH_HTCLEO) += board-htcleo-bt.o board-htcleo-microp.o board-htcleo-wifi.o obj-$(CONFIG_MACH_HTCLEO) += clock-wince.o diff --git a/arch/arm/mach-msm/board-htcleo-acoustic.c b/arch/arm/mach-msm/board-htcleo-acoustic.c new file mode 100644 index 00000000..2760e847 --- /dev/null +++ b/arch/arm/mach-msm/board-htcleo-acoustic.c @@ -0,0 +1,247 @@ +/* arch/arm/mach-msm/board-htcleo-acoustic.c + * + * Copyright (C) 2010 Cotulla + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "smd_private.h" +#include "dex_comm.h" + +#define ACOUSTIC_IOCTL_MAGIC 'p' +#define ACOUSTIC_UPDATE_ADIE \ + _IOW(ACOUSTIC_IOCTL_MAGIC, 24, unsigned int) + + +#define HTC_ACOUSTIC_TABLE_SIZE (0x20000) + +#define D(fmt, args...) printk(KERN_INFO "htc-acoustic: "fmt, ##args) +#define E(fmt, args...) printk(KERN_ERR "htc-acoustic: "fmt, ##args) + +static uint32_t htc_acoustic_vir_addr; +static struct mutex api_lock; +static struct qsd_acoustic_ops *the_ops; + +void acoustic_register_ops(struct qsd_acoustic_ops *ops) +{ + the_ops = ops; +} + +int turn_mic_bias_on(int on) +{ + D("%s called %d\n", __func__, on); + if (the_ops->enable_mic_bias) + the_ops->enable_mic_bias(on); + return 0; +} +EXPORT_SYMBOL(turn_mic_bias_on); + +int force_headset_speaker_on(int enable) +{ + printk("force_headset_speaker_on((%d)\n", enable); + + if (enable) + dex_audio(0x53); + else + dex_audio(0x54); + + return 0; +} +EXPORT_SYMBOL(force_headset_speaker_on); + +int enable_aux_loopback(uint32_t enable) +{ + printk("enable_aux_loopback(%d)\n", enable); + + if (enable) + dex_audio(0x51); + else + dex_audio(0x52); + + return 0; +} +EXPORT_SYMBOL(enable_aux_loopback); + +int set_aux_gain(int level) +{ + /* struct aux_gain_req { + struct rpc_request_hdr hdr; + int level; + } aux_req; + + D("%s called %d\n", __func__, level); + + if (is_rpc_connect() == -1) + return -1; + + aux_req.level = cpu_to_be32(level); + return msm_rpc_call(endpoint, + ONCRPC_SET_AUX_PGA_GAIN_PROC, + &aux_req, sizeof(aux_req), 5 * HZ);*/ + return 0; +} +EXPORT_SYMBOL(set_aux_gain); + + +static int acoustic_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long pgoff; + int rc = -EINVAL; + size_t size; + + D("mmap\n"); + + mutex_lock(&api_lock); + + size = vma->vm_end - vma->vm_start; + + if (vma->vm_pgoff != 0) { + E("mmap failed: page offset %lx\n", vma->vm_pgoff); + goto done; + } + + if (!htc_acoustic_vir_addr) { + E("mmap failed: smem region not allocated\n"); + rc = -EIO; + goto done; + } + + pgoff = MSM_SHARED_RAM_PHYS + + (htc_acoustic_vir_addr - (uint32_t)MSM_SHARED_RAM_BASE); + pgoff = ((pgoff + 4095) & ~4095); + htc_acoustic_vir_addr = ((htc_acoustic_vir_addr + 4095) & ~4095); + + if (pgoff <= 0) { + E("pgoff wrong. %ld\n", pgoff); + goto done; + } + + if (size <= HTC_ACOUSTIC_TABLE_SIZE) { + pgoff = pgoff >> PAGE_SHIFT; + } else { + E("size > HTC_ACOUSTIC_TABLE_SIZE %d\n", size); + goto done; + } + + vma->vm_flags |= VM_IO | VM_RESERVED; + rc = io_remap_pfn_range(vma, vma->vm_start, pgoff, + size, vma->vm_page_prot); + + if (rc < 0) + E("mmap failed: remap error %d\n", rc); + +done: mutex_unlock(&api_lock); + return rc; +} + +static int acoustic_open(struct inode *inode, struct file *file) +{ + int rc = -EIO; + + D("open\n"); + + mutex_lock(&api_lock); + + if (!htc_acoustic_vir_addr) + { + htc_acoustic_vir_addr = (uint32_t)MSM_SHARED_RAM_BASE + 0xF8000; + } + + rc = 0; + mutex_unlock(&api_lock); + return rc; +} + +static int acoustic_release(struct inode *inode, struct file *file) +{ + D("release\n"); + return 0; +} + +static long acoustic_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rc = 0; + + D("ioctl\n"); + + mutex_lock(&api_lock); + + switch (cmd) { + case ACOUSTIC_UPDATE_ADIE: + { + unsigned data = 0xE5; + + D("ioctl: ACOUSTIC_UPDATE_ADIE called %d.\n", current->pid); + +// CotullaTODO: finish this code. if we need android tables really... + + D("ioctl: done.\n"); + break; + } + default: + E("ioctl: invalid command\n"); + rc = -EINVAL; + } + + mutex_unlock(&api_lock); + return rc; +} + +struct rpc_set_uplink_mute_args { + int mute; +}; + +static struct file_operations acoustic_fops = { + .owner = THIS_MODULE, + .open = acoustic_open, + .release = acoustic_release, + .mmap = acoustic_mmap, + .unlocked_ioctl = acoustic_ioctl, +}; + +static struct miscdevice acoustic_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "htc-acoustic", + .fops = &acoustic_fops, +}; + +static int __init acoustic_init(void) +{ + mutex_init(&api_lock); + return misc_register(&acoustic_misc); +} + +static void __exit acoustic_exit(void) +{ + misc_deregister(&acoustic_misc); +} + +module_init(acoustic_init); +module_exit(acoustic_exit); + diff --git a/arch/arm/mach-msm/board-htcleo-audio.c b/arch/arm/mach-msm/board-htcleo-audio.c new file mode 100644 index 00000000..c92ce2de --- /dev/null +++ b/arch/arm/mach-msm/board-htcleo-audio.c @@ -0,0 +1,330 @@ +/* arch/arm/mach-msm/board-htcleo-audio.c + * + * Copyright (C) 2010 Cotulla + * + * 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 +#include +#include +#include +#include +#include + +#include "board-htcleo.h" +#include "devices.h" +#include "dex_comm.h" +#include "proc_comm.h" +#include "pmic.h" + + +#if 1 +#define D(fmt, args...) printk(KERN_INFO "Audio: "fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + +static struct mutex mic_lock; +static struct mutex bt_sco_lock; + +#if 1 + +// LEO +static struct q6_hw_info q6_audio_hw[Q6_HW_COUNT] = +{ + [Q6_HW_HANDSET] = + { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_HEADSET] = + { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_SPEAKER] = + { + .min_gain = -1000, + .max_gain = 500, + }, + [Q6_HW_TTY] = + { + .min_gain = 0, + .max_gain = 0, + }, + [Q6_HW_BT_SCO] = + { + .min_gain = -1100, + .max_gain = 400, + }, + [Q6_HW_BT_A2DP] = + { + .min_gain = -1100, + .max_gain = 400, + }, +}; + +#else + +// old desire one +static struct q6_hw_info q6_audio_hw[Q6_HW_COUNT] = +{ + [Q6_HW_HANDSET] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_HEADSET] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_SPEAKER] = { + .min_gain = -1500, + .max_gain = 0, + }, + [Q6_HW_TTY] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_BT_SCO] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_BT_A2DP] = { + .min_gain = -2000, + .max_gain = 0, + }, +}; + +#endif + +void htcleo_headset_enable(int en) +{ + D("%s %d\n", __func__, en); + /* enable audio amp */ + if (en) mdelay(60); + gpio_set_value(HTCLEO_AUD_JACKHP_EN, !!en); +} + +void htcleo_speaker_enable(int en) +{ + struct spkr_config_mode scm; + memset(&scm, 0, sizeof(scm)); + + D("%s %d\n", __func__, en); + if (en) + { + scm.is_right_chan_en = 0; + scm.is_left_chan_en = 1; + scm.is_stereo_en = 0; + scm.is_hpf_en = 0; //1; // CotullaTODO: check it + pmic_spkr_en_mute(LEFT_SPKR, 0); + pmic_spkr_en_mute(RIGHT_SPKR, 0); + pmic_set_spkr_configuration(&scm); + pmic_spkr_set_mux_hpf_corner_freq(SPKR_FREQ_0_76KHZ); + pmic_spkr_en(LEFT_SPKR, 1); + pmic_spkr_en(RIGHT_SPKR, 0); + + pmic_spkr_en_hpf(ON_CMD); // +LEO + + /* unmute */ + pmic_spkr_en_mute(LEFT_SPKR, 1); + mdelay(40); + } + else + { + pmic_spkr_en_mute(LEFT_SPKR, 0); + + pmic_spkr_en_hpf(OFF_CMD); // +LEO + pmic_spkr_en(LEFT_SPKR, 0); + pmic_spkr_en(RIGHT_SPKR, 0); + + pmic_set_spkr_configuration(&scm); + } +} + +void htcleo_receiver_enable(int en) +{ +// if (is_cdma_version(system_rev) && +// ((system_rev == 0xC1) || (system_rev == 0xC2))) { + struct spkr_config_mode scm; + memset(&scm, 0, sizeof(scm)); + + D("%s %d\n", __func__, en); + if (en) + { + scm.is_right_chan_en = 1; + scm.is_left_chan_en = 0; + scm.is_stereo_en = 0; + scm.is_hpf_en = 1; + pmic_spkr_en_mute(RIGHT_SPKR, 0); + pmic_set_spkr_configuration(&scm); + pmic_spkr_en(RIGHT_SPKR, 1); + + /* unmute */ + pmic_spkr_en_mute(RIGHT_SPKR, 1); + } + else + { + pmic_spkr_en_mute(RIGHT_SPKR, 0); + + pmic_spkr_en(RIGHT_SPKR, 0); + + pmic_set_spkr_configuration(&scm); + + } + // } +} + +static uint32_t bt_sco_enable[] = +{ + PCOM_GPIO_CFG(HTCLEO_BT_PCM_OUT, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(HTCLEO_BT_PCM_IN, 1, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(HTCLEO_BT_PCM_SYNC, 2, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(HTCLEO_BT_PCM_CLK, 2, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA) +}; + +static uint32_t bt_sco_disable[] = +{ + PCOM_GPIO_CFG(HTCLEO_BT_PCM_OUT, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(HTCLEO_BT_PCM_IN, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(HTCLEO_BT_PCM_SYNC, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(HTCLEO_BT_PCM_CLK, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA) +}; + +void htcleo_bt_sco_enable(int en) +{ + static int bt_sco_refcount; + D("%s %d\n", __func__, en); + + mutex_lock(&bt_sco_lock); + if (en) + { + if (++bt_sco_refcount == 1) + { + config_gpio_table(bt_sco_enable, ARRAY_SIZE(bt_sco_enable)); + } + } + else + { + if (--bt_sco_refcount == 0) + { + config_gpio_table(bt_sco_disable, ARRAY_SIZE(bt_sco_disable)); + gpio_set_value(HTCLEO_BT_PCM_OUT, 0); + } + } + mutex_unlock(&bt_sco_lock); +} + +void htcleo_mic_enable(int en) +{ + static int old_state = 0, new_state = 0; + + D("%s %d\n", __func__, en); + + mutex_lock(&mic_lock); + if (!!en) + new_state++; + else + new_state--; + + if (new_state == 1 && old_state == 0) + { + gpio_set_value(HTCLEO_AUD_2V5_EN, 1); + mdelay(60); + } + else if (new_state == 0 && old_state == 1) + { + gpio_set_value(HTCLEO_AUD_2V5_EN, 0); + } + else + { + D("%s: do nothing %d %d\n", __func__, old_state, new_state); + } + + old_state = new_state; + mutex_unlock(&mic_lock); +} + + +void htcleo_analog_init(void) +{ + D("%s\n", __func__); + /* stereo pmic init */ + pmic_spkr_set_gain(LEFT_SPKR, SPKR_GAIN_PLUS12DB); + pmic_spkr_set_gain(RIGHT_SPKR, SPKR_GAIN_PLUS12DB); + pmic_spkr_en_right_chan(OFF_CMD); + pmic_spkr_en_left_chan(OFF_CMD); + pmic_spkr_add_right_left_chan(OFF_CMD); + pmic_spkr_en_stereo(OFF_CMD); + pmic_spkr_select_usb_with_hpf_20hz(OFF_CMD); + pmic_spkr_bypass_mux(OFF_CMD); + pmic_spkr_en_hpf(OFF_CMD); + pmic_spkr_en_sink_curr_from_ref_volt_cir(OFF_CMD); + pmic_spkr_set_mux_hpf_corner_freq(SPKR_FREQ_0_76KHZ); + pmic_mic_set_volt(MIC_VOLT_1_80V); + + gpio_request(HTCLEO_AUD_JACKHP_EN, "aud_jackhp_en"); + gpio_request(HTCLEO_BT_PCM_OUT, "bt_pcm_out"); + + gpio_direction_output(HTCLEO_AUD_JACKHP_EN, 0); + + mutex_lock(&bt_sco_lock); + config_gpio_table(bt_sco_disable, ARRAY_SIZE(bt_sco_disable)); + gpio_direction_output(HTCLEO_BT_PCM_OUT, 0); + mutex_unlock(&bt_sco_lock); +} + +int htcleo_get_rx_vol(uint8_t hw, int level) +{ + int vol; + + if (level > 100) + level = 100; + else if (level < 0) + level = 0; + + // TODO: is it correct? + struct q6_hw_info *info; + info = &q6_audio_hw[hw]; + vol = info->min_gain + ((info->max_gain - info->min_gain) * level) / 100; + + D("%s %d\n", __func__, vol); + return vol; +} + +static struct qsd_acoustic_ops acoustic = +{ + .enable_mic_bias = htcleo_mic_enable, +}; + +static struct q6audio_analog_ops ops = +{ + .init = htcleo_analog_init, + .speaker_enable = htcleo_speaker_enable, + .headset_enable = htcleo_headset_enable, + .receiver_enable = htcleo_receiver_enable, + .bt_sco_enable = htcleo_bt_sco_enable, + .int_mic_enable = htcleo_mic_enable, + .ext_mic_enable = htcleo_mic_enable, + .get_rx_vol = htcleo_get_rx_vol, +}; + +void __init htcleo_audio_init(void) +{ + mutex_init(&mic_lock); + mutex_init(&bt_sco_lock); + q6audio_register_analog_ops(&ops); + acoustic_register_ops(&acoustic); +// q6audio_set_acdb_file("default_PMIC.acdb"); +} + +// END OF FILE diff --git a/arch/arm/mach-msm/board-htcleo.c b/arch/arm/mach-msm/board-htcleo.c index 8db5c7ec..7c25b212 100644 --- a/arch/arm/mach-msm/board-htcleo.c +++ b/arch/arm/mach-msm/board-htcleo.c @@ -58,6 +58,7 @@ void __init htcleo_microp_init(void); #endif extern int __init htcleo_init_mmc(unsigned debug_uart); +extern void __init htcleo_audio_init(void); /////////////////////////////////////////////////////////////////////// // SPI @@ -740,6 +741,7 @@ static void __init htcleo_init(void) platform_device_register(&htcleo_timed_gpios); htcleo_bt_init(); + htcleo_audio_init(); #ifdef CONFIG_USB_ANDROID msm_hsusb_set_vbus_state(htcleo_get_vbus_state()); diff --git a/arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h b/arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h index ca5e735e..4f3c4a5b 100644 --- a/arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h +++ b/arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h @@ -33,7 +33,9 @@ struct audio_client { int dsp_buf; /* next buffer the DSP will touch */ int running; int session; - + + int open_done; + int open_status; wait_queue_head_t wait; struct dal_client *client; @@ -91,6 +93,7 @@ int q6audio_set_tx_mute(int mute); int q6audio_reinit_acdb(char* filename); int q6audio_update_acdb(uint32_t id_src, uint32_t id_dst); int q6audio_set_rx_volume(int level); +int q6audio_set_rx_mute(int mute); int q6audio_set_stream_volume(struct audio_client *ac, int vol); struct q6audio_analog_ops { diff --git a/arch/arm/mach-msm/qdsp6_1550/Makefile b/arch/arm/mach-msm/qdsp6_1550/Makefile new file mode 100644 index 00000000..361c9e60 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/Makefile @@ -0,0 +1,11 @@ +obj-y += dal.o +obj-y += q6audio.o +obj-y += pcm_out.o +obj-y += pcm_in.o +obj-y += mp3.o +obj-y += routing.o +obj-y += audio_ctl.o +obj-y += msm_q6vdec.o +obj-y += msm_q6venc.o +obj-y += aac_in.o +obj-y += qcelp_in.o diff --git a/arch/arm/mach-msm/qdsp6_1550/aac_in.c b/arch/arm/mach-msm/qdsp6_1550/aac_in.c new file mode 100644 index 00000000..5c7042bc --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/aac_in.c @@ -0,0 +1,217 @@ +/* arch/arm/mach-msm/qdsp6/aac_in.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSZ (4096) +#define DMASZ (BUFSZ * 2) + +#if 0 +#define TRACE(x...) pr_info("Q6: "x) +#else +#define TRACE(x...) do{}while(0) +#endif + +static DEFINE_MUTEX(aac_in_lock); +static int aac_in_opened = 0; +static struct aac_format *af; + +void audio_client_dump(struct audio_client *ac); + +static long aac_in_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + + switch (cmd) { + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STATS: { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + case AUDIO_START: { + uint32_t acdb_id; + rc = 0; + + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, + (void*) arg, sizeof(acdb_id))) { + rc = -EFAULT; + break; + } + + mutex_lock(&aac_in_lock); + if (file->private_data) { + rc = -EBUSY; + } else { + file->private_data = q6audio_open_aac( + BUFSZ, 48000, AUDIO_FLAG_READ, af, acdb_id); + if (!file->private_data) + rc = -ENOMEM; + } + mutex_unlock(&aac_in_lock); + break; + } + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void*) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.sample_rate != 48000) + pr_info("only 48KHz AAC encode supported\n"); + af->channel_config = config.channel_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = 48000; + config.channel_count = af->channel_config; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void*) arg, &config, sizeof(config))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + return rc; +} + +static int aac_in_open(struct inode *inode, struct file *file) +{ + int rc; + + pr_info("aac_in: open\n"); + mutex_lock(&aac_in_lock); + if (aac_in_opened) { + pr_err("aac_in: busy\n"); + rc = -EBUSY; + } else { + af = kzalloc(sizeof(*af), GFP_KERNEL); + memset(af, 0, sizeof(struct aac_format)); + af->sample_rate = 3; /* 48000 */ + af->channel_config = 1; + af->block_formats = AUDIO_AAC_FORMAT_ADTS; + af->audio_object_type = 2; /* CAD to ADSP format */ + af->bit_rate = 192000; + + aac_in_opened = 1; + rc = 0; + } + mutex_unlock(&aac_in_lock); + return rc; +} + +static ssize_t aac_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer, res = 0; + + mutex_lock(&aac_in_lock); + ac = file->private_data; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + if (!wait_event_timeout(ac->wait, (ab->used == 0), 5*HZ)) { + audio_client_dump(ac); + pr_err("aac_read: timeout. dsp dead?\n"); + BUG(); + } + + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } +fail: + res = buf - start; + mutex_unlock(&aac_in_lock); + return res; +} + +static int aac_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + pr_info("aac_in: release\n"); + mutex_lock(&aac_in_lock); + if (file->private_data) + rc = q6audio_close(file->private_data); + kfree(af); + aac_in_opened = 0; + mutex_unlock(&aac_in_lock); + return rc; +} + +static struct file_operations aac_in_fops = { + .owner = THIS_MODULE, + .open = aac_in_open, + .read = aac_in_read, + .release = aac_in_release, + .unlocked_ioctl = aac_in_ioctl, +}; + +struct miscdevice aac_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac_in", + .fops = &aac_in_fops, +}; + +static int __init aac_in_init(void) { + return misc_register(&aac_in_misc); +} + +device_initcall(aac_in_init); diff --git a/arch/arm/mach-msm/qdsp6_1550/audio_ctl.c b/arch/arm/mach-msm/qdsp6_1550/audio_ctl.c new file mode 100644 index 00000000..8606f183 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/audio_ctl.c @@ -0,0 +1,252 @@ +/* arch/arm/mach-msm/qdsp6/audio_ctrl.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 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 +#include +#include +#include +#include + +#include +#include + +#define BUFSZ (0) + +#if 1 +#define AUDIO_INFO(x...) pr_info("Audio: "x) +#else +#define AUDIO_INFO(x...) do{}while(0) +#endif + +// from board-htcleo-accoustic +extern int set_aux_gain(int level); + +static DEFINE_MUTEX(voice_lock); +static DEFINE_MUTEX(fm_lock); +static int voice_started; +static int fm_started; +int global_now_phone_call; + +static struct audio_client *voc_tx_clnt; +static struct audio_client *voc_rx_clnt; +static struct audio_client *fm_clnt; + +static int q6_voice_start(uint32_t rx_acdb_id, uint32_t tx_acdb_id) +{ + int rc = 0; + + printk("VOICE START (%d %d)\n", rx_acdb_id, tx_acdb_id); + mutex_lock(&voice_lock); + + if (voice_started) { + pr_err("voice: busy\n"); + rc = -EBUSY; + goto done; + } + global_now_phone_call = 1; + + voc_rx_clnt = q6voice_open(AUDIO_FLAG_WRITE, rx_acdb_id); + if (!voc_rx_clnt) { + pr_err("voice: open voice rx failed.\n"); + rc = -ENOMEM; + goto done; + } + + voc_tx_clnt = q6voice_open(AUDIO_FLAG_READ, tx_acdb_id); + if (!voc_tx_clnt) { + pr_err("voice: open voice tx failed.\n"); + q6voice_close(voc_rx_clnt); + rc = -ENOMEM; + } + + voice_started = 1; +done: + mutex_unlock(&voice_lock); + return rc; +} + +static int q6_voice_stop(void) +{ + mutex_lock(&voice_lock); + global_now_phone_call = 0; + if (voice_started) + { + q6voice_close(voc_tx_clnt); + q6voice_close(voc_rx_clnt); + voice_started = 0; + } + mutex_unlock(&voice_lock); + return 0; +} + +static int q6_fm_start(void) +{ + int rc = 0; + + mutex_lock(&fm_lock); + + if (fm_started) { + pr_err("fm: busy\n"); + rc = -EBUSY; + goto done; + } + + fm_clnt = q6fm_open(); + if (!fm_clnt) { + pr_err("fm: open failed.\n"); + rc = -ENOMEM; + goto done; + } + + fm_started = 1; +done: + mutex_unlock(&fm_lock); + return rc; +} + +static int q6_fm_stop(void) +{ + mutex_lock(&fm_lock); + if (fm_started) { + q6fm_close(fm_clnt); + fm_started = 0; + } + mutex_unlock(&fm_lock); + return 0; +} + +static int q6_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int q6_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int rc; + uint32_t n; + uint32_t id[2]; + char filename[64]; + +// printk("$$$ AUDIO IOCTL=%08X\n", cmd); + + switch (cmd) { + case AUDIO_SWITCH_DEVICE: + rc = copy_from_user(&id, (void *)arg, sizeof(id)); + AUDIO_INFO("SWITCH DEVICE %d, acdb %d\n", id[0], id[1]); + if (!rc) + rc = q6audio_do_routing(id[0], id[1]); + break; + case AUDIO_SET_VOLUME: + rc = copy_from_user(&n, (void *)arg, sizeof(n)); + if (!rc) + rc = q6audio_set_rx_volume(n); + break; + case AUDIO_SET_MUTE: + rc = copy_from_user(&n, (void *)arg, sizeof(n)); + if (!rc) + rc = q6audio_set_tx_mute(n); + break; + case AUDIO_UPDATE_ACDB: + rc = copy_from_user(&id, (void *)arg, sizeof(id)); + if (!rc) + rc = q6audio_update_acdb(id[0], id[1]); + break; + case AUDIO_START_VOICE: + if (arg == 0) { + id[0] = id[1] = 0; + } else if (copy_from_user(&id, (void*) arg, sizeof(id))) { + pr_info("voice: copy acdb_id from user failed\n"); + rc = -EFAULT; + break; + } + AUDIO_INFO("voice: start\n"); + rc = q6_voice_start(id[0], id[1]); + break; + case AUDIO_STOP_VOICE: + AUDIO_INFO("voice: stop\n"); + rc = q6_voice_stop(); + break; + case AUDIO_START_FM: + AUDIO_INFO("FM: start\n"); + rc = q6_fm_start(); + break; + case AUDIO_STOP_FM: + AUDIO_INFO("FM: stop\n"); + rc = q6_fm_stop(); + break; + case AUDIO_REINIT_ACDB: + rc = copy_from_user(&filename, (void *)arg, sizeof(filename)); + if (!rc) + rc = q6audio_reinit_acdb(filename); + break; + case AUDIO_ENABLE_AUXPGA_LOOPBACK: { + uint32_t enable; + if (copy_from_user(&enable, (void*) arg, sizeof(enable))) { + rc = -EFAULT; + break; + } + AUDIO_INFO("audio_ctl: enable aux loopback %d\n", enable); + rc = enable_aux_loopback(enable); + break; + } + case AUDIO_SET_AUXPGA_GAIN: { + int level; + if (copy_from_user(&level, (void*) arg, sizeof(level))) { + rc = -EFAULT; + break; + } + AUDIO_INFO("audio_ctl: set aux gain %d\n", level); + rc = set_aux_gain(level); + break; + } + case AUDIO_SET_RX_MUTE: + rc = copy_from_user(&n, (void *)arg, sizeof(n)); + if (!rc) + rc = q6audio_set_rx_mute(n); + break; + default: + rc = -EINVAL; + } + + return rc; +} + + +static int q6_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations q6_dev_fops = { + .owner = THIS_MODULE, + .open = q6_open, + .ioctl = q6_ioctl, + .release = q6_release, +}; + +struct miscdevice q6_control_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_audio_ctl", + .fops = &q6_dev_fops, +}; + + +static int __init q6_audio_ctl_init(void) { + return misc_register(&q6_control_device); +} + +device_initcall(q6_audio_ctl_init); diff --git a/arch/arm/mach-msm/qdsp6_1550/dal.c b/arch/arm/mach-msm/qdsp6_1550/dal.c new file mode 100644 index 00000000..d05ccfb0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/dal.c @@ -0,0 +1,746 @@ +/* arch/arm/mach-msm/qdsp6/dal.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "dal.h" + +#define DAL_TRACE 0 + +struct dal_hdr { + uint32_t length:16; /* message length (header inclusive) */ + uint32_t version:8; /* DAL protocol version */ + uint32_t priority:7; + uint32_t async:1; + uint32_t ddi:16; /* DDI method number */ + uint32_t prototype:8; /* DDI serialization format */ + uint32_t msgid:8; /* message id (DDI, ATTACH, DETACH, ...) */ + void *from; + void *to; +} __attribute__((packed)); + +#define TRACE_DATA_MAX 128 +#define TRACE_LOG_MAX 32 +#define TRACE_LOG_MASK (TRACE_LOG_MAX - 1) + +struct dal_trace { + unsigned timestamp; + struct dal_hdr hdr; + uint32_t data[TRACE_DATA_MAX]; +}; + +#define DAL_HDR_SIZE (sizeof(struct dal_hdr)) +#define DAL_DATA_MAX 512 +#define DAL_MSG_MAX (DAL_HDR_SIZE + DAL_DATA_MAX) + +#define DAL_VERSION 0x11 + +#define DAL_MSGID_DDI 0x00 +#define DAL_MSGID_ATTACH 0x01 +#define DAL_MSGID_DETACH 0x02 +#define DAL_MSGID_ASYNCH 0xC0 +#define DAL_MSGID_REPLY 0x80 + +struct dal_channel { + struct list_head list; + struct list_head clients; + + /* synchronization for changing channel state, + * adding/removing clients, smd callbacks, etc + */ + spinlock_t lock; + + struct smd_channel *sch; + char *name; + + /* events are delivered at IRQ context immediately, so + * we only need one assembly buffer for the entire channel + */ + struct dal_hdr hdr; + unsigned char data[DAL_DATA_MAX]; + + unsigned count; + void *ptr; + + /* client which the current inbound message is for */ + struct dal_client *active; +}; + +struct dal_client { + struct list_head list; + struct dal_channel *dch; + void *cookie; + dal_event_func_t event; + + /* opaque handle for the far side */ + void *remote; + + /* dal rpc calls are fully synchronous -- only one call may be + * active per client at a time + */ + struct mutex write_lock; + wait_queue_head_t wait; + + unsigned char data[DAL_DATA_MAX]; + + void *reply; + int reply_max; + int status; + unsigned msgid; /* msgid of expected reply */ + + spinlock_t tr_lock; + unsigned tr_head; + unsigned tr_tail; + struct dal_trace *tr_log; +}; + +static unsigned now(void) +{ + struct timespec ts; + ktime_get_ts(&ts); + return (ts.tv_nsec / 1000000) + (ts.tv_sec * 1000); +} + +void dal_trace(struct dal_client *c) +{ + if (c->tr_log) + return; + c->tr_log = kzalloc(sizeof(struct dal_trace) * TRACE_LOG_MAX, + GFP_KERNEL); +} + +void dal_trace_print(struct dal_hdr *hdr, unsigned *data, int len, unsigned when) +{ + int i; + printk("DAL %08x -> %08x L=%03x A=%d D=%04x P=%02x M=%02x T=%d", + (unsigned) hdr->from, (unsigned) hdr->to, + hdr->length, hdr->async, + hdr->ddi, hdr->prototype, hdr->msgid, + when); + len /= 4; + for (i = 0; i < len; i++) { + if (!(i & 7)) + printk("\n%03x", i * 4); + printk(" %08x", data[i]); + } + printk("\n"); +} + +void dal_trace_dump(struct dal_client *c) +{ + struct dal_trace *dt; + unsigned n, len; + + if (!c->tr_log) + return; + + for (n = c->tr_tail; n != c->tr_head; n = (n + 1) & TRACE_LOG_MASK) { + dt = c->tr_log + n; + len = dt->hdr.length; + if (len > TRACE_DATA_MAX) + len = TRACE_DATA_MAX; + dal_trace_print(&dt->hdr, dt->data, len, dt->timestamp); + } +} + +static void dal_trace_log(struct dal_client *c, + struct dal_hdr *hdr, void *data, unsigned len) +{ + unsigned long flags; + unsigned t, n; + struct dal_trace *dt; + + t = now(); + if (len > TRACE_DATA_MAX) + len = TRACE_DATA_MAX; + + spin_lock_irqsave(&c->tr_lock, flags); + n = (c->tr_head + 1) & TRACE_LOG_MASK; + if (c->tr_tail == n) + c->tr_tail = (c->tr_tail + 1) & TRACE_LOG_MASK; + dt = c->tr_log + n; + dt->timestamp = t; + memcpy(&dt->hdr, hdr, sizeof(struct dal_hdr)); + memcpy(dt->data, data, len); + c->tr_head = n; + + spin_unlock_irqrestore(&c->tr_lock, flags); +} + + +static void dal_channel_notify(void *priv, unsigned event) +{ + struct dal_channel *dch = priv; + struct dal_hdr *hdr = &dch->hdr; + struct dal_client *client; + unsigned long flags; + int len; + int r; + + spin_lock_irqsave(&dch->lock, flags); + +again: + if (dch->count == 0) { + if (smd_read_avail(dch->sch) < DAL_HDR_SIZE) + goto done; + + smd_read(dch->sch, hdr, DAL_HDR_SIZE); + + if (hdr->length < DAL_HDR_SIZE) + goto done; + + if (hdr->length > DAL_MSG_MAX) + panic("oversize message"); + + dch->count = hdr->length - DAL_HDR_SIZE; + + /* locate the client this message is targeted to */ + list_for_each_entry(client, &dch->clients, list) { + if (dch->hdr.to == client) { + dch->active = client; + dch->ptr = client->data; + goto check_data; + } + } + pr_err("$$$ receiving unknown message len = %d $$$\n", + dch->count); + dch->active = 0; + dch->ptr = dch->data; + } + +check_data: + len = dch->count; + if (len > 0) { + if (smd_read_avail(dch->sch) < len) + goto done; + + r = smd_read(dch->sch, dch->ptr, len); + if (r != len) + panic("invalid read"); + +#if DAL_TRACE + pr_info("dal recv %p <- %p %02x:%04x:%02x %d\n", + hdr->to, hdr->from, hdr->msgid, hdr->ddi, + hdr->prototype, hdr->length - sizeof(*hdr)); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, dch->ptr, len); +#endif + dch->count = 0; + + client = dch->active; + if (!client) { + pr_err("dal: message to %p discarded\n", dch->hdr.to); + goto again; + } + + if (client->tr_log) + dal_trace_log(client, hdr, dch->ptr, len); + + if (hdr->msgid == DAL_MSGID_ASYNCH) { + if (client->event) + client->event(dch->ptr, len, client->cookie); + else + pr_err("dal: client %p has no event handler\n", client); + goto again; + } + + if (hdr->msgid == client->msgid) { + if (!client->remote) + client->remote = hdr->from; + if (len > client->reply_max) + len = client->reply_max; + memcpy(client->reply, client->data, len); + client->status = len; + wake_up(&client->wait); + goto again; + } + + pr_err("dal: cannot find client %p\n", dch->hdr.to); + goto again; + } + +done: + spin_unlock_irqrestore(&dch->lock, flags); +} + +static LIST_HEAD(dal_channel_list); +static DEFINE_MUTEX(dal_channel_list_lock); + +static struct dal_channel *dal_open_channel(const char *name) +{ + struct dal_channel *dch; + + /* quick sanity check to avoid trying to talk to + * some non-DAL channel... + */ + if (strncmp(name, "DSP_DAL", 7) && strncmp(name, "SMD_DAL", 7)) + return 0; + + mutex_lock(&dal_channel_list_lock); + + list_for_each_entry(dch, &dal_channel_list, list) { + if (!strcmp(dch->name, name)) + goto found_it; + } + + dch = kzalloc(sizeof(*dch) + strlen(name) + 1, GFP_KERNEL); + if (!dch) + goto fail; + + dch->name = (char *) (dch + 1); + strcpy(dch->name, name); + spin_lock_init(&dch->lock); + INIT_LIST_HEAD(&dch->clients); + + list_add(&dch->list, &dal_channel_list); + +found_it: + if (!dch->sch) { + if (smd_open(name, &dch->sch, dch, dal_channel_notify)) + dch = NULL; + /* FIXME: wait for channel to open before returning */ + msleep(100); + } + +fail: + mutex_unlock(&dal_channel_list_lock); + + return dch; +} + +int dal_call_raw(struct dal_client *client, + struct dal_hdr *hdr, + void *data, int data_len, + void *reply, int reply_max) +{ + struct dal_channel *dch = client->dch; + unsigned long flags; + + client->reply = reply; + client->reply_max = reply_max; + client->msgid = hdr->msgid | DAL_MSGID_REPLY; + client->status = -EBUSY; + +#if DAL_TRACE + pr_info("dal send %p -> %p %02x:%04x:%02x %d\n", + hdr->from, hdr->to, hdr->msgid, hdr->ddi, + hdr->prototype, hdr->length - sizeof(*hdr)); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, data, data_len); +#endif + + if (client->tr_log) + dal_trace_log(client, hdr, data, data_len); + + spin_lock_irqsave(&dch->lock, flags); + /* FIXME: ensure entire message is written or none. */ + smd_write(dch->sch, hdr, sizeof(*hdr)); + smd_write(dch->sch, data, data_len); + spin_unlock_irqrestore(&dch->lock, flags); + + if (!wait_event_timeout(client->wait, (client->status != -EBUSY), 5*HZ)) + { + dal_trace_dump(client); + pr_err("dal: call timed out. dsp is probably dead.\n"); + dal_trace_print(hdr, data, data_len, 0); +// BUG(); + } + + return client->status; +} + +int dal_call(struct dal_client *client, + unsigned ddi, unsigned prototype, + void *data, int data_len, + void *reply, int reply_max) +{ + struct dal_hdr hdr; + int r; + + memset(&hdr, 0, sizeof(hdr)); + + hdr.length = data_len + sizeof(hdr); + hdr.version = DAL_VERSION; + hdr.msgid = DAL_MSGID_DDI; + hdr.ddi = ddi; + hdr.prototype = prototype; + hdr.from = client; + hdr.to = client->remote; + + if (hdr.length > DAL_MSG_MAX) + return -EINVAL; + + mutex_lock(&client->write_lock); + r = dal_call_raw(client, &hdr, data, data_len, reply, reply_max); + mutex_unlock(&client->write_lock); +#if 1 + if ((r > 3) && (((uint32_t*) reply)[0] == 0)) { + // pr_info("dal call OK\n"); + } else { + pr_info("dal call %d %d ERROR\n", ddi, prototype); + } +#endif + return r; +} + +struct dal_msg_attach { + uint32_t device_id; + char attach[64]; + char service_name[32]; +} __attribute__((packed)); + +struct dal_reply_attach { + uint32_t status; + char name[64]; +}; + + +struct dal_client *dal_attach_ex(uint32_t device_id, const char *aname, const char *name, dal_event_func_t func, void *cookie) +{ + struct dal_hdr hdr; + struct dal_msg_attach msg; + struct dal_reply_attach reply; + struct dal_channel *dch; + struct dal_client *client; + unsigned long flags; + int r; + + dch = dal_open_channel(name); + if (!dch) + return 0; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return 0; + + client->dch = dch; + client->event = func; + client->cookie = cookie; + mutex_init(&client->write_lock); + spin_lock_init(&client->tr_lock); + init_waitqueue_head(&client->wait); + + spin_lock_irqsave(&dch->lock, flags); + list_add(&client->list, &dch->clients); + spin_unlock_irqrestore(&dch->lock, flags); + + memset(&hdr, 0, sizeof(hdr)); + memset(&msg, 0, sizeof(msg)); + + hdr.length = sizeof(hdr) + sizeof(msg); + hdr.version = DAL_VERSION; + hdr.msgid = DAL_MSGID_ATTACH; + hdr.from = client; + msg.device_id = device_id; + if (aname) + strcpy(msg.attach, aname); + + r = dal_call_raw(client, &hdr, &msg, sizeof(msg), + &reply, sizeof(reply)); + + if ((r == sizeof(reply)) && (reply.status == 0)) { + reply.name[63] = 0; + pr_info("dal_attach: status = %d, name = '%s'\n", + reply.status, reply.name); + return client; + } + + pr_err("dal_attach: failure\n"); + + dal_detach(client); + return 0; +} + + + +struct dal_client *dal_attach(uint32_t device_id, const char *name, + dal_event_func_t func, void *cookie) +{ + struct dal_hdr hdr; + struct dal_msg_attach msg; + struct dal_reply_attach reply; + struct dal_channel *dch; + struct dal_client *client; + unsigned long flags; + int r; + + dch = dal_open_channel(name); + if (!dch) + return 0; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return 0; + + client->dch = dch; + client->event = func; + client->cookie = cookie; + mutex_init(&client->write_lock); + spin_lock_init(&client->tr_lock); + init_waitqueue_head(&client->wait); + + spin_lock_irqsave(&dch->lock, flags); + list_add(&client->list, &dch->clients); + spin_unlock_irqrestore(&dch->lock, flags); + + memset(&hdr, 0, sizeof(hdr)); + memset(&msg, 0, sizeof(msg)); + + hdr.length = sizeof(hdr) + sizeof(msg); + hdr.version = DAL_VERSION; + hdr.msgid = DAL_MSGID_ATTACH; + hdr.from = client; + msg.device_id = device_id; + + r = dal_call_raw(client, &hdr, &msg, sizeof(msg), + &reply, sizeof(reply)); + + if ((r == sizeof(reply)) && (reply.status == 0)) { + reply.name[63] = 0; + pr_info("dal_attach: status = %d, name = '%s'\n", + reply.status, reply.name); + return client; + } + + pr_err("dal_attach: failure\n"); + + dal_detach(client); + return 0; +} + +int dal_detach(struct dal_client *client) +{ + struct dal_channel *dch; + unsigned long flags; + + mutex_lock(&client->write_lock); + if (client->remote) { + struct dal_hdr hdr; + uint32_t data; + + memset(&hdr, 0, sizeof(hdr)); + hdr.length = sizeof(hdr) + sizeof(data); + hdr.version = DAL_VERSION; + hdr.msgid = DAL_MSGID_DETACH; + hdr.from = client; + hdr.to = client->remote; + data = (uint32_t) client; + + dal_call_raw(client, &hdr, &data, sizeof(data), + &data, sizeof(data)); + } + + dch = client->dch; + spin_lock_irqsave(&dch->lock, flags); + if (dch->active == client) { + /* We have received a message header for this client + * but not the body of the message. Ensure that when + * the body arrives we don't write it into the now-closed + * client. In *theory* this should never happen. + */ + dch->active = 0; + dch->ptr = dch->data; + } + list_del(&client->list); + spin_unlock_irqrestore(&dch->lock, flags); + + mutex_unlock(&client->write_lock); + + kfree(client); + return 0; +} + +void *dal_get_remote_handle(struct dal_client *client) +{ + return client->remote; +} + +/* convenience wrappers */ + +int dal_call_f0(struct dal_client *client, uint32_t ddi, uint32_t arg1) +{ + uint32_t tmp = arg1; + int res; + res = dal_call(client, ddi, 0, &tmp, sizeof(tmp), &tmp, sizeof(tmp)); + if (res >= 4) + return (int) tmp; + return res; +} + +int dal_call_f1(struct dal_client *client, uint32_t ddi, uint32_t arg1, uint32_t arg2) +{ + uint32_t tmp[2]; + int res; + tmp[0] = arg1; + tmp[1] = arg2; + res = dal_call(client, ddi, 1, tmp, sizeof(tmp), tmp, sizeof(uint32_t)); + if (res >= 4) + return (int) tmp[0]; + return res; +} + +int dal_call_f5(struct dal_client *client, uint32_t ddi, void *ibuf, uint32_t ilen) +{ +// uint32_t tmp[128]; + uint32_t tmp[DAL_DATA_MAX]; + int res; + int param_idx = 0; + + if (ilen + 4 > DAL_DATA_MAX) + return -EINVAL; + + tmp[param_idx] = ilen; + param_idx++; + + memcpy(&tmp[param_idx], ibuf, ilen); + param_idx += DIV_ROUND_UP(ilen, 4); + + res = dal_call(client, ddi, 5, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + return (int) tmp[0]; + return res; +} + +int dal_call_f6(struct dal_client *client, uint32_t ddi, uint32_t cmd, void *ibuf, uint32_t ilen) +{ + uint32_t tmp[DAL_DATA_MAX]; + int res; + int param_idx = 0; + + if (ilen + 4 > DAL_DATA_MAX) + return -EINVAL; + + tmp[param_idx] = cmd; + param_idx++; + tmp[param_idx] = ilen; + param_idx++; + + memcpy(&tmp[param_idx], ibuf, ilen); + param_idx += DIV_ROUND_UP(ilen, 4); + + res = dal_call(client, ddi, 6, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + return (int) tmp[0]; + return res; +} + +int dal_call_f8(struct dal_client *client, uint32_t ddi, void *ibuf, uint32_t ilen, void *obuf, uint32_t olen) +{ + uint32_t tmp[128]; + int res; + int param_idx = 0; + + if (ilen + 8 > DAL_DATA_MAX) + return -EINVAL; + + tmp[param_idx] = ilen; + param_idx++; + + memcpy(&tmp[param_idx], ibuf, ilen); + param_idx += DIV_ROUND_UP(ilen, 4); + + tmp[param_idx++] = olen; + res = dal_call(client, ddi, 8, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + { + res = (int)tmp[0]; + } + + if (!res) + { + if (tmp[1] > olen) + return -EIO; + memcpy(obuf, &tmp[2], tmp[1]); + } + return res; +} + +int dal_call_f9(struct dal_client *client, uint32_t ddi, void *obuf, uint32_t olen) +{ + uint32_t tmp[128]; + int res; + int param_idx = 0; + + if (olen + 8 > DAL_DATA_MAX) + return -EINVAL; + + tmp[param_idx++] = olen; + res = dal_call(client, ddi, 9, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + { + res = (int)tmp[0]; + } + + if (!res) + { + if (tmp[1] > olen) + return -EIO; + memcpy(obuf, &tmp[2], tmp[1]); + } + return res; +} + + + + +int dal_call_f13(struct dal_client *client, uint32_t ddi, void *ibuf1, + uint32_t ilen1, void *ibuf2, uint32_t ilen2, void *obuf, uint32_t olen) +{ + uint32_t tmp[128]; + int res; + int param_idx = 0; + + if (ilen1 + ilen2 + 8 > DAL_DATA_MAX) + return -EINVAL; + + tmp[param_idx] = ilen1; + param_idx++; + + memcpy(&tmp[param_idx], ibuf1, ilen1); + param_idx += DIV_ROUND_UP(ilen1, 4); + + tmp[param_idx++] = ilen2; + memcpy(&tmp[param_idx], ibuf2, ilen2); + param_idx += DIV_ROUND_UP(ilen2, 4); + + tmp[param_idx++] = olen; + res = dal_call(client, ddi, 13, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + res = (int)tmp[0]; + + if (!res) { + if (tmp[1] > olen) + return -EIO; + memcpy(obuf, &tmp[2], tmp[1]); + } + return res; +} + + +// END OF FILE diff --git a/arch/arm/mach-msm/qdsp6_1550/dal.h b/arch/arm/mach-msm/qdsp6_1550/dal.h new file mode 100644 index 00000000..f7cd6dd0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/dal.h @@ -0,0 +1,65 @@ +/* arch/arm/mach-msm/qdsp6/dal.h + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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. + * + */ + +#ifndef _MACH_MSM_DAL_ +#define _MACH_MSM_DAL_ + +struct dal_client; + +typedef void (*dal_event_func_t)(void *data, int len, void *cookie); + +struct dal_client *dal_attach(uint32_t device_id, const char *name, dal_event_func_t func, void *cookie); +struct dal_client *dal_attach_ex(uint32_t device_id, const char *aname, const char *name, dal_event_func_t func, void *cookie); + +int dal_detach(struct dal_client *client); + +int dal_call(struct dal_client *client, + unsigned ddi, unsigned prototype, + void *data, int data_len, + void *reply, int reply_max); + +void dal_trace(struct dal_client *client); +void dal_trace_dump(struct dal_client *client); + +/* function to call before panic on stalled dal calls */ +void dal_set_oops(struct dal_client *client, void (*oops)(void)); + +/* convenience wrappers */ +int dal_call_f0(struct dal_client *client, uint32_t ddi, uint32_t arg1); +int dal_call_f1(struct dal_client *client, uint32_t ddi, uint32_t arg1, uint32_t arg2); +int dal_call_f5(struct dal_client *client, uint32_t ddi, void *ibuf, uint32_t ilen); +int dal_call_f6(struct dal_client *client, uint32_t ddi, uint32_t cmd, void *ibuf, uint32_t ilen); +int dal_call_f8(struct dal_client *client, uint32_t ddi, void *ibuf, uint32_t ilen, void *obuf, uint32_t olen); +int dal_call_f9(struct dal_client *client, uint32_t ddi, void *obuf, uint32_t olen); + +int dal_call_f13(struct dal_client *client, uint32_t ddi, void *ibuf1, + uint32_t ilen1, void *ibuf2, uint32_t ilen2, void *obuf, uint32_t olen); + +/* common DAL operations */ +enum { + DAL_OP_ATTACH = 0, + DAL_OP_DETACH, + DAL_OP_INIT, + DAL_OP_DEINIT, + DAL_OP_OPEN, + DAL_OP_CLOSE, + DAL_OP_INFO, + DAL_OP_POWEREVENT, + DAL_OP_SYSREQUEST, + DAL_OP_FIRST_DEVICE_API, +}; + +#endif diff --git a/arch/arm/mach-msm/qdsp6_1550/dal_acdb.h b/arch/arm/mach-msm/qdsp6_1550/dal_acdb.h new file mode 100644 index 00000000..74f6fd5f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/dal_acdb.h @@ -0,0 +1,90 @@ +/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora Forum nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#define ACDB_DAL_DEVICE 0x02000069 +//#define ACDB_DAL_PORT "SMD_DAL_AM_AUD" +#define ACDB_DAL_PORT "SMD_DAL00" + +#define ACDB_OP_IOCTL DAL_OP_FIRST_DEVICE_API + +/* ioctls */ +#define ACDB_GET_DEVICE 0x0108bb92 +#define ACDB_SET_DEVICE 0x0108bb93 +#define ACDB_GET_STREAM 0x0108bb95 +#define ACDB_SET_STREAM 0x0108bb96 +#define ACDB_GET_DEVICE_TABLE 0x0108bb97 +#define ACDB_GET_STREAM_TABLE 0x0108bb98 + +#define ACDB_RES_SUCCESS 0 +#define ACDB_RES_FAILURE -1 +#define ACDB_RES_BADPARM -2 +#define ACDB_RES_BADSTATE -3 + +struct acdb_cmd_device +{ + uint32_t size; + + uint32_t command_id; + uint32_t device_id; + uint32_t network_id; + uint32_t sample_rate_id; + uint32_t interface_id; + uint32_t algorithm_block_id; + + /* physical page aligned buffer */ + uint32_t total_bytes; + uint32_t unmapped_buf; +} __attribute__((packed)); + +struct acdb_cmd_device_table +{ + uint32_t size; + + uint32_t command_id; + uint32_t device_id; + uint32_t network_id; + uint32_t sample_rate_id; + + /* physical page aligned buffer */ + uint32_t total_bytes; + uint32_t unmapped_buf; + + uint32_t res_size; +} __attribute__((packed)); + +struct acdb_result +{ + uint32_t dal_status; + uint32_t size; + + uint32_t unmapped_buf; + uint32_t used_bytes; + uint32_t result; +} __attribute__((packed)); + +// END OF FILE diff --git a/arch/arm/mach-msm/qdsp6_1550/dal_adie.h b/arch/arm/mach-msm/qdsp6_1550/dal_adie.h new file mode 100644 index 00000000..4fbaae8f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/dal_adie.h @@ -0,0 +1,110 @@ +/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora Forum nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _MACH_MSM_QDSP6_ADIE_ +#define _MACH_MSM_QDSP6_ADIE_ + +#include "dal.h" + +#define ADIE_DAL_DEVICE 0x02000029 +#define ADIE_DAL_PORT "SMD_DAL00" +//#define ADIE_DAL_PORT "SMD_DAL_AM_AUD" + + +enum { + ADIE_OP_GET_NUM_PATHS = DAL_OP_FIRST_DEVICE_API, + ADIE_OP_GET_ALL_PATH_IDS, + ADIE_OP_SET_PATH, + ADIE_OP_GET_NUM_PATH_FREQUENCY_PLANS, + ADIE_OP_GET_PATH_FREQUENCY_PLANS, + ADIE_OP_SET_PATH_FREQUENCY_PLAN, + ADIE_OP_PROCEED_TO_STAGE, + ADIE_OP_MUTE_PATH +}; + +/* Path IDs for normal operation. */ +#define ADIE_PATH_HANDSET_TX 0x010740f6 +#define ADIE_PATH_HANDSET_RX 0x010740f7 +#define ADIE_PATH_HEADSET_MONO_TX 0x010740f8 +#define ADIE_PATH_HEADSET_STEREO_TX 0x010740f9 +#define ADIE_PATH_HEADSET_MONO_RX 0x010740fa +#define ADIE_PATH_HEADSET_STEREO_RX 0x010740fb +#define ADIE_PATH_SPEAKER_TX 0x010740fc +#define ADIE_PATH_SPEAKER_RX 0x010740fd +#define ADIE_PATH_SPEAKER_STEREO_RX 0x01074101 + +/* Path IDs used for TTY */ +#define ADIE_PATH_TTY_HEADSET_TX 0x010740fe +#define ADIE_PATH_TTY_HEADSET_RX 0x010740ff + +/* Path IDs used by Factory Test Mode. */ +#define ADIE_PATH_FTM_MIC1_TX 0x01074108 +#define ADIE_PATH_FTM_MIC2_TX 0x01074107 +#define ADIE_PATH_FTM_HPH_L_RX 0x01074106 +#define ADIE_PATH_FTM_HPH_R_RX 0x01074104 +#define ADIE_PATH_FTM_EAR_RX 0x01074103 +#define ADIE_PATH_FTM_SPKR_RX 0x01074102 + +/* Path IDs for Loopback */ +/* Path IDs used for Line in -> AuxPGA -> Line Out Stereo Mode*/ +#define ADIE_PATH_AUXPGA_LINEOUT_STEREO_LB 0x01074100 +/* Line in -> AuxPGA -> LineOut Mono */ +#define ADIE_PATH_AUXPGA_LINEOUT_MONO_LB 0x01073d82 +/* Line in -> AuxPGA -> Stereo Headphone */ +#define ADIE_PATH_AUXPGA_HDPH_STEREO_LB 0x01074109 +/* Line in -> AuxPGA -> Mono Headphone */ +#define ADIE_PATH_AUXPGA_HDPH_MONO_LB 0x01073d85 +/* Line in -> AuxPGA -> Earpiece */ +#define ADIE_PATH_AUXPGA_EAP_LB 0x01073d81 +/* Line in -> AuxPGA -> AuxOut */ +#define ADIE_PATH_AUXPGA_AUXOUT_LB 0x01073d86 + +/* Concurrency Profiles */ +#define ADIE_PATH_SPKR_STEREO_HDPH_MONO_RX 0x01073d83 +#define ADIE_PATH_SPKR_MONO_HDPH_MONO_RX 0x01073d84 +#define ADIE_PATH_SPKR_MONO_HDPH_STEREO_RX 0x01073d88 +#define ADIE_PATH_SPKR_STEREO_HDPH_STEREO_RX 0x01073d89 + +/* stages */ +#define ADIE_STAGE_PATH_OFF 0x0050 +#define ADIE_STAGE_DIGITAL_READY 0x0100 +#define ADIE_STAGE_DIGITAL_ANALOG_READY 0x1000 +#define ADIE_STAGE_ANALOG_OFF 0x0750 +#define ADIE_STAGE_DIGITAL_OFF 0x0600 + +/* path types */ +#define ADIE_PATH_RX 0 +#define ADIE_PATH_TX 1 +#define ADIE_PATH_LOOPBACK 2 + +/* mute states */ +#define ADIE_MUTE_OFF 0 +#define ADIE_MUTE_ON 1 + + +#endif diff --git a/arch/arm/mach-msm/qdsp6_1550/dal_audio.h b/arch/arm/mach-msm/qdsp6_1550/dal_audio.h new file mode 100644 index 00000000..f9ea9c72 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/dal_audio.h @@ -0,0 +1,690 @@ +/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora Forum nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __DAL_AUDIO_H__ +#define __DAL_AUDIO_H__ + +#include "dal_audio_format.h" +#include "dal.h" +#include + +#define AUDIO_DAL_DEVICE 0x02000028 +#define AUDIO_DAL_PORT "DSP_DAL_AQ_AUD" + +enum +{ +#if defined(CONFIG_MACH_HTCLEO) + AUDIO_OP_OPEN = DAL_OP_FIRST_DEVICE_API, + AUDIO_OP_WRITE, + AUDIO_OP_READ, + AUDIO_OP_IOCTL, + AUDIO_OP_INIT, + AUDIO_OP_CLOSE, + AUDIO_OP_FLUSH +#else + AUDIO_OP_CONTROL = DAL_OP_FIRST_DEVICE_API, + AUDIO_OP_DATA, + AUDIO_OP_INIT, + +#endif +}; + +/* ---- common audio structures ---- */ + +/* This flag, if set, indicates that the beginning of the data in the*/ +/* buffer is a synchronization point or key frame, meaning no data */ +/* before it in the stream is required in order to render the stream */ +/* from this point onward. */ +#define ADSP_AUDIO_BUFFER_FLAG_SYNC_POINT 0x01 + +/* This flag, if set, indicates that the buffer object is using valid */ +/* physical address used to store the media data */ +#define ADSP_AUDIO_BUFFER_FLAG_PHYS_ADDR 0x04 + +/* This flag, if set, indicates that a media start timestamp has been */ +/* set for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_START_SET 0x08 + +/* This flag, if set, indicates that a media stop timestamp has been set */ +/* for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_STOP_SET 0x10 + +/* This flag, if set, indicates that a preroll timestamp has been set */ +/* for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_PREROLL_SET 0x20 + +/* This flag, if set, indicates that the data in the buffer is a fragment of */ +/* a larger block of data, and will be continued by the data in the next */ +/* buffer to be delivered. */ +#define ADSP_AUDIO_BUFFER_FLAG_CONTINUATION 0x40 + +struct adsp_audio_buffer { + u32 addr; /* Physical Address of buffer */ + u32 max_size; /* Maximum size of buffer */ + u32 actual_size; /* Actual size of valid data in the buffer */ + u32 offset; /* Offset to the first valid byte */ + u32 flags; /* ADSP_AUDIO_BUFFER_FLAGs that has been set */ + s64 start; /* Start timestamp, if any */ + s64 stop; /* Stop timestamp, if any */ + s64 preroll; /* Preroll timestamp, if any */ +} __attribute__ ((packed)); + + + +/* ---- audio commands ---- */ + +/* Command/event response types */ +#define ADSP_AUDIO_RESPONSE_COMMAND 0 +#define ADSP_AUDIO_RESPONSE_ASYNC 1 + +#if !defined(CONFIG_MACH_HTCLEO) +struct adsp_command_hdr { + u32 size; /* sizeof(cmd) - sizeof(u32) */ + + u32 dst; + u32 src; + + u32 opcode; + u32 response_type; + u32 seq_number; + + u32 context; /* opaque to DSP */ + u32 data; + + u32 padding; +} __attribute__ ((packed)); + +#else + +struct adsp_command_hdr +{ + u32 context; + u32 data; +} __attribute__ ((packed)); +#endif + +#define AUDIO_DOMAIN_APP 0 +#define AUDIO_DOMAIN_MODEM 1 +#define AUDIO_DOMAIN_DSP 2 + +#define AUDIO_SERVICE_AUDIO 0 +#define AUDIO_SERVICE_VIDEO 1 /* really? */ + +/* adsp audio addresses are (byte order) domain, service, major, minor */ +//#define AUDIO_ADDR(maj,min) ( (((maj) & 0xff) << 16) | (((min) & 0xff) << 24) | (1) ) + +#define AUDIO_ADDR(maj,min,dom) ( (((min) & 0xff) << 24) | (((maj) & 0xff) << 16) | ((AUDIO_SERVICE_AUDIO) << 8) | (dom) ) + + +/* AAC Encoder modes */ +#define ADSP_AUDIO_ENC_AAC_LC_ONLY_MODE 0 +#define ADSP_AUDIO_ENC_AAC_PLUS_MODE 1 +#define ADSP_AUDIO_ENC_ENHANCED_AAC_PLUS_MODE 2 + +struct adsp_audio_aac_enc_cfg { + u32 bit_rate; /* bits per second */ + u32 encoder_mode; /* ADSP_AUDIO_ENC_* */ +} __attribute__ ((packed)); + +#define ADSP_AUDIO_ENC_SBC_ALLOCATION_METHOD_LOUNDNESS 0 +#define ADSP_AUDIO_ENC_SBC_ALLOCATION_METHOD_SNR 1 + +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_MONO 1 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_STEREO 2 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_DUAL 8 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_JOINT_STEREO 9 + +struct adsp_audio_sbc_encoder_cfg { + u32 num_subbands; + u32 block_len; + u32 channel_mode; + u32 allocation_method; + u32 bit_rate; +} __attribute__ ((packed)); + +/* AMR NB encoder modes */ +#define ADSP_AUDIO_AMR_MR475 0 +#define ADSP_AUDIO_AMR_MR515 1 +#define ADSP_AUDIO_AMR_MMR59 2 +#define ADSP_AUDIO_AMR_MMR67 3 +#define ADSP_AUDIO_AMR_MMR74 4 +#define ADSP_AUDIO_AMR_MMR795 5 +#define ADSP_AUDIO_AMR_MMR102 6 +#define ADSP_AUDIO_AMR_MMR122 7 + +/* The following are valid AMR NB DTX modes */ +#define ADSP_AUDIO_AMR_DTX_MODE_OFF 0 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_VAD1 1 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_VAD2 2 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_AUTO 3 + +/* AMR Encoder configuration */ +struct adsp_audio_amr_enc_cfg { + u32 mode; /* ADSP_AUDIO_AMR_MR* */ + u32 dtx_mode; /* ADSP_AUDIO_AMR_DTX_MODE* */ + u32 enable; /* 1 = enable, 0 = disable */ +} __attribute__ ((packed)); + +struct adsp_audio_qcelp13k_enc_cfg { + u16 min_rate; + u16 max_rate; +} __attribute__ ((packed)); + +struct adsp_audio_evrc_enc_cfg { + u16 min_rate; + u16 max_rate; +} __attribute__ ((packed)); + +union adsp_audio_codec_config { + struct adsp_audio_amr_enc_cfg amr; + struct adsp_audio_aac_enc_cfg aac; + struct adsp_audio_qcelp13k_enc_cfg qcelp13k; + struct adsp_audio_evrc_enc_cfg evrc; + struct adsp_audio_sbc_encoder_cfg sbc; +} __attribute__ ((packed)); + + +/* This is the default value. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_NONE 0x0000 + +/* This bit, if set, indicates that the AVSync mode is activated. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_AVSYNC 0x0001 + +/* This bit, if set, indicates that the Sample Rate/Channel Mode */ +/* Change Notification mode is activated. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_SR_CM_NOTIFY 0x0002 + +/* This bit, if set, indicates that the sync clock is enabled */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_ENABLE_SYNC_CLOCK 0x0004 + +#if !defined(CONFIG_MACH_HTCLEO) +struct adsp_open_command { + struct adsp_command_hdr hdr; + + u32 device; + u32 endpoint; /* address */ + + u32 stream_context; + u32 mode; + + u32 buf_max_size; + + union adsp_audio_format format; + union adsp_audio_codec_config config; +} __attribute__ ((packed)); + +/* Opcode to open a device stream session to capture audio */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_READ 0x0108dd79 + +/* Opcode to open a device stream session to render audio */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE 0x0108dd7a + +/* Opcode to open a device session, must open a device */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_DEVICE 0x0108dd7b + +/* Close an existing stream or device */ +#define ADSP_AUDIO_IOCTL_CMD_CLOSE 0x0108d8bc + +#else + +struct adsp_open_command +{ + struct adsp_command_hdr hdr; + + u32 opcode; + u32 numdev; + u32 dev[4]; + u32 stream_context; + + u32 format; + u32 pblock; + u32 blocklen; + + u32 buf_max_size; + u32 priority; + + union adsp_audio_codec_config config; + u32 mode; +} __attribute__ ((packed)); + +/* --- audio control and stream session ioctls ---- */ + +/* Opcode to open a device stream session to capture audio */ +#define ADSP_AUDIO_OPCODE_OPEN_READ 0x01 + +/* Opcode to open a device stream session to render audio */ +#define ADSP_AUDIO_OPCODE_OPEN_WRITE 0x02 + +/* Opcode to open a device session, must open a device */ +#define ADSP_AUDIO_OPCODE_OPEN_DEV 0x04 + +#endif + +/* A device switch requires three IOCTL */ +/* commands in the following sequence: PREPARE, STANDBY, COMMIT */ + +/* adsp_audio_device_switch_command structure is needed for */ +/* DEVICE_SWITCH_PREPARE */ + +/* Device switch protocol step #1. Pause old device and */ +/* generate silence for the old device. */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_PREPARE 0x010815c4 + +/* Device switch protocol step #2. Release old device, */ +/* create new device and generate silence for the new device. */ + +/* When client receives ack for this IOCTL, the client can */ +/* start sending IOCTL commands to configure, calibrate and */ +/* change filter settings on the new device. */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_STANDBY 0x010815c5 + +/* Device switch protocol step #3. Start normal operations on new device */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_COMMIT 0x01075ee7 + +struct adsp_device_switch_command { + struct adsp_command_hdr hdr; + u32 old_device; + u32 new_device; + u8 device_class; /* 0 = i.rx, 1 = i.tx, 2 = e.rx, 3 = e.tx */ + u8 device_type; /* 0 = rx, 1 = tx, 2 = both */ +} __attribute__ ((packed)); + + + +/* --- audio control session ioctls ---- */ + +#define ADSP_PATH_RX 0 +#define ADSP_PATH_TX 1 +#define ADSP_PATH_BOTH 2 + +/* These commands will affect a logical device and all its associated */ +/* streams. */ + + +/* Set device volume. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_VOL 0x0107605c + +struct adsp_set_dev_volume_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 path; /* 0 = rx, 1 = tx, 2 = both */ + s32 volume; +} __attribute__ ((packed)); + +/* Set Device stereo volume. This command has data payload, */ +/* struct adsp_audio_set_dev_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_STEREO_VOL 0x0108df3e + +/* Set L, R cross channel gain for a Device. This command has */ +/* data payload, struct adsp_audio_set_dev_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_XCHAN_GAIN 0x0108df40 + +/* Set device mute state. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE 0x0107605f + +struct adsp_set_dev_mute_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 path; /* 0 = rx, 1 = tx, 2 = both */ + u32 mute; /* 1 = mute */ +} __attribute__ ((packed)); + +/* Configure Equalizer for a device. */ +/* This command has payload struct adsp_audio_set_dev_equalizer_command. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_EQ_CONFIG 0x0108b10e + +/* Set configuration data for an algorithm aspect of a device. */ +/* This command has payload struct adsp_audio_set_dev_cfg_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG 0x0108b6cb + +struct adsp_set_dev_cfg_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 block_id; + u32 interface_id; + u32 phys_addr; + u32 phys_size; + u32 phys_used; +} __attribute__ ((packed)); + +/* Set configuration data for all interfaces of a device. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG_TABLE 0x0108b6bf + +struct adsp_set_dev_cfg_table_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 phys_addr; + u32 phys_size; + u32 phys_used; +} __attribute__ ((packed)); + +/* ---- audio stream data commands ---- */ + +#define ADSP_AUDIO_IOCTL_CMD_DATA_TX 0x0108dd7f +#define ADSP_AUDIO_IOCTL_CMD_DATA_RX 0x0108dd80 + +struct adsp_buffer_command { + struct adsp_command_hdr hdr; + struct adsp_audio_buffer buffer; +} __attribute__ ((packed)); + + + +/* ---- audio stream ioctls (only affect a single stream in a session) ---- */ + +/* Stop stream for audio device. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_STOP 0x01075c54 + +/* End of stream reached. Client will not send any more data. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_EOS 0x0108b150 + +/* Do sample slipping/stuffing on AAC outputs. The payload of */ +/* this command is struct adsp_audio_slip_sample_command. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_SLIPSAMPLE 0x0108d40e + +/* Set stream volume. */ +/* This command has data payload, struct adsp_audio_set_volume_command. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_STREAM_VOL 0x0108c0de + +/* Set stream stereo volume. This command has data payload, */ +/* struct adsp_audio_set_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_STEREO_VOL 0x0108dd7c + +/* Set L, R cross channel gain for a Stream. This command has */ +/* data payload, struct adsp_audio_set_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_XCHAN_GAIN 0x0108dd7d + +/* Set stream mute state. */ +/* This command has data payload, struct adsp_audio_set_stream_mute. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_STREAM_MUTE 0x0108c0df + +/* Reconfigure bit rate information. This command has data */ +/* payload, struct adsp_audio_set_bit_rate_command */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_BITRATE 0x0108ccf1 + +/* Set Channel Mapping. This command has data payload, struct */ +/* This command has data payload struct adsp_audio_set_channel_map_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_CHANNELMAP 0x0108d32a + +/* Enable/disable AACPlus SBR. */ +/* This command has data payload struct adsp_audio_set_sbr_command */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_SBR 0x0108d416 + +/* Enable/disable WMA Pro Chex and Fex. This command has data payload */ +/* struct adsp_audio_stream_set_wma_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_WMAPRO 0x0108d417 + + +/* ---- audio session ioctls (affect all streams in a session) --- */ + +/* Start stream for audio device. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_START 0x010815c6 + +/* Stop all stream(s) for audio session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_STOP 0x0108dd7e + +/* Pause the data flow for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_PAUSE 0x01075ee8 + +/* Resume the data flow for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_RESUME 0x01075ee9 + +/* Drop any unprocessed data buffers for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_FLUSH 0x01075eea + +/* Start Stream DTMF tone */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_DTMF_START 0x0108c0dd + +/* Stop Stream DTMF tone */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_DTMF_STOP 0x01087554 + +/* Set Session volume. */ +/* This command has data payload, struct adsp_audio_set_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_VOL 0x0108d8bd + +/* Set session stereo volume. This command has data payload, */ +/* struct adsp_audio_set_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_STEREO_VOL 0x0108df3d + +/* Set L, R cross channel gain for a session. This command has */ +/* data payload, struct adsp_audio_set_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_XCHAN_GAIN 0x0108df3f + +/* Set Session mute state. */ +/* This command has data payload, struct adsp_audio_set_mute_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_MUTE 0x0108d8be + +/* Configure Equalizer for a stream. */ +/* This command has payload struct adsp_audio_set_equalizer_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_EQ_CONFIG 0x0108c0e0 + +#define ADSP_AUDIO_MAX_EQ_BANDS 12 + +/* Definition for any one band of Equalizer. */ +struct adsp_audio_eq_band { + u16 band_idx; + u32 filter_type; + u32 center_freq_hz; + s32 filter_gain; + s32 q_factor; +} __attribute__ ((packed)); + +struct adsp_audio_set_equalizer_command { + struct adsp_command_hdr hdr; + u32 enable; + u32 num_bands; + struct adsp_audio_eq_band eq_bands[ADSP_AUDIO_MAX_EQ_BANDS]; +} __attribute__ ((packed)); + + +/* Set Audio Video sync information. */ +/* This command has data payload, struct adsp_audio_set_av_sync_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_AVSYNC 0x0108d1e2 + +/* Get Audio Media Session time. */ +/* This command returns the audioTime in adsp_audio_unsigned64_event */ +#define ADSP_AUDIO_IOCTL_CMD_GET_AUDIO_TIME 0x0108c26c + + +/* these command structures are used for both STREAM and SESSION ioctls */ + +struct adsp_set_volume_command { + struct adsp_command_hdr hdr; + s32 volume; +} __attribute__ ((packed)); + +struct adsp_set_mute_command { + struct adsp_command_hdr hdr; + u32 mute; /* 1 == mute */ +} __attribute__ ((packed)); + + + +/* ---- audio events ---- */ + +/* All IOCTL commands generate an event with the IOCTL opcode as the */ +/* event id after the IOCTL command has been executed. */ + +/* This event is generated after a media stream session is opened. */ +#define ADSP_AUDIO_EVT_STATUS_OPEN 0x0108c0d6 + +/* This event is generated after a media stream session is closed. */ +#define ADSP_AUDIO_EVT_STATUS_CLOSE 0x0108c0d7 + +/* Asyncronous buffer consumption. This event is generated after a */ +/* recived buffer is consumed during rendering or filled during */ +/* capture opeartion. */ +#define ADSP_AUDIO_EVT_STATUS_BUF_DONE 0x0108c0d8 + +/* This event is generated when rendering operation is starving for */ +/* data. In order to avoid audio loss at the end of a plauback, the */ +/* client should wait for this event before issuing the close command. */ +#define ADSP_AUDIO_EVT_STATUS_BUF_UNDERRUN 0x0108c0d9 + +/* This event is generated during capture operation when there are no */ +/* buffers available to copy the captured audio data */ +#define ADSP_AUDIO_EVT_STATUS_BUF_OVERFLOW 0x0108c0da + +/* This asynchronous event is generated as a result of an input */ +/* sample rate change and/or channel mode change detected by the */ +/* decoder. The event payload data is an array of 2 uint32 */ +/* values containing the sample rate in Hz and channel mode. */ +#define ADSP_AUDIO_EVT_SR_CM_CHANGE 0x0108d329 + +struct adsp_event_hdr +{ + u32 evt_handle; /* DAL common header */ + u32 evt_cookie; + u32 evt_length; + + u32 src; /* "source" audio address */ + u32 dst; /* "destination" audio address */ + + u32 event_id; + u32 response_type; + u32 seq_number; + + u32 context; /* opaque to DSP */ + u32 data; + + u32 status; +} __attribute__ ((packed)); + +#if defined(CONFIG_MACH_HTCLEO) +union adsp_event_data +{ + u32 val32; + uint8_t data[32]; +} __attribute__ ((packed)); + + +struct adsp_audio_event +{ + struct adsp_command_hdr hdr; + u32 session; + u32 event_id; + u32 status; + u32 datalen; + union adsp_event_data data; +} __attribute__ ((packed)); + + +struct adsp_audio_dal_event +{ + u32 cb_evt; + u32 loc_evt; + u32 size; + struct adsp_audio_event ae; +} __attribute__ ((packed)); + +#else +struct adsp_buffer_event +{ + struct adsp_event_hdr hdr; + struct adsp_audio_buffer buffer; +} __attribute__ ((packed)); + +#endif + + +/* ---- audio device IDs ---- */ + +/* Device direction Rx/Tx flag */ +#define ADSP_AUDIO_RX_DEVICE 0x00 +#define ADSP_AUDIO_TX_DEVICE 0x01 + +/* Default RX or TX device */ +#define ADSP_AUDIO_DEVICE_ID_DEFAULT 0x1081679 + +/* Source (TX) devices */ +#define ADSP_AUDIO_DEVICE_ID_HANDSET_MIC 0x107ac8d +#define ADSP_AUDIO_DEVICE_ID_HEADSET_MIC 0x1081510 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC 0x1081512 +#define ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC 0x1081518 +#define ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC 0x108151b +#define ADSP_AUDIO_DEVICE_ID_I2S_MIC 0x1089bf3 + +/* Special loopback pseudo device to be paired with an RX device */ +/* with usage ADSP_AUDIO_DEVICE_USAGE_MIXED_PCM_LOOPBACK */ +#define ADSP_AUDIO_DEVICE_ID_MIXED_PCM_LOOPBACK_TX 0x1089bf2 + +/* Sink (RX) devices */ +#define ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR 0x107ac88 +#define ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO 0x1081511 +#define ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO 0x107ac8a +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO 0x1081513 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET 0x108c508 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET 0x108c894 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO 0x1081514 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET 0x108c895 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET 0x108c509 +#define ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR 0x1081519 +#define ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR 0x108151c +#define ADSP_AUDIO_DEVICE_ID_I2S_SPKR 0x1089bf4 +#define ADSP_AUDIO_DEVICE_ID_NULL_SINK 0x108e512 + +/* BT A2DP playback device. */ +/* This device must be paired with */ +/* ADSP_AUDIO_DEVICE_ID_MIXED_PCM_LOOPBACK_TX using */ +/* ADSP_AUDIO_DEVICE_USAGE_MIXED_PCM_LOOPBACK mode */ +#define ADSP_AUDIO_DEVICE_ID_BT_A2DP_SPKR 0x108151a + +/* Voice Destination identifier - specifically used for */ +/* controlling Voice module from the Device Control Session */ +#define ADSP_AUDIO_DEVICE_ID_VOICE 0x0108df3c + +/* Audio device usage types. */ +/* This is a bit mask to determine which topology to use in the */ +/* device session */ +#define ADSP_AUDIO_DEVICE_CONTEXT_VOICE 0x01 +#define ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK 0x02 +#define ADSP_AUDIO_DEVICE_CONTEXT_MIXED_RECORD 0x10 +#define ADSP_AUDIO_DEVICE_CONTEXT_RECORD 0x20 +#define ADSP_AUDIO_DEVICE_CONTEXT_PCM_LOOPBACK 0x40 + +/* for EQ */ +#define CAD_EQ_INVALID_DATA 0xFFFFFFFF + +/* Equalizer filter band types */ +#define ADSP_AUDIO_EQUALIZER_TYPE_NONE 0 +#define ADSP_AUDIO_EQUALIZER_BASS_BOOST 1 +#define ADSP_AUDIO_EQUALIZER_BASS_CUT 2 +#define ADSP_AUDIO_EQUALIZER_TREBLE_BOOST 3 +#define ADSP_AUDIO_EQUALIZER_TREBLE_CUT 4 +#define ADSP_AUDIO_EQUALIZER_BAND_BOOST 5 +#define ADSP_AUDIO_EQUALIZER_BAND_CUT 6 + +struct cad_audio_eq_cfg { + u32 enable; + u32 num_bands; + struct adsp_audio_eq_band eq_bands[ADSP_AUDIO_MAX_EQ_BANDS]; +} __attribute__ ((packed)); + +int q6audio_set_stream_eq(struct audio_client *ac, struct cad_audio_eq_cfg *eq_cfg); + +#endif diff --git a/arch/arm/mach-msm/qdsp6_1550/dal_audio_format.h b/arch/arm/mach-msm/qdsp6_1550/dal_audio_format.h new file mode 100644 index 00000000..71ccf91b --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/dal_audio_format.h @@ -0,0 +1,285 @@ +/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __ADSP_AUDIO_MEDIA_FORMAT_H +#define __ADSP_AUDIO_MEDIA_FORMAT_H + + + +/* Supported audio media formats */ + +/* format block in shmem */ +#define ADSP_AUDIO_FORMAT_SHAREDMEMORY 0x01091a78 +/* adsp_audio_format_raw_pcm type */ +#define ADSP_AUDIO_FORMAT_PCM 0x0103d2fd +/* adsp_audio_format_raw_pcm type */ +#define ADSP_AUDIO_FORMAT_DTMF 0x01087725 +/* adsp_audio_format_adpcm type */ +#define ADSP_AUDIO_FORMAT_ADPCM 0x0103d2ff +/* Yamaha PCM format */ +#define ADSP_AUDIO_FORMAT_YADPCM 0x0108dc07 +/* ISO/IEC 11172 */ +#define ADSP_AUDIO_FORMAT_MP3 0x0103d308 +/* ISO/IEC 14496 */ +#define ADSP_AUDIO_FORMAT_MPEG4_AAC 0x010422f1 +/* AMR-NB audio in FS format */ +#define ADSP_AUDIO_FORMAT_AMRNB_FS 0x0105c16c +/* AMR-WB audio in FS format */ +#define ADSP_AUDIO_FORMAT_AMRWB_FS 0x0105c16e +/* QCELP 13k, IS733 */ +#define ADSP_AUDIO_FORMAT_V13K_FS 0x01080b8a +/* EVRC 8k, IS127 */ +#define ADSP_AUDIO_FORMAT_EVRC_FS 0x01080b89 +/* EVRC-B 8k, 4GV */ +#define ADSP_AUDIO_FORMAT_EVRCB_FS 0x0108f2a3 +/* MIDI command stream */ +#define ADSP_AUDIO_FORMAT_MIDI 0x0103d300 +/* A2DP SBC stream */ +#define ADSP_AUDIO_FORMAT_SBC 0x0108c4d8 +/* Version 10 Professional */ +#define ADSP_AUDIO_FORMAT_WMA_V10PRO 0x0108aa92 +/* Version 9 Starndard */ +#define ADSP_AUDIO_FORMAT_WMA_V9 0x0108d430 +/* AMR WideBand Plus */ +#define ADSP_AUDIO_FORMAT_AMR_WB_PLUS 0x0108f3da +/* AC3 Decoder */ +#define ADSP_AUDIO_FORMAT_AC3_DECODER 0x0108d5f9 + + +/* Not yet supported audio media formats */ + + + +/* ISO/IEC 13818 */ +#define ADSP_AUDIO_FORMAT_MPEG2_AAC 0x0103d309 +/* 3GPP TS 26.101 Sec 4.0 */ +#define ADSP_AUDIO_FORMAT_AMRNB_IF1 0x0103d305 +/* 3GPP TS 26.101 Annex A */ +#define ADSP_AUDIO_FORMAT_AMRNB_IF2 0x01057b31 +/* 3GPP TS 26.201 */ +#define ADSP_AUDIO_FORMAT_AMRWB_IF1 0x0103d306 +/* 3GPP TS 26.201 */ +#define ADSP_AUDIO_FORMAT_AMRWB_IF2 0x0105c16d +/* G.711 */ +#define ADSP_AUDIO_FORMAT_G711 0x0106201d +/* QCELP 8k, IS96A */ +#define ADSP_AUDIO_FORMAT_V8K_FS 0x01081d29 +/* Version 1 codec */ +#define ADSP_AUDIO_FORMAT_WMA_V1 0x01055b2b +/* Version 2, 7 & 8 codec */ +#define ADSP_AUDIO_FORMAT_WMA_V8 0x01055b2c +/* Version 9 Professional codec */ +#define ADSP_AUDIO_FORMAT_WMA_V9PRO 0x01055b2d +/* Version 9 Voice codec */ +#define ADSP_AUDIO_FORMAT_WMA_SP1 0x01055b2e +/* Version 9 Lossless codec */ +#define ADSP_AUDIO_FORMAT_WMA_LOSSLESS 0x01055b2f +/* Real Media content, low-bitrate */ +#define ADSP_AUDIO_FORMAT_RA_SIPR 0x01042a0f +/* Real Media content */ +#define ADSP_AUDIO_FORMAT_RA_COOK 0x01042a0e + + +/* For all of the audio formats, unless specified otherwise, */ +/* the following apply: */ +/* Format block bits are arranged in bytes and words in little-endian */ +/* order, i.e., least-significant bit first and least-significant */ +/* byte first. */ + + + +/* AAC Format Block. */ + +/* AAC format block consist of a format identifier followed by */ +/* AudioSpecificConfig formatted according to ISO/IEC 14496-3 */ + +/* The following AAC format identifiers are supported */ +#define ADSP_AUDIO_AAC_ADTS 0x010619cf +#define ADSP_AUDIO_AAC_MPEG4_ADTS 0x010619d0 +#define ADSP_AUDIO_AAC_LOAS 0x010619d1 +#define ADSP_AUDIO_AAC_ADIF 0x010619d2 +#define ADSP_AUDIO_AAC_RAW 0x010619d3 +#define ADSP_AUDIO_AAC_FRAMED_RAW 0x0108c1fb + + +#define ADSP_AUDIO_COMPANDING_ALAW 0x10619cd +#define ADSP_AUDIO_COMPANDING_MLAW 0x10619ce + +/* Maxmum number of bytes allowed in a format block */ +#define ADSP_AUDIO_FORMAT_DATA_MAX 16 + + +struct adsp_audio_no_payload_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* no payload for this format type */ +} __attribute__ ((packed)); + + +/* For convenience, to be used as a standard format block */ +/* for various media types that don't need a unique format block */ +/* ie. PCM, DTMF, etc. */ +struct adsp_audio_standard_format { + /* Media Format Code (must always be first element) */ +// u32 format; + + /* payload */ + u16 channels; + u16 bits_per_sample; + u32 sampling_rate; + u8 is_signed; + u8 is_interleaved; +} __attribute__ ((packed)); + + + +/* ADPCM format block */ +struct adsp_audio_adpcm_format { + /* Media Format Code (must always be first element) */ +// u32 format; + + /* payload */ + u16 channels; + u16 bits_per_sample; + u32 sampling_rate; + u8 is_signed; + u8 is_interleaved; + u32 block_size; +} __attribute__ ((packed)); + + +/* MIDI format block */ +struct adsp_audio_midi_format { + /* Media Format Code (must always be first element) */ +// u32 format; + + /* payload */ + u32 sampling_rate; + u16 channels; + u16 mode; +} __attribute__ ((packed)); + + +/* G711 format block */ +struct adsp_audio_g711_format { + /* Media Format Code (must always be first element) */ +// u32 format; + + /* payload */ + u32 companding; +} __attribute__ ((packed)); + + +struct adsp_audio_wma_pro_format { + /* Media Format Code (must always be first element) */ + // u32 format; + + /* payload */ + u16 format_tag; + u16 channels; + u32 samples_per_sec; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 channel_mask; + u16 encode_opt; + u16 advanced_encode_opt; + u32 advanced_encode_opt2; + u32 drc_peak_reference; + u32 drc_peak_target; + u32 drc_average_reference; + u32 drc_average_target; +} __attribute__ ((packed)); + + +struct adsp_audio_amrwb_plus_format { + /* Media Format Code (must always be first element) */ + // u32 format; + + /* payload */ + u32 size; + u32 version; + u32 channels; + u32 amr_band_mode; + u32 amr_dtx_mode; + u32 amr_frame_format; + u32 amr_isf_index; +} __attribute__ ((packed)); + + +/* Binary Byte Stream Format */ +/* Binary format type that defines a byte stream, */ +/* can be used to specify any format (ie. AAC) */ +struct adsp_audio_binary_format { + /* Media Format Code (must always be first element) */ + // u32 format; + + /* payload */ + /* number of bytes set in byte stream */ +// u32 num_bytes; + /* Byte stream binary data */ + u8 data[ADSP_AUDIO_FORMAT_DATA_MAX]; +} __attribute__ ((packed)); + + +struct adsp_audio_shared_memory_format { + /* Media Format Code (must always be first element) */ +// u32 format; + + /* Number of bytes in shared memory */ + u32 len; + /* Phyisical address to data in shared memory */ + u32 address; +} __attribute__ ((packed)); + + +/* Union of all format types */ +union adsp_audio_format { + /* Basic format block with no payload */ + struct adsp_audio_no_payload_format no_payload; + /* Generic format block PCM, DTMF */ + struct adsp_audio_standard_format standard; + /* ADPCM format block */ + struct adsp_audio_adpcm_format adpcm; + /* MIDI format block */ + struct adsp_audio_midi_format midi; + /* G711 format block */ + struct adsp_audio_g711_format g711; + /* WmaPro format block */ + struct adsp_audio_wma_pro_format wma_pro; + /* WmaPro format block */ + struct adsp_audio_amrwb_plus_format amrwb_plus; + /* binary (byte stream) format block, used for AAC */ + struct adsp_audio_binary_format binary; + /* format block in shared memory */ + struct adsp_audio_shared_memory_format shared_mem; +}; + +#endif + diff --git a/arch/arm/mach-msm/qdsp6_1550/mp3.c b/arch/arm/mach-msm/qdsp6_1550/mp3.c new file mode 100644 index 00000000..92e8f0f0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/mp3.c @@ -0,0 +1,229 @@ +/* arch/arm/mach-msm/qdsp6/mp3.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "dal_audio.h" + +#define BUFSZ (8192) +#define DMASZ (BUFSZ * 2) + +struct mp3 { + struct mutex lock; + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; +}; + +static long mp3_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct mp3 *mp3 = file->private_data; + struct cad_audio_eq_cfg eq_cfg; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&mp3->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: { + int vol; + if (copy_from_user(&vol, (void*) arg, sizeof(vol))) { + rc = -EFAULT; + break; + } + rc = q6audio_set_stream_volume(mp3->ac, vol); + break; + } + case AUDIO_SET_EQ: { + if (copy_from_user(&eq_cfg, (void *)arg, sizeof(struct cad_audio_eq_cfg))) { + rc = -EFAULT; + break; + } + rc = q6audio_set_stream_eq(mp3->ac, &eq_cfg); + break; + } + case AUDIO_START: { + uint32_t acdb_id; + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void*) arg, sizeof(acdb_id))) { + pr_info("pcm_out: copy acdb_id from user failed\n"); + rc = -EFAULT; + break; + } + if (mp3->ac) { + rc = -EBUSY; + } else { + mp3->ac = q6audio_open_mp3(BUFSZ, + mp3->sample_rate, mp3->channel_count, acdb_id); + if (!mp3->ac) + rc = -ENOMEM; + } + break; + } + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (mp3->ac) { + rc = -EBUSY; + break; + } + if (copy_from_user(&config, (void*) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count < 1 || config.channel_count > 2) { + rc = -EINVAL; + break; + } + mp3->sample_rate = config.sample_rate; + mp3->channel_count = config.channel_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = mp3->sample_rate; + config.channel_count = mp3->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void*) arg, &config, sizeof(config))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&mp3->lock); + return rc; +} + +static int mp3_open(struct inode *inode, struct file *file) +{ + int rc = 0; + + struct mp3 *mp3; + mp3 = kzalloc(sizeof(struct mp3), GFP_KERNEL); + + if (!mp3) + return -ENOMEM; + + mutex_init(&mp3->lock); + mp3->channel_count = 2; + mp3->sample_rate = 44100; + + file->private_data = mp3; + return rc; +} + +static ssize_t mp3_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mp3 *mp3 = file->private_data; + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer; + + if (!mp3->ac) + mp3_ioctl(file, AUDIO_START, 0); + + ac = mp3->ac; + if (!ac) + return -ENODEV; + + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_from_user(ab->data, buf, xfer)) + return -EFAULT; + + buf += xfer; + count -= xfer; + + ab->used = xfer; + q6audio_write(ac, ab); + ac->cpu_buf ^= 1; + } + + return buf - start; +} + +static int mp3_fsync(struct file *f, struct dentry *dentry, int datasync) +{ + struct mp3 *mp3 = f->private_data; + if (mp3->ac) + return q6audio_async(mp3->ac); + return -ENODEV; +} + +static int mp3_release(struct inode *inode, struct file *file) +{ + struct mp3 *mp3 = file->private_data; + if (mp3->ac) + q6audio_mp3_close(mp3->ac); + kfree(mp3); + return 0; +} + +static struct file_operations mp3_fops = { + .owner = THIS_MODULE, + .open = mp3_open, + .write = mp3_write, + .fsync = mp3_fsync, + .release = mp3_release, + .unlocked_ioctl = mp3_ioctl, +}; + +struct miscdevice mp3_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_mp3", + .fops = &mp3_fops, +}; + +static int __init mp3_init(void) { + return misc_register(&mp3_misc); +} + +device_initcall(mp3_init); diff --git a/arch/arm/mach-msm/qdsp6_1550/msm_q6vdec.c b/arch/arm/mach-msm/qdsp6_1550/msm_q6vdec.c new file mode 100644 index 00000000..d3fcfe77 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/msm_q6vdec.c @@ -0,0 +1,940 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora Forum nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* +#define DEBUG_TRACE_VDEC +#define DEBUG +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dal.h" + +#define DALDEVICEID_VDEC_DEVICE 0x02000026 +#define DALDEVICEID_VDEC_PORTNAME "DSP_DAL_AQ_VID" + +#define VDEC_INTERFACE_VERSION 0x00020000 + +#define MAJOR_MASK 0xFFFF0000 +#define MINOR_MASK 0x0000FFFF + +#define VDEC_GET_MAJOR_VERSION(version) (((version)&MAJOR_MASK)>>16) + +#define VDEC_GET_MINOR_VERSION(version) ((version)&MINOR_MASK) + +#ifdef DEBUG_TRACE_VDEC +#define TRACE(fmt,x...) \ + do { pr_debug("%s:%d " fmt, __func__, __LINE__, ##x); } while (0) +#else +#define TRACE(fmt,x...) do { } while (0) +#endif + + +static DEFINE_MUTEX(idlecount_lock); +static int idlecount; +static struct wake_lock wakelock; +static struct wake_lock idlelock; + +static void prevent_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (++idlecount == 1) { + wake_lock(&idlelock); + wake_lock(&wakelock); + } + mutex_unlock(&idlecount_lock); +} + +static void allow_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (--idlecount == 0) { + wake_unlock(&idlelock); + wake_unlock(&wakelock); + } + mutex_unlock(&idlecount_lock); +} + + +enum { + VDEC_DALRPC_INITIALIZE = DAL_OP_FIRST_DEVICE_API, + VDEC_DALRPC_SETBUFFERS, + VDEC_DALRPC_FREEBUFFERS, + VDEC_DALRPC_QUEUE, + VDEC_DALRPC_SIGEOFSTREAM, + VDEC_DALRPC_FLUSH, + VDEC_DALRPC_REUSEFRAMEBUFFER, + VDEC_DALRPC_GETDECATTRIBUTES, +}; + +enum { + VDEC_ASYNCMSG_DECODE_DONE = 0xdec0de00, + VDEC_ASYNCMSG_REUSE_FRAME, +}; + +struct vdec_init_cfg { + u32 decode_done_evt; + u32 reuse_frame_evt; + struct vdec_config cfg; +}; + +struct vdec_buffer_status { + u32 data; + u32 status; +}; + +#define VDEC_MSG_MAX 128 + +struct vdec_msg_list { + struct list_head list; + struct vdec_msg vdec_msg; +}; + +struct vdec_mem_info { + u32 buf_type; + u32 id; + unsigned long phys_addr; + unsigned long len; + struct file *file; +}; + +struct vdec_mem_list { + struct list_head list; + struct vdec_mem_info mem; +}; + +struct vdec_data { + struct dal_client *vdec_handle; + struct list_head vdec_msg_list_head; + struct list_head vdec_msg_list_free; + wait_queue_head_t vdec_msg_evt; + spinlock_t vdec_list_lock; + struct list_head vdec_mem_list_head; + spinlock_t vdec_mem_list_lock; + int mem_initialized; + int running; + int close_decode; +}; + +static struct class *driver_class; +static dev_t vdec_device_no; +static struct cdev vdec_cdev; +static int ref_cnt; +static DEFINE_MUTEX(vdec_ref_lock); + +static inline int vdec_check_version(u32 client, u32 server) +{ + int ret = -EINVAL; + if ((VDEC_GET_MAJOR_VERSION(client) == VDEC_GET_MAJOR_VERSION(server)) + && (VDEC_GET_MINOR_VERSION(client) <= + VDEC_GET_MINOR_VERSION(server))) + ret = 0; + return ret; +} + +static int vdec_get_msg(struct vdec_data *vd, void *msg) +{ + struct vdec_msg_list *l; + unsigned long flags; + int ret = 0; + + if (!vd->running) + return -EPERM; + + spin_lock_irqsave(&vd->vdec_list_lock, flags); + list_for_each_entry_reverse(l, &vd->vdec_msg_list_head, list) { + if (copy_to_user(msg, &l->vdec_msg, sizeof(struct vdec_msg))) + pr_err("vdec_get_msg failed to copy_to_user!\n"); + if (l->vdec_msg.id == VDEC_MSG_REUSEINPUTBUFFER) + TRACE("reuse_input_buffer %d\n", l->vdec_msg.buf_id); + else if (l->vdec_msg.id == VDEC_MSG_FRAMEDONE) + TRACE("frame_done (stat=%d)\n", + l->vdec_msg.vfr_info.status); + else + TRACE("unknown msg (msgid=%d)\n", l->vdec_msg.id); + list_del(&l->list); + list_add(&l->list, &vd->vdec_msg_list_free); + ret = 1; + break; + } + spin_unlock_irqrestore(&vd->vdec_list_lock, flags); + + if (vd->close_decode) + ret = 1; + + return ret; +} + +static void vdec_put_msg(struct vdec_data *vd, struct vdec_msg *msg) +{ + struct vdec_msg_list *l; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&vd->vdec_list_lock, flags); + list_for_each_entry(l, &vd->vdec_msg_list_free, list) { + memcpy(&l->vdec_msg, msg, sizeof(struct vdec_msg)); + list_del(&l->list); + list_add(&l->list, &vd->vdec_msg_list_head); + found = 1; + break; + } + spin_unlock_irqrestore(&vd->vdec_list_lock, flags); + + if (found) + wake_up(&vd->vdec_msg_evt); + else + pr_err("vdec_put_msg can't find free list!\n"); +} + +static struct vdec_mem_list *vdec_get_mem_from_list(struct vdec_data *vd, + u32 pmem_id, u32 buf_type) +{ + struct vdec_mem_list *l; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&vd->vdec_mem_list_lock, flags); + list_for_each_entry(l, &vd->vdec_mem_list_head, list) { + if (l->mem.buf_type == buf_type && l->mem.id == pmem_id) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags); + + if (found) + return l; + else + return NULL; + +} + +static int vdec_initialize(struct vdec_data *vd, void *argp) +{ + struct vdec_config_sps vdec_cfg_sps; + struct vdec_init_cfg vi_cfg; + struct vdec_buf_req vdec_buf_req; + struct u8 *header; + int ret = 0; + + ret = copy_from_user(&vdec_cfg_sps, + &((struct vdec_init *)argp)->sps_cfg, + sizeof(vdec_cfg_sps)); + + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + vi_cfg.decode_done_evt = VDEC_ASYNCMSG_DECODE_DONE; + vi_cfg.reuse_frame_evt = VDEC_ASYNCMSG_REUSE_FRAME; + memcpy(&vi_cfg.cfg, &vdec_cfg_sps.cfg, sizeof(struct vdec_config)); + + header = kmalloc(vdec_cfg_sps.seq.len, GFP_KERNEL); + if (!header) { + pr_err("%s: kmalloc failed\n", __func__); + return -ENOMEM; + } + + ret = copy_from_user(header, + ((struct vdec_init *)argp)->sps_cfg.seq.header, + vdec_cfg_sps.seq.len); + + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + kfree(header); + return ret; + } + + TRACE("vi_cfg: handle=%p fourcc=0x%x w=%d h=%d order=%d notify_en=%d " + "vc1_rb=%d h264_sd=%d h264_nls=%d pp_flag=%d fruc_en=%d\n", + vd->vdec_handle, vi_cfg.cfg.fourcc, vi_cfg.cfg.width, + vi_cfg.cfg.height, vi_cfg.cfg.order, vi_cfg.cfg.notify_enable, + vi_cfg.cfg.vc1_rowbase, vi_cfg.cfg.h264_startcode_detect, + vi_cfg.cfg.h264_nal_len_size, vi_cfg.cfg.postproc_flag, + vi_cfg.cfg.fruc_enable); + ret = dal_call_f13(vd->vdec_handle, VDEC_DALRPC_INITIALIZE, + &vi_cfg, sizeof(vi_cfg), + header, vdec_cfg_sps.seq.len, + &vdec_buf_req, sizeof(vdec_buf_req)); + + kfree(header); + + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, ret); + else + ret = copy_to_user(((struct vdec_init *)argp)->buf_req, + &vdec_buf_req, sizeof(vdec_buf_req)); + + vd->close_decode = 0; + return ret; +} + +static int vdec_setbuffers(struct vdec_data *vd, void *argp) +{ + struct vdec_buffer vmem; + struct vdec_mem_list *l; + unsigned long vstart; + unsigned long flags; + struct { + uint32_t size; + struct vdec_buf_info buf; + } rpc; + uint32_t res; + + int ret = 0; + + vd->mem_initialized = 0; + + ret = copy_from_user(&vmem, argp, sizeof(vmem)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + l = kzalloc(sizeof(struct vdec_mem_list), GFP_KERNEL); + if (!l) { + pr_err("%s: kzalloc failed!\n", __func__); + return -ENOMEM; + } + + l->mem.id = vmem.pmem_id; + l->mem.buf_type = vmem.buf.buf_type; + + ret = get_pmem_file(l->mem.id, &l->mem.phys_addr, &vstart, + &l->mem.len, &l->mem.file); + if (ret) { + pr_err("%s: get_pmem_fd failed\n", __func__); + goto err_get_pmem_file; + } + + TRACE("pmem_id=%d (phys=0x%08lx len=0x%lx) buftype=%d num_buf=%d " + "islast=%d src_id=%d offset=0x%08x size=0x%x\n", + vmem.pmem_id, l->mem.phys_addr, l->mem.len, + vmem.buf.buf_type, vmem.buf.num_buf, vmem.buf.islast, + vmem.buf.region.src_id, vmem.buf.region.offset, + vmem.buf.region.size); + + /* input buffers */ + if ((vmem.buf.region.offset + vmem.buf.region.size) > l->mem.len) { + pr_err("%s: invalid input buffer offset!\n", __func__); + ret = -EINVAL; + goto err_bad_offset; + + } + vmem.buf.region.offset += l->mem.phys_addr; + + rpc.size = sizeof(vmem.buf); + memcpy(&rpc.buf, &vmem.buf, sizeof(struct vdec_buf_info)); + + + ret = dal_call(vd->vdec_handle, VDEC_DALRPC_SETBUFFERS, 5, + &rpc, sizeof(rpc), &res, sizeof(res)); + + if (ret < 4) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + ret = -EIO; + goto err_dal_call; + } + + spin_lock_irqsave(&vd->vdec_mem_list_lock, flags); + list_add(&l->list, &vd->vdec_mem_list_head); + spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags); + + vd->mem_initialized = 1; + return ret; + +err_dal_call: +err_bad_offset: + put_pmem_file(l->mem.file); +err_get_pmem_file: + kfree(l); + return ret; +} + +static int vdec_queue(struct vdec_data *vd, void *argp) +{ + struct { + uint32_t size; + struct vdec_input_buf_info buf_info; + uint32_t osize; + } rpc; + struct vdec_mem_list *l; + struct { + uint32_t result; + uint32_t size; + struct vdec_queue_status status; + } rpc_res; + + u32 pmem_id; + int ret = 0; + + if (!vd->mem_initialized) { + pr_err("%s: memory is not being initialized!\n", __func__); + return -EPERM; + } + + ret = copy_from_user(&rpc.buf_info, + &((struct vdec_input_buf *)argp)->buffer, + sizeof(rpc.buf_info)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + ret = copy_from_user(&pmem_id, + &((struct vdec_input_buf *)argp)->pmem_id, + sizeof(u32)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + l = vdec_get_mem_from_list(vd, pmem_id, VDEC_BUFFER_TYPE_INPUT); + + if (NULL == l) { + pr_err("%s: not able to find the buffer from list\n", __func__); + return -EPERM; + } + + if ((rpc.buf_info.size + rpc.buf_info.offset) >= l->mem.len) { + pr_err("%s: invalid queue buffer offset!\n", __func__); + return -EINVAL; + } + + rpc.buf_info.offset += l->mem.phys_addr; + rpc.size = sizeof(struct vdec_input_buf_info); + rpc.osize = sizeof(struct vdec_queue_status); + + ret = dal_call(vd->vdec_handle, VDEC_DALRPC_QUEUE, 8, + &rpc, sizeof(rpc), &rpc_res, sizeof(rpc_res)); + if (ret < 4) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + ret = -EIO; + } + return ret; +} + +static int vdec_reuse_framebuffer(struct vdec_data *vd, void *argp) +{ + u32 buf_id; + int ret = 0; + + ret = copy_from_user(&buf_id, argp, sizeof(buf_id)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_REUSEFRAMEBUFFER, + buf_id); + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, ret); + + return ret; +} + +static int vdec_flush(struct vdec_data *vd, void *argp) +{ + u32 flush_type; + int ret = 0; + + ret = copy_from_user(&flush_type, argp, sizeof(flush_type)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + TRACE("flush_type=%d\n", flush_type); + ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_FLUSH, flush_type); + if (ret) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + return ret; + } + + return ret; +} + +static int vdec_close(struct vdec_data *vd, void *argp) +{ + struct vdec_mem_list *l; + int ret = 0; + + pr_info("q6vdec_close()\n"); + vd->close_decode = 1; + wake_up(&vd->vdec_msg_evt); + ret = dal_call_f0(vd->vdec_handle, DAL_OP_CLOSE, 0); + if (ret) + pr_err("%s: failed to close daldevice (%d)\n", __func__, ret); + + if (vd->mem_initialized) { + list_for_each_entry(l, &vd->vdec_mem_list_head, list) + put_pmem_file(l->mem.file); + } + + return ret; +} +static int vdec_getdecattributes(struct vdec_data *vd, void *argp) +{ + struct { + uint32_t status; + uint32_t size; + struct vdec_dec_attributes dec_attr; + } rpc; + uint32_t inp; + int ret = 0; + inp = sizeof(struct vdec_dec_attributes); + + ret = dal_call(vd->vdec_handle, VDEC_DALRPC_GETDECATTRIBUTES, 9, + &inp, sizeof(inp), &rpc, sizeof(rpc)); + if (ret < 4 || rpc.size != sizeof(struct vdec_dec_attributes)) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + ret = -EIO; + } else + ret = + copy_to_user(((struct vdec_dec_attributes *)argp), + &rpc.dec_attr, sizeof(rpc.dec_attr)); + return ret; +} + +static int vdec_freebuffers(struct vdec_data *vd, void *argp) +{ + struct vdec_buffer vmem; + struct vdec_mem_list *l; + struct { + uint32_t size; + struct vdec_buf_info buf; + } rpc; + uint32_t res; + + int ret = 0; + + if (!vd->mem_initialized) { + pr_err("%s: memory is not being initialized!\n", __func__); + return -EPERM; + } + + ret = copy_from_user(&vmem, argp, sizeof(vmem)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + l = vdec_get_mem_from_list(vd, vmem.pmem_id, vmem.buf.buf_type); + + if (NULL == l) { + pr_err("%s: not able to find the buffer from list\n", __func__); + return -EPERM; + } + + /* input buffers */ + if ((vmem.buf.region.offset + vmem.buf.region.size) > l->mem.len) { + pr_err("%s: invalid input buffer offset!\n", __func__); + return -EINVAL; + + } + vmem.buf.region.offset += l->mem.phys_addr; + + rpc.size = sizeof(vmem.buf); + memcpy(&rpc.buf, &vmem.buf, sizeof(struct vdec_buf_info)); + + ret = dal_call(vd->vdec_handle, VDEC_DALRPC_FREEBUFFERS, 5, + &rpc, sizeof(rpc), &res, sizeof(res)); + if (ret < 4) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + } + + return ret; +} +static long vdec_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct vdec_data *vd = file->private_data; + void __user *argp = (void __user *)arg; + int ret = 0; + + if (!vd->running) + return -EPERM; + + switch (cmd) { + case VDEC_IOCTL_INITIALIZE: + ret = vdec_initialize(vd, argp); + break; + + case VDEC_IOCTL_SETBUFFERS: + ret = vdec_setbuffers(vd, argp); + break; + + case VDEC_IOCTL_QUEUE: + TRACE("VDEC_IOCTL_QUEUE (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_queue(vd, argp); + break; + + case VDEC_IOCTL_REUSEFRAMEBUFFER: + TRACE("VDEC_IOCTL_REUSEFRAMEBUFFER (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_reuse_framebuffer(vd, argp); + break; + + case VDEC_IOCTL_FLUSH: + ret = vdec_flush(vd, argp); + break; + + case VDEC_IOCTL_EOS: + TRACE("VDEC_IOCTL_EOS (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_SIGEOFSTREAM, 0); + if (ret) + pr_err("%s: remote function failed (%d)\n", + __func__, ret); + break; + + case VDEC_IOCTL_GETMSG: + TRACE("VDEC_IOCTL_GETMSG (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + wait_event_interruptible(vd->vdec_msg_evt, + vdec_get_msg(vd, argp)); + + if (vd->close_decode) + ret = -EINTR; + break; + + case VDEC_IOCTL_CLOSE: + ret = vdec_close(vd, argp); + break; + + case VDEC_IOCTL_GETDECATTRIBUTES: + TRACE("VDEC_IOCTL_GETDECATTRIBUTES (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_getdecattributes(vd, argp); + + if (ret) + pr_err("%s: remote function failed (%d)\n", + __func__, ret); + break; + + case VDEC_IOCTL_FREEBUFFERS: + TRACE("VDEC_IOCTL_FREEBUFFERS (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_freebuffers(vd, argp); + + if (ret) + pr_err("%s: remote function failed (%d)\n", + __func__, ret); + break; + + default: + pr_err("%s: invalid ioctl!\n", __func__); + ret = -EINVAL; + break; + } + + TRACE("ioctl done (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + + return ret; +} + +static void vdec_dcdone_handler(struct vdec_data *vd, void *frame, + uint32_t frame_size) +{ + struct vdec_msg msg; + struct vdec_mem_list *l; + unsigned long flags; + int found = 0; + + if (frame_size != sizeof(struct vdec_frame_info)) { + pr_warning("%s: msg size mismatch %d != %d\n", __func__, + frame_size, sizeof(struct vdec_frame_info)); + return; + } + + memcpy(&msg.vfr_info, (struct vdec_frame_info *)frame, + sizeof(struct vdec_frame_info)); + + if (msg.vfr_info.status == VDEC_FRAME_DECODE_OK) { + spin_lock_irqsave(&vd->vdec_mem_list_lock, flags); + list_for_each_entry(l, &vd->vdec_mem_list_head, list) { + if ((l->mem.buf_type == VDEC_BUFFER_TYPE_OUTPUT) && + (msg.vfr_info.offset >= l->mem.phys_addr) && + (msg.vfr_info.offset < + (l->mem.phys_addr + l->mem.len))) { + found = 1; + msg.vfr_info.offset -= l->mem.phys_addr; + msg.vfr_info.data2 = l->mem.id; + break; + } + } + spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags); + } + + if (found || (msg.vfr_info.status != VDEC_FRAME_DECODE_OK)) { + msg.id = VDEC_MSG_FRAMEDONE; + vdec_put_msg(vd, &msg); + } else { + pr_err("%s: invalid phys addr = 0x%x\n", + __func__, msg.vfr_info.offset); + } + +} + +static void vdec_reuseibuf_handler(struct vdec_data *vd, void *bufstat, + uint32_t bufstat_size) +{ + struct vdec_buffer_status *vdec_bufstat; + struct vdec_msg msg; + + /* TODO: how do we signal the client? If they are waiting on a + * message in an ioctl, they may block forever */ + if (bufstat_size != sizeof(struct vdec_buffer_status)) { + pr_warning("%s: msg size mismatch %d != %d\n", __func__, + bufstat_size, sizeof(struct vdec_buffer_status)); + return; + } + vdec_bufstat = (struct vdec_buffer_status *)bufstat; + msg.id = VDEC_MSG_REUSEINPUTBUFFER; + msg.buf_id = vdec_bufstat->data; + vdec_put_msg(vd, &msg); +} + +static void callback(void *data, int len, void *cookie) +{ + struct vdec_data *vd = (struct vdec_data *)cookie; + uint32_t *tmp = (uint32_t *) data; + + if (!vd->mem_initialized) { + pr_err("%s:memory not initialize but callback called!\n", + __func__); + return; + } + + TRACE("vdec_async: tmp=0x%08x 0x%08x 0x%08x\n", tmp[0], tmp[1], tmp[2]); + switch (tmp[0]) { + case VDEC_ASYNCMSG_DECODE_DONE: + vdec_dcdone_handler(vd, &tmp[3], tmp[2]); + break; + case VDEC_ASYNCMSG_REUSE_FRAME: + vdec_reuseibuf_handler(vd, &tmp[3], tmp[2]); + break; + default: + pr_err("%s: Unknown async message from DSP id=0x%08x sz=%u\n", + __func__, tmp[0], tmp[2]); + } +} +static int vdec_open(struct inode *inode, struct file *file) +{ + int ret; + int i; + struct vdec_msg_list *l; + struct vdec_data *vd; + + pr_info("q6vdec_open()\n"); + mutex_lock(&vdec_ref_lock); + if (ref_cnt > 0) { + pr_err("%s: Instance alredy running\n", __func__); + mutex_unlock(&vdec_ref_lock); + return -ENOMEM; + } + ref_cnt++; + mutex_unlock(&vdec_ref_lock); + vd = kmalloc(sizeof(struct vdec_data), GFP_KERNEL); + if (!vd) { + pr_err("%s: kmalloc failed\n", __func__); + ret = -ENOMEM; + goto vdec_open_err_handle_vd; + } + file->private_data = vd; + + vd->mem_initialized = 0; + INIT_LIST_HEAD(&vd->vdec_msg_list_head); + INIT_LIST_HEAD(&vd->vdec_msg_list_free); + INIT_LIST_HEAD(&vd->vdec_mem_list_head); + init_waitqueue_head(&vd->vdec_msg_evt); + + spin_lock_init(&vd->vdec_list_lock); + spin_lock_init(&vd->vdec_mem_list_lock); + for (i = 0; i < VDEC_MSG_MAX; i++) { + l = kzalloc(sizeof(struct vdec_msg_list), GFP_KERNEL); + if (!l) { + pr_err("%s: kzalloc failed!\n", __func__); + ret = -ENOMEM; + goto vdec_open_err_handle_list; + } + list_add(&l->list, &vd->vdec_msg_list_free); + } + + vd->vdec_handle = dal_attach(DALDEVICEID_VDEC_DEVICE, + DALDEVICEID_VDEC_PORTNAME, + callback, vd); + + if (!vd->vdec_handle) { + pr_err("%s: failed to attach \n", __func__); + ret = -EIO; + goto vdec_open_err_handle_list; + } + + vd->running = 1; + prevent_sleep(); + return 0; + +vdec_open_err_handle_list: + { + struct vdec_msg_list *l, *n; + list_for_each_entry_safe(l, n, &vd->vdec_msg_list_free, list) { + list_del(&l->list); + kfree(l); + } + } +vdec_open_err_handle_vd: + kfree(vd); + return ret; +} + +static int vdec_release(struct inode *inode, struct file *file) +{ + int ret; + struct vdec_msg_list *l, *n; + struct vdec_mem_list *m, *k; + struct vdec_data *vd = file->private_data; + + vd->running = 0; + wake_up_all(&vd->vdec_msg_evt); + + if (!vd->close_decode) + vdec_close(vd, NULL); + + ret = dal_detach(vd->vdec_handle); + if (ret) + printk(KERN_INFO "%s: failed to detach (%d)\n", __func__, ret); + + list_for_each_entry_safe(l, n, &vd->vdec_msg_list_free, list) { + list_del(&l->list); + kfree(l); + } + + list_for_each_entry_safe(l, n, &vd->vdec_msg_list_head, list) { + list_del(&l->list); + kfree(l); + } + + list_for_each_entry_safe(m, k, &vd->vdec_mem_list_head, list) { + list_del(&m->list); + kfree(m); + } + mutex_lock(&vdec_ref_lock); + BUG_ON(ref_cnt <= 0); + ref_cnt--; + mutex_unlock(&vdec_ref_lock); + kfree(vd); + allow_sleep(); + return 0; +} + +static const struct file_operations vdec_fops = { + .owner = THIS_MODULE, + .open = vdec_open, + .release = vdec_release, + .unlocked_ioctl = vdec_ioctl, +}; + +static int __init vdec_init(void) +{ + struct device *class_dev; + int rc = 0; + + wake_lock_init(&idlelock, WAKE_LOCK_IDLE, "vdec_idle"); + wake_lock_init(&wakelock, WAKE_LOCK_SUSPEND, "vdec_suspend"); + + rc = alloc_chrdev_region(&vdec_device_no, 0, 1, "vdec"); + if (rc < 0) { + pr_err("%s: alloc_chrdev_region failed %d\n", __func__, rc); + return rc; + } + + driver_class = class_create(THIS_MODULE, "vdec"); + if (IS_ERR(driver_class)) { + rc = -ENOMEM; + pr_err("%s: class_create failed %d\n", __func__, rc); + goto vdec_init_err_unregister_chrdev_region; + } + class_dev = device_create(driver_class, NULL, + vdec_device_no, NULL, "vdec"); + if (!class_dev) { + pr_err("%s: class_device_create failed %d\n", __func__, rc); + rc = -ENOMEM; + goto vdec_init_err_class_destroy; + } + + cdev_init(&vdec_cdev, &vdec_fops); + vdec_cdev.owner = THIS_MODULE; + rc = cdev_add(&vdec_cdev, MKDEV(MAJOR(vdec_device_no), 0), 1); + + if (rc < 0) { + pr_err("%s: cdev_add failed %d\n", __func__, rc); + goto vdec_init_err_class_device_destroy; + } + + return 0; + +vdec_init_err_class_device_destroy: + device_destroy(driver_class, vdec_device_no); +vdec_init_err_class_destroy: + class_destroy(driver_class); +vdec_init_err_unregister_chrdev_region: + unregister_chrdev_region(vdec_device_no, 1); + return rc; +} + +static void __exit vdec_exit(void) +{ + device_destroy(driver_class, vdec_device_no); + class_destroy(driver_class); + unregister_chrdev_region(vdec_device_no, 1); +} + +MODULE_DESCRIPTION("video decoder driver for QSD platform"); +MODULE_VERSION("2.00"); + +module_init(vdec_init); +module_exit(vdec_exit); diff --git a/arch/arm/mach-msm/qdsp6_1550/msm_q6venc.c b/arch/arm/mach-msm/qdsp6_1550/msm_q6venc.c new file mode 100644 index 00000000..f32f2825 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/msm_q6venc.c @@ -0,0 +1,637 @@ +/* + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009, Google Inc. + * + * Original authors: Code Aurora Forum + * Major cleanup: Dima Zavin + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora Forum nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +//#define DEBUG 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dal.h" + +#define DALDEVICEID_VENC_DEVICE 0x0200002D +#define DALDEVICEID_VENC_PORTNAME "DSP_DAL_AQ_VID" + +enum { + VENC_DALRPC_INITIALIZE = DAL_OP_FIRST_DEVICE_API, + VENC_DALRPC_SET_CB_CHANNEL, + VENC_DALRPC_ENCODE, + VENC_DALRPC_INTRA_REFRESH, + VENC_DALRPC_RC_CONFIG, + VENC_DALRPC_ENCODE_CONFIG, + VENC_DALRPC_STOP, +}; + +struct callback_event_data { + u32 data_notify_event; + u32 enc_cb_handle; + u32 empty_input_buffer_event; +}; + +struct buf_info { + unsigned long paddr; + unsigned long vaddr; + struct file *file; + struct venc_buf venc_buf; +}; + +#define VENC_MAX_BUF_NUM 15 +#define RLC_MAX_BUF_NUM 2 +#define BITS_PER_PIXEL 12 +#define PIXELS_PER_MACROBLOCK 16 + +#define VENC_CB_EVENT_ID 0xd0e4c0de + +struct q6venc_dev { + struct dal_client *venc; + struct callback_event_data cb_ev_data; + bool stop_encode; + struct buf_info rlc_bufs[RLC_MAX_BUF_NUM]; + unsigned int rlc_buf_index; + unsigned int rlc_buf_len; + unsigned int enc_buf_size; + struct buf_info enc_bufs[VENC_MAX_BUF_NUM]; + unsigned int num_enc_bufs; + wait_queue_head_t encode_wq; + + /* protects all state in q6venc_dev except for cb stuff below */ + struct mutex lock; + + /* protects encode_done and done_frame inside the callback */ + spinlock_t done_lock; + struct frame_type done_frame; + bool encode_done; +}; + +static int get_buf_info(struct buf_info *buf_info, struct venc_buf *venc_buf) +{ + unsigned long len; + unsigned long vaddr; + unsigned long paddr; + struct file *file; + int ret; + + ret = get_pmem_file(venc_buf->fd, &paddr, &vaddr, &len, &file); + if (ret) { + pr_err("%s: get_pmem_file failed for fd=%d offset=%ld\n", + __func__, venc_buf->fd, venc_buf->offset); + return ret; + } else if (venc_buf->offset >= len) { + /* XXX: we really should check venc_buf->size too, but userspace + * sometimes leaves this uninitialized (in encode ioctl) */ + pr_err("%s: invalid offset/size (%ld + %ld > %ld) for fd=%d\n", + __func__, venc_buf->offset, venc_buf->size, len, + venc_buf->fd); + put_pmem_file(file); + return -EINVAL; + } + + buf_info->file = file; + buf_info->paddr = paddr + venc_buf->offset; + buf_info->vaddr = vaddr; + memcpy(&buf_info->venc_buf, venc_buf, sizeof(struct venc_buf)); + return 0; +} + +static void put_buf_info(struct buf_info *buf_info) +{ + if (!buf_info || !buf_info->file) + return; + put_pmem_file(buf_info->file); + buf_info->file = NULL; +} + +static void q6venc_callback(void *context, void *data, uint32_t len) +{ + struct q6venc_dev *q6venc = context; + struct q6_frame_type *q6frame = data; + struct buf_info *rlc_buf; + unsigned long flags; + int i; + + pr_debug("%s \n", __func__); + + spin_lock_irqsave(&q6venc->done_lock, flags); + q6venc->encode_done = true; + for (i = 0; i < RLC_MAX_BUF_NUM; ++i) { + rlc_buf = &q6venc->rlc_bufs[i]; + if (rlc_buf->paddr == q6frame->frame_addr) + goto frame_found; + } + + pr_err("%s: got incorrect phy address 0x%08x from q6 \n", __func__, + q6frame->frame_addr); + q6venc->done_frame.q6_frame_type.frame_len = 0; + wake_up_interruptible(&q6venc->encode_wq); + goto done; + +frame_found: + memcpy(&q6venc->done_frame.frame_addr, &rlc_buf->venc_buf, + sizeof(struct venc_buf)); + memcpy(&q6venc->done_frame.q6_frame_type, q6frame, + sizeof(struct q6_frame_type)); + + dmac_inv_range((const void *)q6venc->rlc_bufs[i].vaddr, + (const void *)(q6venc->rlc_bufs[i].vaddr + + q6venc->rlc_buf_len)); + + wake_up_interruptible(&q6venc->encode_wq); + +done: + spin_unlock_irqrestore(&q6venc->done_lock, flags); +} + +static void callback(void *data, int len, void *cookie) +{ + struct q6venc_dev *ve = (struct q6venc_dev *)cookie; + uint32_t *tmp = (uint32_t *) data; + + if (tmp[0] == VENC_CB_EVENT_ID) + q6venc_callback(ve, &tmp[3], tmp[2]); + else + pr_err("%s: Unknown callback received for %p\n", __func__, ve); +} + +static int q6venc_open(struct inode *inode, struct file *file) +{ + struct q6venc_dev *q6venc; + int err; + + q6venc = kzalloc(sizeof(struct q6venc_dev), GFP_KERNEL); + if (!q6venc) { + pr_err("%s: Unable to allocate memory for q6venc_dev\n", + __func__); + return -ENOMEM; + } + + file->private_data = q6venc; + + init_waitqueue_head(&q6venc->encode_wq); + mutex_init(&q6venc->lock); + spin_lock_init(&q6venc->done_lock); + + q6venc->venc = dal_attach(DALDEVICEID_VENC_DEVICE, + DALDEVICEID_VENC_PORTNAME, + callback, q6venc); + if (!q6venc->venc) { + pr_err("%s: dal_attach failed\n", __func__); + err = -EIO; + goto err_dal_attach; + } + + q6venc->cb_ev_data.enc_cb_handle = VENC_CB_EVENT_ID; + err = dal_call_f5(q6venc->venc, VENC_DALRPC_SET_CB_CHANNEL, + &q6venc->cb_ev_data, sizeof(q6venc->cb_ev_data)); + if (err) { + pr_err("%s: set_cb_channgel failed\n", __func__); + goto err_dal_call_set_cb; + } + + pr_info("%s() handle=%p enc_cb=%08x\n", __func__, q6venc->venc, + q6venc->cb_ev_data.enc_cb_handle); + + return 0; + +err_dal_call_set_cb: + dal_detach(q6venc->venc); +err_dal_attach: + file->private_data = NULL; + mutex_destroy(&q6venc->lock); + kfree(q6venc); + return err; +} + +static int q6venc_release(struct inode *inode, struct file *file) +{ + struct q6venc_dev *q6venc; + int id, err; + + q6venc = file->private_data; + file->private_data = NULL; + + pr_info("q6venc_close() handle=%p\n", q6venc->venc); + for (id = 0; id < q6venc->num_enc_bufs; id++) + put_buf_info(&q6venc->enc_bufs[id]); + put_buf_info(&q6venc->rlc_bufs[0]); + put_buf_info(&q6venc->rlc_bufs[1]); + + if(!q6venc->stop_encode) + { + err = dal_call_f0(q6venc->venc, VENC_DALRPC_STOP, 1); + if (err) + pr_err("%s: dal_rpc STOP call failed\n", __func__); + q6venc->stop_encode = true; + } + + dal_detach(q6venc->venc); + mutex_destroy(&q6venc->lock); + kfree(q6venc); + return 0; +} + +static int q6_config_encode(struct q6venc_dev *q6venc, uint32_t type, + struct init_config *init_config) +{ + struct q6_init_config *q6_init_config = &init_config->q6_init_config; + int ret; + int i; + + mutex_lock(&q6venc->lock); + + if (q6venc->num_enc_bufs != 0) { + pr_err("%s: multiple sessions not supported\n", __func__); + ret = -EBUSY; + goto err_busy; + } + + ret = get_buf_info(&q6venc->enc_bufs[0], &init_config->ref_frame_buf1); + if (ret) { + pr_err("%s: can't get ref_frame_buf1\n", __func__); + goto err_get_ref_frame_buf1; + } + + ret = get_buf_info(&q6venc->enc_bufs[1], &init_config->ref_frame_buf2); + if (ret) { + pr_err("%s: can't get ref_frame_buf2\n", __func__); + goto err_get_ref_frame_buf2; + } + + ret = get_buf_info(&q6venc->rlc_bufs[0], &init_config->rlc_buf1); + if (ret) { + pr_err("%s: can't get rlc_buf1\n", __func__); + goto err_get_rlc_buf1; + } + + ret = get_buf_info(&q6venc->rlc_bufs[1], &init_config->rlc_buf2); + if (ret) { + pr_err("%s: can't get rlc_buf2\n", __func__); + goto err_get_rlc_buf2; + } + q6venc->rlc_buf_len = 2 * q6_init_config->rlc_buf_length; + q6venc->num_enc_bufs = 2; + + q6venc->enc_buf_size = + (q6_init_config->enc_frame_width_inmb * PIXELS_PER_MACROBLOCK) * + (q6_init_config->enc_frame_height_inmb * PIXELS_PER_MACROBLOCK) * + BITS_PER_PIXEL / 8; + + q6_init_config->ref_frame_buf1_phy = q6venc->enc_bufs[0].paddr; + q6_init_config->ref_frame_buf2_phy = q6venc->enc_bufs[1].paddr; + q6_init_config->rlc_buf1_phy = q6venc->rlc_bufs[0].paddr; + q6_init_config->rlc_buf2_phy = q6venc->rlc_bufs[1].paddr; + + // The DSP may use the rlc_bufs during initialization, + for (i=0; irlc_bufs[i].vaddr, + (const void *)(q6venc->rlc_bufs[i].vaddr + + q6venc->rlc_buf_len)); + } + + ret = dal_call_f5(q6venc->venc, type, q6_init_config, + sizeof(struct q6_init_config)); + if (ret) { + pr_err("%s: rpc failed \n", __func__); + goto err_dal_rpc_init; + } + mutex_unlock(&q6venc->lock); + return 0; + +err_dal_rpc_init: + q6venc->num_enc_bufs = 0; + put_pmem_file(q6venc->rlc_bufs[1].file); +err_get_rlc_buf2: + put_pmem_file(q6venc->rlc_bufs[0].file); +err_get_rlc_buf1: + put_pmem_file(q6venc->enc_bufs[1].file); +err_get_ref_frame_buf2: + put_pmem_file(q6venc->enc_bufs[0].file); +err_get_ref_frame_buf1: +err_busy: + mutex_unlock(&q6venc->lock); + return ret; +} + +static int q6_encode(struct q6venc_dev *q6venc, struct encode_param *enc_param) +{ + struct q6_encode_param *q6_param = &enc_param->q6_encode_param; + struct file *file; + struct buf_info *buf; + int i; + int ret; + int rlc_buf_index; + + pr_debug("y_addr fd=%d offset=0x%08lx uv_offset=0x%08lx\n", + enc_param->y_addr.fd, enc_param->y_addr.offset, + enc_param->uv_offset); + + file = fget(enc_param->y_addr.fd); + if (!file) { + pr_err("%s: invalid encode buffer fd %d\n", __func__, + enc_param->y_addr.fd); + return -EBADF; + } + + mutex_lock(&q6venc->lock); + + for (i = 0; i < q6venc->num_enc_bufs; i++) { + buf = &q6venc->enc_bufs[i]; + if (buf->file == file + && buf->venc_buf.offset == enc_param->y_addr.offset) + break; + } + + if (i == q6venc->num_enc_bufs) { + if (q6venc->num_enc_bufs == VENC_MAX_BUF_NUM) { + pr_err("%s: too many input buffers\n", __func__); + ret = -ENOMEM; + goto done; + } + + buf = &q6venc->enc_bufs[q6venc->num_enc_bufs]; + ret = get_buf_info(buf, &enc_param->y_addr); + if (ret) { + pr_err("%s: can't get encode buffer\n", __func__); + ret = -EINVAL; + goto done; + } + + if (!IS_ALIGNED(buf->paddr, PAGE_SIZE)) { + pr_err("%s: input buffer not 4k aligned\n", __func__); + put_buf_info(buf); + ret = -EINVAL; + goto done; + } + q6venc->num_enc_bufs++; + } + + /* We must invalidate the buffer that the DSP will write to + * to ensure that a dirty cache line doesn't get flushed on + * top of the data that the DSP is writing. + * Unfortunately, we have to predict which rlc_buf index the + * DSP is going to write to. We assume it will write to buf + * 0 the first time we call q6_encode, and alternate afterwards + * */ + rlc_buf_index = q6venc->rlc_buf_index; + dmac_inv_range((const void *)q6venc->rlc_bufs[rlc_buf_index].vaddr, + (const void *)(q6venc->rlc_bufs[rlc_buf_index].vaddr + + q6venc->rlc_buf_len)); + q6venc->rlc_buf_index = (q6venc->rlc_buf_index + 1) % RLC_MAX_BUF_NUM; + + q6_param->luma_addr = buf->paddr; + q6_param->chroma_addr = q6_param->luma_addr + enc_param->uv_offset; + pr_debug("luma_addr=0x%08x chroma_addr=0x%08x\n", q6_param->luma_addr, + q6_param->chroma_addr); + + /* Ideally, each ioctl that passed in a data buffer would include the size + * of the input buffer, so we can properly flush the cache on it. Since + * userspace does not fill in the size fields, we have to assume the size + * based on the encoder configuration for now. + */ + flush_pmem_file(buf->file, enc_param->y_addr.offset, + q6venc->enc_buf_size); + + ret = dal_call_f5(q6venc->venc, VENC_DALRPC_ENCODE, q6_param, + sizeof(struct q6_encode_param)); + if (ret) { + pr_err("%s: encode rpc failed\n", __func__); + goto done; + } + + ret = 0; + +done: + mutex_unlock(&q6venc->lock); + fput(file); + return ret; +} + +static int q6venc_ioctl(struct inode *inode, struct file *file, + unsigned cmd, unsigned long arg) +{ + struct q6venc_dev *q6venc = file->private_data; + struct init_config config; + struct encode_param encode_param; + struct intra_refresh intra_refresh; + struct rc_config rc_config; + struct frame_type frame_done; + unsigned int id; + unsigned long flags; + int err = 0; + + if (!q6venc) { + pr_err("%s: file has no private data\n", __func__); + return -ENODEV; + } + + pr_debug("%s\n", __func__); + + switch (cmd) { + case VENC_IOCTL_INITIALIZE: + pr_debug("%s: VENC_IOCTL_INITIALIZE\n", __func__); + if (copy_from_user(&config, (void __user *)arg, sizeof(config))) + return -EFAULT; + err = q6_config_encode(q6venc, VENC_DALRPC_INITIALIZE, &config); + break; + + case VENC_IOCTL_ENCODE_CONFIG: + pr_debug("%s: VENC_IOCTL_ENCODE_CONFIG\n", __func__); + if (copy_from_user(&config, (void __user *)arg, sizeof(config))) + return -EFAULT; + + err = q6_config_encode(q6venc, VENC_DALRPC_ENCODE_CONFIG, + &config); + break; + + case VENC_IOCTL_ENCODE: + pr_debug("%s: VENC_IOCTL_ENCODE\n", __func__); + if (copy_from_user(&encode_param, (void __user *)arg, + sizeof(encode_param))) + return -EFAULT; + err = q6_encode(q6venc, &encode_param); + break; + + case VENC_IOCTL_INTRA_REFRESH: + pr_debug("%s: VENC_IOCTL_INTRA_REFRESH\n", __func__); + if (copy_from_user(&intra_refresh, (void __user *)arg, + sizeof(intra_refresh))) + return -EFAULT; + + mutex_lock(&q6venc->lock); + err = dal_call_f5(q6venc->venc, VENC_DALRPC_INTRA_REFRESH, + &intra_refresh, sizeof(struct intra_refresh)); + mutex_unlock(&q6venc->lock); + if (err) + pr_err("%s: intra_refresh rpc failed\n", __func__); + break; + + case VENC_IOCTL_RC_CONFIG: + pr_debug("%s: VENC_IOCTL_RC_CONFIG\n", __func__); + if (copy_from_user(&rc_config, (void __user *)arg, + sizeof(rc_config))) + return -EFAULT; + + mutex_lock(&q6venc->lock); + err = dal_call_f5(q6venc->venc, VENC_DALRPC_RC_CONFIG, + &rc_config, sizeof(rc_config)); + mutex_unlock(&q6venc->lock); + if (err) + pr_err("%s: dal_call_f5 failed\n", __func__); + break; + + case VENC_IOCTL_STOP: + pr_debug("%s: VENC_IOCTL_STOP\n", __func__); + + mutex_lock(&q6venc->lock); + err = dal_call_f0(q6venc->venc, VENC_DALRPC_STOP, 1); + if (err) + pr_err("%s: dal_rpc STOP call failed\n", __func__); + + /* XXX: if the dal call fails we still want to continue to free + * the buffers. Is this correct? */ + for (id = 0; id < q6venc->num_enc_bufs; id++) + put_buf_info(&q6venc->enc_bufs[id]); + put_buf_info(&q6venc->rlc_bufs[0]); + put_buf_info(&q6venc->rlc_bufs[1]); + q6venc->num_enc_bufs = 0; + q6venc->stop_encode = true; + mutex_unlock(&q6venc->lock); + break; + + case VENC_IOCTL_WAIT_FOR_ENCODE: + pr_debug("%s: waiting for encode done event \n", __func__); + err = wait_event_interruptible(q6venc->encode_wq, + (q6venc->encode_done || q6venc->stop_encode)); + if (err < 0) { + err = -ERESTARTSYS; + break; + } + + mutex_lock(&q6venc->lock); + if (q6venc->stop_encode) { + q6venc->stop_encode = false; + mutex_unlock(&q6venc->lock); + pr_debug("%s: Received Stop encode event \n", __func__); + err = -EINTR; + break; + } + + spin_lock_irqsave(&q6venc->done_lock, flags); + if (!q6venc->encode_done) { + spin_unlock_irqrestore(&q6venc->done_lock, flags); + pr_err("%s: encoding not stopped, and is not done.\n", + __func__); + err = -EIO; + break; + } + + memcpy(&frame_done, &q6venc->done_frame, + sizeof(struct frame_type)); + q6venc->encode_done = false; + spin_unlock_irqrestore(&q6venc->done_lock, flags); + mutex_unlock(&q6venc->lock); + + if (frame_done.q6_frame_type.frame_len == 0) { + pr_debug("%s: got incorrect address from q6\n", + __func__); + err = -EIO; + break; + } + + pr_debug("%s: done encoding \n", __func__); + if (copy_to_user((void __user *)arg, &frame_done, + sizeof(struct frame_type))) + err = -EFAULT; + break; + + case VENC_IOCTL_STOP_ENCODE: + pr_debug("%s: Stop encode event \n", __func__); + mutex_lock(&q6venc->lock); + q6venc->stop_encode = true; + wake_up_interruptible(&q6venc->encode_wq); + mutex_unlock(&q6venc->lock); + break; + + default: + err = -ENOTTY; + break; + } + + return err; +} + +static const struct file_operations q6venc_dev_fops = { + .owner = THIS_MODULE, + .open = q6venc_open, + .release = q6venc_release, + .ioctl = q6venc_ioctl, +}; + +static struct miscdevice q6venc_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "q6venc", + .fops = &q6venc_dev_fops, +}; + +static int __init q6venc_init(void) +{ + int rc = 0; + + rc = misc_register(&q6venc_misc); + if (rc) + pr_err("%s: Unable to register q6venc misc device\n", __func__); + return rc; +} + +static void __exit q6venc_exit(void) +{ + misc_deregister(&q6venc_misc); +} + +MODULE_DESCRIPTION("video encoder driver for QSD platform"); +MODULE_VERSION("2.0"); + +module_init(q6venc_init); +module_exit(q6venc_exit); diff --git a/arch/arm/mach-msm/qdsp6_1550/pcm_in.c b/arch/arm/mach-msm/qdsp6_1550/pcm_in.c new file mode 100644 index 00000000..80ce9ddd --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/pcm_in.c @@ -0,0 +1,207 @@ +/* arch/arm/mach-msm/qdsp6/pcm_in.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 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 +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define BUFSZ (4096) +#define DMASZ (BUFSZ * 2) + +static DEFINE_MUTEX(pcm_in_lock); +static uint32_t sample_rate = 8000; +static uint32_t channel_count = 1; +static int pcm_in_opened = 0; + +void audio_client_dump(struct audio_client *ac); + +static long q6_in_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + + switch (cmd) { + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STATS: { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + case AUDIO_START: { + uint32_t acdb_id; + rc = 0; + + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void*) arg, sizeof(acdb_id))) { + rc = -EFAULT; + break; + } + + mutex_lock(&pcm_in_lock); + if (file->private_data) { + rc = -EBUSY; + } else { + file->private_data = q6audio_open_pcm( + BUFSZ, sample_rate, channel_count, AUDIO_FLAG_READ, acdb_id); + if (!file->private_data) + rc = -ENOMEM; + } + mutex_unlock(&pcm_in_lock); + break; + } + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void*) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + sample_rate = config.sample_rate; + channel_count = config.channel_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = sample_rate; + config.channel_count = channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void*) arg, &config, sizeof(config))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + return rc; +} + +static int q6_in_open(struct inode *inode, struct file *file) +{ + int rc; + + pr_info("pcm_in: open\n"); + mutex_lock(&pcm_in_lock); + if (pcm_in_opened) { + pr_err("pcm_in: busy\n"); + rc = -EBUSY; + } else { + pcm_in_opened = 1; + rc = 0; + } + mutex_unlock(&pcm_in_lock); + return rc; +} + +static ssize_t q6_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer; + int res; + + mutex_lock(&pcm_in_lock); + ac = file->private_data; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + if (!wait_event_timeout(ac->wait, (ab->used == 0), 5*HZ)) { + audio_client_dump(ac); + pr_err("pcm_read: timeout. dsp dead?\n"); + //BUG(); + res = -EFAULT; + goto fail; + } + + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } +fail: + res = buf - start; + mutex_unlock(&pcm_in_lock); + + return res; +} + +static int q6_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + mutex_lock(&pcm_in_lock); + if (file->private_data) + rc = q6audio_close(file->private_data); + pcm_in_opened = 0; + mutex_unlock(&pcm_in_lock); + pr_info("pcm_in: release\n"); + return rc; +} + +static struct file_operations q6_in_fops = { + .owner = THIS_MODULE, + .open = q6_in_open, + .read = q6_in_read, + .release = q6_in_release, + .unlocked_ioctl = q6_in_ioctl, +}; + +struct miscdevice q6_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_in", + .fops = &q6_in_fops, +}; + +static int __init q6_in_init(void) { + return misc_register(&q6_in_misc); +} + +device_initcall(q6_in_init); diff --git a/arch/arm/mach-msm/qdsp6_1550/pcm_out.c b/arch/arm/mach-msm/qdsp6_1550/pcm_out.c new file mode 100644 index 00000000..345943fa --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/pcm_out.c @@ -0,0 +1,242 @@ +/* arch/arm/mach-msm/qdsp6/pcm_out.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "dal_audio.h" + +#if 0 +#define AUDIO_INFO(x...) pr_info("Audio: "x) +#else +#define AUDIO_INFO(x...) do{}while(0) +#endif + +void audio_client_dump(struct audio_client *ac); + +#define BUFSZ (4096) //(3072) +#define DMASZ (BUFSZ * 2) + +struct pcm { + struct mutex lock; + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; +}; + +static long pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct pcm *pcm = file->private_data; + struct cad_audio_eq_cfg eq_cfg; + int rc = 0; + + AUDIO_INFO("%s: %X %X\n", __func__, cmd, arg); + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&pcm->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: { + int vol; + if (copy_from_user(&vol, (void*) arg, sizeof(vol))) { + rc = -EFAULT; + break; + } + rc = q6audio_set_stream_volume(pcm->ac, vol); + break; + } + case AUDIO_SET_EQ: { + if (copy_from_user(&eq_cfg, (void *)arg, sizeof(struct cad_audio_eq_cfg))) { + rc = -EFAULT; + break; + } + rc = q6audio_set_stream_eq(pcm->ac, &eq_cfg); + break; + } + case AUDIO_START: { + uint32_t acdb_id; + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void*) arg, sizeof(acdb_id))) { + pr_info("pcm_out: copy acdb_id from user failed\n"); + rc = -EFAULT; + break; + } + if (pcm->ac) { + rc = -EBUSY; + } else { + pcm->ac = q6audio_open_pcm(BUFSZ, pcm->sample_rate, + pcm->channel_count, + AUDIO_FLAG_WRITE, acdb_id); + if (!pcm->ac) + rc = -ENOMEM; + } + break; + } + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (pcm->ac) { + rc = -EBUSY; + break; + } + if (copy_from_user(&config, (void*) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count < 1 || config.channel_count > 2) { + rc = -EINVAL; + break; + } + pcm->sample_rate = config.sample_rate; + pcm->channel_count = config.channel_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = pcm->sample_rate; + config.channel_count = pcm->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void*) arg, &config, sizeof(config))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&pcm->lock); + return rc; +} + +static int pcm_open(struct inode *inode, struct file *file) +{ + struct pcm *pcm; + + AUDIO_INFO("%s\n", __func__); + pr_info("pcm_out: open\n"); + pcm = kzalloc(sizeof(struct pcm), GFP_KERNEL); + + if (!pcm) + return -ENOMEM; + + mutex_init(&pcm->lock); + pcm->channel_count = 2; + pcm->sample_rate = 44100; + + file->private_data = pcm; + return 0; +} + +static ssize_t pcm_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct pcm *pcm = file->private_data; + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer; + + AUDIO_INFO("%s: size = %d\n", __func__, count); + if (!pcm->ac) + pcm_ioctl(file, AUDIO_START, 0); + + ac = pcm->ac; + if (!ac) + return -ENODEV; + + while (count > 0) + { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + if (!wait_event_timeout(ac->wait, (ab->used == 0), 5*HZ)) + { + audio_client_dump(ac); + pr_err("pcm_write: timeout. dsp dead?\n"); + //BUG(); + return -EFAULT; + } + + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_from_user(ab->data, buf, xfer)) + return -EFAULT; + + buf += xfer; + count -= xfer; + + ab->used = xfer; + q6audio_write(ac, ab); + ac->cpu_buf ^= 1; + } + + return buf - start; +} + +static int pcm_release(struct inode *inode, struct file *file) +{ + struct pcm *pcm = file->private_data; + + AUDIO_INFO("%s\n", __func__); + if (pcm->ac) + q6audio_close(pcm->ac); + kfree(pcm); + pr_info("pcm_out: release\n"); + return 0; +} + +static struct file_operations pcm_fops = { + .owner = THIS_MODULE, + .open = pcm_open, + .write = pcm_write, + .release = pcm_release, + .unlocked_ioctl = pcm_ioctl, +}; + +struct miscdevice pcm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_out", + .fops = &pcm_fops, +}; + +static int __init pcm_init(void) { + return misc_register(&pcm_misc); +} + +device_initcall(pcm_init); diff --git a/arch/arm/mach-msm/qdsp6_1550/q6audio.c b/arch/arm/mach-msm/qdsp6_1550/q6audio.c new file mode 100644 index 00000000..e5b75a2c --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/q6audio.c @@ -0,0 +1,2634 @@ +/* arch/arm/mach-msm/qdsp6_10/q6audio.c + * + * Copyright (C) 2010 Cotulla + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "dal.h" +#include "dal_audio.h" +#include "dal_audio_format.h" +#include "dal_acdb.h" +#include "dal_adie.h" +#include +#include +#include +#include + +#include + +#include "q6audio_devices.h" +#include "../dex_comm.h" + +#if 0 +#define TRACE(x...) pr_info("Q6: "x) +#else +#define TRACE(x...) do{}while(0) +#endif + +#if 0 +#define AUDIO_INFO(x...) pr_info("Audio: "x) +#else +#define AUDIO_INFO(x...) do{}while(0) +#endif + + +#define CB_EVENT_COOKIE 0xC00CE13E + + +#if 1 + +// LEO +static struct q6_hw_info q6_audio_hw[Q6_HW_COUNT] = +{ + [Q6_HW_HANDSET] = + { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_HEADSET] = + { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_SPEAKER] = + { + .min_gain = -1000, + .max_gain = 500, + }, + [Q6_HW_TTY] = + { + .min_gain = 0, + .max_gain = 0, + }, + [Q6_HW_BT_SCO] = + { + .min_gain = -1100, + .max_gain = 400, + }, + [Q6_HW_BT_A2DP] = + { + .min_gain = -1100, + .max_gain = 400, + }, +}; + +#else + +// Old one (from Desire) +static struct q6_hw_info q6_audio_hw[Q6_HW_COUNT] = +{ + [Q6_HW_HANDSET] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_HEADSET] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_SPEAKER] = { + .min_gain = -1500, + .max_gain = 0, + }, + [Q6_HW_TTY] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_BT_SCO] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_BT_A2DP] = { + .min_gain = -2000, + .max_gain = 0, + }, +}; +#endif + +extern int global_now_phone_call; + +static struct audio_client * audio_test(void); +static void callback(void *data, int len, void *cookie); +static int audio_init(struct audio_client *ac); +static int audio_info(struct audio_client *ac); +static int q6audio_init_rx_volumes(); + +static struct wake_lock wakelock; +static struct wake_lock idlelock; +static int idlecount; +static DEFINE_MUTEX(idlecount_lock); + +void audio_prevent_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (++idlecount == 1) { + wake_lock(&wakelock); + wake_lock(&idlelock); + } + mutex_unlock(&idlecount_lock); +} + +void audio_allow_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (--idlecount == 0) { + wake_unlock(&idlelock); + wake_unlock(&wakelock); + } + mutex_unlock(&idlecount_lock); +} + +static struct clk *icodec_rx_clk; +static struct clk *icodec_tx_clk; +static struct clk *ecodec_clk; +static struct clk *sdac_clk; + +static struct q6audio_analog_ops default_analog_ops; +static struct q6audio_analog_ops *analog_ops = &default_analog_ops; +static uint32_t tx_clk_freq = 8000; +static int tx_mute_status = 0; +static int rx_vol_level = 100; +static char acdb_file[64] = "default.acdb"; +static uint32_t tx_acdb = 0; +static uint32_t rx_acdb = 0; +static int acdb_use_rpc = 0; + +///////////////////////////////////////////////////////////////////////////////// +// helper functions for device parameters +///////////////////////////////////////////////////////////////////////////////// + +void q6audio_register_analog_ops(struct q6audio_analog_ops *ops) +{ + pr_info("register analog ops = %p\n", ops); + analog_ops = ops; +} + +void q6audio_set_acdb_file(char* filename) +{ + if (filename) { + pr_info("audio: set acdb file as %s\n", filename); + strncpy(acdb_file, filename, sizeof(acdb_file)-1); + } +} + +static struct q6_device_info *q6_lookup_device(uint32_t device_id) +{ + struct q6_device_info *di = q6_audio_devices; + for (;;) { + if (di->id == device_id) + return di; + if (di->id == 0) { + pr_err("q6_lookup_device: bogus id 0x%08x\n", + device_id); + return di; + } + di++; + } +} + +static uint32_t q6_device_to_codec(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->codec; +} + +static uint32_t q6_device_to_dir(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->dir; +} + +static uint32_t q6_device_to_cad_id(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->cad_id; +} + +static uint32_t q6_device_to_path(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->path; +} + +static uint32_t q6_device_to_rate(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->rate; +} + +int q6_device_volume(uint32_t device_id, int level) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + if (analog_ops->get_rx_vol) + return analog_ops->get_rx_vol(di->hw, level); + else { + struct q6_hw_info *hw; + hw = &q6_audio_hw[di->hw]; + return hw->min_gain + ((hw->max_gain - hw->min_gain) * level) / 100; + } +} + +///////////////////////////////////////////////////////////////////////////////// +// ADIE functions +///////////////////////////////////////////////////////////////////////////////// + +static inline int adie_open(struct dal_client *client) +{ + return dal_call_f0(client, DAL_OP_OPEN, 0); +} + +static inline int adie_close(struct dal_client *client) +{ + return dal_call_f0(client, DAL_OP_CLOSE, 0); +} + +static inline int adie_set_path(struct dal_client *client, + uint32_t id, uint32_t path_type) +{ + return dal_call_f1(client, ADIE_OP_SET_PATH, id, path_type); +} + +static inline int adie_set_path_freq_plan(struct dal_client *client, + uint32_t path_type, uint32_t plan) +{ + return dal_call_f1(client, ADIE_OP_SET_PATH_FREQUENCY_PLAN, + path_type, plan); +} + +static inline int adie_proceed_to_stage(struct dal_client *client, + uint32_t path_type, uint32_t stage) +{ + return dal_call_f1(client, ADIE_OP_PROCEED_TO_STAGE, + path_type, stage); +} + +static inline int adie_mute_path(struct dal_client *client, + uint32_t path_type, uint32_t mute_state) +{ + return dal_call_f1(client, ADIE_OP_MUTE_PATH, path_type, mute_state); +} + +static int adie_refcount; + +static struct dal_client *adie; +static struct dal_client *adsp; +static struct dal_client *acdb; + +static int adie_enable(void) +{ + adie_refcount++; + if (adie_refcount == 1) + adie_open(adie); + return 0; +} + +static int adie_disable(void) +{ + adie_refcount--; + if (adie_refcount == 0) + adie_close(adie); + return 0; +} + +/* 4k DMA scratch page used for exchanging acdb device config tables + * and stream format descriptions with the DSP. + */ +static void *audio_data; +static dma_addr_t audio_phys; +// this memory used to pass open params into DSP +static void *params_data; +static dma_addr_t params_phys; + + +#define SESSION_MIN 0 +#define SESSION_MAX 64 + +static DEFINE_MUTEX(session_lock); +static DEFINE_MUTEX(audio_lock); +static DEFINE_MUTEX(open_mem_lock); + + +static struct audio_client *session[SESSION_MAX]; + +static int session_alloc(struct audio_client *ac) +{ + int n; + + mutex_lock(&session_lock); + for (n = SESSION_MIN; n < SESSION_MAX; n++) { + if (!session[n]) { + session[n] = ac; + mutex_unlock(&session_lock); + return n; + } + } + mutex_unlock(&session_lock); + return -ENOMEM; +} + +static void session_free(int n, struct audio_client *ac) +{ + mutex_lock(&session_lock); + if (session[n] == ac) + session[n] = 0; + mutex_unlock(&session_lock); +} + +void audio_client_dump(struct audio_client *ac) +{ + dal_trace_dump(ac->client); +} + + +static void audio_client_free(struct audio_client *ac) +{ + AUDIO_INFO("%s: session %d\n", __func__, ac->session); + + session_free(ac->session, ac); + + if (ac->buf[0].data) + dma_free_coherent(NULL, ac->buf[0].size, ac->buf[0].data, ac->buf[0].phys); + if (ac->buf[1].data) + dma_free_coherent(NULL, ac->buf[1].size, ac->buf[1].data, ac->buf[1].phys); + + if (ac->client) + dal_detach(ac->client); + kfree(ac); +} + +static struct audio_client *audio_client_alloc(unsigned bufsz) +{ + struct audio_client *ac; + struct dal_client *dsp; + int n; + + AUDIO_INFO("%s\n", __func__); + ac = kzalloc(sizeof(*ac), GFP_KERNEL); + if (!ac) + { + AUDIO_INFO("%s: alloc error\n", __func__); + return 0; + } + + n = session_alloc(ac); + if (n < 0) + { + AUDIO_INFO("%s: session alloc error\n", __func__); + goto fail_session; + } + ac->session = n; + AUDIO_INFO("%s: session %d\n", __func__, ac->session); + + if (bufsz > 0) + { + ac->buf[0].data = dma_alloc_coherent(NULL, bufsz, &ac->buf[0].phys, GFP_KERNEL); + if (!ac->buf[0].data) + { + goto fail; + } + + ac->buf[1].data = dma_alloc_coherent(NULL, bufsz, &ac->buf[1].phys, GFP_KERNEL); + if (!ac->buf[1].data) + { + goto fail; + } + + ac->buf[0].size = bufsz; + ac->buf[1].size = bufsz; + } + + init_waitqueue_head(&ac->wait); + + +// dsp = dal_attach(AUDIO_DAL_DEVICE, AUDIO_DAL_PORT, callback, 0); + dsp = dal_attach_ex(AUDIO_DAL_DEVICE, "AudioQdsp", AUDIO_DAL_PORT, callback, 0); + if (!dsp) + { + pr_err("audio_client_alloc: cannot attach to adsp\n"); + goto fail; + } + ac->client = dsp; + + audio_init(ac); + audio_info(ac); + audio_client_dump(ac); + + return ac; + +fail: + if (ac->buf[0].data) + dma_free_coherent(NULL, ac->buf[0].size, ac->buf[0].data, ac->buf[0].phys); + if (ac->buf[1].data) + dma_free_coherent(NULL, ac->buf[1].size, ac->buf[1].data, ac->buf[1].phys); + session_free(n, ac); +fail_session: + audio_client_free(ac); + return 0; +} + + +static int audio_ioctl(struct audio_client *ac, uint32_t cmd, void *ptr, uint32_t len) +{ + int r; + r = dal_call_f6(ac->client, AUDIO_OP_IOCTL, cmd, ptr, len); + return r; +} + +static int audio_command(struct audio_client *ac, uint32_t cmd) +{ + struct adsp_command_hdr rpc; + memset(&rpc, 0, sizeof(rpc)); + return audio_ioctl(ac, cmd, NULL, 0); +} + +static int audio_open_control(struct audio_client *ac) +{ + int r; + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.opcode = ADSP_AUDIO_OPCODE_OPEN_DEV; + + // reset flag before call, callback() will set it later + ac->open_done = 0; + r = dal_call_f5(ac->client, AUDIO_OP_OPEN, &rpc, sizeof(rpc)); + if (r != 0) + return r; + +// wait for async event + if (!wait_event_timeout(ac->wait, (ac->open_done == 1), 3 * HZ)) + { + pr_err("wait for open async event failed!\n"); + return -1; + } + return ac->open_status; +} + +static int audio_out_open(struct audio_client *ac, uint32_t bufsz, + uint32_t rate, uint32_t channels) +{ + int r; + struct adsp_open_command rpc; + union adsp_audio_format *ptr; + + printk("audio_out_open: %x %d %d\n", bufsz, rate, channels); + mutex_lock(&open_mem_lock); + + memset(&rpc, 0, sizeof(rpc)); + ptr = (union adsp_audio_format*)params_data; + memset(ptr, 0, sizeof(union adsp_audio_format)); + + rpc.numdev = 1; +// rpc.dev[0] = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO; + rpc.dev[0] = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + rpc.buf_max_size = bufsz; + + rpc.format = ADSP_AUDIO_FORMAT_PCM; + rpc.pblock = params_phys; + rpc.blocklen = sizeof(struct adsp_audio_standard_format); +// rpc.pblock = 0; +// rpc.blocklen = 0; + + rpc.opcode = ADSP_AUDIO_OPCODE_OPEN_WRITE; + + ptr->standard.channels = channels; + ptr->standard.bits_per_sample = 16; + ptr->standard.sampling_rate = rate; + ptr->standard.is_signed = 1; + ptr->standard.is_interleaved = 1; + + AUDIO_INFO("open out %p\n", ac); + ac->open_done = 0; + r = dal_call_f5(ac->client, AUDIO_OP_OPEN, &rpc, sizeof(rpc)); + +// wait for async event + if (!wait_event_timeout(ac->wait, (ac->open_done == 1), 3 * HZ)) + { + mutex_unlock(&open_mem_lock); + pr_err("wait for open async event failed!\n"); + return -1; + } + mutex_unlock(&open_mem_lock); +// return r; + return ac->open_status; +} + +static int audio_in_open(struct audio_client *ac, uint32_t bufsz, + uint32_t rate, uint32_t channels) +{ + int r; + struct adsp_open_command rpc; + union adsp_audio_format *ptr; + + printk("audio_in_open: %x %d %d\n", bufsz, rate, channels); + mutex_lock(&open_mem_lock); + memset(&rpc, 0, sizeof(rpc)); + ptr = (union adsp_audio_format*)params_data; + memset(ptr, 0, sizeof(union adsp_audio_format)); + + rpc.numdev = 1; + rpc.dev[0] = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + rpc.buf_max_size = bufsz; + + rpc.format = ADSP_AUDIO_FORMAT_PCM; + rpc.pblock = params_phys; + rpc.blocklen = sizeof(struct adsp_audio_standard_format); + rpc.opcode = ADSP_AUDIO_OPCODE_OPEN_READ; + + ptr->standard.channels = channels; + ptr->standard.bits_per_sample = 16; + ptr->standard.sampling_rate = rate; + ptr->standard.is_signed = 1; + ptr->standard.is_interleaved = 1; + + AUDIO_INFO("%p: open in\n", ac); + ac->open_done = 0; + r = dal_call_f5(ac->client, AUDIO_OP_OPEN, &rpc, sizeof(rpc)); +// wait for async event + if (!wait_event_timeout(ac->wait, (ac->open_done == 1), 3 * HZ)) + { + mutex_unlock(&open_mem_lock); + pr_err("wait for open async event failed (IN)!\n"); + return -1; + } + mutex_unlock(&open_mem_lock); +// return r; + return ac->open_status; +} + +static int audio_mp3_open(struct audio_client *ac, uint32_t bufsz, + uint32_t rate, uint32_t channels) +{ + int r; + struct adsp_open_command rpc; + union adsp_audio_format *ptr; + + printk("audio_mp3_open: %x %d %d\n", bufsz, rate, channels); + mutex_lock(&open_mem_lock); + memset(&rpc, 0, sizeof(rpc)); + ptr = (union adsp_audio_format*)params_data; + memset(ptr, 0, sizeof(union adsp_audio_format)); + + rpc.numdev = 1; + rpc.dev[0] = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + rpc.buf_max_size = bufsz; + + rpc.format = ADSP_AUDIO_FORMAT_MP3; + rpc.pblock = params_phys; + rpc.blocklen = sizeof(struct adsp_audio_standard_format); + rpc.opcode = ADSP_AUDIO_OPCODE_OPEN_WRITE; + + ptr->standard.channels = channels; + ptr->standard.bits_per_sample = 16; + ptr->standard.sampling_rate = rate; + ptr->standard.is_signed = 1; + ptr->standard.is_interleaved = 1; + + r = dal_call_f5(ac->client, AUDIO_OP_OPEN, &rpc, sizeof(rpc)); +// wait for async event + if (!wait_event_timeout(ac->wait, (ac->open_done == 1), 3 * HZ)) + { + mutex_unlock(&open_mem_lock); + pr_err("wait for open async event failed (MP3)!\n"); + return -1; + } + mutex_unlock(&open_mem_lock); +// return r; + return ac->open_status; +} + +static int audio_aac_open(struct audio_client *ac, uint32_t bufsz, void *data) +{ + int r; + struct aac_format *af = data; + struct adsp_open_command rpc; + uint32_t *aac_type; + int idx = 0; // sizeof(uint32_t); + struct adsp_audio_binary_format *fmt = (struct adsp_audio_binary_format *)params_data; + union adsp_audio_format *ptr; + + + mutex_lock(&open_mem_lock); + memset(&rpc, 0, sizeof(rpc)); + ptr = (union adsp_audio_format*)params_data; + memset(ptr, 0, sizeof(union adsp_audio_format)); + + rpc.numdev = 1; + rpc.dev[0] = ADSP_AUDIO_DEVICE_ID_DEFAULT; + + rpc.format = ADSP_AUDIO_FORMAT_MPEG4_AAC; + rpc.pblock = params_phys; + + aac_type = (uint32_t *)(fmt->data); + switch (af->block_formats) + { + case 0xffff: + if (ac->flags & AUDIO_FLAG_WRITE) + *aac_type = ADSP_AUDIO_AAC_ADTS; + else + *aac_type = ADSP_AUDIO_AAC_MPEG4_ADTS; + break; + case 0: + if (ac->flags & AUDIO_FLAG_WRITE) + *aac_type = ADSP_AUDIO_AAC_ADIF; + else + *aac_type = ADSP_AUDIO_AAC_RAW; + break; + case 1: + *aac_type = ADSP_AUDIO_AAC_RAW; + break; + case 2: + *aac_type = ADSP_AUDIO_AAC_LOAS; + break; + case 3: + *aac_type = ADSP_AUDIO_AAC_FRAMED_RAW; + break; + case 4: + *aac_type = ADSP_AUDIO_AAC_RAW; + break; + default: + pr_err("unsupported AAC type %d\n", af->block_formats); + mutex_unlock(&open_mem_lock); + return -EINVAL; + } + + AUDIO_INFO("aac_open: type %x, obj %d, idx %d\n", + *aac_type, af->audio_object_type, idx); + fmt->data[idx++] = (u8)(((af->audio_object_type & 0x1F) << 3) | + ((af->sample_rate >> 1) & 0x7)); + fmt->data[idx] = (u8)(((af->sample_rate & 0x1) << 7) | + ((af->channel_config & 0x7) << 3)); + + switch (af->audio_object_type) { + case AAC_OBJECT_ER_LC: + case AAC_OBJECT_ER_LTP: + case AAC_OBJECT_ER_LD: + /* extension flag */ + fmt->data[idx++] |= 0x1; + fmt->data[idx] = (u8)( + ((af->aac_section_data_resilience_flag & 0x1) << 7) | + ((af->aac_scalefactor_data_resilience_flag & 0x1) << 6) | + ((af->aac_spectral_data_resilience_flag & 0x1) << 5) | + ((af->ep_config & 0x3) << 2)); + break; + + case AAC_OBJECT_ER_SCALABLE: + fmt->data[idx++] |= 0x1; + /* extension flag */ + fmt->data[idx++] = (u8)( + ((af->aac_section_data_resilience_flag & 0x1) << 4) | + ((af->aac_scalefactor_data_resilience_flag & 0x1) << 3) | + ((af->aac_spectral_data_resilience_flag & 0x1) << 2) | + ((af->ep_config >> 1) & 0x1)); + fmt->data[idx] = (u8)((af->ep_config & 0x1) << 7); + break; + + case AAC_OBJECT_BSAC: + fmt->data[++idx] = (u8)((af->ep_config & 0x3) << 6); + break; + + default: + pr_err("dbg unknown object type \n"); + break; + } +// fmt->num_bytes = idx + 1; + rpc.blocklen = idx + 1; + + TRACE("aac_open: format %x%x %x%x%x%x %x%x, \n", + fmt->data[0], fmt->data[1], fmt->data[2], fmt->data[3], + fmt->data[4], fmt->data[5], fmt->data[6], fmt->data[7]); + + rpc.config.aac.bit_rate = af->bit_rate; + if (ac->flags & AUDIO_FLAG_WRITE) + { + rpc.opcode = ADSP_AUDIO_OPCODE_OPEN_WRITE; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + } + else + { + rpc.opcode = ADSP_AUDIO_OPCODE_OPEN_READ; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + } + + if ((af->sbr_on_flag == 0) && (af->sbr_ps_on_flag == 0)) + { + rpc.config.aac.encoder_mode = ADSP_AUDIO_ENC_AAC_LC_ONLY_MODE; + } + else if ((af->sbr_on_flag == 1) && (af->sbr_ps_on_flag == 0)) + { + rpc.config.aac.encoder_mode = ADSP_AUDIO_ENC_AAC_PLUS_MODE; + } + else if ((af->sbr_on_flag == 1) && (af->sbr_ps_on_flag == 1)) + { + rpc.config.aac.encoder_mode = ADSP_AUDIO_ENC_ENHANCED_AAC_PLUS_MODE; + } + else + { + pr_err("unsupported SBR flag\n"); + mutex_unlock(&open_mem_lock); + return -EINVAL; + } + rpc.buf_max_size = bufsz; /* XXX ??? */ + + TRACE("aac_open: opcode %x, stream_context 0x%x, " + "mode %d, bytes %d, bbuffer size %d\n", + rpc.opcode, rpc.stream_context, + rpc.config.aac.encoder_mode, rpc.blocklen, bufsz); + + r = dal_call_f5(ac->client, AUDIO_OP_OPEN, &rpc, sizeof(rpc)); + mutex_unlock(&open_mem_lock); + return r; +} + +static int audio_qcelp_open(struct audio_client *ac, uint32_t bufsz, + void *data) +{ + int r; + struct msm_audio_qcelp_config *qf = data; + struct adsp_open_command rpc; + struct adsp_audio_standard_format *fmt; + union adsp_audio_format *ptr; + + + mutex_lock(&open_mem_lock); + memset(&rpc, 0, sizeof(rpc)); + fmt = (struct adsp_audio_standard_format *)params_data; + memset(ptr, 0, sizeof(struct adsp_audio_standard_format)); + + rpc.numdev = 1; + rpc.dev[0] = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + + rpc.format = ADSP_AUDIO_FORMAT_V13K_FS; + rpc.pblock = params_phys; + rpc.blocklen = sizeof(struct adsp_audio_standard_format); + rpc.opcode = ADSP_AUDIO_OPCODE_OPEN_READ; + + fmt->sampling_rate = 8000; + fmt->channels = 1; + fmt->bits_per_sample = 16; + fmt->is_signed = 1; + fmt->is_interleaved = 0; + + rpc.config.qcelp13k.min_rate = (uint16_t) qf->min_bit_rate; + rpc.config.qcelp13k.max_rate = (uint16_t) qf->max_bit_rate; + rpc.buf_max_size = bufsz; /* XXX ??? */ + + r = dal_call_f5(ac->client, AUDIO_OP_OPEN, &rpc, sizeof(rpc)); + + mutex_unlock(&open_mem_lock); + return r; +} + +static int audio_close(struct audio_client *ac) +{ + AUDIO_INFO("%p: close\n", ac); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_STREAM_STOP); +// audio_command(ac, ADSP_AUDIO_IOCTL_CMD_CLOSE); + + dal_call_f0(ac->client, AUDIO_OP_CLOSE, ac->session); + return 0; +} + +static int audio_set_table(struct audio_client *ac,int32_t device_id, int size) +{ + struct adsp_set_dev_cfg_table_command rpc; + + AUDIO_INFO("%s: %x %d\n", __func__, device_id, size); + +// print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, audio_data, size); + + memset(&rpc, 0, sizeof(rpc)); + if (q6_device_to_dir(device_id) == Q6_TX) + rpc.hdr.data = tx_clk_freq; + rpc.device_id = device_id; + rpc.phys_addr = audio_phys; + rpc.phys_size = size; + rpc.phys_used = size; + + TRACE("control: set table %x\n", device_id); + return audio_ioctl(ac, ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG_TABLE, &rpc, sizeof(rpc)); +} + +int q6audio_read(struct audio_client *ac, struct audio_buffer *ab) +{ + struct adsp_buffer_command rpc; + int r; + + AUDIO_INFO("%s: %X %X %X\n", __func__, ab->phys , ab->size, ab->used); + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.context = ac->session; + rpc.hdr.data = (uint32_t)ab->data; + rpc.buffer.addr = ab->phys; + rpc.buffer.max_size = ab->size; + rpc.buffer.actual_size = ab->used; + rpc.buffer.flags = ADSP_AUDIO_BUFFER_FLAG_PHYS_ADDR | ADSP_AUDIO_BUFFER_FLAG_START_SET; + + TRACE("%p: read\n", ac); +// r = dal_call(ac->client, AUDIO_OP_DATA, 5, &rpc, sizeof(rpc), &res, sizeof(res)); + + r = dal_call_f5(ac->client, AUDIO_OP_READ, &rpc, sizeof(rpc)); + return 0; +} + +int q6audio_write(struct audio_client *ac, struct audio_buffer *ab) +{ + struct adsp_buffer_command rpc; + int r; + + AUDIO_INFO("%s: %X %X %X\n", __func__, ab->phys , ab->size, ab->used); + memset(&rpc, 0, sizeof(rpc)); +// rpc.hdr.context = ac->session; + rpc.hdr.data = (uint32_t)ab->data; + rpc.buffer.addr = ab->phys; + rpc.buffer.max_size = ab->size; + rpc.buffer.actual_size = ab->used; + rpc.buffer.start = (~0x7fffffffffffffffLL); + rpc.buffer.flags = ADSP_AUDIO_BUFFER_FLAG_PHYS_ADDR | ADSP_AUDIO_BUFFER_FLAG_START_SET; + + TRACE("%p: write\n", ac); +// r = dal_call(ac->client, AUDIO_OP_DATA, 5, &rpc, sizeof(rpc), &res, sizeof(res)); + + r = dal_call_f5(ac->client, AUDIO_OP_WRITE, &rpc, sizeof(rpc)); + return 0; +} + +static int audio_rx_volume(struct audio_client *ac, uint32_t dev_id, int32_t volume) +{ + struct adsp_set_dev_volume_command rpc; + + AUDIO_INFO("%s: dev_id 0x%08x, volume = %d\n", __func__, dev_id , volume); + + memset(&rpc, 0, sizeof(rpc)); + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_RX; + rpc.volume = volume; + return audio_ioctl(ac, ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_VOL, &rpc, sizeof(rpc)); +} + +static int audio_tx_volume(struct audio_client *ac, uint32_t dev_id, int32_t volume) +{ + struct adsp_set_dev_volume_command rpc; + + AUDIO_INFO("%s: dev_id 0x%08x, volume = %d\n", __func__, dev_id , volume); + + memset(&rpc, 0, sizeof(rpc)); + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_TX; + rpc.volume = volume; + return audio_ioctl(ac, ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_VOL, &rpc, sizeof(rpc)); +} + + +static int audio_rx_mute(struct audio_client *ac, uint32_t dev_id, int mute) +{ + struct adsp_set_dev_mute_command rpc; + + AUDIO_INFO("%s: dev_id 0x%08x, mute %d\n", __func__, dev_id , mute); + + memset(&rpc, 0, sizeof(rpc)); + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_RX; + rpc.mute = !!mute; + return audio_ioctl(ac, ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE, &rpc, sizeof(rpc)); +} + + +static int audio_tx_mute(struct audio_client *ac, uint32_t dev_id, int mute) +{ + struct adsp_set_dev_mute_command rpc; + + AUDIO_INFO("%s: dev_id 0x%08x, mute %d\n", __func__, dev_id , mute); + + memset(&rpc, 0, sizeof(rpc)); + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_TX; + rpc.mute = !!mute; + return audio_ioctl(ac, ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE, &rpc, sizeof(rpc)); +} + +static int audio_stream_volume(struct audio_client *ac, int volume) +{ + struct adsp_set_volume_command rpc; + int rc; + + AUDIO_INFO("%s: volume %d\n", __func__, volume); +// +// CotullaHACK: this function called from libaudio-qsd8k / AudioHardware.cpp +// status = ioctl(mFd, AUDIO_SET_VOLUME, &stream_volume); +// stream_volume have fixed -300 value, replace it to zero +// +// printk("$$$ audio_stream_volume(%d)\n", volume); + volume = 0; + + memset(&rpc, 0, sizeof(rpc)); + rpc.volume = volume; + rc = audio_ioctl(ac, ADSP_AUDIO_IOCTL_CMD_SET_STREAM_VOL, &rpc, sizeof(rpc)); + return rc; +} + +static int audio_stream_mute(struct audio_client *ac, int mute) +{ + struct adsp_set_mute_command rpc; + int rc; + + AUDIO_INFO("%s: mute %d\n", __func__, mute); + memset(&rpc, 0, sizeof(rpc)); + rpc.mute = mute; + rc = audio_ioctl(ac, ADSP_AUDIO_IOCTL_CMD_SET_STREAM_MUTE, &rpc, sizeof(rpc)); + return rc; +} + +static int audio_stream_eq(struct audio_client *ac, struct cad_audio_eq_cfg *eq_cfg) +{ + struct adsp_audio_set_equalizer_command rpc; + int rc; + uint32_t i = 0; + + AUDIO_INFO("%s\n", __func__); + memset(&rpc, 0, sizeof(rpc)); + rpc.enable = eq_cfg->enable; + rpc.num_bands = eq_cfg->num_bands; + for (i = 0; i < rpc.num_bands; i++) + { + rpc.eq_bands[i].band_idx = eq_cfg->eq_bands[i].band_idx; + rpc.eq_bands[i].filter_type = eq_cfg->eq_bands[i].filter_type; + rpc.eq_bands[i].center_freq_hz = eq_cfg->eq_bands[i].center_freq_hz; + rpc.eq_bands[i].filter_gain = eq_cfg->eq_bands[i].filter_gain; + rpc.eq_bands[i].q_factor = eq_cfg->eq_bands[i].q_factor; +#if 1 + pr_info(">>>>>> band_idx = %d\n", rpc.eq_bands[i].band_idx); + pr_info(">>>>>> filter_type = %d\n", rpc.eq_bands[i].filter_type); + pr_info(">>>>>> center_freq_hz = %d\n", rpc.eq_bands[i].center_freq_hz); + pr_info(">>>>>> filter_gain = %d\n", rpc.eq_bands[i].filter_gain); + pr_info(">>>>>> q_factor = %d\n\n", rpc.eq_bands[i].q_factor); +#endif + } + rc = audio_ioctl(ac, ADSP_AUDIO_IOCTL_SET_SESSION_EQ_CONFIG, &rpc, sizeof(rpc)); + return rc; +} + +static void callback(void *data, int len, void *cookie) +{ +// struct adsp_event_hdr *e = data; + struct adsp_audio_dal_event *e = data; + struct audio_client *ac; + struct adsp_audio_event* ae; + +// printk("dal event\n"); + TRACE("audio callback: CB: %X LOC: %X SIZE=%X\n", e->cb_evt, e->loc_evt, e->size); + + if (e->cb_evt != CB_EVENT_COOKIE) + { + pr_err("audio callback: bad cb_event %X\n", e->cb_evt); + return; + } + + ae = &e->ae; + if (ae->session >= SESSION_MAX) + { + pr_err("audio callback: bogus session %d\n", ae->session); + return; + } + ac = session[ae->session]; + if (!ac) + { + pr_err("audio callback: unknown session %d\n", ae->session); + return; + } + + if (ae->event_id == ADSP_AUDIO_EVT_STATUS_OPEN) + { + pr_info("open done!\n"); + ac->open_status = ae->status; + ac->open_done = 1; + wake_up(&ac->wait); + return; + } + + if (ae->event_id == ADSP_AUDIO_IOCTL_CMD_STREAM_EOS) + { + TRACE("%p: CB stream eos\n", ac); + if (ae->status) + { + pr_err("playback status %d\n", ae->status); + } + if (ac->cb_status == -EBUSY) + { + ac->cb_status = ae->status; + wake_up(&ac->wait); + } + return; + } + + if (ae->event_id == ADSP_AUDIO_EVT_STATUS_BUF_DONE) + { + TRACE("%p: CB done (%d)\n", ac, ae->status); +// TRACE("%p: actual_size %d, buffer_size %d\n", +// ac, abe->buffer.actual_size, ac->buf[ac->dsp_buf].size); + + if (ae->status) + { + pr_err("buffer status %d\n", ae->status); + } + ac->buf[ac->dsp_buf].used = 0; + ac->dsp_buf ^= 1; + wake_up(&ac->wait); + return; + } + + TRACE("%p: CB %08x status %d\n", ac, ae->event_id, ae->status); + if (ae->status) + { + pr_warning("audio_cb: s=%d e=%08x status=%d\n", ae->session, ae->event_id, ae->status); + } + + if (ac->cb_status == -EBUSY) + { + ac->cb_status = ae->status; + wake_up(&ac->wait); + } +} + + +struct rpc_config_info +{ + u32 cb_evt; + u32 loc_evt; + u32 session_id; + u32 processor_id; +}; + +struct rpc_info +{ + uint32_t size; + uint32_t version; + char name[32]; + +}; + + + + +static int audio_init(struct audio_client *ac) +{ + + int r; + struct rpc_config_info info; + + AUDIO_INFO("%s\n", __func__); + + info.session_id = ac->session; + info.processor_id = 1; // AARM + info.cb_evt = CB_EVENT_COOKIE; + info.loc_evt = (u32)(((u32)ac->session + 1) << 16); + + r = dal_call_f5(ac->client, AUDIO_OP_INIT, &info, sizeof(info)); + return r; +} + +static int audio_info(struct audio_client *ac) +{ + int r; + struct rpc_info info; + + AUDIO_INFO("%s\n", __func__); + + r = dal_call_f9(ac->client, DAL_OP_INFO, &info, sizeof(info)); + return r; +} + +static struct audio_client *ac_control; +static int zero_already_inited; + +struct audio_config_data { + uint32_t device_id; + uint32_t sample_rate; + uint32_t offset; + uint32_t length; +}; + +struct audio_config_database { + uint8_t magic[8]; + uint32_t entry_count; + uint32_t unused; + struct audio_config_data entry[0]; +}; + +void *acdb_data; +const struct firmware *acdb_fw; +extern struct miscdevice q6_control_device; + +static int acdb_init(char *filename) +{ + const struct audio_config_database *db; + const struct firmware *fw; + int n; + + return -ENODEV; + + pr_info("acdb: load '%s'\n", filename); + if (request_firmware(&fw, filename, q6_control_device.this_device) < 0) { + pr_err("acdb: load 'default.acdb' failed...\n"); + return -ENODEV; + } + db = (void*) fw->data; + + if (fw->size < sizeof(struct audio_config_database)) { + pr_err("acdb: undersized database\n"); + goto fail; + } + if (strcmp(db->magic, "ACDB1.0")) { + pr_err("acdb: invalid magic\n"); + goto fail; + } + if (db->entry_count > 1024) { + pr_err("acdb: too many entries\n"); + goto fail; + } + if (fw->size < (sizeof(struct audio_config_database) + + db->entry_count * sizeof(struct audio_config_data))) { + pr_err("acdb: undersized TOC\n"); + goto fail; + } + for (n = 0; n < db->entry_count; n++) { + if (db->entry[n].length > 4096) { + pr_err("acdb: entry %d too large (%d)\n", + n, db->entry[n].length); + goto fail; + } + if ((db->entry[n].offset + db->entry[n].length) > fw->size) { + pr_err("acdb: entry %d outside of data\n", n); + goto fail; + } + } + if (acdb_data) + release_firmware(acdb_fw); + acdb_data = (void*) fw->data; + acdb_fw = fw; + return 0; +fail: + release_firmware(fw); + return -ENODEV; +} + +static int q6audio_init(void) +{ + struct audio_client *ac = 0; + int res; + + AUDIO_INFO("%s\n", __func__); + mutex_lock(&audio_lock); + if (ac_control) + { + res = 0; + goto done; + } + + pr_info("q6audio_init\n"); + + icodec_rx_clk = clk_get(0, "icodec_rx_clk"); + icodec_tx_clk = clk_get(0, "icodec_tx_clk"); + ecodec_clk = clk_get(0, "ecodec_clk"); + sdac_clk = clk_get(0, "sdac_clk"); + audio_data = dma_alloc_coherent(NULL, 4096, &audio_phys, GFP_KERNEL); + params_data = dma_alloc_coherent(NULL, 4096, ¶ms_phys, GFP_KERNEL); +// printk("allocated: %p %x\n", params_data, params_phys); + +// pr_info("audio: init: INIT\n"); + + if (!zero_already_inited) + { + zero_already_inited = 1; + pr_info("audio: init: first run, init dummy control\n"); + audio_client_alloc(0); + } + + ac = audio_client_alloc(0); + if (!ac) + { + pr_err("audio_init: cannot allocate client\n"); + res = -ENOMEM; + goto done; + } + +// pr_info("audio: init: OPEN control\n"); + if (audio_open_control(ac)) + { + pr_err("audio_init: cannot open control channel\n"); + res = -ENODEV; + goto done; + } + +// pr_info("audio: init: attach ACDB\n"); + acdb = dal_attach(ACDB_DAL_DEVICE, ACDB_DAL_PORT, 0, 0); + if (!acdb) + { + pr_err("audio_init: cannot attach to acdb channel\n"); + res = -ENODEV; + goto done; + } + +// pr_info("audio: init: attach ADIE\n"); + adie = dal_attach(ADIE_DAL_DEVICE, ADIE_DAL_PORT, 0, 0); + if (!adie) { + pr_err("audio_init: cannot attach to adie\n"); + res = -ENODEV; + goto done; + } + + if (analog_ops->init) + analog_ops->init(); + + if (!acdb_data && !acdb_use_rpc) + { + if (acdb_init(acdb_file)) + { + pr_info("use RPC to query ACDB.\n"); + acdb_use_rpc = 1; + } + } + + res = 0; + ac_control = ac; + + wake_lock_init(&idlelock, WAKE_LOCK_IDLE, "audio_pcm_idle"); + wake_lock_init(&wakelock, WAKE_LOCK_SUSPEND, "audio_pcm_suspend"); + + q6audio_init_rx_volumes(); + +// TEST HERE +// pr_info("START TEST\n"); +// audio_tx_mute(ac_control, ADSP_AUDIO_DEVICE_ID_DEFAULT, 1); +// pr_info("END TEST\n"); +done: + if ((res < 0) && ac) + { + audio_client_free(ac); + printk(" init failed!\n"); + } + mutex_unlock(&audio_lock); + return res; +} + + +static int map_cad_dev_to_virtual(int cd) +{ + if (global_now_phone_call) + { + return cd; + } + + switch (cd) + { + case 1: return 507; + case 2: return 208; + case 3: return 307; + case 5: return 407; + case 6: return 507; + case 7: return 607; + case 17: return 408; + default: return cd; + } + return 0; +} + +static int acdb_get_config_table(uint32_t device_id, uint32_t sample_rate) +{ + AUDIO_INFO("%s\n", __func__); + + if (q6audio_init()) + return 0; + + if (acdb_use_rpc) + { + struct acdb_cmd_device_table rpc; + struct acdb_result res; + int r; + uint32_t new_device_id; + + memset(audio_data, 0, 4096); + memset(&rpc, 0, sizeof(rpc)); + + new_device_id = map_cad_dev_to_virtual(device_id); + printk(" ACDB MAP: %d -> %d\n", device_id, new_device_id); + + rpc.size = sizeof(rpc) - (2 * sizeof(uint32_t)); + rpc.command_id = ACDB_GET_DEVICE_TABLE; + rpc.device_id = new_device_id; //0x25F; //device_id; + rpc.sample_rate_id = sample_rate; + rpc.total_bytes = 4096; + rpc.unmapped_buf = audio_phys; + rpc.res_size = sizeof(res) - (2 * sizeof(uint32_t)); + +// printk("ACDB dal call\n"); + r = dal_call(acdb, ACDB_OP_IOCTL, 8, &rpc, sizeof(rpc), &res, sizeof(res)); +// r = dal_call_f8(acdb, ACDB_OP_IOCTL, &rpc, sizeof(rpc), &res, sizeof(res)); + + pr_info("acdb ret: %d %X %X %X %d\n", res. dal_status, res.size, res.unmapped_buf, res.used_bytes, res.result); + if ((r == sizeof(res)) && (res.result == 0)) + { + pr_info("acdb: %d bytes for device %d, rate %d.\n", res.used_bytes, device_id, sample_rate); + return res.used_bytes; + } + return 0; + } + else + { + struct audio_config_database *db; + int n; + + printk("table\n"); + db = acdb_data; + for (n = 0; n < db->entry_count; n++) + { + if (db->entry[n].device_id != device_id) + continue; + if (db->entry[n].sample_rate != sample_rate) + continue; + break; + } + + if (n == db->entry_count) + { + pr_err("acdb: no entry for device %d, rate %d.\n", + device_id, sample_rate); + return 0; + } + + pr_info("acdb: %d bytes for device %d, rate %d.\n", + db->entry[n].length, device_id, sample_rate); + + memcpy(audio_data, acdb_data + db->entry[n].offset, db->entry[n].length); + return db->entry[n].length; + } +} + +//static uint32_t audio_rx_path_id = ADIE_PATH_HANDSET_RX; +//static uint32_t audio_rx_device_id = ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR; +static uint32_t audio_rx_path_id = ADIE_PATH_SPEAKER_RX; +static uint32_t audio_rx_device_id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO; +static uint32_t audio_rx_device_group = -1; +static uint32_t audio_tx_path_id = ADIE_PATH_HANDSET_TX; +static uint32_t audio_tx_device_id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC; +static uint32_t audio_tx_device_group = -1; + +static int qdsp6_devchg_notify(struct audio_client *ac, uint32_t dev_type, uint32_t dev_id) +{ + struct adsp_device_switch_command rpc; + + AUDIO_INFO("%s\n", __func__); + + if (dev_type != ADSP_AUDIO_RX_DEVICE && dev_type != ADSP_AUDIO_TX_DEVICE) + return -EINVAL; + + memset(&rpc, 0, sizeof(rpc)); + if (dev_type == ADSP_AUDIO_RX_DEVICE) + { + rpc.old_device = audio_rx_device_id; + rpc.new_device = dev_id; + } + else + { + rpc.old_device = audio_tx_device_id; + rpc.new_device = dev_id; + } + rpc.device_class = 0; + rpc.device_type = dev_type; + return audio_ioctl(ac, ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_PREPARE, &rpc, sizeof(rpc)); +} + +static int qdsp6_standby(struct audio_client *ac) +{ + AUDIO_INFO("%s\n", __func__); + return audio_command(ac, ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_STANDBY); +} + +static int qdsp6_start(struct audio_client *ac) +{ + AUDIO_INFO("%s\n", __func__); + return audio_command(ac, ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_COMMIT); +} + +static void audio_rx_analog_enable(int en) +{ + AUDIO_INFO("%s\n", __func__); + switch (audio_rx_device_id) { + case ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO: + case ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO: + case ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR: + if (analog_ops->headset_enable) + analog_ops->headset_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET: + if (analog_ops->headset_enable) + analog_ops->headset_enable(en); + if (analog_ops->speaker_enable) + analog_ops->speaker_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO: + if (analog_ops->speaker_enable) + analog_ops->speaker_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR: + if (analog_ops->bt_sco_enable) + analog_ops->bt_sco_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR: + if (analog_ops->receiver_enable) + analog_ops->receiver_enable(en); + break; +/* + TODO: we need this? + case ADSP_AUDIO_DEVICE_ID_I2S_SPKR: + if (analog_ops->i2s_enable) + analog_ops->i2s_enable(en); + break; +*/ + } + +} + +static void audio_tx_analog_enable(int en) +{ + AUDIO_INFO("%s\n", __func__); + switch (audio_tx_device_id) { + case ADSP_AUDIO_DEVICE_ID_HANDSET_MIC: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC: + if (analog_ops->int_mic_enable) + analog_ops->int_mic_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_HEADSET_MIC: + case ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC: + if (analog_ops->ext_mic_enable) + analog_ops->ext_mic_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC: + if (analog_ops->bt_sco_enable) + analog_ops->bt_sco_enable(en); + break; + } +} + +static int audio_update_acdb(uint32_t adev, uint32_t acdb_id) +{ + uint32_t sample_rate; + int sz = -1; + + printk("%s (%d, %d)\n", __func__, adev, acdb_id); + AUDIO_INFO("%s (%d, %d)\n", __func__, adev, acdb_id); +// dex_comm(PCOM_UPDATE_ACDB, 0, 0); + sample_rate = q6_device_to_rate(adev); + + if (q6_device_to_dir(adev) == Q6_RX) + rx_acdb = acdb_id; + else + tx_acdb = acdb_id; + + if (acdb_id != 0) + { + sz = acdb_get_config_table(acdb_id, sample_rate); + } + printk("res1 = %d\n", sz); + if (sz <= 0) + { + acdb_id = q6_device_to_cad_id(adev); + printk(" new acdb = %d\n", acdb_id); + sz = acdb_get_config_table(acdb_id, sample_rate); + } + printk("res2 = %d\n", sz); + if (sz > 0) + { + printk("call audio_set_table\n"); + audio_set_table(ac_control, adev, sz); + } + return 0; +} + +static void _audio_rx_path_enable(int reconf, uint32_t acdb_id) +{ + AUDIO_INFO("%s\n", __func__); + adie_enable(); + adie_set_path(adie, audio_rx_path_id, ADIE_PATH_RX); + adie_set_path_freq_plan(adie, ADIE_PATH_RX, 48000); + + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_DIGITAL_READY); + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_DIGITAL_ANALOG_READY); + +// audio_update_acdb(audio_rx_device_id, acdb_id); + if (!reconf) + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, audio_rx_device_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + + audio_update_acdb(audio_rx_device_id, acdb_id); + + audio_rx_analog_enable(1); +} + +static void _audio_tx_path_enable(int reconf, uint32_t acdb_id) +{ + AUDIO_INFO("%s\n", __func__); + audio_tx_analog_enable(1); + + adie_enable(); + adie_set_path(adie, audio_tx_path_id, ADIE_PATH_TX); + + if (tx_clk_freq > 8000) + adie_set_path_freq_plan(adie, ADIE_PATH_TX, 48000); + else + adie_set_path_freq_plan(adie, ADIE_PATH_TX, 8000); + + adie_proceed_to_stage(adie, ADIE_PATH_TX, ADIE_STAGE_DIGITAL_READY); + adie_proceed_to_stage(adie, ADIE_PATH_TX, ADIE_STAGE_DIGITAL_ANALOG_READY); + + audio_update_acdb(audio_tx_device_id, acdb_id); + + if (!reconf) + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, audio_tx_device_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + + audio_tx_mute(ac_control, audio_tx_device_id, tx_mute_status); +} + +static void _audio_rx_path_disable(void) +{ + AUDIO_INFO("%s\n", __func__); + audio_rx_analog_enable(0); + + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_ANALOG_OFF); + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_DIGITAL_OFF); + adie_disable(); +} + +static void _audio_tx_path_disable(void) +{ + AUDIO_INFO("%s\n", __func__); + audio_tx_analog_enable(0); + + adie_proceed_to_stage(adie, ADIE_PATH_TX, ADIE_STAGE_ANALOG_OFF); + adie_proceed_to_stage(adie, ADIE_PATH_TX, ADIE_STAGE_DIGITAL_OFF); + adie_disable(); +} + +static int icodec_rx_clk_refcount; +static int icodec_tx_clk_refcount; +static int ecodec_clk_refcount; +static int sdac_clk_refcount; + +static void _audio_rx_clk_enable(void) +{ + uint32_t device_group = q6_device_to_codec(audio_rx_device_id); + AUDIO_INFO("%s: device 0x%08x, group %d\n", + __func__, audio_rx_device_id, device_group); + switch(device_group) { + case Q6_ICODEC_RX: + icodec_rx_clk_refcount++; + AUDIO_INFO("%s: icodec_rx_clk_refcount = %d\n", + __func__, icodec_rx_clk_refcount); + if (icodec_rx_clk_refcount == 1) { + clk_set_rate(icodec_rx_clk, 12288000); + clk_enable(icodec_rx_clk); + AUDIO_INFO("%s: icodec_rx_clk enabled\n", __func__); + } + break; + case Q6_ECODEC_RX: + ecodec_clk_refcount++; + AUDIO_INFO("%s: ecodec_clk_refcount = %d\n", + __func__, ecodec_clk_refcount); + if (ecodec_clk_refcount == 1) { + clk_set_rate(ecodec_clk, 2048000); + clk_enable(ecodec_clk); + AUDIO_INFO("%s: ecodec_clk enabled\n", __func__); + } + break; + case Q6_SDAC_RX: + sdac_clk_refcount++; + AUDIO_INFO("%s: sdac_clk_refcount = %d\n", + __func__, sdac_clk_refcount); + if (sdac_clk_refcount == 1) { + clk_set_rate(sdac_clk, 12288000); + clk_enable(sdac_clk); + AUDIO_INFO("%s: sdac_clk enabled\n", __func__); + } + break; + default: + return; + } + audio_rx_device_group = device_group; +} + +static void _audio_tx_clk_enable(void) +{ + uint32_t device_group = q6_device_to_codec(audio_tx_device_id); + AUDIO_INFO("%s: device 0x%08x, group %d\n", + __func__, audio_tx_device_id, device_group); + switch (device_group) { + case Q6_ICODEC_TX: + icodec_tx_clk_refcount++; + AUDIO_INFO("%s: icodec_tx_clk_refcount = %d\n", + __func__, icodec_tx_clk_refcount); + if (icodec_tx_clk_refcount == 1) { + clk_set_rate(icodec_tx_clk, tx_clk_freq * 256); + clk_enable(icodec_tx_clk); + AUDIO_INFO("%s: icodec_tx_clk enabled\n", __func__); + } + break; + case Q6_ECODEC_TX: + ecodec_clk_refcount++; + AUDIO_INFO("%s: ecodec_clk_refcount = %d\n", + __func__, ecodec_clk_refcount); + if (ecodec_clk_refcount == 1) { + clk_set_rate(ecodec_clk, 2048000); + clk_enable(ecodec_clk); + AUDIO_INFO("%s: ecodec_clk enabled\n", __func__); + } + break; + case Q6_SDAC_TX: + /* TODO: In QCT BSP, clk rate was set to 20480000 */ + sdac_clk_refcount++; + AUDIO_INFO("%s: sdac_clk_refcount = %d\n", + __func__, sdac_clk_refcount); + if (sdac_clk_refcount == 1) { + clk_set_rate(sdac_clk, 12288000); + clk_enable(sdac_clk); + AUDIO_INFO("%s: sdac_clk enabled\n", __func__); + } + break; + default: + return; + } + audio_tx_device_group = device_group; +} + +static void _audio_rx_clk_disable(void) +{ + AUDIO_INFO("%s: group %d\n", __func__, audio_rx_device_group); + switch (audio_rx_device_group) { + case Q6_ICODEC_RX: + icodec_rx_clk_refcount--; + AUDIO_INFO("%s: icodec_rx_clk_refcount = %d\n", + __func__, icodec_rx_clk_refcount); + if (icodec_rx_clk_refcount == 0) { + clk_disable(icodec_rx_clk); + audio_rx_device_group = -1; + AUDIO_INFO("%s: icodec_rx_clk disabled\n", __func__); + } + break; + case Q6_ECODEC_RX: + ecodec_clk_refcount--; + AUDIO_INFO("%s: ecodec_clk_refcount = %d\n", + __func__, ecodec_clk_refcount); + if (ecodec_clk_refcount == 0) { + clk_disable(ecodec_clk); + audio_rx_device_group = -1; + AUDIO_INFO("%s: ecodec_clk disabled\n", __func__); + } + break; + case Q6_SDAC_RX: + sdac_clk_refcount--; + AUDIO_INFO("%s: sdac_clk_refcount = %d\n", + __func__, sdac_clk_refcount); + if (sdac_clk_refcount == 0) { + clk_disable(sdac_clk); + audio_rx_device_group = -1; + AUDIO_INFO("%s: sdac_clk disabled\n", __func__); + } + break; + default: + pr_err("audiolib: invalid rx device group %d\n", + audio_rx_device_group); + break; + } +} + +static void _audio_tx_clk_disable(void) +{ + AUDIO_INFO("%s: group %d\n", __func__, audio_tx_device_group); + switch (audio_tx_device_group) { + case Q6_ICODEC_TX: + icodec_tx_clk_refcount--; + AUDIO_INFO("%s: icodec_tx_clk_refcount = %d\n", + __func__, icodec_tx_clk_refcount); + if (icodec_tx_clk_refcount == 0) { + clk_disable(icodec_tx_clk); + audio_tx_device_group = -1; + AUDIO_INFO("%s: icodec_tx_clk disabled\n", __func__); + } + break; + case Q6_ECODEC_TX: + ecodec_clk_refcount--; + AUDIO_INFO("%s: ecodec_clk_refcount = %d\n", + __func__, ecodec_clk_refcount); + if (ecodec_clk_refcount == 0) { + clk_disable(ecodec_clk); + audio_tx_device_group = -1; + AUDIO_INFO("%s: ecodec_clk disabled\n", __func__); + } + break; + case Q6_SDAC_TX: + sdac_clk_refcount--; + AUDIO_INFO("%s: sdac_clk_refcount = %d\n", + __func__, sdac_clk_refcount); + if (sdac_clk_refcount == 0) { + clk_disable(sdac_clk); + audio_tx_device_group = -1; + AUDIO_INFO("%s: sdac_clk disabled\n", __func__); + } + break; + default: + pr_err("audiolib: invalid tx device group %d\n", + audio_tx_device_group); + break; + } +} + +static void _audio_rx_clk_reinit(uint32_t rx_device) +{ + uint32_t device_group = q6_device_to_codec(rx_device); + + AUDIO_INFO("%s\n", __func__); + + if (device_group != audio_rx_device_group) + _audio_rx_clk_disable(); + + audio_rx_device_id = rx_device; + audio_rx_path_id = q6_device_to_path(rx_device); + + if (device_group != audio_rx_device_group) + _audio_rx_clk_enable(); + +} + +static void _audio_tx_clk_reinit(uint32_t tx_device) +{ + uint32_t device_group = q6_device_to_codec(tx_device); + + AUDIO_INFO("%s\n", __func__); + if (device_group != audio_tx_device_group) + _audio_tx_clk_disable(); + + audio_tx_device_id = tx_device; + audio_tx_path_id = q6_device_to_path(tx_device); + + if (device_group != audio_tx_device_group) + _audio_tx_clk_enable(); +} + +static DEFINE_MUTEX(audio_path_lock); +static int audio_rx_path_refcount; +static int audio_tx_path_refcount; + +static int audio_rx_path_enable(int en, uint32_t acdb_id) +{ + AUDIO_INFO("%s\n", __func__); + mutex_lock(&audio_path_lock); + if (en) { + audio_rx_path_refcount++; + if (audio_rx_path_refcount == 1) { + _audio_rx_clk_enable(); + _audio_rx_path_enable(0, acdb_id); + } + } else { + audio_rx_path_refcount--; + if (audio_rx_path_refcount == 0) { + _audio_rx_path_disable(); + _audio_rx_clk_disable(); + } + } + mutex_unlock(&audio_path_lock); + return 0; +} + +static int audio_tx_path_enable(int en, uint32_t acdb_id) +{ + AUDIO_INFO("%s\n", __func__); + mutex_lock(&audio_path_lock); + if (en) { + audio_tx_path_refcount++; + if (audio_tx_path_refcount == 1) { + _audio_tx_clk_enable(); + _audio_tx_path_enable(0, acdb_id); + } + } else { + audio_tx_path_refcount--; + if (audio_tx_path_refcount == 0) { + _audio_tx_path_disable(); + _audio_tx_clk_disable(); + } + } + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_reinit_acdb(char* filename) +{ + int res; + + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + if (strlen(filename) < 0) { + res = -EINVAL; + goto done; + } + res = acdb_init(filename); + if (!res) + strcpy(acdb_file, filename); +done: + mutex_unlock(&audio_path_lock); + return res; + +} + +int q6audio_update_acdb(uint32_t id_src, uint32_t id_dst) +{ + int res; + + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + res = audio_update_acdb(id_dst, id_src); + if (res) + goto done; + + if (q6_device_to_dir(id_dst) == Q6_RX) + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, id_dst); + else + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, id_dst); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); +done: + mutex_unlock(&audio_path_lock); + return res; +} + +int q6audio_set_tx_mute(int mute) +{ + uint32_t adev; + int rc; + + AUDIO_INFO("%s\n", __func__); + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + + if (mute == tx_mute_status) { + mutex_unlock(&audio_path_lock); + return 0; + } + + adev = audio_tx_device_id; + rc = audio_tx_mute(ac_control, adev, mute); + tx_mute_status = mute; + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_stream_volume(struct audio_client *ac, int vol) +{ + AUDIO_INFO("%s\n", __func__); + if (vol > 1200 || vol < -4000) { + pr_err("unsupported volume level %d\n", vol); + return -EINVAL; + } + mutex_lock(&audio_path_lock); + audio_stream_mute(ac, 0); + audio_stream_volume(ac, vol); + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_stream_eq(struct audio_client *ac, struct cad_audio_eq_cfg *eq_cfg) +{ + AUDIO_INFO("%s\n", __func__); + mutex_lock(&audio_path_lock); + audio_stream_eq(ac, eq_cfg); + mutex_unlock(&audio_path_lock); + return 0; +} + + +int q6audio_set_rx_dev_volume(int level) +{ + int vol; + + AUDIO_INFO("%s\n", __func__); + + mutex_lock(&audio_path_lock); + + vol = q6_device_volume(audio_rx_device_id, level); + printk("$$ DEV=%08X: vol is %d\n", audio_rx_device_id, vol); + audio_rx_volume(ac_control, audio_rx_device_id, vol); + + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_rx_volume(int level) +{ +#if 0 + uint32_t adev; + int vol; + + AUDIO_INFO("%s\n", __func__); + if (q6audio_init()) + return 0; + + if (level < 0 || level > 100) + return -EINVAL; + + mutex_lock(&audio_path_lock); + adev = ADSP_AUDIO_DEVICE_ID_VOICE; + vol = q6_device_volume(audio_rx_device_id, level); + audio_rx_mute(ac_control, adev, 0); + printk("@@@@ rx volume: adev=%d, rx_dev_id=%d, level=%d @@@@\n", adev, audio_rx_device_id, vol); + audio_rx_volume(ac_control, adev, vol); + rx_vol_level = level; + mutex_unlock(&audio_path_lock); +#else + q6audio_set_rx_dev_volume(level); +#endif + return 0; +} + +static int q6audio_init_rx_volumes() +{ + int vol; + struct q6_device_info *di = q6_audio_devices; + + AUDIO_INFO("%s\n", __func__); + + mutex_lock(&audio_path_lock); + + printk("$$$ q6audio_init_rx_volumes\n"); + while (1) + { + if (di->id == 0) break; + + vol = q6_device_volume(di->id, 100); + audio_rx_volume(ac_control, di->id, vol); + printk("$$ DEV=%08X: vol is %d\n", di->id, vol); + + di++; + } + + mutex_unlock(&audio_path_lock); + return 0; +} + + +int q6audio_set_rx_mute(int mute) +{ + uint32_t adev; + + AUDIO_INFO("%s\n", __func__); + if (q6audio_init()) + return 0; + + if (mute < 0 || mute > 1) + return -EINVAL; + + mutex_lock(&audio_path_lock); + AUDIO_INFO("%s: set mute status %d\n", __func__, mute); + adev = ADSP_AUDIO_DEVICE_ID_VOICE; + audio_rx_mute(ac_control, adev, mute); + mutex_unlock(&audio_path_lock); + return 0; +} + +static void do_rx_routing(uint32_t device_id, uint32_t acdb_id) +{ + AUDIO_INFO("%s\n", __func__); + if (device_id == audio_rx_device_id) { + if (acdb_id != rx_acdb) { + audio_update_acdb(device_id, acdb_id); + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, device_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + } + return; + } + + if (audio_rx_path_refcount > 0) { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, device_id); + _audio_rx_path_disable(); + _audio_rx_clk_reinit(device_id); + _audio_rx_path_enable(1, acdb_id); + } else { + audio_rx_device_id = device_id; + audio_rx_path_id = q6_device_to_path(device_id); + } +} + +static void do_tx_routing(uint32_t device_id, uint32_t acdb_id) +{ + AUDIO_INFO("%s\n", __func__); + if (device_id == audio_tx_device_id) { + if (acdb_id != tx_acdb) { + audio_update_acdb(device_id, acdb_id); + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, device_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + } + return; + } + + if (audio_tx_path_refcount > 0) { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, device_id); + _audio_tx_path_disable(); + _audio_tx_clk_reinit(device_id); + _audio_tx_path_enable(1, acdb_id); + } else { + audio_tx_device_id = device_id; + audio_tx_path_id = q6_device_to_path(device_id); + } +} + +int q6audio_do_routing(uint32_t device_id, uint32_t acdb_id) +{ + AUDIO_INFO("%s\n", __func__); + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + + switch(q6_device_to_dir(device_id)) { + case Q6_RX: + do_rx_routing(device_id, acdb_id); + break; + case Q6_TX: + do_tx_routing(device_id, acdb_id); + break; + } + + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_route(const char *name) +{ + uint32_t route; + AUDIO_INFO("%s\n", __func__); + if (!strcmp(name, "speaker")) { + route = ADIE_PATH_SPEAKER_STEREO_RX; + } else if (!strcmp(name, "headphones")) { + route = ADIE_PATH_HEADSET_STEREO_RX; + } else if (!strcmp(name, "handset")) { + route = ADIE_PATH_HANDSET_RX; + } else { + return -EINVAL; + } + + mutex_lock(&audio_path_lock); + if (route == audio_rx_path_id) + goto done; + + audio_rx_path_id = route; + + if (audio_rx_path_refcount > 0) + { + _audio_rx_path_disable(); + _audio_rx_path_enable(1, 0); + } + if (audio_tx_path_refcount > 0) + { + _audio_tx_path_disable(); + _audio_tx_path_enable(1, 0); + } +done: + mutex_unlock(&audio_path_lock); + return 0; +} + +struct audio_client *q6audio_open_pcm(uint32_t bufsz, uint32_t rate, + uint32_t channels, uint32_t flags, uint32_t acdb_id) +{ +#if 1 + int rc, retry = 5; + struct audio_client *ac; + + AUDIO_INFO("%s\n", __func__); + if (q6audio_init()) + return 0; + +// printk("afer init();\n"); + ac = audio_client_alloc(bufsz); + if (!ac) + { + printk("audio_client_alloc failed\n"); + return 0; + } +// printk("after alloc);\n"); + ac->flags = flags; + + mutex_lock(&audio_path_lock); + + if (ac->flags & AUDIO_FLAG_WRITE) + { + audio_rx_path_refcount++; + if (audio_rx_path_refcount == 1) + { + _audio_rx_clk_enable(); +// audio_update_acdb(audio_rx_device_id, acdb_id); + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, audio_rx_device_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + audio_update_acdb(audio_rx_device_id, acdb_id); + } + } + else + { + /* TODO: consider concurrency with voice call */ + tx_clk_freq = rate; + audio_tx_path_refcount++; + if (audio_tx_path_refcount == 1) + { + _audio_tx_clk_enable(); + _audio_tx_path_enable(0, acdb_id); + } + } +// printk("about to open\n"); + + for (retry = 5;;retry--) + { + if (ac->flags & AUDIO_FLAG_WRITE) + rc = audio_out_open(ac, bufsz, rate, channels); + else + rc = audio_in_open(ac, bufsz, rate, channels); + if (rc == 0) + break; + if (retry == 0) + { + // BUG(); + break; + } + pr_err("q6audio: open pcm error %d, retrying\n", rc); + msleep(1); + } + + if (retry == 0) + { + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(0, 0); + else + audio_tx_path_enable(0, 0); + audio_client_free(ac); + pr_err("q6audio: open pcm error\n"); + return NULL; + } + +// printk("after open\n"); + + if (ac->flags & AUDIO_FLAG_WRITE) + { + if (audio_rx_path_refcount == 1) + { + adie_enable(); + adie_set_path(adie, audio_rx_path_id, ADIE_PATH_RX); + adie_set_path_freq_plan(adie, ADIE_PATH_RX, 48000); + + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_DIGITAL_READY); + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_DIGITAL_ANALOG_READY); + + audio_rx_analog_enable(1); + } + } +// printk("about to start session\n"); + + mutex_unlock(&audio_path_lock); + + for (retry = 5;;retry--) + { + rc = audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + if (rc == 0) + break; + if (retry == 0) + { +// BUG(); + break; + } + pr_err("q6audio: stream start error %d, retrying\n", rc); + } + + if (retry == 0) + { + audio_close(ac); + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(0, 0); + else + audio_tx_path_enable(0, 0); + audio_client_free(ac); + pr_err("q6audio: open pcm error2\n"); + return NULL; + } + + if (!(ac->flags & AUDIO_FLAG_WRITE)) + { + ac->buf[0].used = 1; + ac->buf[1].used = 1; + q6audio_read(ac, &ac->buf[0]); + q6audio_read(ac, &ac->buf[1]); + } + + audio_prevent_sleep(); + return ac; +#else + return audio_test(); +#endif +} + +int q6audio_close(struct audio_client *ac) +{ + AUDIO_INFO("%s\n", __func__); + audio_close(ac); + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(0, 0); + else + audio_tx_path_enable(0, 0); + + audio_client_free(ac); + audio_allow_sleep(); + return 0; +} + +struct audio_client *q6voice_open(uint32_t flags, uint32_t acdb_id) +{ + struct audio_client *ac; + + AUDIO_INFO("%s\n", __func__); + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(0); + if (!ac) + return 0; + + ac->flags = flags; + if (ac->flags & AUDIO_FLAG_WRITE) { + audio_rx_path_enable(1, acdb_id); + audio_rx_mute(ac_control, ADSP_AUDIO_DEVICE_ID_VOICE, 0); + } else { + tx_clk_freq = 8000; + audio_tx_path_enable(1, acdb_id); + } + + return ac; +} + +int q6voice_close(struct audio_client *ac) +{ + AUDIO_INFO("%s\n", __func__); + if (ac->flags & AUDIO_FLAG_WRITE) { + audio_rx_mute(ac_control, ADSP_AUDIO_DEVICE_ID_VOICE, 1); + audio_rx_path_enable(0, 0); + } else + audio_tx_path_enable(0, 0); + + audio_client_free(ac); + return 0; +} + +struct audio_client *q6audio_open_mp3(uint32_t bufsz, uint32_t rate, + uint32_t channels, uint32_t acdb_id) +{ + struct audio_client *ac; + + printk("q6audio_open_mp3()\n"); + + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + return 0; + + ac->flags = AUDIO_FLAG_WRITE; + audio_rx_path_enable(1, acdb_id); + + audio_mp3_open(ac, bufsz, rate, channels); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + return ac; +} + +int q6audio_mp3_close(struct audio_client *ac) +{ + AUDIO_INFO("%s\n", __func__); + audio_close(ac); + audio_rx_path_enable(0, 0); + audio_client_free(ac); + return 0; +} + +int q6audio_async(struct audio_client *ac) +{ + struct adsp_command_hdr rpc; + memset(&rpc, 0, sizeof(rpc)); +// rpc.response_type = ADSP_AUDIO_RESPONSE_ASYNC; + return audio_ioctl(ac, ADSP_AUDIO_IOCTL_CMD_STREAM_EOS, &rpc, sizeof(rpc)); +} + + + +struct audio_client *q6audio_open_aac(uint32_t bufsz, uint32_t rate, + uint32_t flags, void *data, uint32_t acdb_id) +{ + struct audio_client *ac; + + TRACE("q6audio_open_aac flags=%d rate=%d\n", flags, rate); + + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + return 0; + + ac->flags = flags; + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(1, acdb_id); + else { + /* TODO: consider concourrency with voice call */ + tx_clk_freq = rate; + audio_tx_path_enable(1, acdb_id); + } + + audio_aac_open(ac, bufsz, data); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + if (!(ac->flags & AUDIO_FLAG_WRITE)) { + ac->buf[0].used = 1; + ac->buf[1].used = 1; + q6audio_read(ac, &ac->buf[0]); + q6audio_read(ac, &ac->buf[1]); + } + audio_prevent_sleep(); + return ac; +} + +int q6audio_aac_close(struct audio_client *ac) +{ + audio_close(ac); + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(0, 0); + else + audio_tx_path_enable(0, 0); + + audio_client_free(ac); + audio_allow_sleep(); + return 0; +} + +struct audio_client *q6fm_open(void) +{ + struct audio_client *ac; + + printk("q6fm_open()\n"); + + if (q6audio_init()) + return 0; + + if (audio_rx_device_id != ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO && + audio_rx_device_id != ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO) + return 0; + + ac = audio_client_alloc(0); + if (!ac) + return 0; + + ac->flags = AUDIO_FLAG_WRITE; + audio_rx_path_enable(1, 0); + enable_aux_loopback(1); + + return ac; +} + +int q6fm_close(struct audio_client *ac) +{ + printk("q6fm_close\n"); + + audio_rx_path_enable(0, 0); + enable_aux_loopback(0); + audio_client_free(ac); + return 0; +} + +struct audio_client *q6audio_open_qcelp(uint32_t bufsz, uint32_t rate, + void *data, uint32_t acdb_id) +{ + struct audio_client *ac; + + AUDIO_INFO("%s\n", __func__); + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + { + return 0; + } + + ac->flags = AUDIO_FLAG_READ; + tx_clk_freq = rate; + audio_tx_path_enable(1, acdb_id); + + audio_qcelp_open(ac, bufsz, data); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + ac->buf[0].used = 1; + ac->buf[1].used = 1; + q6audio_read(ac, &ac->buf[0]); + q6audio_read(ac, &ac->buf[1]); + + audio_prevent_sleep(); + return ac; +} + +int q6audio_qcelp_close(struct audio_client *ac) +{ + audio_close(ac); + audio_tx_path_enable(0, 0); + audio_client_free(ac); + audio_allow_sleep(); + return 0; +} + +/////////////////////////////////////////////////////////////////////////// + +int acdb_get_table(int dev_id, int sample_rate) +{ + struct acdb_cmd_device_table rpc; + struct acdb_result res; + int r; + + memset(audio_data, 0, 4096); + memset(&rpc, 0, sizeof(rpc)); + + rpc.size = sizeof(rpc) - (2 * sizeof(uint32_t)); + rpc.command_id = ACDB_GET_DEVICE_TABLE; + rpc.device_id = dev_id; + rpc.sample_rate_id = sample_rate; + rpc.total_bytes = 4096; + rpc.unmapped_buf = audio_phys; + rpc.res_size = sizeof(res) - (2 * sizeof(uint32_t)); + + r = dal_call(acdb, ACDB_OP_IOCTL, 8, &rpc, sizeof(rpc), &res, sizeof(res)); + + if ((r == sizeof(res)) && (res.dal_status == 0)) + { + pr_info("acdb: %d bytes for device %d, rate %d.\n", + res.used_bytes, dev_id, sample_rate); + return res.used_bytes; + } + return 0; +} + +static struct audio_client * audio_test(void) +{ + struct audio_client *ac = 0; + struct audio_client *ac2 = 0; + int size; + struct rpc_info info; + + pr_info("audio: init: codecs\n"); + icodec_rx_clk = clk_get(0, "icodec_rx_clk"); + icodec_tx_clk = clk_get(0, "icodec_tx_clk"); + ecodec_clk = clk_get(0, "ecodec_clk"); + sdac_clk = clk_get(0, "sdac_clk"); + audio_data = dma_alloc_coherent(NULL, 4096, &audio_phys, GFP_KERNEL); + params_data = dma_alloc_coherent(NULL, 4096, ¶ms_phys, GFP_KERNEL); + printk("allocated: %p %08X\n", params_data, params_phys); + + clk_set_rate(icodec_rx_clk, 12288000); + clk_enable(icodec_rx_clk); + clk_set_rate(ecodec_clk, 2048000); + clk_enable(ecodec_clk); + clk_set_rate(sdac_clk, 12288000); + clk_enable(sdac_clk); + + // 1. attach ADIE + adie = dal_attach_ex(ADIE_DAL_DEVICE, "NULL", ADIE_DAL_PORT , 0, 0); + if (!adie) + { + pr_err("audio_init: cannot attach to adie\n"); + return 0; + } + + if (analog_ops->init) + analog_ops->init(); + + audio_rx_device_id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO; + audio_rx_analog_enable(1); + + dal_call_f9(adie, DAL_OP_INFO, &info, sizeof(info)); + + + acdb = dal_attach(ACDB_DAL_DEVICE, ACDB_DAL_PORT, 0, 0); + if (!acdb) + { + pr_err("audio_init: cannot attach to acdb channel\n"); + return 0; + } + + dal_call_f9(acdb, DAL_OP_INFO, &info, sizeof(info)); + + audio_client_alloc(0); + + // audio_client_alloc(0); + + ac = audio_client_alloc(0); + + audio_open_control(ac); +//mdelay(1000); + + audio_rx_mute(ac, ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR, 0); + audio_rx_volume(ac, ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR, 0); + + acdb_get_table(7, 48000); + + audio_rx_device_id = ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR; + qdsp6_devchg_notify(ac, ADSP_AUDIO_RX_DEVICE, ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO); + + qdsp6_standby(ac); + qdsp6_start(ac); + + size = acdb_get_table(0x25F, 48000); + audio_set_table(ac, ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, size); + + audio_rx_device_id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO; + qdsp6_devchg_notify(ac, ADSP_AUDIO_RX_DEVICE, ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO); + + qdsp6_standby(ac); + qdsp6_start(ac); + + audio_rx_mute(ac, ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, 0); + audio_rx_volume(ac, ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, 500); + + + size = acdb_get_table(507, 8000); + audio_set_table(ac, ADSP_AUDIO_DEVICE_ID_HANDSET_MIC, size); + + audio_rx_device_id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC; + qdsp6_devchg_notify(ac, ADSP_AUDIO_TX_DEVICE, ADSP_AUDIO_DEVICE_ID_HANDSET_MIC); + + qdsp6_standby(ac); + qdsp6_start(ac); + + size = acdb_get_table(0x25F, 48000); + audio_set_table(ac, ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, size); + + ac2 = audio_client_alloc(4096); + + audio_out_open(ac2, 4096, 44100, 2); + + adie_open(adie); + + adie_set_path(adie, ADIE_PATH_SPEAKER_RX, ADIE_PATH_RX); + adie_set_path_freq_plan(adie, ADIE_PATH_RX, 48000); + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_DIGITAL_READY); + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_DIGITAL_ANALOG_READY); + + audio_command(ac2, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + audio_rx_mute(ac, ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, 0); + audio_rx_volume(ac, ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, 500); + + return ac2; +} + + +// END OF FILE diff --git a/arch/arm/mach-msm/qdsp6_1550/q6audio_devices.h b/arch/arm/mach-msm/qdsp6_1550/q6audio_devices.h new file mode 100644 index 00000000..7f6c257f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/q6audio_devices.h @@ -0,0 +1,268 @@ +/* arch/arm/mach-msm/qdsp6/q6audio_devices.h + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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. + * + */ + +struct q6_device_info { + uint32_t id; + uint32_t cad_id; + uint32_t path; + uint32_t rate; + uint8_t dir; + uint8_t codec; + uint8_t hw; +}; + +#define Q6_ICODEC_RX 0 +#define Q6_ICODEC_TX 1 +#define Q6_ECODEC_RX 2 +#define Q6_ECODEC_TX 3 +#define Q6_SDAC_RX 6 +#define Q6_SDAC_TX 7 +#define Q6_CODEC_NONE 255 + +#define Q6_TX 1 +#define Q6_RX 2 +#define Q6_TX_RX 3 + +#define CAD_HW_DEVICE_ID_HANDSET_MIC 0x01 +#define CAD_HW_DEVICE_ID_HANDSET_SPKR 0x02 +#define CAD_HW_DEVICE_ID_HEADSET_MIC 0x03 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO 0x04 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO 0x05 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_MIC 0x06 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_MONO 0x07 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_STEREO 0x08 +#define CAD_HW_DEVICE_ID_BT_SCO_MIC 0x09 +#define CAD_HW_DEVICE_ID_BT_SCO_SPKR 0x0A +#define CAD_HW_DEVICE_ID_BT_A2DP_SPKR 0x0B +#define CAD_HW_DEVICE_ID_TTY_HEADSET_MIC 0x0C +#define CAD_HW_DEVICE_ID_TTY_HEADSET_SPKR 0x0D + +#define CAD_HW_DEVICE_ID_DEFAULT_TX 0x0E +#define CAD_HW_DEVICE_ID_DEFAULT_RX 0x0F + +/* Logical Device to indicate A2DP routing */ +#define CAD_HW_DEVICE_ID_BT_A2DP_TX 0x10 +#define CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_MONO_RX 0x11 +#define CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_STEREO_RX 0x12 +#define CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_MONO_RX 0x13 +#define CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX 0x14 + +#define CAD_HW_DEVICE_ID_VOICE 0x15 + +#define CAD_HW_DEVICE_ID_I2S_RX 0x20 +#define CAD_HW_DEVICE_ID_I2S_TX 0x21 + +/* AUXPGA */ +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO_LB 0x22 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO_LB 0x23 +#define CAD_HW_DEVICE_ID_SPEAKER_SPKR_STEREO_LB 0x24 +#define CAD_HW_DEVICE_ID_SPEAKER_SPKR_MONO_LB 0x25 + +#define CAD_HW_DEVICE_ID_NULL_RX 0x2A + +#define CAD_HW_DEVICE_ID_MAX_NUM 0x2F + +#define CAD_HW_DEVICE_ID_INVALID 0xFF + +#define CAD_RX_DEVICE 0x00 +#define CAD_TX_DEVICE 0x01 + +static struct q6_device_info q6_audio_devices[] = { + { + .id = ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR, + .cad_id = CAD_HW_DEVICE_ID_HANDSET_SPKR, + .path = ADIE_PATH_HANDSET_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HANDSET, + }, + + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO, + .path = ADIE_PATH_HEADSET_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO, + .path = ADIE_PATH_HEADSET_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HEADSET, + }, + + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_MONO, + .path = ADIE_PATH_SPEAKER_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_STEREO, + .path = ADIE_PATH_SPEAKER_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_MONO_RX, + .path = ADIE_PATH_SPKR_MONO_HDPH_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_MONO_RX, + .path = ADIE_PATH_SPKR_MONO_HDPH_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_STEREO_RX, + .path = ADIE_PATH_SPKR_STEREO_HDPH_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX, + .path = ADIE_PATH_SPKR_STEREO_HDPH_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR, + .cad_id = CAD_HW_DEVICE_ID_TTY_HEADSET_SPKR, + .path = ADIE_PATH_TTY_HEADSET_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_TTY, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_HANDSET_MIC, + .path = ADIE_PATH_HANDSET_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HANDSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MIC, + .path = ADIE_PATH_HEADSET_MONO_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_MIC, + .path = ADIE_PATH_SPEAKER_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_TTY_HEADSET_MIC, + .path = ADIE_PATH_TTY_HEADSET_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR, + .cad_id = CAD_HW_DEVICE_ID_BT_SCO_SPKR, + .path = 0, /* XXX */ + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ECODEC_RX, + .hw = Q6_HW_BT_SCO, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_A2DP_SPKR, + .cad_id = CAD_HW_DEVICE_ID_BT_A2DP_SPKR, + .path = 0, /* XXX */ + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ECODEC_RX, + .hw = Q6_HW_BT_A2DP, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC, + .cad_id = CAD_HW_DEVICE_ID_BT_SCO_MIC, + .path = 0, /* XXX */ + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ECODEC_TX, + .hw = Q6_HW_BT_SCO, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_I2S_SPKR, + .cad_id = CAD_HW_DEVICE_ID_I2S_RX, + .path = 0, /* XXX */ + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_SDAC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_I2S_MIC, + .cad_id = CAD_HW_DEVICE_ID_I2S_TX, + .path = 0, /* XXX */ + .rate = 16000, + .dir = Q6_TX, + .codec = Q6_SDAC_TX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = 0, + .cad_id = 0, + .path = 0, + .rate = 8000, + .dir = 0, + .codec = Q6_CODEC_NONE, + .hw = 0, + }, +}; + diff --git a/arch/arm/mach-msm/qdsp6_1550/qcelp_in.c b/arch/arm/mach-msm/qdsp6_1550/qcelp_in.c new file mode 100644 index 00000000..e5843181 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/qcelp_in.c @@ -0,0 +1,212 @@ +/* arch/arm/mach-msm/qdsp6/qcelp_in.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSZ (734) +#define DMASZ (BUFSZ * 2) + +#if 0 +#define TRACE(x...) pr_info("Q6: "x) +#else +#define TRACE(x...) do{}while(0) +#endif + +static DEFINE_MUTEX(qcelp_in_lock); +static int qcelp_in_opened = 0; +static struct msm_audio_qcelp_config *qf; + +void audio_client_dump(struct audio_client *ac); + +static long qcelp_in_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + + switch (cmd) { + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STATS: { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + case AUDIO_START: { + uint32_t acdb_id; + rc = 0; + + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void*) arg, sizeof(acdb_id))) { + rc = -EFAULT; + break; + } + + mutex_lock(&qcelp_in_lock); + if (file->private_data) { + rc = -EBUSY; + } else { + file->private_data = q6audio_open_qcelp( + BUFSZ, 8000, qf, acdb_id); + if (!file->private_data) + rc = -ENOMEM; + } + mutex_unlock(&qcelp_in_lock); + break; + } + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_GET_CONFIG: + if (copy_to_user((void *)arg, qf, + sizeof(struct msm_audio_qcelp_config))) + return -EFAULT; + break; + case AUDIO_SET_CONFIG: + if (copy_from_user(qf, (void *)arg, + sizeof(struct msm_audio_qcelp_config))) + return -EFAULT; + if (qf->min_bit_rate > 4 || qf->min_bit_rate < 1) { + pr_err("invalid min bitrate\n"); + return -EINVAL; + } + if (qf->max_bit_rate > 4 || qf->max_bit_rate < 1) { + pr_err("invalid max bitrate\n"); + return -EINVAL; + } + if (qf->cdma_rate > CDMA_RATE_ERASURE || + qf->cdma_rate < CDMA_RATE_BLANK) { + pr_err("invalid qcelp cdma rate\n"); + return -EINVAL; + } + break; + default: + rc = -EINVAL; + } + return rc; +} + +static int qcelp_in_open(struct inode *inode, struct file *file) +{ + int rc; + + pr_info("qcelp_in: open\n"); + mutex_lock(&qcelp_in_lock); + if (qcelp_in_opened) { + pr_err("qcelp_in: busy\n"); + rc = -EBUSY; + } else { + qf = kzalloc(sizeof(*qf), GFP_KERNEL); + memset(qf, 0, sizeof(struct msm_audio_qcelp_config)); + qf->channels = 1; + qf->cdma_rate = 0x04; /* CDMA_RATE_FULL */ + qf->min_bit_rate = 1; + qf->max_bit_rate = 4; + qcelp_in_opened = 1; + rc = 0; + } + mutex_unlock(&qcelp_in_lock); + return rc; +} + +static ssize_t qcelp_in_read(struct file *file, char __user *buf, size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer, res = 0; + + mutex_lock(&qcelp_in_lock); + ac = file->private_data; + if (!ac) { + res = -ENODEV; + pr_err("qcelp_in_read ac NULL\n"); + goto fail; + } + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + TRACE("qcelp_in_read wait count=%d ab=%d ac->buf=%d cpu_buf=%d ac->buf[1]=%d\n", + count, ab, ac->buf, ac->cpu_buf, &(ac->buf[1])); + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + TRACE(" qcelp_in_read event arrive ab->size=%d\n", ab->size); + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + pr_err("Tomdbg copy to user failed \n"); + goto fail; + } + TRACE("qcelp_in read buf = %d,xfer = %d,cnt = %d\n", buf, xfer, count); + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } +fail: + res = buf - start; + mutex_unlock(&qcelp_in_lock); + return res; +} + +static int qcelp_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + pr_info("qcelp_in: release\n"); + mutex_lock(&qcelp_in_lock); + if (file->private_data) + rc = q6audio_close(file->private_data); + kfree(qf); + qcelp_in_opened = 0; + mutex_unlock(&qcelp_in_lock); + return rc; +} + +static struct file_operations qcelp_in_fops = { + .owner = THIS_MODULE, + .open = qcelp_in_open, + .read = qcelp_in_read, + .release = qcelp_in_release, + .unlocked_ioctl = qcelp_in_ioctl, +}; + +struct miscdevice qcelp_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp_in", + .fops = &qcelp_in_fops, +}; + +static int __init qcelp_in_init(void) { + return misc_register(&qcelp_in_misc); +} + +device_initcall(qcelp_in_init); diff --git a/arch/arm/mach-msm/qdsp6_1550/routing.c b/arch/arm/mach-msm/qdsp6_1550/routing.c new file mode 100644 index 00000000..a8518964 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6_1550/routing.c @@ -0,0 +1,71 @@ +/* arch/arm/mach-msm/qdsp6/routing.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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 +#include +#include +#include + +extern int q6audio_set_route(const char *name); + +static int q6_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t q6_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char cmd[32]; + + if (count >= sizeof(cmd)) + return -EINVAL; + if (copy_from_user(cmd, buf, count)) + return -EFAULT; + cmd[count] = 0; + + if ((count > 1) && (cmd[count-1] == '\n')) + cmd[count-1] = 0; + + q6audio_set_route(cmd); + + return count; +} + +static int q6_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations q6_fops = { + .owner = THIS_MODULE, + .open = q6_open, + .write = q6_write, + .release = q6_release, +}; + +static struct miscdevice q6_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_audio_route", + .fops = &q6_fops, +}; + + +static int __init q6_init(void) { + return misc_register(&q6_misc); +} + +device_initcall(q6_init); diff --git a/include/linux/msm_q6venc_1550.h b/include/linux/msm_q6venc_1550.h new file mode 100755 index 00000000..2ca4832d --- /dev/null +++ b/include/linux/msm_q6venc_1550.h @@ -0,0 +1,125 @@ +/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora Forum nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +#ifndef _MSM_VENC_H_ +#define _MSM_VENC_H_ + +#include + +struct venc_buf { + unsigned int src_id; + int fd; + unsigned long offset; + unsigned long size; +}; + +struct q6_init_config { + unsigned short venc_standard; + unsigned short partial_run_length_flag; + unsigned short h263_annex_ispt; + unsigned short h263_annex_jspt; + unsigned short h263_annex_tspt; + unsigned short rc_flag; + unsigned short one_mv_flag; + unsigned short acdc_pred_enable; + unsigned short rounding_bit_ctrl; + unsigned short rotation_flag; + unsigned short max_mvx; + unsigned short max_mvy; + unsigned short enc_frame_height_inmb; + unsigned short enc_frame_width_inmb; + unsigned short dvs_frame_height; + unsigned short dvs_frame_width; + + /* unused by userspace, filled in by kernel */ + unsigned int ref_frame_buf1_phy; + unsigned int ref_frame_buf2_phy; + unsigned int rlc_buf1_phy; + unsigned int rlc_buf2_phy; + unsigned int rlc_buf_length; +}; + +struct init_config { + struct venc_buf ref_frame_buf1; + struct venc_buf ref_frame_buf2; + struct venc_buf rlc_buf1; + struct venc_buf rlc_buf2; + struct q6_init_config q6_init_config; +}; + +struct q6_encode_param { + unsigned int luma_addr; + unsigned int chroma_addr; + unsigned int x_offset; + unsigned int y_offset; + unsigned int frame_rho_budget; + unsigned int frame_type; + unsigned int qp; +}; + +struct encode_param { + struct venc_buf y_addr; + unsigned long uv_offset; + struct q6_encode_param q6_encode_param; +}; + +struct intra_refresh { + unsigned int intra_refresh_enable; + unsigned int intra_mb_num; +}; + +struct rc_config { + unsigned short max_frame_qp_up_delta; + unsigned short max_frame_qp_down_delta; + unsigned short min_frame_qp; + unsigned short max_frame_qp; +}; + +struct q6_frame_type { + unsigned int frame_type; + unsigned int frame_len; + unsigned int frame_addr; + unsigned int map_table; +}; + +struct frame_type { + struct venc_buf frame_addr; + struct q6_frame_type q6_frame_type; +}; + +#define VENC_IOCTL_MAGIC 'V' + +#define VENC_IOCTL_INITIALIZE _IOW(VENC_IOCTL_MAGIC, 1, struct init_config) +#define VENC_IOCTL_ENCODE _IOW(VENC_IOCTL_MAGIC, 2, struct encode_param) +#define VENC_IOCTL_INTRA_REFRESH _IOW(VENC_IOCTL_MAGIC, 3, struct intra_refresh) +#define VENC_IOCTL_RC_CONFIG _IOW(VENC_IOCTL_MAGIC, 4, struct rc_config) +#define VENC_IOCTL_ENCODE_CONFIG _IOW(VENC_IOCTL_MAGIC, 5, struct init_config) +#define VENC_IOCTL_STOP _IO(VENC_IOCTL_MAGIC, 6) +#define VENC_IOCTL_WAIT_FOR_ENCODE _IOR(VENC_IOCTL_MAGIC, 7, struct frame_type) +#define VENC_IOCTL_STOP_ENCODE _IO(VENC_IOCTL_MAGIC, 8) + +#endif /* _MSM_VENC_H_ */