1// SPDX-License-Identifier: GPL-2.0
2// Copyright (c) 2021, Linaro Limited
3
4#include <linux/slab.h>
5#include <linux/wait.h>
6#include <linux/kernel.h>
7#include <linux/module.h>
8#include <linux/of.h>
9#include <linux/delay.h>
10#include <linux/of_platform.h>
11#include <linux/jiffies.h>
12#include <linux/soc/qcom/apr.h>
13#include <dt-bindings/soc/qcom,gpr.h>
14#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>
15#include "q6apm.h"
16#include "q6prm.h"
17#include "audioreach.h"
18
19struct q6prm {
20 struct device *dev;
21 gpr_device_t *gdev;
22 wait_queue_head_t wait;
23 struct gpr_ibasic_rsp_result_t result;
24 struct mutex lock;
25};
26
27#define PRM_CMD_REQUEST_HW_RSC 0x0100100F
28#define PRM_CMD_RSP_REQUEST_HW_RSC 0x02001002
29#define PRM_CMD_RELEASE_HW_RSC 0x01001010
30#define PRM_CMD_RSP_RELEASE_HW_RSC 0x02001003
31#define PARAM_ID_RSC_HW_CORE 0x08001032
32#define PARAM_ID_RSC_LPASS_CORE 0x0800102B
33#define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C
34
35struct prm_cmd_request_hw_core {
36 struct apm_module_param_data param_data;
37 uint32_t hw_clk_id;
38} __packed;
39
40struct prm_cmd_request_rsc {
41 struct apm_module_param_data param_data;
42 uint32_t num_clk_id;
43 struct audio_hw_clk_cfg clock_id;
44} __packed;
45
46struct prm_cmd_release_rsc {
47 struct apm_module_param_data param_data;
48 uint32_t num_clk_id;
49 struct audio_hw_clk_rel_cfg clock_id;
50} __packed;
51
52static int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode)
53{
54 return audioreach_send_cmd_sync(dev: prm->dev, gdev: prm->gdev, result: &prm->result, cmd_lock: &prm->lock,
55 NULL, cmd_wait: &prm->wait, pkt, rsp_opcode);
56}
57
58static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool enable)
59{
60 struct q6prm *prm = dev_get_drvdata(dev: dev->parent);
61 struct apm_module_param_data *param_data;
62 struct prm_cmd_request_hw_core *req;
63 gpr_device_t *gdev = prm->gdev;
64 uint32_t opcode, rsp_opcode;
65 struct gpr_pkt *pkt __free(kfree) = NULL;
66
67 if (enable) {
68 opcode = PRM_CMD_REQUEST_HW_RSC;
69 rsp_opcode = PRM_CMD_RSP_REQUEST_HW_RSC;
70 } else {
71 opcode = PRM_CMD_RELEASE_HW_RSC;
72 rsp_opcode = PRM_CMD_RSP_RELEASE_HW_RSC;
73 }
74
75 pkt = audioreach_alloc_cmd_pkt(payload_size: sizeof(*req), opcode, token: 0, src_port: gdev->svc.id, GPR_PRM_MODULE_IID);
76 if (IS_ERR(ptr: pkt))
77 return PTR_ERR(ptr: pkt);
78
79 req = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
80
81 param_data = &req->param_data;
82
83 param_data->module_instance_id = GPR_PRM_MODULE_IID;
84 param_data->error_code = 0;
85 param_data->param_id = PARAM_ID_RSC_HW_CORE;
86 param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE;
87
88 req->hw_clk_id = hw_block_id;
89
90 return q6prm_send_cmd_sync(prm, pkt, rsp_opcode);
91}
92
93int q6prm_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id,
94 const char *client_name, uint32_t *client_handle)
95{
96 return q6prm_set_hw_core_req(dev, hw_block_id, enable: true);
97
98}
99EXPORT_SYMBOL_GPL(q6prm_vote_lpass_core_hw);
100
101int q6prm_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, uint32_t client_handle)
102{
103 return q6prm_set_hw_core_req(dev, hw_block_id, enable: false);
104}
105EXPORT_SYMBOL_GPL(q6prm_unvote_lpass_core_hw);
106
107static int q6prm_request_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root,
108 unsigned int freq)
109{
110 struct q6prm *prm = dev_get_drvdata(dev: dev->parent);
111 struct apm_module_param_data *param_data;
112 struct prm_cmd_request_rsc *req;
113 gpr_device_t *gdev = prm->gdev;
114 struct gpr_pkt *pkt __free(kfree) = NULL;
115
116 pkt = audioreach_alloc_cmd_pkt(payload_size: sizeof(*req), PRM_CMD_REQUEST_HW_RSC, token: 0, src_port: gdev->svc.id,
117 GPR_PRM_MODULE_IID);
118 if (IS_ERR(ptr: pkt))
119 return PTR_ERR(ptr: pkt);
120
121 req = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
122
123 param_data = &req->param_data;
124
125 param_data->module_instance_id = GPR_PRM_MODULE_IID;
126 param_data->error_code = 0;
127 param_data->param_id = PARAM_ID_RSC_AUDIO_HW_CLK;
128 param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE;
129
130 req->num_clk_id = 1;
131 req->clock_id.clock_id = clk_id;
132 req->clock_id.clock_freq = freq;
133 req->clock_id.clock_attri = clk_attr;
134 req->clock_id.clock_root = clk_root;
135
136 return q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_REQUEST_HW_RSC);
137}
138
139static int q6prm_release_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root,
140 unsigned int freq)
141{
142 struct q6prm *prm = dev_get_drvdata(dev: dev->parent);
143 struct apm_module_param_data *param_data;
144 struct prm_cmd_release_rsc *rel;
145 gpr_device_t *gdev = prm->gdev;
146 struct gpr_pkt *pkt __free(kfree) = NULL;
147
148 pkt = audioreach_alloc_cmd_pkt(payload_size: sizeof(*rel), PRM_CMD_RELEASE_HW_RSC, token: 0, src_port: gdev->svc.id,
149 GPR_PRM_MODULE_IID);
150 if (IS_ERR(ptr: pkt))
151 return PTR_ERR(ptr: pkt);
152
153 rel = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
154
155 param_data = &rel->param_data;
156
157 param_data->module_instance_id = GPR_PRM_MODULE_IID;
158 param_data->error_code = 0;
159 param_data->param_id = PARAM_ID_RSC_AUDIO_HW_CLK;
160 param_data->param_size = sizeof(*rel) - APM_MODULE_PARAM_DATA_SIZE;
161
162 rel->num_clk_id = 1;
163 rel->clock_id.clock_id = clk_id;
164
165 return q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_RELEASE_HW_RSC);
166}
167
168int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root,
169 unsigned int freq)
170{
171 if (freq)
172 return q6prm_request_lpass_clock(dev, clk_id, clk_attr, clk_root, freq);
173
174 return q6prm_release_lpass_clock(dev, clk_id, clk_attr, clk_root, freq);
175}
176EXPORT_SYMBOL_GPL(q6prm_set_lpass_clock);
177
178static int prm_callback(struct gpr_resp_pkt *data, void *priv, int op)
179{
180 gpr_device_t *gdev = priv;
181 struct q6prm *prm = dev_get_drvdata(dev: &gdev->dev);
182 struct gpr_ibasic_rsp_result_t *result;
183 struct gpr_hdr *hdr = &data->hdr;
184
185 switch (hdr->opcode) {
186 case PRM_CMD_RSP_REQUEST_HW_RSC:
187 case PRM_CMD_RSP_RELEASE_HW_RSC:
188 result = data->payload;
189 prm->result.opcode = hdr->opcode;
190 prm->result.status = result->status;
191 wake_up(&prm->wait);
192 break;
193 default:
194 break;
195 }
196
197 return 0;
198}
199
200static int prm_probe(gpr_device_t *gdev)
201{
202 struct device *dev = &gdev->dev;
203 struct q6prm *cc;
204
205 cc = devm_kzalloc(dev, size: sizeof(*cc), GFP_KERNEL);
206 if (!cc)
207 return -ENOMEM;
208
209 cc->dev = dev;
210 cc->gdev = gdev;
211 mutex_init(&cc->lock);
212 init_waitqueue_head(&cc->wait);
213 dev_set_drvdata(dev, data: cc);
214
215 if (!q6apm_is_adsp_ready())
216 return -EPROBE_DEFER;
217
218 return devm_of_platform_populate(dev);
219}
220
221#ifdef CONFIG_OF
222static const struct of_device_id prm_device_id[] = {
223 { .compatible = "qcom,q6prm" },
224 {},
225};
226MODULE_DEVICE_TABLE(of, prm_device_id);
227#endif
228
229static gpr_driver_t prm_driver = {
230 .probe = prm_probe,
231 .gpr_callback = prm_callback,
232 .driver = {
233 .name = "qcom-prm",
234 .of_match_table = of_match_ptr(prm_device_id),
235 },
236};
237
238module_gpr_driver(prm_driver);
239MODULE_DESCRIPTION("Q6 Proxy Resource Manager");
240MODULE_LICENSE("GPL");
241

source code of linux/sound/soc/qcom/qdsp6/q6prm.c