1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * EEPROM driver for RAVE SP |
5 | * |
6 | * Copyright (C) 2018 Zodiac Inflight Innovations |
7 | * |
8 | */ |
9 | #include <linux/kernel.h> |
10 | #include <linux/mfd/rave-sp.h> |
11 | #include <linux/module.h> |
12 | #include <linux/nvmem-provider.h> |
13 | #include <linux/of.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/sizes.h> |
16 | |
17 | /** |
18 | * enum rave_sp_eeprom_access_type - Supported types of EEPROM access |
19 | * |
20 | * @RAVE_SP_EEPROM_WRITE: EEPROM write |
21 | * @RAVE_SP_EEPROM_READ: EEPROM read |
22 | */ |
23 | enum rave_sp_eeprom_access_type { |
24 | RAVE_SP_EEPROM_WRITE = 0, |
25 | RAVE_SP_EEPROM_READ = 1, |
26 | }; |
27 | |
28 | /** |
29 | * enum rave_sp_eeprom_header_size - EEPROM command header sizes |
30 | * |
31 | * @RAVE_SP_EEPROM_HEADER_SMALL: EEPROM header size for "small" devices (< 8K) |
32 | * @RAVE_SP_EEPROM_HEADER_BIG: EEPROM header size for "big" devices (> 8K) |
33 | */ |
34 | enum { |
35 | = 4U, |
36 | = 5U, |
37 | }; |
38 | #define RAVE_SP_EEPROM_HEADER_BIG |
39 | |
40 | #define RAVE_SP_EEPROM_PAGE_SIZE 32U |
41 | |
42 | /** |
43 | * struct rave_sp_eeprom_page - RAVE SP EEPROM page |
44 | * |
45 | * @type: Access type (see enum rave_sp_eeprom_access_type) |
46 | * @success: Success flag (Success = 1, Failure = 0) |
47 | * @data: Read data |
48 | * |
49 | * Note this structure corresponds to RSP_*_EEPROM payload from RAVE |
50 | * SP ICD |
51 | */ |
52 | struct rave_sp_eeprom_page { |
53 | u8 type; |
54 | u8 success; |
55 | u8 data[RAVE_SP_EEPROM_PAGE_SIZE]; |
56 | } __packed; |
57 | |
58 | /** |
59 | * struct rave_sp_eeprom - RAVE SP EEPROM device |
60 | * |
61 | * @sp: Pointer to parent RAVE SP device |
62 | * @mutex: Lock protecting access to EEPROM |
63 | * @address: EEPROM device address |
64 | * @header_size: Size of EEPROM command header for this device |
65 | * @dev: Pointer to corresponding struct device used for logging |
66 | */ |
67 | struct rave_sp_eeprom { |
68 | struct rave_sp *sp; |
69 | struct mutex mutex; |
70 | u8 address; |
71 | unsigned int ; |
72 | struct device *dev; |
73 | }; |
74 | |
75 | /** |
76 | * rave_sp_eeprom_io - Low-level part of EEPROM page access |
77 | * |
78 | * @eeprom: EEPROM device to write to |
79 | * @type: EEPROM access type (read or write) |
80 | * @idx: number of the EEPROM page |
81 | * @page: Data to write or buffer to store result (via page->data) |
82 | * |
83 | * This function does all of the low-level work required to perform a |
84 | * EEPROM access. This includes formatting correct command payload, |
85 | * sending it and checking received results. |
86 | * |
87 | * Returns zero in case of success or negative error code in |
88 | * case of failure. |
89 | */ |
90 | static int rave_sp_eeprom_io(struct rave_sp_eeprom *eeprom, |
91 | enum rave_sp_eeprom_access_type type, |
92 | u16 idx, |
93 | struct rave_sp_eeprom_page *page) |
94 | { |
95 | const bool is_write = type == RAVE_SP_EEPROM_WRITE; |
96 | const unsigned int data_size = is_write ? sizeof(page->data) : 0; |
97 | const unsigned int cmd_size = eeprom->header_size + data_size; |
98 | const unsigned int rsp_size = |
99 | is_write ? sizeof(*page) - sizeof(page->data) : sizeof(*page); |
100 | unsigned int offset = 0; |
101 | u8 cmd[RAVE_SP_EEPROM_HEADER_MAX + sizeof(page->data)]; |
102 | int ret; |
103 | |
104 | if (WARN_ON(cmd_size > sizeof(cmd))) |
105 | return -EINVAL; |
106 | |
107 | cmd[offset++] = eeprom->address; |
108 | cmd[offset++] = 0; |
109 | cmd[offset++] = type; |
110 | cmd[offset++] = idx; |
111 | |
112 | /* |
113 | * If there's still room in this command's header it means we |
114 | * are talkin to EEPROM that uses 16-bit page numbers and we |
115 | * have to specify index's MSB in payload as well. |
116 | */ |
117 | if (offset < eeprom->header_size) |
118 | cmd[offset++] = idx >> 8; |
119 | /* |
120 | * Copy our data to write to command buffer first. In case of |
121 | * a read data_size should be zero and memcpy would become a |
122 | * no-op |
123 | */ |
124 | memcpy(&cmd[offset], page->data, data_size); |
125 | |
126 | ret = rave_sp_exec(sp: eeprom->sp, data: cmd, data_size: cmd_size, reply_data: page, reply_data_size: rsp_size); |
127 | if (ret) |
128 | return ret; |
129 | |
130 | if (page->type != type) |
131 | return -EPROTO; |
132 | |
133 | if (!page->success) |
134 | return -EIO; |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | /** |
140 | * rave_sp_eeprom_page_access - Access single EEPROM page |
141 | * |
142 | * @eeprom: EEPROM device to access |
143 | * @type: Access type to perform (read or write) |
144 | * @offset: Offset within EEPROM to access |
145 | * @data: Data buffer |
146 | * @data_len: Size of the data buffer |
147 | * |
148 | * This function performs a generic access to a single page or a |
149 | * portion thereof. Requested access MUST NOT cross the EEPROM page |
150 | * boundary. |
151 | * |
152 | * Returns zero in case of success or negative error code in |
153 | * case of failure. |
154 | */ |
155 | static int |
156 | rave_sp_eeprom_page_access(struct rave_sp_eeprom *eeprom, |
157 | enum rave_sp_eeprom_access_type type, |
158 | unsigned int offset, u8 *data, |
159 | size_t data_len) |
160 | { |
161 | const unsigned int page_offset = offset % RAVE_SP_EEPROM_PAGE_SIZE; |
162 | const unsigned int page_nr = offset / RAVE_SP_EEPROM_PAGE_SIZE; |
163 | struct rave_sp_eeprom_page page; |
164 | int ret; |
165 | |
166 | /* |
167 | * This function will not work if data access we've been asked |
168 | * to do is crossing EEPROM page boundary. Normally this |
169 | * should never happen and getting here would indicate a bug |
170 | * in the code. |
171 | */ |
172 | if (WARN_ON(data_len > sizeof(page.data) - page_offset)) |
173 | return -EINVAL; |
174 | |
175 | if (type == RAVE_SP_EEPROM_WRITE) { |
176 | /* |
177 | * If doing a partial write we need to do a read first |
178 | * to fill the rest of the page with correct data. |
179 | */ |
180 | if (data_len < RAVE_SP_EEPROM_PAGE_SIZE) { |
181 | ret = rave_sp_eeprom_io(eeprom, type: RAVE_SP_EEPROM_READ, |
182 | idx: page_nr, page: &page); |
183 | if (ret) |
184 | return ret; |
185 | } |
186 | |
187 | memcpy(&page.data[page_offset], data, data_len); |
188 | } |
189 | |
190 | ret = rave_sp_eeprom_io(eeprom, type, idx: page_nr, page: &page); |
191 | if (ret) |
192 | return ret; |
193 | |
194 | /* |
195 | * Since we receive the result of the read via 'page.data' |
196 | * buffer we need to copy that to 'data' |
197 | */ |
198 | if (type == RAVE_SP_EEPROM_READ) |
199 | memcpy(data, &page.data[page_offset], data_len); |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | /** |
205 | * rave_sp_eeprom_access - Access EEPROM data |
206 | * |
207 | * @eeprom: EEPROM device to access |
208 | * @type: Access type to perform (read or write) |
209 | * @offset: Offset within EEPROM to access |
210 | * @data: Data buffer |
211 | * @data_len: Size of the data buffer |
212 | * |
213 | * This function performs a generic access (either read or write) at |
214 | * arbitrary offset (not necessary page aligned) of arbitrary length |
215 | * (is not constrained by EEPROM page size). |
216 | * |
217 | * Returns zero in case of success or negative error code in case of |
218 | * failure. |
219 | */ |
220 | static int rave_sp_eeprom_access(struct rave_sp_eeprom *eeprom, |
221 | enum rave_sp_eeprom_access_type type, |
222 | unsigned int offset, u8 *data, |
223 | unsigned int data_len) |
224 | { |
225 | unsigned int residue; |
226 | unsigned int chunk; |
227 | unsigned int head; |
228 | int ret; |
229 | |
230 | mutex_lock(&eeprom->mutex); |
231 | |
232 | head = offset % RAVE_SP_EEPROM_PAGE_SIZE; |
233 | residue = data_len; |
234 | |
235 | do { |
236 | /* |
237 | * First iteration, if we are doing an access that is |
238 | * not 32-byte aligned, we need to access only data up |
239 | * to a page boundary to avoid corssing it in |
240 | * rave_sp_eeprom_page_access() |
241 | */ |
242 | if (unlikely(head)) { |
243 | chunk = RAVE_SP_EEPROM_PAGE_SIZE - head; |
244 | /* |
245 | * This can only happen once per |
246 | * rave_sp_eeprom_access() call, so we set |
247 | * head to zero to process all the other |
248 | * iterations normally. |
249 | */ |
250 | head = 0; |
251 | } else { |
252 | chunk = RAVE_SP_EEPROM_PAGE_SIZE; |
253 | } |
254 | |
255 | /* |
256 | * We should never read more that 'residue' bytes |
257 | */ |
258 | chunk = min(chunk, residue); |
259 | ret = rave_sp_eeprom_page_access(eeprom, type, offset, |
260 | data, data_len: chunk); |
261 | if (ret) |
262 | goto out; |
263 | |
264 | residue -= chunk; |
265 | offset += chunk; |
266 | data += chunk; |
267 | } while (residue); |
268 | out: |
269 | mutex_unlock(lock: &eeprom->mutex); |
270 | return ret; |
271 | } |
272 | |
273 | static int rave_sp_eeprom_reg_read(void *eeprom, unsigned int offset, |
274 | void *val, size_t bytes) |
275 | { |
276 | return rave_sp_eeprom_access(eeprom, type: RAVE_SP_EEPROM_READ, |
277 | offset, data: val, data_len: bytes); |
278 | } |
279 | |
280 | static int rave_sp_eeprom_reg_write(void *eeprom, unsigned int offset, |
281 | void *val, size_t bytes) |
282 | { |
283 | return rave_sp_eeprom_access(eeprom, type: RAVE_SP_EEPROM_WRITE, |
284 | offset, data: val, data_len: bytes); |
285 | } |
286 | |
287 | static int rave_sp_eeprom_probe(struct platform_device *pdev) |
288 | { |
289 | struct device *dev = &pdev->dev; |
290 | struct rave_sp *sp = dev_get_drvdata(dev: dev->parent); |
291 | struct device_node *np = dev->of_node; |
292 | struct nvmem_config config = { 0 }; |
293 | struct rave_sp_eeprom *eeprom; |
294 | struct nvmem_device *nvmem; |
295 | u32 reg[2], size; |
296 | |
297 | if (of_property_read_u32_array(np, propname: "reg" , out_values: reg, ARRAY_SIZE(reg))) { |
298 | dev_err(dev, "Failed to parse \"reg\" property\n" ); |
299 | return -EINVAL; |
300 | } |
301 | |
302 | size = reg[1]; |
303 | /* |
304 | * Per ICD, we have no more than 2 bytes to specify EEPROM |
305 | * page. |
306 | */ |
307 | if (size > U16_MAX * RAVE_SP_EEPROM_PAGE_SIZE) { |
308 | dev_err(dev, "Specified size is too big\n" ); |
309 | return -EINVAL; |
310 | } |
311 | |
312 | eeprom = devm_kzalloc(dev, size: sizeof(*eeprom), GFP_KERNEL); |
313 | if (!eeprom) |
314 | return -ENOMEM; |
315 | |
316 | eeprom->address = reg[0]; |
317 | eeprom->sp = sp; |
318 | eeprom->dev = dev; |
319 | |
320 | if (size > SZ_8K) |
321 | eeprom->header_size = RAVE_SP_EEPROM_HEADER_BIG; |
322 | else |
323 | eeprom->header_size = RAVE_SP_EEPROM_HEADER_SMALL; |
324 | |
325 | mutex_init(&eeprom->mutex); |
326 | |
327 | config.id = -1; |
328 | of_property_read_string(np, propname: "zii,eeprom-name" , out_string: &config.name); |
329 | config.priv = eeprom; |
330 | config.dev = dev; |
331 | config.add_legacy_fixed_of_cells = true; |
332 | config.size = size; |
333 | config.reg_read = rave_sp_eeprom_reg_read; |
334 | config.reg_write = rave_sp_eeprom_reg_write; |
335 | config.word_size = 1; |
336 | config.stride = 1; |
337 | |
338 | nvmem = devm_nvmem_register(dev, cfg: &config); |
339 | |
340 | return PTR_ERR_OR_ZERO(ptr: nvmem); |
341 | } |
342 | |
343 | static const struct of_device_id rave_sp_eeprom_of_match[] = { |
344 | { .compatible = "zii,rave-sp-eeprom" }, |
345 | {} |
346 | }; |
347 | MODULE_DEVICE_TABLE(of, rave_sp_eeprom_of_match); |
348 | |
349 | static struct platform_driver rave_sp_eeprom_driver = { |
350 | .probe = rave_sp_eeprom_probe, |
351 | .driver = { |
352 | .name = KBUILD_MODNAME, |
353 | .of_match_table = rave_sp_eeprom_of_match, |
354 | }, |
355 | }; |
356 | module_platform_driver(rave_sp_eeprom_driver); |
357 | |
358 | MODULE_LICENSE("GPL" ); |
359 | MODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>" ); |
360 | MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>" ); |
361 | MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>" ); |
362 | MODULE_DESCRIPTION("RAVE SP EEPROM driver" ); |
363 | |