// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TIMEOUT_MS 500 #define MAX_RETRY_COUNT 3 #define APM_READY_WAIT_DURATION 2 #define GPR_SEND_PKT_APM_TIMEOUT_MS 0 struct audio_prm { struct gpr_device *adev; wait_queue_head_t wait; struct mutex lock; bool resp_received; atomic_t state; atomic_t status; int lpi_pcm_logging_enable; bool is_adsp_up; u32 prm_sleep_api_supported; }; static struct audio_prm g_prm; static bool is_apm_ready_check_done = false; static int audio_prm_callback(struct gpr_device *adev, void *data) { struct gpr_hdr *hdr = (struct gpr_hdr *)data; uint32_t *payload = GPR_PKT_GET_PAYLOAD(uint32_t, data); //dev_err(&adev->dev, "%s: Payload %x", __func__, hdr->opcode); switch (hdr->opcode) { case GPR_IBASIC_RSP_RESULT: pr_err("%s: Failed response received",__func__); atomic_set(&g_prm.status, payload[1]); g_prm.resp_received = true; break; case PRM_CMD_RSP_REQUEST_HW_RSC: case PRM_CMD_RSP_RELEASE_HW_RSC: /* payload[1] contains the error status for response */ if (payload[1] != 0) { atomic_set(&g_prm.status, payload[1]); pr_err("%s: cmd = 0x%x returned error = 0x%x\n", __func__, payload[0], payload[1]); } g_prm.resp_received = true; /* payload[0] contains the param_ID for response */ switch (payload[0]) { case PARAM_ID_RSC_AUDIO_HW_CLK: case PARAM_ID_RSC_LPASS_CORE: case PARAM_ID_RSC_HW_CORE: case PARAM_ID_RSC_CPU_LPR: if (payload[1] != 0) pr_err("%s: PRM command failed with error %d\n", __func__, payload[1]); atomic_set(&g_prm.state, payload[1]); break; default: pr_err("%s: hit default case",__func__); }; break; default: break; }; if (g_prm.resp_received) wake_up(&g_prm.wait); return 0; } static int prm_gpr_send_pkt(struct gpr_pkt *pkt, wait_queue_head_t *wait) { int ret = 0; int retry; mutex_lock(&g_prm.lock); pr_debug("%s: enter",__func__); if (wait) atomic_set(&g_prm.state, 1); atomic_set(&g_prm.status, 0); if (g_prm.adev == NULL) { pr_err("%s: apr is unregistered\n", __func__); mutex_unlock(&g_prm.lock); return -ENODEV; } if (!is_apm_ready_check_done && g_prm.is_adsp_up && (gpr_get_q6_state() == GPR_SUBSYS_LOADED)) { pr_info("%s: apm ready check not done\n", __func__); retry = 0; while (!spf_core_is_apm_ready(GPR_SEND_PKT_APM_TIMEOUT_MS) && retry < MAX_RETRY_COUNT) { msleep(APM_READY_WAIT_DURATION); ++retry; } is_apm_ready_check_done = true; pr_info("%s: apm ready check done\n", __func__); } g_prm.resp_received = false; ret = gpr_send_pkt(g_prm.adev, pkt); if (ret < 0) { pr_err("%s: packet not transmitted %d\n", __func__, ret); mutex_unlock(&g_prm.lock); return ret; } if (wait) { ret = wait_event_timeout(g_prm.wait, (g_prm.resp_received), msecs_to_jiffies(2 * TIMEOUT_MS)); if (!ret) { pr_err("%s: pkt send timeout\n", __func__); ret = -ETIMEDOUT; } else if (atomic_read(&g_prm.status) > 0) { pr_err("%s: DSP returned error %d\n", __func__, atomic_read(&g_prm.status)); ret = -EINVAL; } else { ret = 0; } } pr_debug("%s: exit",__func__); mutex_unlock(&g_prm.lock); return ret; } void audio_prm_set_lpi_logging_status(int lpi_pcm_logging_enable) { g_prm.lpi_pcm_logging_enable = lpi_pcm_logging_enable; } EXPORT_SYMBOL(audio_prm_set_lpi_logging_status); static int _audio_prm_set_lpass_cpu_lpr_req(uint8_t enable) { struct gpr_pkt *pkt; struct prm_cpu_lpr_request_t prm_lpr_request; int ret = 0; uint32_t size; size = GPR_HDR_SIZE + sizeof(struct prm_cpu_lpr_request_t); pkt = kzalloc(size, GFP_KERNEL); if (!pkt) return -ENOMEM; pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) | GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) | GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size); pkt->hdr.src_port = GPR_SVC_ASM; pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID; pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V; pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V; pkt->hdr.token = 0; /* TBD */ if (enable) pkt->hdr.opcode = PRM_CMD_REQUEST_HW_RSC; else pkt->hdr.opcode = PRM_CMD_RELEASE_HW_RSC; prm_lpr_request.payload_header.payload_address_lsw = 0; prm_lpr_request.payload_header.payload_address_msw = 0; prm_lpr_request.payload_header.mem_map_handle = 0; prm_lpr_request.payload_header.payload_size = sizeof(struct prm_cpu_lpr_request_t) - sizeof(apm_cmd_header_t); /** Populate the param payload */ prm_lpr_request.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID; prm_lpr_request.module_payload_0.error_code = 0; prm_lpr_request.module_payload_0.param_id = PARAM_ID_RSC_CPU_LPR; prm_lpr_request.module_payload_0.param_size = sizeof(struct prm_cpu_lpr_request_t) - sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t); prm_lpr_request.lpr_state = LPR_CPU_SS_SLEEP_DISABLED; memcpy(&pkt->payload, &prm_lpr_request, sizeof(struct prm_cpu_lpr_request_t)); ret = prm_gpr_send_pkt(pkt, &g_prm.wait); kfree(pkt); return ret; } /** */ int _audio_prm_set_lpass_hw_core_req(uint32_t hw_core_id, uint8_t enable) { struct gpr_pkt *pkt; prm_cmd_request_hw_core_t prm_rsc_request; int ret = 0; uint32_t size; size = GPR_HDR_SIZE + sizeof(prm_cmd_request_hw_core_t); pkt = kzalloc(size, GFP_KERNEL); if (!pkt) return -ENOMEM; pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) | GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) | GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size); pkt->hdr.src_port = GPR_SVC_ASM; pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID; pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V; pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V; pkt->hdr.token = 0; /* TBD */ if (enable) pkt->hdr.opcode = PRM_CMD_REQUEST_HW_RSC; else pkt->hdr.opcode = PRM_CMD_RELEASE_HW_RSC; prm_rsc_request.payload_header.payload_address_lsw = 0; prm_rsc_request.payload_header.payload_address_msw = 0; prm_rsc_request.payload_header.mem_map_handle = 0; prm_rsc_request.payload_header.payload_size = sizeof(prm_cmd_request_hw_core_t) - sizeof(apm_cmd_header_t); /** Populate the param payload */ prm_rsc_request.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID; prm_rsc_request.module_payload_0.error_code = 0; prm_rsc_request.module_payload_0.param_id = PARAM_ID_RSC_HW_CORE; prm_rsc_request.module_payload_0.param_size = sizeof(prm_cmd_request_hw_core_t) - sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t); prm_rsc_request.hw_core_id = hw_core_id; memcpy(&pkt->payload, &prm_rsc_request, sizeof(prm_cmd_request_hw_core_t)); ret = prm_gpr_send_pkt(pkt, &g_prm.wait); kfree(pkt); return ret; } int audio_prm_set_lpass_hw_core_req(struct clk_cfg *cfg, uint32_t hw_core_id, uint8_t enable) { if (hw_core_id == HW_CORE_ID_LPASS) { if (g_prm.prm_sleep_api_supported) return _audio_prm_set_lpass_cpu_lpr_req(enable); else return _audio_prm_set_lpass_hw_core_req(hw_core_id, enable); } return _audio_prm_set_lpass_hw_core_req(hw_core_id, enable); } EXPORT_SYMBOL(audio_prm_set_lpass_hw_core_req); /** * prm_set_lpass_clk_cfg() - Set PRM clock * * Return: 0 if clock set is success */ static int audio_prm_set_lpass_clk_cfg_req(struct clk_cfg *cfg) { struct gpr_pkt *pkt; prm_cmd_request_rsc_t prm_rsc_request; int ret = 0; uint32_t size; size = GPR_HDR_SIZE + sizeof(prm_cmd_request_rsc_t); pkt = kzalloc(size, GFP_KERNEL); if (!pkt) return -ENOMEM; pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) | GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) | GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size); pkt->hdr.src_port = GPR_SVC_ASM; pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID; pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V; pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V; pkt->hdr.token = 0; /* TBD */ pkt->hdr.opcode = PRM_CMD_REQUEST_HW_RSC; //pr_err("%s: clk_id %d size of cmd_req %ld \n",__func__, cfg->clk_id, sizeof(prm_cmd_request_rsc_t)); prm_rsc_request.payload_header.payload_address_lsw = 0; prm_rsc_request.payload_header.payload_address_msw = 0; prm_rsc_request.payload_header.mem_map_handle = 0; prm_rsc_request.payload_header.payload_size = sizeof(prm_cmd_request_rsc_t) - sizeof(apm_cmd_header_t); /** Populate the param payload */ prm_rsc_request.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID; prm_rsc_request.module_payload_0.error_code = 0; prm_rsc_request.module_payload_0.param_id = PARAM_ID_RSC_AUDIO_HW_CLK; prm_rsc_request.module_payload_0.param_size = sizeof(prm_cmd_request_rsc_t) - sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t); prm_rsc_request.num_clk_id_t.num_clock_id = MAX_AUD_HW_CLK_NUM_REQ; prm_rsc_request.clock_ids_t[0].clock_id = cfg->clk_id; prm_rsc_request.clock_ids_t[0].clock_freq = cfg->clk_freq_in_hz; prm_rsc_request.clock_ids_t[0].clock_attri = cfg->clk_attri; /* * Set TX RCG to RCO if lpi pcm logging is enabled and any one of the * tx core clocks are enabled */ if (g_prm.lpi_pcm_logging_enable && ((cfg->clk_id == CLOCK_ID_TX_CORE_MCLK) || (cfg->clk_id == CLOCK_ID_WSA_CORE_TX_MCLK) || (cfg->clk_id == CLOCK_ID_WSA2_CORE_TX_MCLK) || (cfg->clk_id == CLOCK_ID_RX_CORE_TX_MCLK))) prm_rsc_request.clock_ids_t[0].clock_root = CLOCK_ROOT_SRC_RCO; else prm_rsc_request.clock_ids_t[0].clock_root = cfg->clk_root; memcpy(&pkt->payload, &prm_rsc_request, sizeof(prm_cmd_request_rsc_t)); ret = prm_gpr_send_pkt(pkt, &g_prm.wait); kfree(pkt); return ret; } static int audio_prm_set_lpass_clk_cfg_rel(struct clk_cfg *cfg) { struct gpr_pkt *pkt; prm_cmd_release_rsc_t prm_rsc_release; int ret = 0; uint32_t size; size = GPR_HDR_SIZE + sizeof(prm_cmd_release_rsc_t); pkt = kzalloc(size, GFP_KERNEL); if (!pkt) return -ENOMEM; pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) | GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) | GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size); pkt->hdr.src_port = GPR_SVC_ASM; pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID; pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V; pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V; pkt->hdr.token = 0; /* TBD */ pkt->hdr.opcode = PRM_CMD_RELEASE_HW_RSC; //pr_err("%s: clk_id %d size of cmd_req %ld \n",__func__, cfg->clk_id, sizeof(prm_cmd_release_rsc_t)); prm_rsc_release.payload_header.payload_address_lsw = 0; prm_rsc_release.payload_header.payload_address_msw = 0; prm_rsc_release.payload_header.mem_map_handle = 0; prm_rsc_release.payload_header.payload_size = sizeof(prm_cmd_release_rsc_t) - sizeof(apm_cmd_header_t); /** Populate the param payload */ prm_rsc_release.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID; prm_rsc_release.module_payload_0.error_code = 0; prm_rsc_release.module_payload_0.param_id = PARAM_ID_RSC_AUDIO_HW_CLK; prm_rsc_release.module_payload_0.param_size = sizeof(prm_cmd_release_rsc_t) - sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t); prm_rsc_release.num_clk_id_t.num_clock_id = MAX_AUD_HW_CLK_NUM_REQ; prm_rsc_release.clock_ids_t[0].clock_id = cfg->clk_id; memcpy(&pkt->payload, &prm_rsc_release, sizeof(prm_cmd_release_rsc_t)); ret = prm_gpr_send_pkt(pkt, &g_prm.wait); kfree(pkt); return ret; } /** * audio_prm_set_cdc_earpa_duty_cycling_req() - send codec reg values * for codec duty cycling. * * Return: 0 if reg passing is success. */ int audio_prm_set_cdc_earpa_duty_cycling_req(struct prm_earpa_hw_intf_config *earpa_config, uint32_t enable) { struct gpr_pkt *pkt; prm_cmd_request_cdc_duty_cycling_t prm_rsc_request_reg_info; int ret = 0; uint32_t size; size = GPR_HDR_SIZE + sizeof(prm_cmd_request_cdc_duty_cycling_t); pkt = kzalloc(size, GFP_KERNEL); if (!pkt) return -ENOMEM; pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) | GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) | GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size); pkt->hdr.src_port = GPR_SVC_ASM; pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID; pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V; pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V; pkt->hdr.token = 0; if (enable) pkt->hdr.opcode = PRM_CMD_REQUEST_HW_RSC; else pkt->hdr.opcode = PRM_CMD_RELEASE_HW_RSC; memset(&prm_rsc_request_reg_info, 0, sizeof(prm_cmd_request_cdc_duty_cycling_t)); prm_rsc_request_reg_info.payload_header.payload_address_lsw = 0; prm_rsc_request_reg_info.payload_header.payload_address_msw = 0; prm_rsc_request_reg_info.payload_header.mem_map_handle = 0; prm_rsc_request_reg_info.payload_header.payload_size = sizeof(prm_cmd_request_cdc_duty_cycling_t) - sizeof(apm_cmd_header_t); /* Populate the param payload */ prm_rsc_request_reg_info.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID; prm_rsc_request_reg_info.module_payload_0.error_code = 0; prm_rsc_request_reg_info.module_payload_0.param_id = PARAM_ID_RSC_HW_CODEC_REG_INFO; prm_rsc_request_reg_info.module_payload_0.param_size = sizeof(prm_cmd_request_cdc_duty_cycling_t) - sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t); prm_rsc_request_reg_info.hw_codec_reg_info_t.num_reg_info_t = MAX_EARPA_REG; /* Setting up DIGITAL Mute register value */ prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_reg_id = HW_CODEC_DIG_REG_ID_MUTE_CTRL; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_reg_addr_msw = 0; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_reg_addr_lsw = earpa_config->ear_pa_hw_reg_cfg.lpass_cdc_rx0_rx_path_ctl_phy_addr; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].num_ops = MAX_EARPA_CDC_DUTY_CYC_OPERATION; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_op[0].hw_codec_op_id = HW_CODEC_OP_DIG_MUTE_ENABLE; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_op[0].hw_codec_op_value = DIG_MUTE_ENABLE; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_op[1].hw_codec_op_id = HW_CODEC_OP_DIG_MUTE_DISABLE; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_op[1].hw_codec_op_value = DIG_MUTE_DISABLE; /* Setting up LPASS_PA_REG Values */ prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_reg_id = HW_CODEC_ANALOG_REG_ID_CMD_FIFO_WRITE; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_reg_addr_msw = 0; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_reg_addr_lsw = earpa_config->ear_pa_hw_reg_cfg.lpass_wr_fifo_reg_phy_addr; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].num_ops = MAX_EARPA_CDC_DUTY_CYC_OPERATION; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_op[0].hw_codec_op_id = HW_CODEC_OP_ANA_PGA_ENABLE; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_op[0].hw_codec_op_value = earpa_config->ear_pa_pkd_cfg.ear_pa_enable_pkd_reg_addr; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_op[1].hw_codec_op_id = HW_CODEC_OP_ANA_PGA_DISABLE; prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_op[1].hw_codec_op_value = earpa_config->ear_pa_pkd_cfg.ear_pa_disable_pkd_reg_addr; memcpy(&pkt->payload, &prm_rsc_request_reg_info, sizeof(prm_cmd_request_cdc_duty_cycling_t)); ret = prm_gpr_send_pkt(pkt, &g_prm.wait); kfree(pkt); return ret; } EXPORT_SYMBOL(audio_prm_set_cdc_earpa_duty_cycling_req); int audio_prm_set_vote_against_sleep(uint8_t enable) { if (g_prm.prm_sleep_api_supported) return _audio_prm_set_lpass_cpu_lpr_req(enable); else return _audio_prm_set_lpass_hw_core_req(HW_CORE_ID_LPASS, enable); } EXPORT_SYMBOL(audio_prm_set_vote_against_sleep); int audio_prm_set_lpass_clk_cfg (struct clk_cfg *clk, uint8_t enable) { int ret = 0; if (enable) ret = audio_prm_set_lpass_clk_cfg_req (clk); else ret = audio_prm_set_lpass_clk_cfg_rel (clk); return ret; } EXPORT_SYMBOL(audio_prm_set_lpass_clk_cfg); static int audio_prm_service_cb(struct notifier_block *this, unsigned long opcode, void *data) { pr_info("%s: Service opcode 0x%lx\n", __func__, opcode); switch (opcode) { case AUDIO_NOTIFIER_SERVICE_DOWN: mutex_lock(&g_prm.lock); pr_debug("%s: making apm_ready check false\n", __func__); is_apm_ready_check_done = false; g_prm.is_adsp_up = false; mutex_unlock(&g_prm.lock); break; case AUDIO_NOTIFIER_SERVICE_UP: mutex_lock(&g_prm.lock); g_prm.is_adsp_up = true; mutex_unlock(&g_prm.lock); break; default: break; } return NOTIFY_OK; } static struct notifier_block service_nb = { .notifier_call = audio_prm_service_cb, .priority = -INT_MAX, }; static int audio_prm_probe(struct gpr_device *adev) { int ret = 0; if (!audio_notifier_probe_status()) { pr_err("%s: Audio notify probe not completed, defer audio prm probe\n", __func__); return -EPROBE_DEFER; } ret = of_property_read_u32(adev->dev.of_node, "qcom,sleep-api-supported", &g_prm.prm_sleep_api_supported); if (ret < 0) pr_debug("%s: sleep API not supported\n", __func__); ret = audio_notifier_register("audio_prm", AUDIO_NOTIFIER_ADSP_DOMAIN, &service_nb); if (ret < 0) { if (ret != -EPROBE_DEFER) pr_err("%s: Audio notifier register failed ret = %d\n", __func__, ret); return ret; } dev_set_drvdata(&adev->dev, &g_prm); g_prm.adev = adev; init_waitqueue_head(&g_prm.wait); g_prm.is_adsp_up = true; pr_err("%s: prm probe success, Sleep API supported :%d\n", __func__, g_prm.prm_sleep_api_supported); return ret; } static int audio_prm_remove(struct gpr_device *adev) { int ret = 0; audio_notifier_deregister("audio_prm"); mutex_lock(&g_prm.lock); g_prm.is_adsp_up = false; g_prm.adev = NULL; mutex_unlock(&g_prm.lock); return ret; } static const struct of_device_id audio_prm_device_id[] = { { .compatible = "qcom,audio_prm" }, {}, }; MODULE_DEVICE_TABLE(of, audio_prm_device_id); static struct gpr_driver qcom_audio_prm_driver = { .probe = audio_prm_probe, .remove = audio_prm_remove, .callback = audio_prm_callback, .driver = { .name = "qcom-audio_prm", .of_match_table = of_match_ptr(audio_prm_device_id), }, }; static int __init audio_prm_module_init(void) { int ret; ret = gpr_driver_register(&qcom_audio_prm_driver); if (ret) pr_err("%s: gpr driver register failed = %d\n", __func__, ret); mutex_init(&g_prm.lock); return ret; } static void __exit audio_prm_module_exit(void) { mutex_destroy(&g_prm.lock); gpr_driver_unregister(&qcom_audio_prm_driver); } module_init(audio_prm_module_init); module_exit(audio_prm_module_exit); MODULE_DESCRIPTION("audio prm"); MODULE_LICENSE("GPL v2");