1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> |
4 | */ |
5 | |
6 | #include <linux/io.h> |
7 | #include <linux/module.h> |
8 | #include <linux/nvmem-provider.h> |
9 | #include <linux/of_reserved_mem.h> |
10 | #include <linux/platform_device.h> |
11 | |
12 | struct rmem { |
13 | struct device *dev; |
14 | struct nvmem_device *nvmem; |
15 | struct reserved_mem *mem; |
16 | |
17 | phys_addr_t size; |
18 | }; |
19 | |
20 | static int rmem_read(void *context, unsigned int offset, |
21 | void *val, size_t bytes) |
22 | { |
23 | struct rmem *priv = context; |
24 | size_t available = priv->mem->size; |
25 | loff_t off = offset; |
26 | void *addr; |
27 | int count; |
28 | |
29 | /* |
30 | * Only map the reserved memory at this point to avoid potential rogue |
31 | * kernel threads inadvertently modifying it. Based on the current |
32 | * uses-cases for this driver, the performance hit isn't a concern. |
33 | * Nor is likely to be, given the nature of the subsystem. Most nvmem |
34 | * devices operate over slow buses to begin with. |
35 | * |
36 | * An alternative would be setting the memory as RO, set_memory_ro(), |
37 | * but as of Dec 2020 this isn't possible on arm64. |
38 | */ |
39 | addr = memremap(offset: priv->mem->base, size: available, flags: MEMREMAP_WB); |
40 | if (!addr) { |
41 | dev_err(priv->dev, "Failed to remap memory region\n" ); |
42 | return -ENOMEM; |
43 | } |
44 | |
45 | count = memory_read_from_buffer(to: val, count: bytes, ppos: &off, from: addr, available); |
46 | |
47 | memunmap(addr); |
48 | |
49 | return count; |
50 | } |
51 | |
52 | static int rmem_probe(struct platform_device *pdev) |
53 | { |
54 | struct nvmem_config config = { }; |
55 | struct device *dev = &pdev->dev; |
56 | struct reserved_mem *mem; |
57 | struct rmem *priv; |
58 | |
59 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
60 | if (!priv) |
61 | return -ENOMEM; |
62 | priv->dev = dev; |
63 | |
64 | mem = of_reserved_mem_lookup(np: dev->of_node); |
65 | if (!mem) { |
66 | dev_err(dev, "Failed to lookup reserved memory\n" ); |
67 | return -EINVAL; |
68 | } |
69 | priv->mem = mem; |
70 | |
71 | config.dev = dev; |
72 | config.priv = priv; |
73 | config.name = "rmem" ; |
74 | config.id = NVMEM_DEVID_AUTO; |
75 | config.size = mem->size; |
76 | config.reg_read = rmem_read; |
77 | |
78 | return PTR_ERR_OR_ZERO(ptr: devm_nvmem_register(dev, cfg: &config)); |
79 | } |
80 | |
81 | static const struct of_device_id rmem_match[] = { |
82 | { .compatible = "nvmem-rmem" , }, |
83 | { /* sentinel */ }, |
84 | }; |
85 | MODULE_DEVICE_TABLE(of, rmem_match); |
86 | |
87 | static struct platform_driver rmem_driver = { |
88 | .probe = rmem_probe, |
89 | .driver = { |
90 | .name = "rmem" , |
91 | .of_match_table = rmem_match, |
92 | }, |
93 | }; |
94 | module_platform_driver(rmem_driver); |
95 | |
96 | MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>" ); |
97 | MODULE_DESCRIPTION("Reserved Memory Based nvmem Driver" ); |
98 | MODULE_LICENSE("GPL" ); |
99 | |