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