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 device *dev) 85 { 86 struct fsi_device *fsi_dev = to_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 int i2cr_scom_remove(struct device *dev) 119 { 120 struct i2cr_scom *scom = dev_get_drvdata(dev); 121 122 cdev_device_del(&scom->cdev, &scom->dev); 123 fsi_free_minor(scom->dev.devt); 124 125 return 0; 126 } 127 128 static const struct of_device_id i2cr_scom_of_ids[] = { 129 { .compatible = "ibm,i2cr-scom" }, 130 { } 131 }; 132 MODULE_DEVICE_TABLE(of, i2cr_scom_of_ids); 133 134 static const struct fsi_device_id i2cr_scom_ids[] = { 135 { 0x5, FSI_VERSION_ANY }, 136 { } 137 }; 138 139 static struct fsi_driver i2cr_scom_driver = { 140 .id_table = i2cr_scom_ids, 141 .drv = { 142 .name = "i2cr_scom", 143 .bus = &fsi_bus_type, 144 .of_match_table = i2cr_scom_of_ids, 145 .probe = i2cr_scom_probe, 146 .remove = i2cr_scom_remove, 147 } 148 }; 149 150 module_fsi_driver(i2cr_scom_driver); 151 152 MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>"); 153 MODULE_DESCRIPTION("IBM I2C Responder SCOM driver"); 154 MODULE_LICENSE("GPL"); 155