Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drivers/fsi: Add SCOM FSI client device driver

Create a simple SCOM engine device driver that reads and writes
its control registers via an FSI bus.

Includes changes from Edward A. James <eajames@us.ibm.com>.

Signed-off-by: Christopher Bostic <cbostic@linux.vnet.ibm.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Christopher Bostic and committed by
Greg Kroah-Hartman
680ca6dc 1247cf7a

+269
+5
drivers/fsi/Kconfig
··· 20 20 ---help--- 21 21 This option enables a FSI master driver using GPIO lines. 22 22 23 + config FSI_SCOM 24 + tristate "SCOM FSI client device driver" 25 + ---help--- 26 + This option enables an FSI based SCOM device driver. 27 + 23 28 endif 24 29 25 30 endmenu
+1
drivers/fsi/Makefile
··· 1 1 2 2 obj-$(CONFIG_FSI) += fsi-core.o 3 3 obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o 4 + obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
+263
drivers/fsi/fsi-scom.c
··· 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");