| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| * Author: Kai Chieh Chuang <kaichieh.chuang@mediatek.com> |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/miscdevice.h> |
| #include <linux/mm.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/notifier.h> |
| |
| #include <mtk_ccci_common.h> |
| #include "audio_messenger_ipi.h" |
| #include "audio_task.h" |
| #include "audio_speech_msg_id.h" |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| #include <adsp_helper.h> |
| #endif |
| #if IS_ENABLED(CONFIG_MTK_SCP_AUDIO) |
| #include <scp_helper.h> |
| #include "audio_ipi_platform.h" |
| #endif |
| |
| |
| #define USIP_EMP_IOC_MAGIC 'D' |
| #define GET_USIP_EMI_SIZE _IOWR(USIP_EMP_IOC_MAGIC, 0xF0, unsigned long long) |
| #define GET_USIP_ADSP_PHONE_CALL_ENH_CONFIG _IOWR(USIP_EMP_IOC_MAGIC, 0xF1, unsigned long long) |
| #define SET_USIP_ADSP_PHONE_CALL_ENH_CONFIG _IOWR(USIP_EMP_IOC_MAGIC, 0xF2, unsigned long long) |
| #define SCP_REGISTER_FEATURE_FOR_VOICE_CALL _IOWR(USIP_EMP_IOC_MAGIC, 0xF3, unsigned long long) |
| #define SCP_DEREGISTER_FEATURE_FOR_VOICE_CALL _IOWR(USIP_EMP_IOC_MAGIC, 0xF4, unsigned long long) |
| |
| #define NUM_MPU_REGION 3 |
| |
| static void usip_send_emi_info_to_dsp(void); |
| static void usip_send_emi_info_to_dsp_ble(void); |
| |
| int EMI_TABLE[3][3] |
| = {{0, 0, 0x30000}, {1, 0x30000, 0x8000}, {2, 0x38000, 0x28000} }; |
| |
| enum { |
| SP_EMI_AP_USIP_PARAMETER, |
| SP_EMI_ADSP_USIP_PHONECALL, |
| SP_EMI_ADSP_USIP_SMARTPA |
| }; |
| |
| enum { |
| SP_EMI_TYPE, |
| SP_EMI_OFFSET, |
| SP_EMI_SIZE |
| }; |
| |
| struct usip_info { |
| bool memory_ready; |
| size_t memory_size; |
| |
| void *memory_area; |
| dma_addr_t memory_addr; |
| phys_addr_t addr_phy; |
| |
| unsigned int adsp_phone_call_enh_config; |
| unsigned int adsp_ble_phone_call_config; |
| }; |
| |
| static struct usip_info usip; |
| |
| static long usip_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) |
| { |
| int ret = 0; |
| long size_for_spe = 0; |
| unsigned int temp_confg = usip.adsp_phone_call_enh_config; |
| |
| pr_info("%s(), cmd 0x%x, arg %lu, memory_size = %ld addr_phy = 0x%llx\n", |
| __func__, cmd, arg, usip.memory_size, usip.addr_phy); |
| |
| size_for_spe = EMI_TABLE[SP_EMI_AP_USIP_PARAMETER][SP_EMI_SIZE]; |
| switch (cmd) { |
| |
| case GET_USIP_EMI_SIZE: |
| if (!usip.memory_ready) { |
| pr_info("no phy addr from ccci"); |
| ret = -ENODEV; |
| } else if (copy_to_user((void __user *)arg, &size_for_spe, |
| sizeof(size_for_spe))) { |
| pr_warn("Fail copy to user Ptr:%p, r_sz:%zu", |
| (char *)&size_for_spe, sizeof(size_for_spe)); |
| ret = -1; |
| } |
| break; |
| |
| case GET_USIP_ADSP_PHONE_CALL_ENH_CONFIG: |
| if (copy_to_user((void __user *)arg, &(usip.adsp_phone_call_enh_config), |
| sizeof(usip.adsp_phone_call_enh_config))) { |
| pr_info("%s(), Fail copy CALL_ENH_CONFIG to user Ptr: %x", |
| __func__, |
| usip.adsp_phone_call_enh_config); |
| ret = -1; |
| } |
| break; |
| |
| case SET_USIP_ADSP_PHONE_CALL_ENH_CONFIG: |
| if (copy_from_user(&(usip.adsp_phone_call_enh_config), (void __user *)arg, |
| sizeof(usip.adsp_phone_call_enh_config))) { |
| pr_info("%s(), Fail copy CALL_ENH_CONFIG from user Ptr: %x", |
| __func__, |
| arg); |
| ret = -1; |
| } |
| pr_info("%s(): in SET_USIP_ADSP_PHONE_CALL_ENH_CONFIG: %d", |
| __func__, |
| usip.adsp_phone_call_enh_config); |
| if ((usip.adsp_phone_call_enh_config & 0x1) > (temp_confg & 0x1)) |
| usip_send_emi_info_to_dsp(); |
| break; |
| |
| case SCP_REGISTER_FEATURE_FOR_VOICE_CALL: |
| #if IS_ENABLED(CONFIG_MTK_SCP_AUDIO) |
| if ((usip.adsp_phone_call_enh_config & 0x4) == 0x4) |
| scp_register_feature(RVVOICE_CALL_FEATURE_ID); |
| #endif |
| break; |
| |
| case SCP_DEREGISTER_FEATURE_FOR_VOICE_CALL: |
| #if IS_ENABLED(CONFIG_MTK_SCP_AUDIO) |
| if ((usip.adsp_phone_call_enh_config & 0x4) == 0x4) |
| scp_deregister_feature(RVVOICE_CALL_FEATURE_ID); |
| #endif |
| break; |
| |
| default: |
| pr_debug("%s(), default\n", __func__); |
| break; |
| } |
| return ret; |
| } |
| |
| #if IS_ENABLED(CONFIG_COMPAT) |
| static long usip_compat_ioctl(struct file *fp, unsigned int cmd, |
| unsigned long arg) |
| { |
| long ret; |
| |
| if (!fp->f_op || !fp->f_op->unlocked_ioctl) |
| return -ENOTTY; |
| ret = fp->f_op->unlocked_ioctl(fp, cmd, arg); |
| if (ret < 0) |
| pr_err("%s(), fail, ret %ld, cmd 0x%x, arg %lu\n", |
| __func__, ret, cmd, arg); |
| return ret; |
| } |
| #endif |
| |
| int usip_mmap_data(struct usip_info *usip, struct vm_area_struct *area) |
| { |
| long size; |
| unsigned long offset; |
| size_t align_bytes = PAGE_ALIGN(usip->memory_size); |
| unsigned long pfn; |
| int ret; |
| |
| pr_info("%s(), memory ready %d, size %zu, align_bytes %zu\n", |
| __func__, usip->memory_ready, usip->memory_size, align_bytes); |
| |
| if (!usip->memory_ready) |
| return -EBADFD; |
| |
| size = area->vm_end - area->vm_start; |
| offset = area->vm_pgoff << PAGE_SHIFT; |
| |
| if ((size_t)size > align_bytes) |
| return -EINVAL; |
| if (offset > align_bytes - size) |
| return -EINVAL; |
| |
| /*area->vm_ops = &usip_vm_ops_data;*/ |
| /*area->vm_private_data = usip;*/ |
| |
| pfn = usip->addr_phy >> PAGE_SHIFT; |
| /* ensure that memory does not get swapped to disk */ |
| area->vm_flags |= VM_IO; |
| /* ensure non-cacheable */ |
| area->vm_page_prot = pgprot_noncached(area->vm_page_prot); |
| ret = remap_pfn_range(area, area->vm_start, |
| pfn, size, area->vm_page_prot); |
| if (ret) |
| pr_err("%s(), ret %d, remap failed 0x%lx, phys_addr %pa -> vm_start 0x%lx\n", |
| __func__, ret, pfn, |
| &usip->addr_phy, |
| area->vm_start); |
| /*Comment*/ |
| smp_mb(); |
| |
| return ret; |
| } |
| |
| static int usip_mmap(struct file *file, struct vm_area_struct *area) |
| { |
| unsigned long offset; |
| struct usip_info *usip = file->private_data; |
| |
| pr_debug("%s(), vm_flags 0x%lx, vm_pgoff 0x%lx\n", |
| __func__, area->vm_flags, area->vm_pgoff); |
| |
| offset = area->vm_pgoff << PAGE_SHIFT; |
| switch (offset) { |
| default: |
| return usip_mmap_data(usip, area); |
| } |
| return 0; |
| } |
| static void usip_get_addr(void) |
| { |
| #if IS_ENABLED(CONFIG_MTK_ECCCI_DRIVER) |
| int size_o = 0; |
| phys_addr_t phys_addr; |
| |
| phys_addr_t srw_base; |
| unsigned int srw_size; |
| phys_addr_t r_rw_base; |
| unsigned int r_rw_size; |
| |
| phys_addr = get_smem_phy_start_addr(0, SMEM_USER_RAW_USIP, &size_o); |
| if (phys_addr == 0) { |
| pr_info("%s(), cannot get emi addr from ccci", __func__); |
| usip.memory_ready = false; |
| } else { |
| usip.memory_ready = true; |
| |
| get_md_resv_mem_info(&r_rw_base, &r_rw_size, &srw_base, &srw_size); |
| pr_info("%s(), 0x%llx %d 0x%llx %d 0x%llx", __func__, |
| r_rw_base, r_rw_size, srw_base, srw_size, phys_addr); |
| |
| usip.memory_size = size_o; |
| usip.addr_phy = phys_addr; |
| } |
| #endif |
| } |
| |
| static void usip_send_emi_info_to_dsp_ble(void) |
| { |
| int send_result = 0; |
| struct ipi_msg_t ipi_msg; |
| long long usip_emi_info[2]; //idx0 for addr, idx1 for size |
| phys_addr_t offset = 0; |
| |
| if ((usip.adsp_ble_phone_call_config & 0x1) == 0) { |
| pr_info("%s(), adsp_ble_phone_call_config(%d) is close", |
| __func__, |
| usip.adsp_ble_phone_call_config); |
| return; |
| } |
| |
| if (usip.addr_phy == 0) { |
| pr_info("%s(), cannot get emi addr from ccci", __func__); |
| return; |
| } |
| |
| offset = EMI_TABLE[SP_EMI_ADSP_USIP_PHONECALL][SP_EMI_OFFSET]; |
| usip_emi_info[0] = usip.addr_phy + offset; |
| usip_emi_info[1] = EMI_TABLE[SP_EMI_ADSP_USIP_PHONECALL][SP_EMI_SIZE] + |
| EMI_TABLE[SP_EMI_ADSP_USIP_SMARTPA][SP_EMI_SIZE]; |
| pr_debug("%s(), usip_emi_info[0] 0x%x, usip_emi_info[1] 0x%x\n", |
| __func__, usip_emi_info[0], usip_emi_info[1]); |
| |
| ipi_msg.magic = IPI_MSG_MAGIC_NUMBER; |
| ipi_msg.task_scene = TASK_SCENE_BLECALLUL; |
| ipi_msg.source_layer = AUDIO_IPI_LAYER_FROM_KERNEL; |
| ipi_msg.target_layer = AUDIO_IPI_LAYER_TO_DSP; |
| ipi_msg.data_type = AUDIO_IPI_PAYLOAD; |
| ipi_msg.ack_type = AUDIO_IPI_MSG_BYPASS_ACK; |
| ipi_msg.msg_id = IPI_MSG_A2D_GET_EMI_ADDRESS; |
| ipi_msg.param1 = sizeof(usip_emi_info); |
| ipi_msg.param2 = 0; |
| |
| // [BLE UL] Send EMI Address to Hifi3 Via IPI |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_register_feature(BLE_CALL_UL_FEATURE_ID); |
| #endif |
| send_result = audio_send_ipi_msg( |
| &ipi_msg, TASK_SCENE_BLECALLUL, |
| AUDIO_IPI_LAYER_TO_DSP, |
| AUDIO_IPI_PAYLOAD, |
| AUDIO_IPI_MSG_BYPASS_ACK, |
| IPI_MSG_A2D_GET_EMI_ADDRESS, |
| sizeof(usip_emi_info), |
| 0, |
| (char *)&usip_emi_info); |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_deregister_feature(BLE_CALL_UL_FEATURE_ID); |
| #endif |
| if (send_result != 0) |
| pr_info("%s(), BLE UL scp_ipi send fail\n", __func__); |
| else |
| pr_debug("%s(), BLE UL scp_ipi send succeed\n", __func__); |
| |
| // [BLE DL] Send EMI Address to Hifi3 Via IPI |
| ipi_msg.task_scene = TASK_SCENE_BLECALLDL; |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_register_feature(BLE_CALL_DL_FEATURE_ID); |
| #endif |
| send_result = audio_send_ipi_msg( |
| &ipi_msg, TASK_SCENE_BLECALLDL, |
| AUDIO_IPI_LAYER_TO_DSP, |
| AUDIO_IPI_PAYLOAD, |
| AUDIO_IPI_MSG_BYPASS_ACK, |
| IPI_MSG_A2D_GET_EMI_ADDRESS, |
| sizeof(usip_emi_info), |
| 0, |
| (char *)&usip_emi_info); |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_deregister_feature(BLE_CALL_DL_FEATURE_ID); |
| #endif |
| if (send_result != 0) |
| pr_info("%s(), BLE DL scp_ipi send fail\n", __func__); |
| else |
| pr_debug("%s(), BLE DL scp_ipi send succeed\n", __func__); |
| } |
| |
| static void usip_send_emi_info_to_dsp(void) |
| { |
| int send_result = 0; |
| struct ipi_msg_t ipi_msg; |
| long long usip_emi_info[2]; //idx0 for addr, idx1 for size |
| phys_addr_t offset = 0; |
| |
| if ((usip.adsp_phone_call_enh_config & 0x1) == 0) { |
| pr_info("%s(), adsp_phone_call_enh_config(%d) is close", |
| __func__, |
| usip.adsp_phone_call_enh_config); |
| return; |
| } |
| |
| if (!usip.memory_ready) { |
| usip_get_addr(); |
| if (!usip.memory_ready) { |
| pr_info("%s(), cannot get emi addr from ccci", |
| __func__); |
| return; |
| } |
| } |
| |
| offset = EMI_TABLE[SP_EMI_ADSP_USIP_PHONECALL][SP_EMI_OFFSET]; |
| usip_emi_info[0] = usip.addr_phy + offset; |
| usip_emi_info[1] = EMI_TABLE[SP_EMI_ADSP_USIP_PHONECALL][SP_EMI_SIZE] + |
| EMI_TABLE[SP_EMI_ADSP_USIP_SMARTPA][SP_EMI_SIZE]; |
| |
| ipi_msg.magic = IPI_MSG_MAGIC_NUMBER; |
| ipi_msg.task_scene = TASK_SCENE_PHONE_CALL; |
| ipi_msg.source_layer = AUDIO_IPI_LAYER_FROM_KERNEL; |
| ipi_msg.target_layer = AUDIO_IPI_LAYER_TO_DSP; |
| ipi_msg.data_type = AUDIO_IPI_PAYLOAD; |
| ipi_msg.ack_type = AUDIO_IPI_MSG_BYPASS_ACK; |
| ipi_msg.msg_id = IPI_MSG_A2D_GET_EMI_ADDRESS; |
| ipi_msg.param1 = sizeof(usip_emi_info); |
| ipi_msg.param2 = 0; |
| |
| /* [Call_Main]Send EMI Address to Hifi3 Via IPI*/ |
| if ((usip.adsp_phone_call_enh_config & 0x4) == 0) { |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_register_feature(VOICE_CALL_FEATURE_ID); |
| #endif |
| } else { |
| #if IS_ENABLED(CONFIG_MTK_SCP_AUDIO) |
| scp_register_feature(RVVOICE_CALL_FEATURE_ID); |
| #endif |
| } |
| send_result = audio_send_ipi_msg( |
| &ipi_msg, TASK_SCENE_PHONE_CALL, |
| AUDIO_IPI_LAYER_TO_DSP, |
| AUDIO_IPI_PAYLOAD, |
| AUDIO_IPI_MSG_BYPASS_ACK, |
| IPI_MSG_A2D_GET_EMI_ADDRESS, |
| sizeof(usip_emi_info), |
| 0, |
| (char *)&usip_emi_info); |
| if ((usip.adsp_phone_call_enh_config & 0x4) == 0) { |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_deregister_feature(VOICE_CALL_FEATURE_ID); |
| #endif |
| } else { |
| #if IS_ENABLED(CONFIG_MTK_SCP_AUDIO) |
| scp_deregister_feature(RVVOICE_CALL_FEATURE_ID); |
| #endif |
| } |
| |
| if (send_result != 0) |
| pr_info("%s(), scp_ipi send fail\n", __func__); |
| else |
| pr_debug("%s(), scp_ipi send succeed\n", __func__); |
| |
| /* [Call_Sub] Send EMI Address to Hifi3 Via IPI*/ |
| if ((usip.adsp_phone_call_enh_config & 0x2) == 0x2) { |
| ipi_msg.task_scene = TASK_SCENE_PHONE_CALL_SUB; |
| if ((usip.adsp_phone_call_enh_config & 0x4) == 0) { |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_register_feature(VOICE_CALL_FEATURE_ID); |
| #endif |
| } |
| send_result = audio_send_ipi_msg( |
| &ipi_msg, TASK_SCENE_PHONE_CALL_SUB, |
| AUDIO_IPI_LAYER_TO_DSP, |
| AUDIO_IPI_PAYLOAD, |
| AUDIO_IPI_MSG_BYPASS_ACK, |
| IPI_MSG_A2D_GET_EMI_ADDRESS, |
| sizeof(usip_emi_info), |
| 0, |
| (char *)&usip_emi_info); |
| if ((usip.adsp_phone_call_enh_config & 0x4) == 0) { |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_deregister_feature(VOICE_CALL_FEATURE_ID); |
| #endif |
| } |
| if (send_result != 0) |
| pr_info("%s(), scp_ipi send sub fail\n", __func__); |
| else |
| pr_debug("%s(), scp_ipi send sub succeed\n", __func__); |
| } |
| } |
| |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| static int audio_call_event_receive(struct notifier_block *this, |
| unsigned long event, |
| void *ptr) |
| { |
| switch (event) { |
| case ADSP_EVENT_STOP: |
| break; |
| case ADSP_EVENT_READY: |
| usip_send_emi_info_to_dsp(); |
| usip_send_emi_info_to_dsp_ble(); |
| break; |
| default: |
| pr_info("event %lu err", event); |
| } |
| return 0; |
| } |
| |
| static struct notifier_block audio_call_notifier = { |
| .notifier_call = audio_call_event_receive, |
| .priority = VOICE_CALL_FEATURE_PRI, |
| }; |
| #endif |
| |
| #if IS_ENABLED(CONFIG_MTK_SCP_AUDIO) |
| static int audio_call_event_receive_scp(struct notifier_block *this, |
| unsigned long event, |
| void *ptr) |
| { |
| switch (event) { |
| case SCP_EVENT_STOP: |
| break; |
| case SCP_EVENT_READY: |
| usip_send_emi_info_to_dsp(); |
| break; |
| default: |
| pr_info("event %lu err", event); |
| } |
| return 0; |
| } |
| |
| static struct notifier_block audio_call_notifier_scp = { |
| .notifier_call = audio_call_event_receive_scp, |
| }; |
| #endif |
| |
| static int usip_open(struct inode *inode, struct file *file) |
| { |
| |
| file->private_data = &usip; |
| |
| if (!usip.memory_ready) { |
| usip_get_addr(); |
| usip_send_emi_info_to_dsp(); |
| usip_send_emi_info_to_dsp_ble(); |
| } |
| |
| return 0; |
| } |
| |
| static const struct file_operations usip_fops = { |
| .owner = THIS_MODULE, |
| .open = usip_open, |
| .unlocked_ioctl = usip_ioctl, |
| #if IS_ENABLED(CONFIG_COMPAT) |
| .compat_ioctl = usip_compat_ioctl, |
| #endif |
| .mmap = usip_mmap, |
| }; |
| |
| #if IS_ENABLED(CONFIG_MTK_ECCCI_DRIVER) |
| static struct miscdevice usip_miscdevice = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "usip", |
| .fops = &usip_fops, |
| }; |
| #endif |
| static const struct of_device_id usip_dt_match[] = { |
| { .compatible = "mediatek,speech-usip-mem", }, |
| {}, |
| }; |
| |
| static int speech_usip_dev_probe(struct platform_device *pdev) |
| { |
| /* get adsp phone call config*/ |
| int ret = of_property_read_u32(pdev->dev.of_node, |
| "adsp-phone-call-enh-enable", |
| &(usip.adsp_phone_call_enh_config)); |
| if (ret != 0) |
| pr_info("%s adsp_phone_call_enh_enable error\n", __func__); |
| else |
| pr_debug("%s adsp_phone_call_enh_enable is %d\n", |
| __func__, usip.adsp_phone_call_enh_config); |
| |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "adsp-ble-phone-call-enable", |
| &(usip.adsp_ble_phone_call_config)); |
| if (ret != 0) |
| pr_info("%s adsp_ble_phone_call_enable error\n", __func__); |
| else |
| pr_debug("%s adsp_ble_phone_call_enable is %d\n", |
| __func__, usip.adsp_ble_phone_call_config); |
| |
| usip_send_emi_info_to_dsp(); |
| usip_send_emi_info_to_dsp_ble(); |
| return 0; |
| } |
| |
| static int speech_usip_dev_remove(struct platform_device *pdev) |
| { |
| return 0; |
| } |
| |
| static struct platform_driver speech_usip_mem = { |
| .driver = { |
| .name = "speech-usip-mem", |
| .owner = THIS_MODULE, |
| .of_match_table = usip_dt_match, |
| }, |
| .probe = speech_usip_dev_probe, |
| .remove = speech_usip_dev_remove, |
| }; |
| |
| static int __init usip_init(void) |
| { |
| int ret; |
| |
| usip.memory_ready = false; |
| usip.memory_size = 0; |
| usip.addr_phy = 0; |
| |
| #if IS_ENABLED(CONFIG_MTK_ECCCI_DRIVER) |
| ret = misc_register(&usip_miscdevice); |
| if (ret) { |
| pr_err("%s(), cannot register miscdev on minor %d, ret %d\n", |
| __func__, usip_miscdevice.minor, ret); |
| ret = -ENODEV; |
| } |
| #else |
| ret = -ENODEV; |
| #endif |
| |
| /* init usip info */ |
| usip.memory_addr = 0x11220000L; |
| |
| |
| #if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT) |
| adsp_register_notify(&audio_call_notifier); |
| #endif |
| #if IS_ENABLED(CONFIG_MTK_SCP_AUDIO) |
| if (is_audio_scp_support()) |
| scp_A_register_notify(&audio_call_notifier_scp); |
| #endif |
| ret = platform_driver_register(&speech_usip_mem); |
| |
| return ret; |
| } |
| |
| static void __exit usip_exit(void) |
| { |
| #if IS_ENABLED(CONFIG_MTK_ECCCI_DRIVER) |
| misc_deregister(&usip_miscdevice); |
| #endif |
| } |
| |
| module_init(usip_init); |
| module_exit(usip_exit); |
| |
| MODULE_DESCRIPTION("Mediatek uSip memory control"); |
| MODULE_LICENSE("GPL v2"); |
| |