1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl> |
4 | */ |
5 | |
6 | #include <linux/bcm47xx_nvram.h> |
7 | #include <linux/etherdevice.h> |
8 | #include <linux/if_ether.h> |
9 | #include <linux/io.h> |
10 | #include <linux/mod_devicetable.h> |
11 | #include <linux/module.h> |
12 | #include <linux/nvmem-consumer.h> |
13 | #include <linux/nvmem-provider.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/slab.h> |
17 | |
18 | #define NVRAM_MAGIC "FLSH" |
19 | |
20 | struct brcm_nvram { |
21 | struct device *dev; |
22 | void __iomem *base; |
23 | struct nvmem_cell_info *cells; |
24 | int ncells; |
25 | }; |
26 | |
27 | struct { |
28 | char [4]; |
29 | __le32 ; |
30 | __le32 ; /* 0:7 crc, 8:15 ver, 16:31 sdram_init */ |
31 | __le32 ; /* 0:15 sdram_config, 16:31 sdram_refresh */ |
32 | __le32 ; /* ncdl values for memc */ |
33 | }; |
34 | |
35 | static int brcm_nvram_read(void *context, unsigned int offset, void *val, |
36 | size_t bytes) |
37 | { |
38 | struct brcm_nvram *priv = context; |
39 | u8 *dst = val; |
40 | |
41 | while (bytes--) |
42 | *dst++ = readb(addr: priv->base + offset++); |
43 | |
44 | return 0; |
45 | } |
46 | |
47 | static int brcm_nvram_read_post_process_macaddr(void *context, const char *id, int index, |
48 | unsigned int offset, void *buf, size_t bytes) |
49 | { |
50 | u8 mac[ETH_ALEN]; |
51 | |
52 | if (bytes != 3 * ETH_ALEN - 1) |
53 | return -EINVAL; |
54 | |
55 | if (!mac_pton(s: buf, mac)) |
56 | return -EINVAL; |
57 | |
58 | if (index) |
59 | eth_addr_add(addr: mac, offset: index); |
60 | |
61 | ether_addr_copy(dst: buf, src: mac); |
62 | |
63 | return 0; |
64 | } |
65 | |
66 | static int brcm_nvram_add_cells(struct brcm_nvram *priv, uint8_t *data, |
67 | size_t len) |
68 | { |
69 | struct device *dev = priv->dev; |
70 | char *var, *value, *eq; |
71 | int idx; |
72 | |
73 | priv->ncells = 0; |
74 | for (var = data + sizeof(struct brcm_nvram_header); |
75 | var < (char *)data + len && *var; |
76 | var += strlen(var) + 1) { |
77 | priv->ncells++; |
78 | } |
79 | |
80 | priv->cells = devm_kcalloc(dev, n: priv->ncells, size: sizeof(*priv->cells), GFP_KERNEL); |
81 | if (!priv->cells) |
82 | return -ENOMEM; |
83 | |
84 | for (var = data + sizeof(struct brcm_nvram_header), idx = 0; |
85 | var < (char *)data + len && *var; |
86 | var = value + strlen(value) + 1, idx++) { |
87 | eq = strchr(var, '='); |
88 | if (!eq) |
89 | break; |
90 | *eq = '\0'; |
91 | value = eq + 1; |
92 | |
93 | priv->cells[idx].name = devm_kstrdup(dev, s: var, GFP_KERNEL); |
94 | if (!priv->cells[idx].name) |
95 | return -ENOMEM; |
96 | priv->cells[idx].offset = value - (char *)data; |
97 | priv->cells[idx].bytes = strlen(value); |
98 | priv->cells[idx].np = of_get_child_by_name(node: dev->of_node, name: priv->cells[idx].name); |
99 | if (!strcmp(var, "et0macaddr" ) || |
100 | !strcmp(var, "et1macaddr" ) || |
101 | !strcmp(var, "et2macaddr" )) { |
102 | priv->cells[idx].raw_len = strlen(value); |
103 | priv->cells[idx].bytes = ETH_ALEN; |
104 | priv->cells[idx].read_post_process = brcm_nvram_read_post_process_macaddr; |
105 | } |
106 | } |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static int brcm_nvram_parse(struct brcm_nvram *priv) |
112 | { |
113 | struct device *dev = priv->dev; |
114 | struct brcm_nvram_header ; |
115 | uint8_t *data; |
116 | size_t len; |
117 | int err; |
118 | |
119 | memcpy_fromio(&header, priv->base, sizeof(header)); |
120 | |
121 | if (memcmp(p: header.magic, NVRAM_MAGIC, size: 4)) { |
122 | dev_err(dev, "Invalid NVRAM magic\n" ); |
123 | return -EINVAL; |
124 | } |
125 | |
126 | len = le32_to_cpu(header.len); |
127 | |
128 | data = kzalloc(size: len, GFP_KERNEL); |
129 | if (!data) |
130 | return -ENOMEM; |
131 | |
132 | memcpy_fromio(data, priv->base, len); |
133 | data[len - 1] = '\0'; |
134 | |
135 | err = brcm_nvram_add_cells(priv, data, len); |
136 | if (err) { |
137 | dev_err(dev, "Failed to add cells: %d\n" , err); |
138 | return err; |
139 | } |
140 | |
141 | kfree(objp: data); |
142 | |
143 | return 0; |
144 | } |
145 | |
146 | static int brcm_nvram_probe(struct platform_device *pdev) |
147 | { |
148 | struct nvmem_config config = { |
149 | .name = "brcm-nvram" , |
150 | .reg_read = brcm_nvram_read, |
151 | }; |
152 | struct device *dev = &pdev->dev; |
153 | struct resource *res; |
154 | struct brcm_nvram *priv; |
155 | int err; |
156 | |
157 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
158 | if (!priv) |
159 | return -ENOMEM; |
160 | priv->dev = dev; |
161 | |
162 | priv->base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
163 | if (IS_ERR(ptr: priv->base)) |
164 | return PTR_ERR(ptr: priv->base); |
165 | |
166 | err = brcm_nvram_parse(priv); |
167 | if (err) |
168 | return err; |
169 | |
170 | bcm47xx_nvram_init_from_iomem(nvram_start: priv->base, res_size: resource_size(res)); |
171 | |
172 | config.dev = dev; |
173 | config.cells = priv->cells; |
174 | config.ncells = priv->ncells; |
175 | config.priv = priv; |
176 | config.size = resource_size(res); |
177 | |
178 | return PTR_ERR_OR_ZERO(ptr: devm_nvmem_register(dev, cfg: &config)); |
179 | } |
180 | |
181 | static const struct of_device_id brcm_nvram_of_match_table[] = { |
182 | { .compatible = "brcm,nvram" , }, |
183 | {}, |
184 | }; |
185 | |
186 | static struct platform_driver brcm_nvram_driver = { |
187 | .probe = brcm_nvram_probe, |
188 | .driver = { |
189 | .name = "brcm_nvram" , |
190 | .of_match_table = brcm_nvram_of_match_table, |
191 | }, |
192 | }; |
193 | |
194 | static int __init brcm_nvram_init(void) |
195 | { |
196 | return platform_driver_register(&brcm_nvram_driver); |
197 | } |
198 | |
199 | subsys_initcall_sync(brcm_nvram_init); |
200 | |
201 | MODULE_AUTHOR("Rafał Miłecki" ); |
202 | MODULE_LICENSE("GPL" ); |
203 | MODULE_DEVICE_TABLE(of, brcm_nvram_of_match_table); |
204 | |