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> 23 #include <linux/miscdevice.h> 24 #include <linux/list.h> 25 #include <linux/idr.h> 26 27 #define FSI_ENGID_SCOM 0x5 28 29 #define SCOM_FSI2PIB_DELAY 50 30 31 /* SCOM engine register set */ 32 #define SCOM_DATA0_REG 0x00 33 #define SCOM_DATA1_REG 0x04 34 #define SCOM_CMD_REG 0x08 35 #define SCOM_RESET_REG 0x1C 36 37 #define SCOM_RESET_CMD 0x80000000 38 #define SCOM_WRITE_CMD 0x80000000 39 40 struct scom_device { 41 struct list_head link; 42 struct fsi_device *fsi_dev; 43 struct miscdevice mdev; 44 char name[32]; 45 int idx; 46 }; 47 48 #define to_scom_dev(x) container_of((x), struct scom_device, mdev) 49 50 static struct list_head scom_devices; 51 52 static DEFINE_IDA(scom_ida); 53 54 static int put_scom(struct scom_device *scom_dev, uint64_t value, 55 uint32_t addr) 56 { 57 int rc; 58 uint32_t data; 59 60 data = cpu_to_be32(SCOM_RESET_CMD); 61 rc = fsi_device_write(scom_dev->fsi_dev, SCOM_RESET_REG, &data, 62 sizeof(uint32_t)); 63 if (rc) 64 return rc; 65 66 data = cpu_to_be32((value >> 32) & 0xffffffff); 67 rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, 68 sizeof(uint32_t)); 69 if (rc) 70 return rc; 71 72 data = cpu_to_be32(value & 0xffffffff); 73 rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, 74 sizeof(uint32_t)); 75 if (rc) 76 return rc; 77 78 data = cpu_to_be32(SCOM_WRITE_CMD | addr); 79 return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, 80 sizeof(uint32_t)); 81 } 82 83 static int get_scom(struct scom_device *scom_dev, uint64_t *value, 84 uint32_t addr) 85 { 86 uint32_t result, data; 87 int rc; 88 89 *value = 0ULL; 90 data = cpu_to_be32(addr); 91 rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, 92 sizeof(uint32_t)); 93 if (rc) 94 return rc; 95 96 rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, 97 sizeof(uint32_t)); 98 if (rc) 99 return rc; 100 101 *value |= (uint64_t)cpu_to_be32(result) << 32; 102 rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, 103 sizeof(uint32_t)); 104 if (rc) 105 return rc; 106 107 *value |= cpu_to_be32(result); 108 109 return 0; 110 } 111 112 static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, 113 loff_t *offset) 114 { 115 int rc; 116 struct miscdevice *mdev = 117 (struct miscdevice *)filep->private_data; 118 struct scom_device *scom = to_scom_dev(mdev); 119 struct device *dev = &scom->fsi_dev->dev; 120 uint64_t val; 121 122 if (len != sizeof(uint64_t)) 123 return -EINVAL; 124 125 rc = get_scom(scom, &val, *offset); 126 if (rc) { 127 dev_dbg(dev, "get_scom fail:%d\n", rc); 128 return rc; 129 } 130 131 rc = copy_to_user(buf, &val, len); 132 if (rc) 133 dev_dbg(dev, "copy to user failed:%d\n", rc); 134 135 return rc ? rc : len; 136 } 137 138 static ssize_t scom_write(struct file *filep, const char __user *buf, 139 size_t len, loff_t *offset) 140 { 141 int rc; 142 struct miscdevice *mdev = filep->private_data; 143 struct scom_device *scom = to_scom_dev(mdev); 144 struct device *dev = &scom->fsi_dev->dev; 145 uint64_t val; 146 147 if (len != sizeof(uint64_t)) 148 return -EINVAL; 149 150 rc = copy_from_user(&val, buf, len); 151 if (rc) { 152 dev_dbg(dev, "copy from user failed:%d\n", rc); 153 return -EINVAL; 154 } 155 156 rc = put_scom(scom, val, *offset); 157 if (rc) { 158 dev_dbg(dev, "put_scom failed with:%d\n", rc); 159 return rc; 160 } 161 162 return len; 163 } 164 165 static loff_t scom_llseek(struct file *file, loff_t offset, int whence) 166 { 167 switch (whence) { 168 case SEEK_CUR: 169 break; 170 case SEEK_SET: 171 file->f_pos = offset; 172 break; 173 default: 174 return -EINVAL; 175 } 176 177 return offset; 178 } 179 180 static const struct file_operations scom_fops = { 181 .owner = THIS_MODULE, 182 .llseek = scom_llseek, 183 .read = scom_read, 184 .write = scom_write, 185 }; 186 187 static int scom_probe(struct device *dev) 188 { 189 struct fsi_device *fsi_dev = to_fsi_dev(dev); 190 struct scom_device *scom; 191 192 scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); 193 if (!scom) 194 return -ENOMEM; 195 196 scom->idx = ida_simple_get(&scom_ida, 1, INT_MAX, GFP_KERNEL); 197 snprintf(scom->name, sizeof(scom->name), "scom%d", scom->idx); 198 scom->fsi_dev = fsi_dev; 199 scom->mdev.minor = MISC_DYNAMIC_MINOR; 200 scom->mdev.fops = &scom_fops; 201 scom->mdev.name = scom->name; 202 scom->mdev.parent = dev; 203 list_add(&scom->link, &scom_devices); 204 205 return misc_register(&scom->mdev); 206 } 207 208 static int scom_remove(struct device *dev) 209 { 210 struct scom_device *scom, *scom_tmp; 211 struct fsi_device *fsi_dev = to_fsi_dev(dev); 212 213 list_for_each_entry_safe(scom, scom_tmp, &scom_devices, link) { 214 if (scom->fsi_dev == fsi_dev) { 215 list_del(&scom->link); 216 ida_simple_remove(&scom_ida, scom->idx); 217 misc_deregister(&scom->mdev); 218 } 219 } 220 221 return 0; 222 } 223 224 static struct fsi_device_id scom_ids[] = { 225 { 226 .engine_type = FSI_ENGID_SCOM, 227 .version = FSI_VERSION_ANY, 228 }, 229 { 0 } 230 }; 231 232 static struct fsi_driver scom_drv = { 233 .id_table = scom_ids, 234 .drv = { 235 .name = "scom", 236 .bus = &fsi_bus_type, 237 .probe = scom_probe, 238 .remove = scom_remove, 239 } 240 }; 241 242 static int scom_init(void) 243 { 244 INIT_LIST_HEAD(&scom_devices); 245 return fsi_driver_register(&scom_drv); 246 } 247 248 static void scom_exit(void) 249 { 250 struct list_head *pos; 251 struct scom_device *scom; 252 253 list_for_each(pos, &scom_devices) { 254 scom = list_entry(pos, struct scom_device, link); 255 misc_deregister(&scom->mdev); 256 devm_kfree(&scom->fsi_dev->dev, scom); 257 } 258 fsi_driver_unregister(&scom_drv); 259 } 260 261 module_init(scom_init); 262 module_exit(scom_exit); 263 MODULE_LICENSE("GPL"); 264