Commit | Line | Data |
---|---|---|
680ca6dc CB |
1 | /* |
2 | * SCOM FSI Client device driver | |
3 | * | |
4 | * Copyright (C) IBM Corporation 2016 | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | */ | |
15 | ||
16 | #include <linux/fsi.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/cdev.h> | |
19 | #include <linux/delay.h> | |
20 | #include <linux/fs.h> | |
21 | #include <linux/uaccess.h> | |
22 | #include <linux/slab.h> | |
680ca6dc | 23 | #include <linux/list.h> |
680ca6dc | 24 | |
6b293258 BH |
25 | #include <uapi/linux/fsi.h> |
26 | ||
680ca6dc CB |
27 | #define FSI_ENGID_SCOM 0x5 |
28 | ||
680ca6dc CB |
29 | /* SCOM engine register set */ |
30 | #define SCOM_DATA0_REG 0x00 | |
31 | #define SCOM_DATA1_REG 0x04 | |
32 | #define SCOM_CMD_REG 0x08 | |
f1433044 BH |
33 | #define SCOM_FSI2PIB_RESET_REG 0x18 |
34 | #define SCOM_STATUS_REG 0x1C /* Read */ | |
35 | #define SCOM_PIB_RESET_REG 0x1C /* Write */ | |
680ca6dc | 36 | |
f1433044 | 37 | /* Command register */ |
680ca6dc | 38 | #define SCOM_WRITE_CMD 0x80000000 |
f1433044 BH |
39 | #define SCOM_READ_CMD 0x00000000 |
40 | ||
41 | /* Status register bits */ | |
42 | #define SCOM_STATUS_ERR_SUMMARY 0x80000000 | |
43 | #define SCOM_STATUS_PROTECTION 0x01000000 | |
6b293258 | 44 | #define SCOM_STATUS_PARITY 0x04000000 |
f1433044 BH |
45 | #define SCOM_STATUS_PIB_ABORT 0x00100000 |
46 | #define SCOM_STATUS_PIB_RESP_MASK 0x00007000 | |
47 | #define SCOM_STATUS_PIB_RESP_SHIFT 12 | |
48 | ||
49 | #define SCOM_STATUS_ANY_ERR (SCOM_STATUS_ERR_SUMMARY | \ | |
50 | SCOM_STATUS_PROTECTION | \ | |
6b293258 | 51 | SCOM_STATUS_PARITY | \ |
f1433044 BH |
52 | SCOM_STATUS_PIB_ABORT | \ |
53 | SCOM_STATUS_PIB_RESP_MASK) | |
6b293258 BH |
54 | /* SCOM address encodings */ |
55 | #define XSCOM_ADDR_IND_FLAG BIT_ULL(63) | |
56 | #define XSCOM_ADDR_INF_FORM1 BIT_ULL(60) | |
57 | ||
58 | /* SCOM indirect stuff */ | |
59 | #define XSCOM_ADDR_DIRECT_PART 0x7fffffffull | |
60 | #define XSCOM_ADDR_INDIRECT_PART 0x000fffff00000000ull | |
61 | #define XSCOM_DATA_IND_READ BIT_ULL(63) | |
62 | #define XSCOM_DATA_IND_COMPLETE BIT_ULL(31) | |
63 | #define XSCOM_DATA_IND_ERR_MASK 0x70000000ull | |
64 | #define XSCOM_DATA_IND_ERR_SHIFT 28 | |
65 | #define XSCOM_DATA_IND_DATA 0x0000ffffull | |
66 | #define XSCOM_DATA_IND_FORM1_DATA 0x000fffffffffffffull | |
67 | #define XSCOM_ADDR_FORM1_LOW 0x000ffffffffull | |
68 | #define XSCOM_ADDR_FORM1_HI 0xfff00000000ull | |
69 | #define XSCOM_ADDR_FORM1_HI_SHIFT 20 | |
70 | ||
71 | /* Retries */ | |
72 | #define SCOM_MAX_RETRIES 100 /* Retries on busy */ | |
73 | #define SCOM_MAX_IND_RETRIES 10 /* Retries indirect not ready */ | |
680ca6dc CB |
74 | |
75 | struct scom_device { | |
76 | struct list_head link; | |
77 | struct fsi_device *fsi_dev; | |
d8f45876 BH |
78 | struct device dev; |
79 | struct cdev cdev; | |
162c3946 | 80 | struct mutex lock; |
d8f45876 | 81 | bool dead; |
680ca6dc CB |
82 | }; |
83 | ||
6b293258 BH |
84 | static int __put_scom(struct scom_device *scom_dev, uint64_t value, |
85 | uint32_t addr, uint32_t *status) | |
680ca6dc | 86 | { |
6b293258 | 87 | __be32 data, raw_status; |
680ca6dc | 88 | int rc; |
680ca6dc | 89 | |
680ca6dc CB |
90 | data = cpu_to_be32((value >> 32) & 0xffffffff); |
91 | rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, | |
92 | sizeof(uint32_t)); | |
93 | if (rc) | |
6b293258 | 94 | return rc; |
680ca6dc CB |
95 | |
96 | data = cpu_to_be32(value & 0xffffffff); | |
97 | rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, | |
98 | sizeof(uint32_t)); | |
99 | if (rc) | |
6b293258 | 100 | return rc; |
680ca6dc CB |
101 | |
102 | data = cpu_to_be32(SCOM_WRITE_CMD | addr); | |
162c3946 | 103 | rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, |
680ca6dc | 104 | sizeof(uint32_t)); |
6b293258 BH |
105 | if (rc) |
106 | return rc; | |
107 | rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status, | |
108 | sizeof(uint32_t)); | |
109 | if (rc) | |
110 | return rc; | |
111 | *status = be32_to_cpu(raw_status); | |
112 | ||
113 | return 0; | |
680ca6dc CB |
114 | } |
115 | ||
6b293258 BH |
116 | static int __get_scom(struct scom_device *scom_dev, uint64_t *value, |
117 | uint32_t addr, uint32_t *status) | |
680ca6dc | 118 | { |
6b293258 | 119 | __be32 data, raw_status; |
680ca6dc CB |
120 | int rc; |
121 | ||
162c3946 | 122 | |
680ca6dc | 123 | *value = 0ULL; |
f1433044 | 124 | data = cpu_to_be32(SCOM_READ_CMD | addr); |
680ca6dc CB |
125 | rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, |
126 | sizeof(uint32_t)); | |
127 | if (rc) | |
6b293258 BH |
128 | return rc; |
129 | rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status, | |
130 | sizeof(uint32_t)); | |
131 | if (rc) | |
132 | return rc; | |
680ca6dc | 133 | |
6b293258 BH |
134 | /* |
135 | * Read the data registers even on error, so we don't have | |
136 | * to interpret the status register here. | |
137 | */ | |
138 | rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, | |
680ca6dc CB |
139 | sizeof(uint32_t)); |
140 | if (rc) | |
6b293258 BH |
141 | return rc; |
142 | *value |= (uint64_t)be32_to_cpu(data) << 32; | |
143 | rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, | |
680ca6dc CB |
144 | sizeof(uint32_t)); |
145 | if (rc) | |
6b293258 BH |
146 | return rc; |
147 | *value |= be32_to_cpu(data); | |
148 | *status = be32_to_cpu(raw_status); | |
149 | ||
150 | return rc; | |
151 | } | |
152 | ||
153 | static int put_indirect_scom_form0(struct scom_device *scom, uint64_t value, | |
154 | uint64_t addr, uint32_t *status) | |
155 | { | |
156 | uint64_t ind_data, ind_addr; | |
157 | int rc, retries, err = 0; | |
158 | ||
159 | if (value & ~XSCOM_DATA_IND_DATA) | |
160 | return -EINVAL; | |
161 | ||
162 | ind_addr = addr & XSCOM_ADDR_DIRECT_PART; | |
163 | ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | value; | |
164 | rc = __put_scom(scom, ind_data, ind_addr, status); | |
165 | if (rc || (*status & SCOM_STATUS_ANY_ERR)) | |
166 | return rc; | |
167 | ||
168 | for (retries = 0; retries < SCOM_MAX_IND_RETRIES; retries++) { | |
169 | rc = __get_scom(scom, &ind_data, addr, status); | |
170 | if (rc || (*status & SCOM_STATUS_ANY_ERR)) | |
171 | return rc; | |
172 | ||
173 | err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT; | |
174 | *status = err << SCOM_STATUS_PIB_RESP_SHIFT; | |
175 | if ((ind_data & XSCOM_DATA_IND_COMPLETE) || (err != SCOM_PIB_BLOCKED)) | |
176 | return 0; | |
177 | ||
178 | msleep(1); | |
179 | } | |
180 | return rc; | |
181 | } | |
182 | ||
183 | static int put_indirect_scom_form1(struct scom_device *scom, uint64_t value, | |
184 | uint64_t addr, uint32_t *status) | |
185 | { | |
186 | uint64_t ind_data, ind_addr; | |
187 | ||
188 | if (value & ~XSCOM_DATA_IND_FORM1_DATA) | |
189 | return -EINVAL; | |
190 | ||
191 | ind_addr = addr & XSCOM_ADDR_FORM1_LOW; | |
192 | ind_data = value | (addr & XSCOM_ADDR_FORM1_HI) << XSCOM_ADDR_FORM1_HI_SHIFT; | |
193 | return __put_scom(scom, ind_data, ind_addr, status); | |
194 | } | |
195 | ||
196 | static int get_indirect_scom_form0(struct scom_device *scom, uint64_t *value, | |
197 | uint64_t addr, uint32_t *status) | |
198 | { | |
199 | uint64_t ind_data, ind_addr; | |
200 | int rc, retries, err = 0; | |
201 | ||
202 | ind_addr = addr & XSCOM_ADDR_DIRECT_PART; | |
203 | ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | XSCOM_DATA_IND_READ; | |
204 | rc = __put_scom(scom, ind_data, ind_addr, status); | |
205 | if (rc || (*status & SCOM_STATUS_ANY_ERR)) | |
206 | return rc; | |
207 | ||
208 | for (retries = 0; retries < SCOM_MAX_IND_RETRIES; retries++) { | |
209 | rc = __get_scom(scom, &ind_data, addr, status); | |
210 | if (rc || (*status & SCOM_STATUS_ANY_ERR)) | |
211 | return rc; | |
212 | ||
213 | err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT; | |
214 | *status = err << SCOM_STATUS_PIB_RESP_SHIFT; | |
215 | *value = ind_data & XSCOM_DATA_IND_DATA; | |
216 | ||
217 | if ((ind_data & XSCOM_DATA_IND_COMPLETE) || (err != SCOM_PIB_BLOCKED)) | |
218 | return 0; | |
219 | ||
220 | msleep(1); | |
221 | } | |
222 | return rc; | |
223 | } | |
224 | ||
225 | static int raw_put_scom(struct scom_device *scom, uint64_t value, | |
226 | uint64_t addr, uint32_t *status) | |
227 | { | |
228 | if (addr & XSCOM_ADDR_IND_FLAG) { | |
229 | if (addr & XSCOM_ADDR_INF_FORM1) | |
230 | return put_indirect_scom_form1(scom, value, addr, status); | |
231 | else | |
232 | return put_indirect_scom_form0(scom, value, addr, status); | |
233 | } else | |
234 | return __put_scom(scom, value, addr, status); | |
235 | } | |
236 | ||
237 | static int raw_get_scom(struct scom_device *scom, uint64_t *value, | |
238 | uint64_t addr, uint32_t *status) | |
239 | { | |
240 | if (addr & XSCOM_ADDR_IND_FLAG) { | |
241 | if (addr & XSCOM_ADDR_INF_FORM1) | |
242 | return -ENXIO; | |
243 | return get_indirect_scom_form0(scom, value, addr, status); | |
244 | } else | |
245 | return __get_scom(scom, value, addr, status); | |
246 | } | |
247 | ||
248 | static int handle_fsi2pib_status(struct scom_device *scom, uint32_t status) | |
249 | { | |
250 | uint32_t dummy = -1; | |
251 | ||
252 | if (status & SCOM_STATUS_PROTECTION) | |
253 | return -EPERM; | |
254 | if (status & SCOM_STATUS_PARITY) { | |
255 | fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, | |
256 | sizeof(uint32_t)); | |
257 | return -EIO; | |
258 | } | |
259 | /* Return -EBUSY on PIB abort to force a retry */ | |
260 | if (status & SCOM_STATUS_PIB_ABORT) | |
261 | return -EBUSY; | |
262 | if (status & SCOM_STATUS_ERR_SUMMARY) { | |
263 | fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, | |
264 | sizeof(uint32_t)); | |
265 | return -EIO; | |
266 | } | |
267 | return 0; | |
268 | } | |
269 | ||
270 | static int handle_pib_status(struct scom_device *scom, uint8_t status) | |
271 | { | |
272 | uint32_t dummy = -1; | |
273 | ||
274 | if (status == SCOM_PIB_SUCCESS) | |
275 | return 0; | |
276 | if (status == SCOM_PIB_BLOCKED) | |
277 | return -EBUSY; | |
278 | ||
279 | /* Reset the bridge */ | |
280 | fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, | |
281 | sizeof(uint32_t)); | |
282 | ||
283 | switch(status) { | |
284 | case SCOM_PIB_OFFLINE: | |
285 | return -ENODEV; | |
286 | case SCOM_PIB_BAD_ADDR: | |
287 | return -ENXIO; | |
288 | case SCOM_PIB_TIMEOUT: | |
289 | return -ETIMEDOUT; | |
290 | case SCOM_PIB_PARTIAL: | |
291 | case SCOM_PIB_CLK_ERR: | |
292 | case SCOM_PIB_PARITY_ERR: | |
293 | default: | |
294 | return -EIO; | |
295 | } | |
296 | } | |
297 | ||
298 | static int put_scom(struct scom_device *scom, uint64_t value, | |
299 | uint64_t addr) | |
300 | { | |
301 | uint32_t status, dummy = -1; | |
302 | int rc, retries; | |
303 | ||
304 | for (retries = 0; retries < SCOM_MAX_RETRIES; retries++) { | |
305 | rc = raw_put_scom(scom, value, addr, &status); | |
306 | if (rc) { | |
307 | /* Try resetting the bridge if FSI fails */ | |
308 | if (rc != -ENODEV && retries == 0) { | |
309 | fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, | |
310 | &dummy, sizeof(uint32_t)); | |
311 | rc = -EBUSY; | |
312 | } else | |
313 | return rc; | |
314 | } else | |
315 | rc = handle_fsi2pib_status(scom, status); | |
316 | if (rc && rc != -EBUSY) | |
317 | break; | |
318 | if (rc == 0) { | |
319 | rc = handle_pib_status(scom, | |
320 | (status & SCOM_STATUS_PIB_RESP_MASK) | |
321 | >> SCOM_STATUS_PIB_RESP_SHIFT); | |
322 | if (rc && rc != -EBUSY) | |
323 | break; | |
324 | } | |
325 | if (rc == 0) | |
326 | break; | |
327 | msleep(1); | |
328 | } | |
329 | return rc; | |
330 | } | |
680ca6dc | 331 | |
6b293258 BH |
332 | static int get_scom(struct scom_device *scom, uint64_t *value, |
333 | uint64_t addr) | |
334 | { | |
335 | uint32_t status, dummy = -1; | |
336 | int rc, retries; | |
337 | ||
338 | for (retries = 0; retries < SCOM_MAX_RETRIES; retries++) { | |
339 | rc = raw_get_scom(scom, value, addr, &status); | |
340 | if (rc) { | |
341 | /* Try resetting the bridge if FSI fails */ | |
342 | if (rc != -ENODEV && retries == 0) { | |
343 | fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, | |
344 | &dummy, sizeof(uint32_t)); | |
345 | rc = -EBUSY; | |
346 | } else | |
347 | return rc; | |
348 | } else | |
349 | rc = handle_fsi2pib_status(scom, status); | |
350 | if (rc && rc != -EBUSY) | |
351 | break; | |
352 | if (rc == 0) { | |
353 | rc = handle_pib_status(scom, | |
354 | (status & SCOM_STATUS_PIB_RESP_MASK) | |
355 | >> SCOM_STATUS_PIB_RESP_SHIFT); | |
356 | if (rc && rc != -EBUSY) | |
357 | break; | |
358 | } | |
359 | if (rc == 0) | |
360 | break; | |
361 | msleep(1); | |
362 | } | |
162c3946 | 363 | return rc; |
680ca6dc CB |
364 | } |
365 | ||
366 | static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, | |
bd213364 | 367 | loff_t *offset) |
680ca6dc | 368 | { |
d8f45876 | 369 | struct scom_device *scom = filep->private_data; |
680ca6dc CB |
370 | struct device *dev = &scom->fsi_dev->dev; |
371 | uint64_t val; | |
6b293258 | 372 | int rc; |
680ca6dc CB |
373 | |
374 | if (len != sizeof(uint64_t)) | |
375 | return -EINVAL; | |
376 | ||
6b293258 | 377 | mutex_lock(&scom->lock); |
d8f45876 BH |
378 | if (scom->dead) |
379 | rc = -ENODEV; | |
380 | else | |
381 | rc = get_scom(scom, &val, *offset); | |
6b293258 | 382 | mutex_unlock(&scom->lock); |
680ca6dc CB |
383 | if (rc) { |
384 | dev_dbg(dev, "get_scom fail:%d\n", rc); | |
385 | return rc; | |
386 | } | |
387 | ||
388 | rc = copy_to_user(buf, &val, len); | |
389 | if (rc) | |
390 | dev_dbg(dev, "copy to user failed:%d\n", rc); | |
391 | ||
392 | return rc ? rc : len; | |
393 | } | |
394 | ||
395 | static ssize_t scom_write(struct file *filep, const char __user *buf, | |
bd213364 | 396 | size_t len, loff_t *offset) |
680ca6dc CB |
397 | { |
398 | int rc; | |
d8f45876 | 399 | struct scom_device *scom = filep->private_data; |
680ca6dc CB |
400 | struct device *dev = &scom->fsi_dev->dev; |
401 | uint64_t val; | |
402 | ||
403 | if (len != sizeof(uint64_t)) | |
404 | return -EINVAL; | |
405 | ||
406 | rc = copy_from_user(&val, buf, len); | |
407 | if (rc) { | |
408 | dev_dbg(dev, "copy from user failed:%d\n", rc); | |
409 | return -EINVAL; | |
410 | } | |
411 | ||
6b293258 | 412 | mutex_lock(&scom->lock); |
d8f45876 BH |
413 | if (scom->dead) |
414 | rc = -ENODEV; | |
415 | else | |
416 | rc = put_scom(scom, val, *offset); | |
6b293258 | 417 | mutex_unlock(&scom->lock); |
680ca6dc CB |
418 | if (rc) { |
419 | dev_dbg(dev, "put_scom failed with:%d\n", rc); | |
420 | return rc; | |
421 | } | |
422 | ||
423 | return len; | |
424 | } | |
425 | ||
426 | static loff_t scom_llseek(struct file *file, loff_t offset, int whence) | |
427 | { | |
428 | switch (whence) { | |
429 | case SEEK_CUR: | |
430 | break; | |
431 | case SEEK_SET: | |
432 | file->f_pos = offset; | |
433 | break; | |
434 | default: | |
435 | return -EINVAL; | |
436 | } | |
437 | ||
438 | return offset; | |
439 | } | |
440 | ||
6b293258 BH |
441 | static void raw_convert_status(struct scom_access *acc, uint32_t status) |
442 | { | |
443 | acc->pib_status = (status & SCOM_STATUS_PIB_RESP_MASK) >> | |
444 | SCOM_STATUS_PIB_RESP_SHIFT; | |
445 | acc->intf_errors = 0; | |
446 | ||
447 | if (status & SCOM_STATUS_PROTECTION) | |
448 | acc->intf_errors |= SCOM_INTF_ERR_PROTECTION; | |
449 | else if (status & SCOM_STATUS_PARITY) | |
450 | acc->intf_errors |= SCOM_INTF_ERR_PARITY; | |
451 | else if (status & SCOM_STATUS_PIB_ABORT) | |
452 | acc->intf_errors |= SCOM_INTF_ERR_ABORT; | |
453 | else if (status & SCOM_STATUS_ERR_SUMMARY) | |
454 | acc->intf_errors |= SCOM_INTF_ERR_UNKNOWN; | |
455 | } | |
456 | ||
457 | static int scom_raw_read(struct scom_device *scom, void __user *argp) | |
458 | { | |
459 | struct scom_access acc; | |
460 | uint32_t status; | |
461 | int rc; | |
462 | ||
463 | if (copy_from_user(&acc, argp, sizeof(struct scom_access))) | |
464 | return -EFAULT; | |
465 | ||
466 | rc = raw_get_scom(scom, &acc.data, acc.addr, &status); | |
467 | if (rc) | |
468 | return rc; | |
469 | raw_convert_status(&acc, status); | |
470 | if (copy_to_user(argp, &acc, sizeof(struct scom_access))) | |
471 | return -EFAULT; | |
472 | return 0; | |
473 | } | |
474 | ||
475 | static int scom_raw_write(struct scom_device *scom, void __user *argp) | |
476 | { | |
477 | u64 prev_data, mask, data; | |
478 | struct scom_access acc; | |
479 | uint32_t status; | |
480 | int rc; | |
481 | ||
482 | if (copy_from_user(&acc, argp, sizeof(struct scom_access))) | |
483 | return -EFAULT; | |
484 | ||
485 | if (acc.mask) { | |
486 | rc = raw_get_scom(scom, &prev_data, acc.addr, &status); | |
487 | if (rc) | |
488 | return rc; | |
489 | if (status & SCOM_STATUS_ANY_ERR) | |
490 | goto fail; | |
491 | mask = acc.mask; | |
492 | } else { | |
493 | prev_data = mask = -1ull; | |
494 | } | |
495 | data = (prev_data & ~mask) | (acc.data & mask); | |
496 | rc = raw_put_scom(scom, data, acc.addr, &status); | |
497 | if (rc) | |
498 | return rc; | |
499 | fail: | |
500 | raw_convert_status(&acc, status); | |
501 | if (copy_to_user(argp, &acc, sizeof(struct scom_access))) | |
502 | return -EFAULT; | |
503 | return 0; | |
504 | } | |
505 | ||
506 | static int scom_reset(struct scom_device *scom, void __user *argp) | |
507 | { | |
508 | uint32_t flags, dummy = -1; | |
509 | int rc = 0; | |
510 | ||
511 | if (get_user(flags, (__u32 __user *)argp)) | |
512 | return -EFAULT; | |
513 | if (flags & SCOM_RESET_PIB) | |
514 | rc = fsi_device_write(scom->fsi_dev, SCOM_PIB_RESET_REG, &dummy, | |
515 | sizeof(uint32_t)); | |
516 | if (!rc && (flags & (SCOM_RESET_PIB | SCOM_RESET_INTF))) | |
517 | rc = fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, | |
518 | sizeof(uint32_t)); | |
519 | return rc; | |
520 | } | |
521 | ||
522 | static int scom_check(struct scom_device *scom, void __user *argp) | |
523 | { | |
524 | /* Still need to find out how to get "protected" */ | |
525 | return put_user(SCOM_CHECK_SUPPORTED, (__u32 __user *)argp); | |
526 | } | |
527 | ||
528 | static long scom_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |
529 | { | |
d8f45876 | 530 | struct scom_device *scom = file->private_data; |
6b293258 BH |
531 | void __user *argp = (void __user *)arg; |
532 | int rc = -ENOTTY; | |
533 | ||
534 | mutex_lock(&scom->lock); | |
d8f45876 BH |
535 | if (scom->dead) { |
536 | mutex_unlock(&scom->lock); | |
537 | return -ENODEV; | |
538 | } | |
6b293258 BH |
539 | switch(cmd) { |
540 | case FSI_SCOM_CHECK: | |
541 | rc = scom_check(scom, argp); | |
542 | break; | |
543 | case FSI_SCOM_READ: | |
544 | rc = scom_raw_read(scom, argp); | |
545 | break; | |
546 | case FSI_SCOM_WRITE: | |
547 | rc = scom_raw_write(scom, argp); | |
548 | break; | |
549 | case FSI_SCOM_RESET: | |
550 | rc = scom_reset(scom, argp); | |
551 | break; | |
552 | } | |
553 | mutex_unlock(&scom->lock); | |
554 | return rc; | |
555 | } | |
556 | ||
d8f45876 BH |
557 | static int scom_open(struct inode *inode, struct file *file) |
558 | { | |
559 | struct scom_device *scom = container_of(inode->i_cdev, struct scom_device, cdev); | |
560 | ||
561 | file->private_data = scom; | |
562 | ||
563 | return 0; | |
564 | } | |
565 | ||
680ca6dc | 566 | static const struct file_operations scom_fops = { |
6b293258 | 567 | .owner = THIS_MODULE, |
d8f45876 | 568 | .open = scom_open, |
6b293258 BH |
569 | .llseek = scom_llseek, |
570 | .read = scom_read, | |
571 | .write = scom_write, | |
572 | .unlocked_ioctl = scom_ioctl, | |
680ca6dc CB |
573 | }; |
574 | ||
d8f45876 BH |
575 | static void scom_free(struct device *dev) |
576 | { | |
577 | struct scom_device *scom = container_of(dev, struct scom_device, dev); | |
578 | ||
579 | put_device(&scom->fsi_dev->dev); | |
580 | kfree(scom); | |
581 | } | |
582 | ||
680ca6dc CB |
583 | static int scom_probe(struct device *dev) |
584 | { | |
585 | struct fsi_device *fsi_dev = to_fsi_dev(dev); | |
586 | struct scom_device *scom; | |
d8f45876 | 587 | int rc, didx; |
680ca6dc | 588 | |
d8f45876 | 589 | scom = kzalloc(sizeof(*scom), GFP_KERNEL); |
680ca6dc CB |
590 | if (!scom) |
591 | return -ENOMEM; | |
d8f45876 | 592 | dev_set_drvdata(dev, scom); |
162c3946 | 593 | mutex_init(&scom->lock); |
d8f45876 BH |
594 | |
595 | /* Grab a reference to the device (parent of our cdev), we'll drop it later */ | |
596 | if (!get_device(dev)) { | |
597 | kfree(scom); | |
598 | return -ENODEV; | |
599 | } | |
aa1221b2 | 600 | scom->fsi_dev = fsi_dev; |
d8f45876 BH |
601 | |
602 | /* Create chardev for userspace access */ | |
603 | scom->dev.type = &fsi_cdev_type; | |
604 | scom->dev.parent = dev; | |
605 | scom->dev.release = scom_free; | |
606 | device_initialize(&scom->dev); | |
607 | ||
608 | /* Allocate a minor in the FSI space */ | |
609 | rc = fsi_get_new_minor(fsi_dev, fsi_dev_scom, &scom->dev.devt, &didx); | |
610 | if (rc) | |
611 | goto err; | |
612 | ||
613 | dev_set_name(&scom->dev, "scom%d", didx); | |
614 | cdev_init(&scom->cdev, &scom_fops); | |
615 | rc = cdev_device_add(&scom->cdev, &scom->dev); | |
616 | if (rc) { | |
617 | dev_err(dev, "Error %d creating char device %s\n", | |
618 | rc, dev_name(&scom->dev)); | |
619 | goto err_free_minor; | |
620 | } | |
621 | ||
622 | return 0; | |
623 | err_free_minor: | |
624 | fsi_free_minor(scom->dev.devt); | |
625 | err: | |
626 | put_device(&scom->dev); | |
627 | return rc; | |
680ca6dc CB |
628 | } |
629 | ||
630 | static int scom_remove(struct device *dev) | |
631 | { | |
d8f45876 | 632 | struct scom_device *scom = dev_get_drvdata(dev); |
680ca6dc | 633 | |
d8f45876 BH |
634 | mutex_lock(&scom->lock); |
635 | scom->dead = true; | |
636 | mutex_unlock(&scom->lock); | |
637 | cdev_device_del(&scom->cdev, &scom->dev); | |
638 | fsi_free_minor(scom->dev.devt); | |
639 | put_device(&scom->dev); | |
680ca6dc CB |
640 | |
641 | return 0; | |
642 | } | |
643 | ||
644 | static struct fsi_device_id scom_ids[] = { | |
645 | { | |
646 | .engine_type = FSI_ENGID_SCOM, | |
647 | .version = FSI_VERSION_ANY, | |
648 | }, | |
649 | { 0 } | |
650 | }; | |
651 | ||
652 | static struct fsi_driver scom_drv = { | |
653 | .id_table = scom_ids, | |
654 | .drv = { | |
655 | .name = "scom", | |
656 | .bus = &fsi_bus_type, | |
657 | .probe = scom_probe, | |
658 | .remove = scom_remove, | |
659 | } | |
660 | }; | |
661 | ||
662 | static int scom_init(void) | |
663 | { | |
680ca6dc CB |
664 | return fsi_driver_register(&scom_drv); |
665 | } | |
666 | ||
667 | static void scom_exit(void) | |
668 | { | |
680ca6dc CB |
669 | fsi_driver_unregister(&scom_drv); |
670 | } | |
671 | ||
672 | module_init(scom_init); | |
673 | module_exit(scom_exit); | |
674 | MODULE_LICENSE("GPL"); |