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