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