1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (C) IBM Corporation 2023 */ 3 4 #include <linux/cdev.h> 5 #include <linux/device.h> 6 #include <linux/fs.h> 7 #include <linux/fsi.h> 8 #include <linux/module.h> 9 10 #include "fsi-master-i2cr.h" 11 #include "fsi-slave.h" 12 13 struct i2cr_scom { 14 struct device dev; 15 struct cdev cdev; 16 struct fsi_master_i2cr *i2cr; 17 }; 18 19 static loff_t i2cr_scom_llseek(struct file *file, loff_t offset, int whence) 20 { 21 switch (whence) { 22 case SEEK_CUR: 23 break; 24 case SEEK_SET: 25 file->f_pos = offset; 26 break; 27 default: 28 return -EINVAL; 29 } 30 31 return offset; 32 } 33 34 static ssize_t i2cr_scom_read(struct file *filep, char __user *buf, size_t len, loff_t *offset) 35 { 36 struct i2cr_scom *scom = filep->private_data; 37 u64 data; 38 int ret; 39 40 if (len != sizeof(data)) 41 return -EINVAL; 42 43 ret = fsi_master_i2cr_read(scom->i2cr, (u32)*offset, &data); 44 if (ret) 45 return ret; 46 47 ret = copy_to_user(buf, &data, len); 48 if (ret) 49 return ret; 50 51 return len; 52 } 53 54 static ssize_t i2cr_scom_write(struct file *filep, const char __user *buf, size_t len, 55 loff_t *offset) 56 { 57 struct i2cr_scom *scom = filep->private_data; 58 u64 data; 59 int ret; 60 61 if (len != sizeof(data)) 62 return -EINVAL; 63 64 ret = copy_from_user(&data, buf, len); 65 if (ret) 66 return ret; 67 68 ret = fsi_master_i2cr_write(scom->i2cr, (u32)*offset, data); 69 if (ret) 70 return ret; 71 72 return len; 73 } 74 75 static const struct file_operations i2cr_scom_fops = { 76 .owner = THIS_MODULE, 77 .open = simple_open, 78 .llseek = i2cr_scom_llseek, 79 .read = i2cr_scom_read, 80 .write = i2cr_scom_write, 81 }; 82 83 static int i2cr_scom_probe(struct fsi_device *fsi_dev) 84 { 85 struct device *dev = &fsi_dev->dev; 86 struct i2cr_scom *scom; 87 int didx; 88 int ret; 89 90 if (!is_fsi_master_i2cr(fsi_dev->slave->master)) 91 return -ENODEV; 92 93 scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); 94 if (!scom) 95 return -ENOMEM; 96 97 scom->i2cr = to_fsi_master_i2cr(fsi_dev->slave->master); 98 dev_set_drvdata(dev, scom); 99 100 scom->dev.type = &fsi_cdev_type; 101 scom->dev.parent = dev; 102 device_initialize(&scom->dev); 103 104 ret = fsi_get_new_minor(fsi_dev, fsi_dev_scom, &scom->dev.devt, &didx); 105 if (ret) 106 return ret; 107 108 dev_set_name(&scom->dev, "scom%d", didx); 109 cdev_init(&scom->cdev, &i2cr_scom_fops); 110 ret = cdev_device_add(&scom->cdev, &scom->dev); 111 if (ret) 112 fsi_free_minor(scom->dev.devt); 113 114 return ret; 115 } 116 117 static void i2cr_scom_remove(struct fsi_device *fsi_dev) 118 { 119 struct i2cr_scom *scom = dev_get_drvdata(&fsi_dev->dev); 120 121 cdev_device_del(&scom->cdev, &scom->dev); 122 fsi_free_minor(scom->dev.devt); 123 } 124 125 static const struct of_device_id i2cr_scom_of_ids[] = { 126 { .compatible = "ibm,i2cr-scom" }, 127 { } 128 }; 129 MODULE_DEVICE_TABLE(of, i2cr_scom_of_ids); 130 131 static const struct fsi_device_id i2cr_scom_ids[] = { 132 { 0x5, FSI_VERSION_ANY }, 133 { } 134 }; 135 136 static struct fsi_driver i2cr_scom_driver = { 137 .probe = i2cr_scom_probe, 138 .remove = i2cr_scom_remove, 139 .id_table = i2cr_scom_ids, 140 .drv = { 141 .name = "i2cr_scom", 142 .of_match_table = i2cr_scom_of_ids, 143 } 144 }; 145 146 module_fsi_driver(i2cr_scom_driver); 147 148 MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>"); 149 MODULE_DESCRIPTION("IBM I2C Responder SCOM driver"); 150 MODULE_LICENSE("GPL"); 151