1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | #define pr_fmt(fmt) "of_pmem: " fmt |
4 | |
5 | #include <linux/of.h> |
6 | #include <linux/libnvdimm.h> |
7 | #include <linux/module.h> |
8 | #include <linux/ioport.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/slab.h> |
11 | |
12 | struct of_pmem_private { |
13 | struct nvdimm_bus_descriptor bus_desc; |
14 | struct nvdimm_bus *bus; |
15 | }; |
16 | |
17 | static int of_pmem_region_probe(struct platform_device *pdev) |
18 | { |
19 | struct of_pmem_private *priv; |
20 | struct device_node *np; |
21 | struct nvdimm_bus *bus; |
22 | bool is_volatile; |
23 | int i; |
24 | |
25 | np = dev_of_node(dev: &pdev->dev); |
26 | if (!np) |
27 | return -ENXIO; |
28 | |
29 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
30 | if (!priv) |
31 | return -ENOMEM; |
32 | |
33 | priv->bus_desc.provider_name = devm_kstrdup(dev: &pdev->dev, s: pdev->name, |
34 | GFP_KERNEL); |
35 | if (!priv->bus_desc.provider_name) { |
36 | kfree(objp: priv); |
37 | return -ENOMEM; |
38 | } |
39 | |
40 | priv->bus_desc.module = THIS_MODULE; |
41 | priv->bus_desc.of_node = np; |
42 | |
43 | priv->bus = bus = nvdimm_bus_register(parent: &pdev->dev, nfit_desc: &priv->bus_desc); |
44 | if (!bus) { |
45 | kfree(objp: priv); |
46 | return -ENODEV; |
47 | } |
48 | platform_set_drvdata(pdev, data: priv); |
49 | |
50 | is_volatile = !!of_find_property(np, name: "volatile" , NULL); |
51 | dev_dbg(&pdev->dev, "Registering %s regions from %pOF\n" , |
52 | is_volatile ? "volatile" : "non-volatile" , np); |
53 | |
54 | for (i = 0; i < pdev->num_resources; i++) { |
55 | struct nd_region_desc ndr_desc; |
56 | struct nd_region *region; |
57 | |
58 | /* |
59 | * NB: libnvdimm copies the data from ndr_desc into it's own |
60 | * structures so passing a stack pointer is fine. |
61 | */ |
62 | memset(&ndr_desc, 0, sizeof(ndr_desc)); |
63 | ndr_desc.numa_node = dev_to_node(dev: &pdev->dev); |
64 | ndr_desc.target_node = ndr_desc.numa_node; |
65 | ndr_desc.res = &pdev->resource[i]; |
66 | ndr_desc.of_node = np; |
67 | set_bit(nr: ND_REGION_PAGEMAP, addr: &ndr_desc.flags); |
68 | |
69 | if (is_volatile) |
70 | region = nvdimm_volatile_region_create(nvdimm_bus: bus, ndr_desc: &ndr_desc); |
71 | else { |
72 | set_bit(nr: ND_REGION_PERSIST_MEMCTRL, addr: &ndr_desc.flags); |
73 | region = nvdimm_pmem_region_create(nvdimm_bus: bus, ndr_desc: &ndr_desc); |
74 | } |
75 | |
76 | if (!region) |
77 | dev_warn(&pdev->dev, "Unable to register region %pR from %pOF\n" , |
78 | ndr_desc.res, np); |
79 | else |
80 | dev_dbg(&pdev->dev, "Registered region %pR from %pOF\n" , |
81 | ndr_desc.res, np); |
82 | } |
83 | |
84 | return 0; |
85 | } |
86 | |
87 | static int of_pmem_region_remove(struct platform_device *pdev) |
88 | { |
89 | struct of_pmem_private *priv = platform_get_drvdata(pdev); |
90 | |
91 | nvdimm_bus_unregister(nvdimm_bus: priv->bus); |
92 | kfree(objp: priv); |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | static const struct of_device_id of_pmem_region_match[] = { |
98 | { .compatible = "pmem-region" }, |
99 | { .compatible = "pmem-region-v2" }, |
100 | { }, |
101 | }; |
102 | |
103 | static struct platform_driver of_pmem_region_driver = { |
104 | .probe = of_pmem_region_probe, |
105 | .remove = of_pmem_region_remove, |
106 | .driver = { |
107 | .name = "of_pmem" , |
108 | .of_match_table = of_pmem_region_match, |
109 | }, |
110 | }; |
111 | |
112 | module_platform_driver(of_pmem_region_driver); |
113 | MODULE_DEVICE_TABLE(of, of_pmem_region_match); |
114 | MODULE_LICENSE("GPL" ); |
115 | MODULE_AUTHOR("IBM Corporation" ); |
116 | |