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 | /** |
21 | * struct brcm_nvram - driver state internal struct |
22 | * |
23 | * @dev: NVMEM device pointer |
24 | * @nvmem_size: Size of the whole space available for NVRAM |
25 | * @data: NVRAM data copy stored to avoid poking underlaying flash controller |
26 | * @data_len: NVRAM data size |
27 | * @padding_byte: Padding value used to fill remaining space |
28 | * @cells: Array of discovered NVMEM cells |
29 | * @ncells: Number of elements in cells |
30 | */ |
31 | struct brcm_nvram { |
32 | struct device *dev; |
33 | size_t nvmem_size; |
34 | uint8_t *data; |
35 | size_t data_len; |
36 | uint8_t padding_byte; |
37 | struct nvmem_cell_info *cells; |
38 | int ncells; |
39 | }; |
40 | |
41 | struct { |
42 | char [4]; |
43 | __le32 ; |
44 | __le32 ; /* 0:7 crc, 8:15 ver, 16:31 sdram_init */ |
45 | __le32 ; /* 0:15 sdram_config, 16:31 sdram_refresh */ |
46 | __le32 ; /* ncdl values for memc */ |
47 | }; |
48 | |
49 | static int brcm_nvram_read(void *context, unsigned int offset, void *val, |
50 | size_t bytes) |
51 | { |
52 | struct brcm_nvram *priv = context; |
53 | size_t to_copy; |
54 | |
55 | if (offset + bytes > priv->data_len) |
56 | to_copy = max_t(ssize_t, (ssize_t)priv->data_len - offset, 0); |
57 | else |
58 | to_copy = bytes; |
59 | |
60 | memcpy(val, priv->data + offset, to_copy); |
61 | |
62 | memset((uint8_t *)val + to_copy, priv->padding_byte, bytes - to_copy); |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static int brcm_nvram_copy_data(struct brcm_nvram *priv, struct platform_device *pdev) |
68 | { |
69 | struct resource *res; |
70 | void __iomem *base; |
71 | |
72 | base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
73 | if (IS_ERR(ptr: base)) |
74 | return PTR_ERR(ptr: base); |
75 | |
76 | priv->nvmem_size = resource_size(res); |
77 | |
78 | priv->padding_byte = readb(addr: base + priv->nvmem_size - 1); |
79 | for (priv->data_len = priv->nvmem_size; |
80 | priv->data_len; |
81 | priv->data_len--) { |
82 | if (readb(addr: base + priv->data_len - 1) != priv->padding_byte) |
83 | break; |
84 | } |
85 | WARN(priv->data_len > SZ_128K, "Unexpected (big) NVRAM size: %zu B\n" , priv->data_len); |
86 | |
87 | priv->data = devm_kzalloc(dev: priv->dev, size: priv->data_len, GFP_KERNEL); |
88 | if (!priv->data) |
89 | return -ENOMEM; |
90 | |
91 | memcpy_fromio(priv->data, base, priv->data_len); |
92 | |
93 | bcm47xx_nvram_init_from_iomem(nvram_start: base, res_size: priv->data_len); |
94 | |
95 | return 0; |
96 | } |
97 | |
98 | static int brcm_nvram_read_post_process_macaddr(void *context, const char *id, int index, |
99 | unsigned int offset, void *buf, size_t bytes) |
100 | { |
101 | u8 mac[ETH_ALEN]; |
102 | |
103 | if (bytes != 3 * ETH_ALEN - 1) |
104 | return -EINVAL; |
105 | |
106 | if (!mac_pton(s: buf, mac)) |
107 | return -EINVAL; |
108 | |
109 | if (index) |
110 | eth_addr_add(addr: mac, offset: index); |
111 | |
112 | ether_addr_copy(dst: buf, src: mac); |
113 | |
114 | return 0; |
115 | } |
116 | |
117 | static int brcm_nvram_add_cells(struct brcm_nvram *priv, uint8_t *data, |
118 | size_t len) |
119 | { |
120 | struct device *dev = priv->dev; |
121 | char *var, *value; |
122 | uint8_t tmp; |
123 | int idx; |
124 | int err = 0; |
125 | |
126 | tmp = priv->data[len - 1]; |
127 | priv->data[len - 1] = '\0'; |
128 | |
129 | priv->ncells = 0; |
130 | for (var = data + sizeof(struct brcm_nvram_header); |
131 | var < (char *)data + len && *var; |
132 | var += strlen(var) + 1) { |
133 | priv->ncells++; |
134 | } |
135 | |
136 | priv->cells = devm_kcalloc(dev, n: priv->ncells, size: sizeof(*priv->cells), GFP_KERNEL); |
137 | if (!priv->cells) { |
138 | err = -ENOMEM; |
139 | goto out; |
140 | } |
141 | |
142 | for (var = data + sizeof(struct brcm_nvram_header), idx = 0; |
143 | var < (char *)data + len && *var; |
144 | var = value + strlen(value) + 1, idx++) { |
145 | char *eq, *name; |
146 | |
147 | eq = strchr(var, '='); |
148 | if (!eq) |
149 | break; |
150 | *eq = '\0'; |
151 | name = devm_kstrdup(dev, s: var, GFP_KERNEL); |
152 | *eq = '='; |
153 | if (!name) { |
154 | err = -ENOMEM; |
155 | goto out; |
156 | } |
157 | value = eq + 1; |
158 | |
159 | priv->cells[idx].name = name; |
160 | priv->cells[idx].offset = value - (char *)data; |
161 | priv->cells[idx].bytes = strlen(value); |
162 | priv->cells[idx].np = of_get_child_by_name(node: dev->of_node, name: priv->cells[idx].name); |
163 | if (!strcmp(name, "et0macaddr" ) || |
164 | !strcmp(name, "et1macaddr" ) || |
165 | !strcmp(name, "et2macaddr" )) { |
166 | priv->cells[idx].raw_len = strlen(value); |
167 | priv->cells[idx].bytes = ETH_ALEN; |
168 | priv->cells[idx].read_post_process = brcm_nvram_read_post_process_macaddr; |
169 | } |
170 | } |
171 | |
172 | out: |
173 | priv->data[len - 1] = tmp; |
174 | return err; |
175 | } |
176 | |
177 | static int brcm_nvram_parse(struct brcm_nvram *priv) |
178 | { |
179 | struct brcm_nvram_header * = (struct brcm_nvram_header *)priv->data; |
180 | struct device *dev = priv->dev; |
181 | size_t len; |
182 | int err; |
183 | |
184 | if (memcmp(p: header->magic, NVRAM_MAGIC, size: 4)) { |
185 | dev_err(dev, "Invalid NVRAM magic\n" ); |
186 | return -EINVAL; |
187 | } |
188 | |
189 | len = le32_to_cpu(header->len); |
190 | if (len > priv->nvmem_size) { |
191 | dev_err(dev, "NVRAM length (%zd) exceeds mapped size (%zd)\n" , len, |
192 | priv->nvmem_size); |
193 | return -EINVAL; |
194 | } |
195 | |
196 | err = brcm_nvram_add_cells(priv, data: priv->data, len); |
197 | if (err) |
198 | dev_err(dev, "Failed to add cells: %d\n" , err); |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | static int brcm_nvram_probe(struct platform_device *pdev) |
204 | { |
205 | struct nvmem_config config = { |
206 | .name = "brcm-nvram" , |
207 | .reg_read = brcm_nvram_read, |
208 | }; |
209 | struct device *dev = &pdev->dev; |
210 | struct brcm_nvram *priv; |
211 | int err; |
212 | |
213 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
214 | if (!priv) |
215 | return -ENOMEM; |
216 | priv->dev = dev; |
217 | |
218 | err = brcm_nvram_copy_data(priv, pdev); |
219 | if (err) |
220 | return err; |
221 | |
222 | err = brcm_nvram_parse(priv); |
223 | if (err) |
224 | return err; |
225 | |
226 | config.dev = dev; |
227 | config.cells = priv->cells; |
228 | config.ncells = priv->ncells; |
229 | config.priv = priv; |
230 | config.size = priv->nvmem_size; |
231 | |
232 | return PTR_ERR_OR_ZERO(ptr: devm_nvmem_register(dev, cfg: &config)); |
233 | } |
234 | |
235 | static const struct of_device_id brcm_nvram_of_match_table[] = { |
236 | { .compatible = "brcm,nvram" , }, |
237 | {}, |
238 | }; |
239 | |
240 | static struct platform_driver brcm_nvram_driver = { |
241 | .probe = brcm_nvram_probe, |
242 | .driver = { |
243 | .name = "brcm_nvram" , |
244 | .of_match_table = brcm_nvram_of_match_table, |
245 | }, |
246 | }; |
247 | |
248 | static int __init brcm_nvram_init(void) |
249 | { |
250 | return platform_driver_register(&brcm_nvram_driver); |
251 | } |
252 | |
253 | subsys_initcall_sync(brcm_nvram_init); |
254 | |
255 | MODULE_AUTHOR("Rafał Miłecki" ); |
256 | MODULE_LICENSE("GPL" ); |
257 | MODULE_DEVICE_TABLE(of, brcm_nvram_of_match_table); |
258 | |