Commit | Line | Data |
---|---|---|
dea85242 PG |
1 | /* |
2 | * Qualcomm SCM driver | |
3 | * | |
4 | * Copyright (c) 2010,2015, The Linux Foundation. All rights reserved. | |
2ce76a6a | 5 | * Copyright (C) 2015 Linaro Ltd. |
2a1eb58a SB |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 and | |
9 | * only version 2 as published by the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
2a1eb58a | 16 | */ |
d0f6fa7b | 17 | #include <linux/platform_device.h> |
dea85242 | 18 | #include <linux/init.h> |
b6a1dfbc KG |
19 | #include <linux/cpumask.h> |
20 | #include <linux/export.h> | |
f01e90fe | 21 | #include <linux/dma-mapping.h> |
b6a1dfbc | 22 | #include <linux/types.h> |
916f743d | 23 | #include <linux/qcom_scm.h> |
d0f6fa7b AG |
24 | #include <linux/of.h> |
25 | #include <linux/of_platform.h> | |
26 | #include <linux/clk.h> | |
dd4fe5b2 | 27 | #include <linux/reset-controller.h> |
2a1eb58a | 28 | |
b6a1dfbc | 29 | #include "qcom_scm.h" |
a353e4a0 | 30 | |
d0f6fa7b AG |
31 | struct qcom_scm { |
32 | struct device *dev; | |
33 | struct clk *core_clk; | |
34 | struct clk *iface_clk; | |
35 | struct clk *bus_clk; | |
dd4fe5b2 | 36 | struct reset_controller_dev reset; |
d0f6fa7b AG |
37 | }; |
38 | ||
39 | static struct qcom_scm *__scm; | |
40 | ||
41 | static int qcom_scm_clk_enable(void) | |
42 | { | |
43 | int ret; | |
44 | ||
45 | ret = clk_prepare_enable(__scm->core_clk); | |
46 | if (ret) | |
47 | goto bail; | |
48 | ||
49 | ret = clk_prepare_enable(__scm->iface_clk); | |
50 | if (ret) | |
51 | goto disable_core; | |
52 | ||
53 | ret = clk_prepare_enable(__scm->bus_clk); | |
54 | if (ret) | |
55 | goto disable_iface; | |
56 | ||
57 | return 0; | |
58 | ||
59 | disable_iface: | |
60 | clk_disable_unprepare(__scm->iface_clk); | |
61 | disable_core: | |
62 | clk_disable_unprepare(__scm->core_clk); | |
63 | bail: | |
64 | return ret; | |
65 | } | |
66 | ||
67 | static void qcom_scm_clk_disable(void) | |
68 | { | |
69 | clk_disable_unprepare(__scm->core_clk); | |
70 | clk_disable_unprepare(__scm->iface_clk); | |
71 | clk_disable_unprepare(__scm->bus_clk); | |
72 | } | |
73 | ||
a353e4a0 LI |
74 | /** |
75 | * qcom_scm_set_cold_boot_addr() - Set the cold boot address for cpus | |
76 | * @entry: Entry point function for the cpus | |
77 | * @cpus: The cpumask of cpus that will use the entry point | |
78 | * | |
79 | * Set the cold boot address of the cpus. Any cpu outside the supported | |
80 | * range would be removed from the cpu present mask. | |
81 | */ | |
82 | int qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus) | |
83 | { | |
b6a1dfbc | 84 | return __qcom_scm_set_cold_boot_addr(entry, cpus); |
a353e4a0 LI |
85 | } |
86 | EXPORT_SYMBOL(qcom_scm_set_cold_boot_addr); | |
2ce76a6a LI |
87 | |
88 | /** | |
89 | * qcom_scm_set_warm_boot_addr() - Set the warm boot address for cpus | |
90 | * @entry: Entry point function for the cpus | |
91 | * @cpus: The cpumask of cpus that will use the entry point | |
92 | * | |
93 | * Set the Linux entry point for the SCM to transfer control to when coming | |
94 | * out of a power down. CPU power down may be executed on cpuidle or hotplug. | |
95 | */ | |
96 | int qcom_scm_set_warm_boot_addr(void *entry, const cpumask_t *cpus) | |
97 | { | |
16e59467 | 98 | return __qcom_scm_set_warm_boot_addr(__scm->dev, entry, cpus); |
2ce76a6a LI |
99 | } |
100 | EXPORT_SYMBOL(qcom_scm_set_warm_boot_addr); | |
767b0235 | 101 | |
767b0235 LI |
102 | /** |
103 | * qcom_scm_cpu_power_down() - Power down the cpu | |
104 | * @flags - Flags to flush cache | |
105 | * | |
106 | * This is an end point to power down cpu. If there was a pending interrupt, | |
107 | * the control would return from this function, otherwise, the cpu jumps to the | |
108 | * warm boot entry point set for this cpu upon reset. | |
109 | */ | |
110 | void qcom_scm_cpu_power_down(u32 flags) | |
111 | { | |
b6a1dfbc | 112 | __qcom_scm_cpu_power_down(flags); |
767b0235 LI |
113 | } |
114 | EXPORT_SYMBOL(qcom_scm_cpu_power_down); | |
9626b699 | 115 | |
116 | /** | |
117 | * qcom_scm_hdcp_available() - Check if secure environment supports HDCP. | |
118 | * | |
119 | * Return true if HDCP is supported, false if not. | |
120 | */ | |
121 | bool qcom_scm_hdcp_available(void) | |
122 | { | |
d0f6fa7b AG |
123 | int ret = qcom_scm_clk_enable(); |
124 | ||
125 | if (ret) | |
126 | return ret; | |
9626b699 | 127 | |
16e59467 | 128 | ret = __qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_HDCP, |
d0f6fa7b AG |
129 | QCOM_SCM_CMD_HDCP); |
130 | ||
131 | qcom_scm_clk_disable(); | |
9626b699 | 132 | |
d0f6fa7b | 133 | return ret > 0 ? true : false; |
9626b699 | 134 | } |
135 | EXPORT_SYMBOL(qcom_scm_hdcp_available); | |
136 | ||
137 | /** | |
138 | * qcom_scm_hdcp_req() - Send HDCP request. | |
139 | * @req: HDCP request array | |
140 | * @req_cnt: HDCP request array count | |
141 | * @resp: response buffer passed to SCM | |
142 | * | |
143 | * Write HDCP register(s) through SCM. | |
144 | */ | |
145 | int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp) | |
146 | { | |
d0f6fa7b AG |
147 | int ret = qcom_scm_clk_enable(); |
148 | ||
149 | if (ret) | |
150 | return ret; | |
151 | ||
16e59467 | 152 | ret = __qcom_scm_hdcp_req(__scm->dev, req, req_cnt, resp); |
d0f6fa7b AG |
153 | qcom_scm_clk_disable(); |
154 | return ret; | |
9626b699 | 155 | } |
156 | EXPORT_SYMBOL(qcom_scm_hdcp_req); | |
d0f6fa7b | 157 | |
f01e90fe BA |
158 | /** |
159 | * qcom_scm_pas_supported() - Check if the peripheral authentication service is | |
160 | * available for the given peripherial | |
161 | * @peripheral: peripheral id | |
162 | * | |
163 | * Returns true if PAS is supported for this peripheral, otherwise false. | |
164 | */ | |
165 | bool qcom_scm_pas_supported(u32 peripheral) | |
166 | { | |
167 | int ret; | |
168 | ||
169 | ret = __qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL, | |
170 | QCOM_SCM_PAS_IS_SUPPORTED_CMD); | |
171 | if (ret <= 0) | |
172 | return false; | |
173 | ||
174 | return __qcom_scm_pas_supported(__scm->dev, peripheral); | |
175 | } | |
176 | EXPORT_SYMBOL(qcom_scm_pas_supported); | |
177 | ||
178 | /** | |
179 | * qcom_scm_pas_init_image() - Initialize peripheral authentication service | |
180 | * state machine for a given peripheral, using the | |
181 | * metadata | |
182 | * @peripheral: peripheral id | |
183 | * @metadata: pointer to memory containing ELF header, program header table | |
184 | * and optional blob of data used for authenticating the metadata | |
185 | * and the rest of the firmware | |
186 | * @size: size of the metadata | |
187 | * | |
188 | * Returns 0 on success. | |
189 | */ | |
190 | int qcom_scm_pas_init_image(u32 peripheral, const void *metadata, size_t size) | |
191 | { | |
192 | dma_addr_t mdata_phys; | |
193 | void *mdata_buf; | |
194 | int ret; | |
195 | ||
196 | /* | |
197 | * During the scm call memory protection will be enabled for the meta | |
198 | * data blob, so make sure it's physically contiguous, 4K aligned and | |
199 | * non-cachable to avoid XPU violations. | |
200 | */ | |
201 | mdata_buf = dma_alloc_coherent(__scm->dev, size, &mdata_phys, | |
202 | GFP_KERNEL); | |
203 | if (!mdata_buf) { | |
204 | dev_err(__scm->dev, "Allocation of metadata buffer failed.\n"); | |
205 | return -ENOMEM; | |
206 | } | |
207 | memcpy(mdata_buf, metadata, size); | |
208 | ||
209 | ret = qcom_scm_clk_enable(); | |
210 | if (ret) | |
211 | goto free_metadata; | |
212 | ||
213 | ret = __qcom_scm_pas_init_image(__scm->dev, peripheral, mdata_phys); | |
214 | ||
215 | qcom_scm_clk_disable(); | |
216 | ||
217 | free_metadata: | |
218 | dma_free_coherent(__scm->dev, size, mdata_buf, mdata_phys); | |
219 | ||
220 | return ret; | |
221 | } | |
222 | EXPORT_SYMBOL(qcom_scm_pas_init_image); | |
223 | ||
224 | /** | |
225 | * qcom_scm_pas_mem_setup() - Prepare the memory related to a given peripheral | |
226 | * for firmware loading | |
227 | * @peripheral: peripheral id | |
228 | * @addr: start address of memory area to prepare | |
229 | * @size: size of the memory area to prepare | |
230 | * | |
231 | * Returns 0 on success. | |
232 | */ | |
233 | int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr, phys_addr_t size) | |
234 | { | |
235 | int ret; | |
236 | ||
237 | ret = qcom_scm_clk_enable(); | |
238 | if (ret) | |
239 | return ret; | |
240 | ||
241 | ret = __qcom_scm_pas_mem_setup(__scm->dev, peripheral, addr, size); | |
242 | qcom_scm_clk_disable(); | |
243 | ||
244 | return ret; | |
245 | } | |
246 | EXPORT_SYMBOL(qcom_scm_pas_mem_setup); | |
247 | ||
248 | /** | |
249 | * qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware | |
250 | * and reset the remote processor | |
251 | * @peripheral: peripheral id | |
252 | * | |
253 | * Return 0 on success. | |
254 | */ | |
255 | int qcom_scm_pas_auth_and_reset(u32 peripheral) | |
256 | { | |
257 | int ret; | |
258 | ||
259 | ret = qcom_scm_clk_enable(); | |
260 | if (ret) | |
261 | return ret; | |
262 | ||
263 | ret = __qcom_scm_pas_auth_and_reset(__scm->dev, peripheral); | |
264 | qcom_scm_clk_disable(); | |
265 | ||
266 | return ret; | |
267 | } | |
268 | EXPORT_SYMBOL(qcom_scm_pas_auth_and_reset); | |
269 | ||
270 | /** | |
271 | * qcom_scm_pas_shutdown() - Shut down the remote processor | |
272 | * @peripheral: peripheral id | |
273 | * | |
274 | * Returns 0 on success. | |
275 | */ | |
276 | int qcom_scm_pas_shutdown(u32 peripheral) | |
277 | { | |
278 | int ret; | |
279 | ||
280 | ret = qcom_scm_clk_enable(); | |
281 | if (ret) | |
282 | return ret; | |
283 | ||
284 | ret = __qcom_scm_pas_shutdown(__scm->dev, peripheral); | |
285 | qcom_scm_clk_disable(); | |
286 | ||
287 | return ret; | |
288 | } | |
289 | EXPORT_SYMBOL(qcom_scm_pas_shutdown); | |
290 | ||
dd4fe5b2 BA |
291 | static int qcom_scm_pas_reset_assert(struct reset_controller_dev *rcdev, |
292 | unsigned long idx) | |
293 | { | |
294 | if (idx != 0) | |
295 | return -EINVAL; | |
296 | ||
297 | return __qcom_scm_pas_mss_reset(__scm->dev, 1); | |
298 | } | |
299 | ||
300 | static int qcom_scm_pas_reset_deassert(struct reset_controller_dev *rcdev, | |
301 | unsigned long idx) | |
302 | { | |
303 | if (idx != 0) | |
304 | return -EINVAL; | |
305 | ||
306 | return __qcom_scm_pas_mss_reset(__scm->dev, 0); | |
307 | } | |
308 | ||
309 | static const struct reset_control_ops qcom_scm_pas_reset_ops = { | |
310 | .assert = qcom_scm_pas_reset_assert, | |
311 | .deassert = qcom_scm_pas_reset_deassert, | |
312 | }; | |
313 | ||
72d43419 AG |
314 | /** |
315 | * qcom_scm_is_available() - Checks if SCM is available | |
316 | */ | |
317 | bool qcom_scm_is_available(void) | |
318 | { | |
319 | return !!__scm; | |
320 | } | |
321 | EXPORT_SYMBOL(qcom_scm_is_available); | |
dd4fe5b2 | 322 | |
d0f6fa7b AG |
323 | static int qcom_scm_probe(struct platform_device *pdev) |
324 | { | |
325 | struct qcom_scm *scm; | |
326 | int ret; | |
327 | ||
328 | scm = devm_kzalloc(&pdev->dev, sizeof(*scm), GFP_KERNEL); | |
329 | if (!scm) | |
330 | return -ENOMEM; | |
331 | ||
332 | scm->core_clk = devm_clk_get(&pdev->dev, "core"); | |
333 | if (IS_ERR(scm->core_clk)) { | |
334 | if (PTR_ERR(scm->core_clk) == -EPROBE_DEFER) | |
335 | return PTR_ERR(scm->core_clk); | |
336 | ||
337 | scm->core_clk = NULL; | |
338 | } | |
339 | ||
340 | if (of_device_is_compatible(pdev->dev.of_node, "qcom,scm")) { | |
341 | scm->iface_clk = devm_clk_get(&pdev->dev, "iface"); | |
342 | if (IS_ERR(scm->iface_clk)) { | |
343 | if (PTR_ERR(scm->iface_clk) != -EPROBE_DEFER) | |
344 | dev_err(&pdev->dev, "failed to acquire iface clk\n"); | |
345 | return PTR_ERR(scm->iface_clk); | |
346 | } | |
347 | ||
348 | scm->bus_clk = devm_clk_get(&pdev->dev, "bus"); | |
349 | if (IS_ERR(scm->bus_clk)) { | |
350 | if (PTR_ERR(scm->bus_clk) != -EPROBE_DEFER) | |
351 | dev_err(&pdev->dev, "failed to acquire bus clk\n"); | |
352 | return PTR_ERR(scm->bus_clk); | |
353 | } | |
354 | } | |
355 | ||
dd4fe5b2 BA |
356 | scm->reset.ops = &qcom_scm_pas_reset_ops; |
357 | scm->reset.nr_resets = 1; | |
358 | scm->reset.of_node = pdev->dev.of_node; | |
359 | reset_controller_register(&scm->reset); | |
360 | ||
d0f6fa7b AG |
361 | /* vote for max clk rate for highest performance */ |
362 | ret = clk_set_rate(scm->core_clk, INT_MAX); | |
363 | if (ret) | |
364 | return ret; | |
365 | ||
366 | __scm = scm; | |
367 | __scm->dev = &pdev->dev; | |
368 | ||
6b1751a8 KG |
369 | __qcom_scm_init(); |
370 | ||
d0f6fa7b AG |
371 | return 0; |
372 | } | |
373 | ||
374 | static const struct of_device_id qcom_scm_dt_match[] = { | |
375 | { .compatible = "qcom,scm-apq8064",}, | |
376 | { .compatible = "qcom,scm-msm8660",}, | |
377 | { .compatible = "qcom,scm-msm8960",}, | |
378 | { .compatible = "qcom,scm",}, | |
379 | {} | |
380 | }; | |
381 | ||
d0f6fa7b AG |
382 | static struct platform_driver qcom_scm_driver = { |
383 | .driver = { | |
384 | .name = "qcom_scm", | |
385 | .of_match_table = qcom_scm_dt_match, | |
386 | }, | |
387 | .probe = qcom_scm_probe, | |
388 | }; | |
389 | ||
390 | static int __init qcom_scm_init(void) | |
391 | { | |
392 | struct device_node *np, *fw_np; | |
393 | int ret; | |
394 | ||
395 | fw_np = of_find_node_by_name(NULL, "firmware"); | |
396 | ||
397 | if (!fw_np) | |
398 | return -ENODEV; | |
399 | ||
400 | np = of_find_matching_node(fw_np, qcom_scm_dt_match); | |
401 | ||
402 | if (!np) { | |
403 | of_node_put(fw_np); | |
404 | return -ENODEV; | |
405 | } | |
406 | ||
407 | of_node_put(np); | |
408 | ||
409 | ret = of_platform_populate(fw_np, qcom_scm_dt_match, NULL, NULL); | |
410 | ||
411 | of_node_put(fw_np); | |
412 | ||
413 | if (ret) | |
414 | return ret; | |
415 | ||
416 | return platform_driver_register(&qcom_scm_driver); | |
417 | } | |
6c8e99d8 | 418 | subsys_initcall(qcom_scm_init); |