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