162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// Copyright (c) 2021, Linaro Limited 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include <linux/slab.h> 562306a36Sopenharmony_ci#include <linux/wait.h> 662306a36Sopenharmony_ci#include <linux/kernel.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/of.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/of_platform.h> 1162306a36Sopenharmony_ci#include <linux/jiffies.h> 1262306a36Sopenharmony_ci#include <linux/soc/qcom/apr.h> 1362306a36Sopenharmony_ci#include <dt-bindings/soc/qcom,gpr.h> 1462306a36Sopenharmony_ci#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h> 1562306a36Sopenharmony_ci#include "q6apm.h" 1662306a36Sopenharmony_ci#include "q6prm.h" 1762306a36Sopenharmony_ci#include "audioreach.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistruct q6prm { 2062306a36Sopenharmony_ci struct device *dev; 2162306a36Sopenharmony_ci gpr_device_t *gdev; 2262306a36Sopenharmony_ci wait_queue_head_t wait; 2362306a36Sopenharmony_ci struct gpr_ibasic_rsp_result_t result; 2462306a36Sopenharmony_ci struct mutex lock; 2562306a36Sopenharmony_ci}; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define PRM_CMD_REQUEST_HW_RSC 0x0100100F 2862306a36Sopenharmony_ci#define PRM_CMD_RSP_REQUEST_HW_RSC 0x02001002 2962306a36Sopenharmony_ci#define PRM_CMD_RELEASE_HW_RSC 0x01001010 3062306a36Sopenharmony_ci#define PRM_CMD_RSP_RELEASE_HW_RSC 0x02001003 3162306a36Sopenharmony_ci#define PARAM_ID_RSC_HW_CORE 0x08001032 3262306a36Sopenharmony_ci#define PARAM_ID_RSC_LPASS_CORE 0x0800102B 3362306a36Sopenharmony_ci#define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistruct prm_cmd_request_hw_core { 3662306a36Sopenharmony_ci struct apm_module_param_data param_data; 3762306a36Sopenharmony_ci uint32_t hw_clk_id; 3862306a36Sopenharmony_ci} __packed; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistruct prm_cmd_request_rsc { 4162306a36Sopenharmony_ci struct apm_module_param_data param_data; 4262306a36Sopenharmony_ci uint32_t num_clk_id; 4362306a36Sopenharmony_ci struct audio_hw_clk_cfg clock_id; 4462306a36Sopenharmony_ci} __packed; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistruct prm_cmd_release_rsc { 4762306a36Sopenharmony_ci struct apm_module_param_data param_data; 4862306a36Sopenharmony_ci uint32_t num_clk_id; 4962306a36Sopenharmony_ci struct audio_hw_clk_rel_cfg clock_id; 5062306a36Sopenharmony_ci} __packed; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci return audioreach_send_cmd_sync(prm->dev, prm->gdev, &prm->result, &prm->lock, 5562306a36Sopenharmony_ci NULL, &prm->wait, pkt, rsp_opcode); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool enable) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci struct q6prm *prm = dev_get_drvdata(dev->parent); 6162306a36Sopenharmony_ci struct apm_module_param_data *param_data; 6262306a36Sopenharmony_ci struct prm_cmd_request_hw_core *req; 6362306a36Sopenharmony_ci gpr_device_t *gdev = prm->gdev; 6462306a36Sopenharmony_ci uint32_t opcode, rsp_opcode; 6562306a36Sopenharmony_ci struct gpr_pkt *pkt; 6662306a36Sopenharmony_ci int rc; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (enable) { 6962306a36Sopenharmony_ci opcode = PRM_CMD_REQUEST_HW_RSC; 7062306a36Sopenharmony_ci rsp_opcode = PRM_CMD_RSP_REQUEST_HW_RSC; 7162306a36Sopenharmony_ci } else { 7262306a36Sopenharmony_ci opcode = PRM_CMD_RELEASE_HW_RSC; 7362306a36Sopenharmony_ci rsp_opcode = PRM_CMD_RSP_RELEASE_HW_RSC; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci pkt = audioreach_alloc_cmd_pkt(sizeof(*req), opcode, 0, gdev->svc.id, GPR_PRM_MODULE_IID); 7762306a36Sopenharmony_ci if (IS_ERR(pkt)) 7862306a36Sopenharmony_ci return PTR_ERR(pkt); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci req = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci param_data = &req->param_data; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci param_data->module_instance_id = GPR_PRM_MODULE_IID; 8562306a36Sopenharmony_ci param_data->error_code = 0; 8662306a36Sopenharmony_ci param_data->param_id = PARAM_ID_RSC_HW_CORE; 8762306a36Sopenharmony_ci param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci req->hw_clk_id = hw_block_id; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci rc = q6prm_send_cmd_sync(prm, pkt, rsp_opcode); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci kfree(pkt); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return rc; 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ciint q6prm_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, 9962306a36Sopenharmony_ci const char *client_name, uint32_t *client_handle) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci return q6prm_set_hw_core_req(dev, hw_block_id, true); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(q6prm_vote_lpass_core_hw); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ciint q6prm_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, uint32_t client_handle) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci return q6prm_set_hw_core_req(dev, hw_block_id, false); 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(q6prm_unvote_lpass_core_hw); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic int q6prm_request_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root, 11362306a36Sopenharmony_ci unsigned int freq) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci struct q6prm *prm = dev_get_drvdata(dev->parent); 11662306a36Sopenharmony_ci struct apm_module_param_data *param_data; 11762306a36Sopenharmony_ci struct prm_cmd_request_rsc *req; 11862306a36Sopenharmony_ci gpr_device_t *gdev = prm->gdev; 11962306a36Sopenharmony_ci struct gpr_pkt *pkt; 12062306a36Sopenharmony_ci int rc; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci pkt = audioreach_alloc_cmd_pkt(sizeof(*req), PRM_CMD_REQUEST_HW_RSC, 0, gdev->svc.id, 12362306a36Sopenharmony_ci GPR_PRM_MODULE_IID); 12462306a36Sopenharmony_ci if (IS_ERR(pkt)) 12562306a36Sopenharmony_ci return PTR_ERR(pkt); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci req = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci param_data = &req->param_data; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci param_data->module_instance_id = GPR_PRM_MODULE_IID; 13262306a36Sopenharmony_ci param_data->error_code = 0; 13362306a36Sopenharmony_ci param_data->param_id = PARAM_ID_RSC_AUDIO_HW_CLK; 13462306a36Sopenharmony_ci param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci req->num_clk_id = 1; 13762306a36Sopenharmony_ci req->clock_id.clock_id = clk_id; 13862306a36Sopenharmony_ci req->clock_id.clock_freq = freq; 13962306a36Sopenharmony_ci req->clock_id.clock_attri = clk_attr; 14062306a36Sopenharmony_ci req->clock_id.clock_root = clk_root; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci rc = q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_REQUEST_HW_RSC); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci kfree(pkt); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci return rc; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic int q6prm_release_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root, 15062306a36Sopenharmony_ci unsigned int freq) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci struct q6prm *prm = dev_get_drvdata(dev->parent); 15362306a36Sopenharmony_ci struct apm_module_param_data *param_data; 15462306a36Sopenharmony_ci struct prm_cmd_release_rsc *rel; 15562306a36Sopenharmony_ci gpr_device_t *gdev = prm->gdev; 15662306a36Sopenharmony_ci struct gpr_pkt *pkt; 15762306a36Sopenharmony_ci int rc; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci pkt = audioreach_alloc_cmd_pkt(sizeof(*rel), PRM_CMD_RELEASE_HW_RSC, 0, gdev->svc.id, 16062306a36Sopenharmony_ci GPR_PRM_MODULE_IID); 16162306a36Sopenharmony_ci if (IS_ERR(pkt)) 16262306a36Sopenharmony_ci return PTR_ERR(pkt); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci rel = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci param_data = &rel->param_data; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci param_data->module_instance_id = GPR_PRM_MODULE_IID; 16962306a36Sopenharmony_ci param_data->error_code = 0; 17062306a36Sopenharmony_ci param_data->param_id = PARAM_ID_RSC_AUDIO_HW_CLK; 17162306a36Sopenharmony_ci param_data->param_size = sizeof(*rel) - APM_MODULE_PARAM_DATA_SIZE; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci rel->num_clk_id = 1; 17462306a36Sopenharmony_ci rel->clock_id.clock_id = clk_id; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci rc = q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_RELEASE_HW_RSC); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci kfree(pkt); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci return rc; 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ciint q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root, 18462306a36Sopenharmony_ci unsigned int freq) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci if (freq) 18762306a36Sopenharmony_ci return q6prm_request_lpass_clock(dev, clk_id, clk_attr, clk_root, freq); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return q6prm_release_lpass_clock(dev, clk_id, clk_attr, clk_root, freq); 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(q6prm_set_lpass_clock); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic int prm_callback(struct gpr_resp_pkt *data, void *priv, int op) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci gpr_device_t *gdev = priv; 19662306a36Sopenharmony_ci struct q6prm *prm = dev_get_drvdata(&gdev->dev); 19762306a36Sopenharmony_ci struct gpr_ibasic_rsp_result_t *result; 19862306a36Sopenharmony_ci struct gpr_hdr *hdr = &data->hdr; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci switch (hdr->opcode) { 20162306a36Sopenharmony_ci case PRM_CMD_RSP_REQUEST_HW_RSC: 20262306a36Sopenharmony_ci case PRM_CMD_RSP_RELEASE_HW_RSC: 20362306a36Sopenharmony_ci result = data->payload; 20462306a36Sopenharmony_ci prm->result.opcode = hdr->opcode; 20562306a36Sopenharmony_ci prm->result.status = result->status; 20662306a36Sopenharmony_ci wake_up(&prm->wait); 20762306a36Sopenharmony_ci break; 20862306a36Sopenharmony_ci default: 20962306a36Sopenharmony_ci break; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci return 0; 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic int prm_probe(gpr_device_t *gdev) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct device *dev = &gdev->dev; 21862306a36Sopenharmony_ci struct q6prm *cc; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); 22162306a36Sopenharmony_ci if (!cc) 22262306a36Sopenharmony_ci return -ENOMEM; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci cc->dev = dev; 22562306a36Sopenharmony_ci cc->gdev = gdev; 22662306a36Sopenharmony_ci mutex_init(&cc->lock); 22762306a36Sopenharmony_ci init_waitqueue_head(&cc->wait); 22862306a36Sopenharmony_ci dev_set_drvdata(dev, cc); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci if (!q6apm_is_adsp_ready()) 23162306a36Sopenharmony_ci return -EPROBE_DEFER; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci return devm_of_platform_populate(dev); 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci#ifdef CONFIG_OF 23762306a36Sopenharmony_cistatic const struct of_device_id prm_device_id[] = { 23862306a36Sopenharmony_ci { .compatible = "qcom,q6prm" }, 23962306a36Sopenharmony_ci {}, 24062306a36Sopenharmony_ci}; 24162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, prm_device_id); 24262306a36Sopenharmony_ci#endif 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic gpr_driver_t prm_driver = { 24562306a36Sopenharmony_ci .probe = prm_probe, 24662306a36Sopenharmony_ci .gpr_callback = prm_callback, 24762306a36Sopenharmony_ci .driver = { 24862306a36Sopenharmony_ci .name = "qcom-prm", 24962306a36Sopenharmony_ci .of_match_table = of_match_ptr(prm_device_id), 25062306a36Sopenharmony_ci }, 25162306a36Sopenharmony_ci}; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cimodule_gpr_driver(prm_driver); 25462306a36Sopenharmony_ciMODULE_DESCRIPTION("Q6 Proxy Resource Manager"); 25562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 256