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