1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Crypto acceleration support for Rockchip RK3288 |
4 | * |
5 | * Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd |
6 | * |
7 | * Author: Zain Wang <zain.wang@rock-chips.com> |
8 | * |
9 | * Some ideas are from marvell-cesa.c and s5p-sss.c driver. |
10 | */ |
11 | |
12 | #include "rk3288_crypto.h" |
13 | #include <crypto/engine.h> |
14 | #include <crypto/internal/hash.h> |
15 | #include <crypto/internal/skcipher.h> |
16 | #include <linux/clk.h> |
17 | #include <linux/dma-mapping.h> |
18 | #include <linux/debugfs.h> |
19 | #include <linux/delay.h> |
20 | #include <linux/err.h> |
21 | #include <linux/kernel.h> |
22 | #include <linux/io.h> |
23 | #include <linux/module.h> |
24 | #include <linux/platform_device.h> |
25 | #include <linux/of.h> |
26 | #include <linux/reset.h> |
27 | #include <linux/spinlock.h> |
28 | |
29 | static struct rockchip_ip rocklist = { |
30 | .dev_list = LIST_HEAD_INIT(rocklist.dev_list), |
31 | .lock = __SPIN_LOCK_UNLOCKED(rocklist.lock), |
32 | }; |
33 | |
34 | struct rk_crypto_info *get_rk_crypto(void) |
35 | { |
36 | struct rk_crypto_info *first; |
37 | |
38 | spin_lock(lock: &rocklist.lock); |
39 | first = list_first_entry_or_null(&rocklist.dev_list, |
40 | struct rk_crypto_info, list); |
41 | list_rotate_left(head: &rocklist.dev_list); |
42 | spin_unlock(lock: &rocklist.lock); |
43 | return first; |
44 | } |
45 | |
46 | static const struct rk_variant rk3288_variant = { |
47 | .num_clks = 4, |
48 | .rkclks = { |
49 | { "sclk" , 150000000}, |
50 | } |
51 | }; |
52 | |
53 | static const struct rk_variant rk3328_variant = { |
54 | .num_clks = 3, |
55 | }; |
56 | |
57 | static const struct rk_variant rk3399_variant = { |
58 | .num_clks = 3, |
59 | }; |
60 | |
61 | static int rk_crypto_get_clks(struct rk_crypto_info *dev) |
62 | { |
63 | int i, j, err; |
64 | unsigned long cr; |
65 | |
66 | dev->num_clks = devm_clk_bulk_get_all(dev: dev->dev, clks: &dev->clks); |
67 | if (dev->num_clks < dev->variant->num_clks) { |
68 | dev_err(dev->dev, "Missing clocks, got %d instead of %d\n" , |
69 | dev->num_clks, dev->variant->num_clks); |
70 | return -EINVAL; |
71 | } |
72 | |
73 | for (i = 0; i < dev->num_clks; i++) { |
74 | cr = clk_get_rate(clk: dev->clks[i].clk); |
75 | for (j = 0; j < ARRAY_SIZE(dev->variant->rkclks); j++) { |
76 | if (dev->variant->rkclks[j].max == 0) |
77 | continue; |
78 | if (strcmp(dev->variant->rkclks[j].name, dev->clks[i].id)) |
79 | continue; |
80 | if (cr > dev->variant->rkclks[j].max) { |
81 | err = clk_set_rate(clk: dev->clks[i].clk, |
82 | rate: dev->variant->rkclks[j].max); |
83 | if (err) |
84 | dev_err(dev->dev, "Fail downclocking %s from %lu to %lu\n" , |
85 | dev->variant->rkclks[j].name, cr, |
86 | dev->variant->rkclks[j].max); |
87 | else |
88 | dev_info(dev->dev, "Downclocking %s from %lu to %lu\n" , |
89 | dev->variant->rkclks[j].name, cr, |
90 | dev->variant->rkclks[j].max); |
91 | } |
92 | } |
93 | } |
94 | return 0; |
95 | } |
96 | |
97 | static int rk_crypto_enable_clk(struct rk_crypto_info *dev) |
98 | { |
99 | int err; |
100 | |
101 | err = clk_bulk_prepare_enable(num_clks: dev->num_clks, clks: dev->clks); |
102 | if (err) |
103 | dev_err(dev->dev, "Could not enable clock clks\n" ); |
104 | |
105 | return err; |
106 | } |
107 | |
108 | static void rk_crypto_disable_clk(struct rk_crypto_info *dev) |
109 | { |
110 | clk_bulk_disable_unprepare(num_clks: dev->num_clks, clks: dev->clks); |
111 | } |
112 | |
113 | /* |
114 | * Power management strategy: The device is suspended until a request |
115 | * is handled. For avoiding suspend/resume yoyo, the autosuspend is set to 2s. |
116 | */ |
117 | static int rk_crypto_pm_suspend(struct device *dev) |
118 | { |
119 | struct rk_crypto_info *rkdev = dev_get_drvdata(dev); |
120 | |
121 | rk_crypto_disable_clk(dev: rkdev); |
122 | reset_control_assert(rstc: rkdev->rst); |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | static int rk_crypto_pm_resume(struct device *dev) |
128 | { |
129 | struct rk_crypto_info *rkdev = dev_get_drvdata(dev); |
130 | int ret; |
131 | |
132 | ret = rk_crypto_enable_clk(dev: rkdev); |
133 | if (ret) |
134 | return ret; |
135 | |
136 | reset_control_deassert(rstc: rkdev->rst); |
137 | return 0; |
138 | |
139 | } |
140 | |
141 | static const struct dev_pm_ops rk_crypto_pm_ops = { |
142 | SET_RUNTIME_PM_OPS(rk_crypto_pm_suspend, rk_crypto_pm_resume, NULL) |
143 | }; |
144 | |
145 | static int rk_crypto_pm_init(struct rk_crypto_info *rkdev) |
146 | { |
147 | int err; |
148 | |
149 | pm_runtime_use_autosuspend(dev: rkdev->dev); |
150 | pm_runtime_set_autosuspend_delay(dev: rkdev->dev, delay: 2000); |
151 | |
152 | err = pm_runtime_set_suspended(dev: rkdev->dev); |
153 | if (err) |
154 | return err; |
155 | pm_runtime_enable(dev: rkdev->dev); |
156 | return err; |
157 | } |
158 | |
159 | static void rk_crypto_pm_exit(struct rk_crypto_info *rkdev) |
160 | { |
161 | pm_runtime_disable(dev: rkdev->dev); |
162 | } |
163 | |
164 | static irqreturn_t rk_crypto_irq_handle(int irq, void *dev_id) |
165 | { |
166 | struct rk_crypto_info *dev = platform_get_drvdata(pdev: dev_id); |
167 | u32 interrupt_status; |
168 | |
169 | interrupt_status = CRYPTO_READ(dev, RK_CRYPTO_INTSTS); |
170 | CRYPTO_WRITE(dev, RK_CRYPTO_INTSTS, interrupt_status); |
171 | |
172 | dev->status = 1; |
173 | if (interrupt_status & 0x0a) { |
174 | dev_warn(dev->dev, "DMA Error\n" ); |
175 | dev->status = 0; |
176 | } |
177 | complete(&dev->complete); |
178 | |
179 | return IRQ_HANDLED; |
180 | } |
181 | |
182 | static struct rk_crypto_tmp *rk_cipher_algs[] = { |
183 | &rk_ecb_aes_alg, |
184 | &rk_cbc_aes_alg, |
185 | &rk_ecb_des_alg, |
186 | &rk_cbc_des_alg, |
187 | &rk_ecb_des3_ede_alg, |
188 | &rk_cbc_des3_ede_alg, |
189 | &rk_ahash_sha1, |
190 | &rk_ahash_sha256, |
191 | &rk_ahash_md5, |
192 | }; |
193 | |
194 | static int rk_crypto_debugfs_show(struct seq_file *seq, void *v) |
195 | { |
196 | struct rk_crypto_info *dd; |
197 | unsigned int i; |
198 | |
199 | spin_lock(lock: &rocklist.lock); |
200 | list_for_each_entry(dd, &rocklist.dev_list, list) { |
201 | seq_printf(m: seq, fmt: "%s %s requests: %lu\n" , |
202 | dev_driver_string(dev: dd->dev), dev_name(dev: dd->dev), |
203 | dd->nreq); |
204 | } |
205 | spin_unlock(lock: &rocklist.lock); |
206 | |
207 | for (i = 0; i < ARRAY_SIZE(rk_cipher_algs); i++) { |
208 | if (!rk_cipher_algs[i]->dev) |
209 | continue; |
210 | switch (rk_cipher_algs[i]->type) { |
211 | case CRYPTO_ALG_TYPE_SKCIPHER: |
212 | seq_printf(m: seq, fmt: "%s %s reqs=%lu fallback=%lu\n" , |
213 | rk_cipher_algs[i]->alg.skcipher.base.base.cra_driver_name, |
214 | rk_cipher_algs[i]->alg.skcipher.base.base.cra_name, |
215 | rk_cipher_algs[i]->stat_req, rk_cipher_algs[i]->stat_fb); |
216 | seq_printf(m: seq, fmt: "\tfallback due to length: %lu\n" , |
217 | rk_cipher_algs[i]->stat_fb_len); |
218 | seq_printf(m: seq, fmt: "\tfallback due to alignment: %lu\n" , |
219 | rk_cipher_algs[i]->stat_fb_align); |
220 | seq_printf(m: seq, fmt: "\tfallback due to SGs: %lu\n" , |
221 | rk_cipher_algs[i]->stat_fb_sgdiff); |
222 | break; |
223 | case CRYPTO_ALG_TYPE_AHASH: |
224 | seq_printf(m: seq, fmt: "%s %s reqs=%lu fallback=%lu\n" , |
225 | rk_cipher_algs[i]->alg.hash.base.halg.base.cra_driver_name, |
226 | rk_cipher_algs[i]->alg.hash.base.halg.base.cra_name, |
227 | rk_cipher_algs[i]->stat_req, rk_cipher_algs[i]->stat_fb); |
228 | break; |
229 | } |
230 | } |
231 | return 0; |
232 | } |
233 | |
234 | DEFINE_SHOW_ATTRIBUTE(rk_crypto_debugfs); |
235 | |
236 | static void register_debugfs(struct rk_crypto_info *crypto_info) |
237 | { |
238 | struct dentry *dbgfs_dir __maybe_unused; |
239 | struct dentry *dbgfs_stats __maybe_unused; |
240 | |
241 | /* Ignore error of debugfs */ |
242 | dbgfs_dir = debugfs_create_dir(name: "rk3288_crypto" , NULL); |
243 | dbgfs_stats = debugfs_create_file(name: "stats" , mode: 0444, parent: dbgfs_dir, data: &rocklist, |
244 | fops: &rk_crypto_debugfs_fops); |
245 | |
246 | #ifdef CONFIG_CRYPTO_DEV_ROCKCHIP_DEBUG |
247 | rocklist.dbgfs_dir = dbgfs_dir; |
248 | rocklist.dbgfs_stats = dbgfs_stats; |
249 | #endif |
250 | } |
251 | |
252 | static int rk_crypto_register(struct rk_crypto_info *crypto_info) |
253 | { |
254 | unsigned int i, k; |
255 | int err = 0; |
256 | |
257 | for (i = 0; i < ARRAY_SIZE(rk_cipher_algs); i++) { |
258 | rk_cipher_algs[i]->dev = crypto_info; |
259 | switch (rk_cipher_algs[i]->type) { |
260 | case CRYPTO_ALG_TYPE_SKCIPHER: |
261 | dev_info(crypto_info->dev, "Register %s as %s\n" , |
262 | rk_cipher_algs[i]->alg.skcipher.base.base.cra_name, |
263 | rk_cipher_algs[i]->alg.skcipher.base.base.cra_driver_name); |
264 | err = crypto_engine_register_skcipher(alg: &rk_cipher_algs[i]->alg.skcipher); |
265 | break; |
266 | case CRYPTO_ALG_TYPE_AHASH: |
267 | dev_info(crypto_info->dev, "Register %s as %s\n" , |
268 | rk_cipher_algs[i]->alg.hash.base.halg.base.cra_name, |
269 | rk_cipher_algs[i]->alg.hash.base.halg.base.cra_driver_name); |
270 | err = crypto_engine_register_ahash(alg: &rk_cipher_algs[i]->alg.hash); |
271 | break; |
272 | default: |
273 | dev_err(crypto_info->dev, "unknown algorithm\n" ); |
274 | } |
275 | if (err) |
276 | goto err_cipher_algs; |
277 | } |
278 | return 0; |
279 | |
280 | err_cipher_algs: |
281 | for (k = 0; k < i; k++) { |
282 | if (rk_cipher_algs[i]->type == CRYPTO_ALG_TYPE_SKCIPHER) |
283 | crypto_engine_unregister_skcipher(alg: &rk_cipher_algs[k]->alg.skcipher); |
284 | else |
285 | crypto_engine_unregister_ahash(alg: &rk_cipher_algs[i]->alg.hash); |
286 | } |
287 | return err; |
288 | } |
289 | |
290 | static void rk_crypto_unregister(void) |
291 | { |
292 | unsigned int i; |
293 | |
294 | for (i = 0; i < ARRAY_SIZE(rk_cipher_algs); i++) { |
295 | if (rk_cipher_algs[i]->type == CRYPTO_ALG_TYPE_SKCIPHER) |
296 | crypto_engine_unregister_skcipher(alg: &rk_cipher_algs[i]->alg.skcipher); |
297 | else |
298 | crypto_engine_unregister_ahash(alg: &rk_cipher_algs[i]->alg.hash); |
299 | } |
300 | } |
301 | |
302 | static const struct of_device_id crypto_of_id_table[] = { |
303 | { .compatible = "rockchip,rk3288-crypto" , |
304 | .data = &rk3288_variant, |
305 | }, |
306 | { .compatible = "rockchip,rk3328-crypto" , |
307 | .data = &rk3328_variant, |
308 | }, |
309 | { .compatible = "rockchip,rk3399-crypto" , |
310 | .data = &rk3399_variant, |
311 | }, |
312 | {} |
313 | }; |
314 | MODULE_DEVICE_TABLE(of, crypto_of_id_table); |
315 | |
316 | static int rk_crypto_probe(struct platform_device *pdev) |
317 | { |
318 | struct device *dev = &pdev->dev; |
319 | struct rk_crypto_info *crypto_info, *first; |
320 | int err = 0; |
321 | |
322 | crypto_info = devm_kzalloc(dev: &pdev->dev, |
323 | size: sizeof(*crypto_info), GFP_KERNEL); |
324 | if (!crypto_info) { |
325 | err = -ENOMEM; |
326 | goto err_crypto; |
327 | } |
328 | |
329 | crypto_info->dev = &pdev->dev; |
330 | platform_set_drvdata(pdev, data: crypto_info); |
331 | |
332 | crypto_info->variant = of_device_get_match_data(dev: &pdev->dev); |
333 | if (!crypto_info->variant) { |
334 | dev_err(&pdev->dev, "Missing variant\n" ); |
335 | return -EINVAL; |
336 | } |
337 | |
338 | crypto_info->rst = devm_reset_control_array_get_exclusive(dev); |
339 | if (IS_ERR(ptr: crypto_info->rst)) { |
340 | err = PTR_ERR(ptr: crypto_info->rst); |
341 | goto err_crypto; |
342 | } |
343 | |
344 | reset_control_assert(rstc: crypto_info->rst); |
345 | usleep_range(min: 10, max: 20); |
346 | reset_control_deassert(rstc: crypto_info->rst); |
347 | |
348 | crypto_info->reg = devm_platform_ioremap_resource(pdev, index: 0); |
349 | if (IS_ERR(ptr: crypto_info->reg)) { |
350 | err = PTR_ERR(ptr: crypto_info->reg); |
351 | goto err_crypto; |
352 | } |
353 | |
354 | err = rk_crypto_get_clks(dev: crypto_info); |
355 | if (err) |
356 | goto err_crypto; |
357 | |
358 | crypto_info->irq = platform_get_irq(pdev, 0); |
359 | if (crypto_info->irq < 0) { |
360 | err = crypto_info->irq; |
361 | goto err_crypto; |
362 | } |
363 | |
364 | err = devm_request_irq(dev: &pdev->dev, irq: crypto_info->irq, |
365 | handler: rk_crypto_irq_handle, IRQF_SHARED, |
366 | devname: "rk-crypto" , dev_id: pdev); |
367 | |
368 | if (err) { |
369 | dev_err(&pdev->dev, "irq request failed.\n" ); |
370 | goto err_crypto; |
371 | } |
372 | |
373 | crypto_info->engine = crypto_engine_alloc_init(dev: &pdev->dev, rt: true); |
374 | crypto_engine_start(engine: crypto_info->engine); |
375 | init_completion(x: &crypto_info->complete); |
376 | |
377 | err = rk_crypto_pm_init(rkdev: crypto_info); |
378 | if (err) |
379 | goto err_pm; |
380 | |
381 | spin_lock(lock: &rocklist.lock); |
382 | first = list_first_entry_or_null(&rocklist.dev_list, |
383 | struct rk_crypto_info, list); |
384 | list_add_tail(new: &crypto_info->list, head: &rocklist.dev_list); |
385 | spin_unlock(lock: &rocklist.lock); |
386 | |
387 | if (!first) { |
388 | err = rk_crypto_register(crypto_info); |
389 | if (err) { |
390 | dev_err(dev, "Fail to register crypto algorithms" ); |
391 | goto err_register_alg; |
392 | } |
393 | |
394 | register_debugfs(crypto_info); |
395 | } |
396 | |
397 | return 0; |
398 | |
399 | err_register_alg: |
400 | rk_crypto_pm_exit(rkdev: crypto_info); |
401 | err_pm: |
402 | crypto_engine_exit(engine: crypto_info->engine); |
403 | err_crypto: |
404 | dev_err(dev, "Crypto Accelerator not successfully registered\n" ); |
405 | return err; |
406 | } |
407 | |
408 | static void rk_crypto_remove(struct platform_device *pdev) |
409 | { |
410 | struct rk_crypto_info *crypto_tmp = platform_get_drvdata(pdev); |
411 | struct rk_crypto_info *first; |
412 | |
413 | spin_lock_bh(lock: &rocklist.lock); |
414 | list_del(entry: &crypto_tmp->list); |
415 | first = list_first_entry_or_null(&rocklist.dev_list, |
416 | struct rk_crypto_info, list); |
417 | spin_unlock_bh(lock: &rocklist.lock); |
418 | |
419 | if (!first) { |
420 | #ifdef CONFIG_CRYPTO_DEV_ROCKCHIP_DEBUG |
421 | debugfs_remove_recursive(rocklist.dbgfs_dir); |
422 | #endif |
423 | rk_crypto_unregister(); |
424 | } |
425 | rk_crypto_pm_exit(rkdev: crypto_tmp); |
426 | crypto_engine_exit(engine: crypto_tmp->engine); |
427 | } |
428 | |
429 | static struct platform_driver crypto_driver = { |
430 | .probe = rk_crypto_probe, |
431 | .remove_new = rk_crypto_remove, |
432 | .driver = { |
433 | .name = "rk3288-crypto" , |
434 | .pm = &rk_crypto_pm_ops, |
435 | .of_match_table = crypto_of_id_table, |
436 | }, |
437 | }; |
438 | |
439 | module_platform_driver(crypto_driver); |
440 | |
441 | MODULE_AUTHOR("Zain Wang <zain.wang@rock-chips.com>" ); |
442 | MODULE_DESCRIPTION("Support for Rockchip's cryptographic engine" ); |
443 | MODULE_LICENSE("GPL" ); |
444 | |