Message ID | 1477865379-11566-19-git-send-email-christopher.lee.bostic@gmail.com |
---|---|
State | Changes Requested, archived |
Headers | show |
Hi Chris, > Create a simple SCOM engine device driver that reads and writes > across an FSI bus. Looking good. Is this functional at the moment? A few comments: > +static struct list_head scom_devices; > +static atomic_t scom_idx = ATOMIC_INIT(0); > +static int scom_probe(struct device *); You don't need this forward declaration anymore > +static int get_scom(struct scom_device *scom_dev, uint64_t *value, > + uint32_t addr) > +{ > + uint32_t result, data; > + int rc; > + > + udelay(SCOM_FSI2PIB_DELAY); Why this udelay()? Does put_scom() not need it too then? If it's a matter of scom engine state, should we adjust this based on the last operation? If it's necessary, can you add a short comment? > +static loff_t scom_llseek(struct file *filep, loff_t offset, int whence) > +{ > + if (whence != 0) /* SEEK_SET */ > + return -EINVAL; > + > + filep->f_pos = offset; > + return offset; > +} I think you can drop this and use .llseek = no_seek_end_llseek instead (and then you get SEEK_CUR support too). > +static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, > + loff_t *offset) > +{ > + int rc = 0; > + struct miscdevice *mdev = > + (struct miscdevice *)filep->private_data; > + struct scom_device *scom = to_scom_dev(mdev); > + struct device *dev = &scom->fsi_dev->dev; > + uint64_t val; > + > + if (len != sizeof(uint64_t)) > + return -EINVAL; > + > + rc = get_scom(scom, &val, *offset); > + if (rc) { > + dev_dbg(dev, "get_scom fail:%d\n", rc); > + return rc; > + } > + > + rc = copy_to_user(buf, &val, len); > + if (rc) > + dev_dbg(dev, "copy to user failed:%d\n", rc); > + > + return rc ? rc : len; > +} What are the semantics of offset after reading & writing? Should two reads (with no intervening llseek) read the same address, or subsequent addresses? > +static ssize_t scom_write(struct file *filep, const char __user *buf, > + size_t len, loff_t *offset) > +{ > + int rc = 0; No need to initialise this (and in scom_read too). > + struct miscdevice *mdev = > + (struct miscdevice *)filep->private_data; > + struct scom_device *scom = to_scom_dev(mdev); > + struct device *dev = &scom->fsi_dev->dev; > + uint64_t val; > + > + if (len != sizeof(uint64_t)) > + return -EINVAL; > + > + rc = copy_from_user(&val, buf, len); > + if (rc) { > + dev_dbg(dev, "copy from user failed:%d\n", rc); > + return -EINVAL; > + } You don't want to squash that return value; just return rc there. > + > + rc = put_scom(scom, val, *offset); > + if (rc) > + dev_dbg(dev, "put_scom failed with:%d\n", rc); > + > + > + return rc ? rc : len; > +} > + > +static const struct file_operations scom_fops = { > + .owner = THIS_MODULE, > + .llseek = scom_llseek, > + .read = scom_read, > + .write = scom_write, > +}; > + > +static int scom_probe(struct device *dev) > +{ > + struct fsi_device *fsi_dev = to_fsi_dev(dev); > + struct scom_device *scom = NULL; No need to initialise this either. > + > + scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); > + if (!scom) > + return -ENOMEM; > + > + snprintf(scom->name, sizeof(scom->name), > + "scom%d", atomic_inc_return(&scom_idx)); > + scom->fsi_dev = fsi_dev; > + scom->mdev.minor = MISC_DYNAMIC_MINOR; > + scom->mdev.fops = &scom_fops; > + scom->mdev.name = scom->name; > + scom->mdev.parent = dev; > + list_add(&scom->link, &scom_devices); > + > + return misc_register(&scom->mdev); > +} > + > +static struct fsi_device_id scom_ids[] = { > + { > + .engine_type = FSI_ENGID_SCOM, > + .version = FSI_VERSION_ANY, > + }, > + { 0 } > +}; > + > +static struct fsi_driver scom_drv = { > + .id_table = scom_ids, > + .drv = { > + .name = "scom", > + .bus = &fsi_bus_type, > + .probe = scom_probe, > + } > +}; > + > +static int scom_init(void) > +{ > + INIT_LIST_HEAD(&scom_devices); > + return fsi_driver_register(&scom_drv); > +} > + > +static void scom_exit(void) > +{ > + struct list_head *pos; > + struct scom_device *scom = NULL; ... or this. > + > + list_for_each(pos, &scom_devices) { > + scom = list_entry(pos, struct scom_device, link); > + misc_deregister(&scom->mdev); > + devm_kfree(&scom->fsi_dev->dev, scom); > + } > + fsi_driver_unregister(&scom_drv); > +} > + > +module_init(scom_init); > +module_exit(scom_exit); > +MODULE_LICENSE("GPL"); Cheers, Jeremy
>> +static atomic_t scom_idx = ATOMIC_INIT(0); >> +static int scom_probe(struct device *); > > You don't need this forward declaration anymore > Hi Jeremy, OK will remove. >> +static int get_scom(struct scom_device *scom_dev, uint64_t *value, >> + uint32_t addr) >> +{ >> + uint32_t result, data; >> + int rc; >> + >> + udelay(SCOM_FSI2PIB_DELAY); > > Why this udelay()? Does put_scom() not need it too then? If it's a > matter of scom engine state, should we adjust this based on the last > operation? This is a carry over from previous code. I don't know the history behind why it was added. Will remove. > > If it's necessary, can you add a short comment? > >> +static loff_t scom_llseek(struct file *filep, loff_t offset, int whence) >> +{ >> + if (whence != 0) /* SEEK_SET */ >> + return -EINVAL; >> + >> + filep->f_pos = offset; >> + return offset; >> +} > > I think you can drop this and use .llseek = no_seek_end_llseek instead > (and then you get SEEK_CUR support too). Will change. > >> +static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, >> + loff_t *offset) >> +{ >> + int rc = 0; >> + struct miscdevice *mdev = >> + (struct miscdevice *)filep->private_data; >> + struct scom_device *scom = to_scom_dev(mdev); >> + struct device *dev = &scom->fsi_dev->dev; >> + uint64_t val; >> + >> + if (len != sizeof(uint64_t)) >> + return -EINVAL; >> + >> + rc = get_scom(scom, &val, *offset); >> + if (rc) { >> + dev_dbg(dev, "get_scom fail:%d\n", rc); >> + return rc; >> + } >> + >> + rc = copy_to_user(buf, &val, len); >> + if (rc) >> + dev_dbg(dev, "copy to user failed:%d\n", rc); >> + >> + return rc ? rc : len; >> +} > > What are the semantics of offset after reading & writing? Should two > reads (with no intervening llseek) read the same address, or subsequent > addresses? The user is to specify the address for each scom read and write. No support has been provided for any other address auto set. > >> +static ssize_t scom_write(struct file *filep, const char __user *buf, >> + size_t len, loff_t *offset) >> +{ >> + int rc = 0; > > No need to initialise this (and in scom_read too). > Will remove. >> + struct miscdevice *mdev = >> + (struct miscdevice *)filep->private_data; >> + struct scom_device *scom = to_scom_dev(mdev); >> + struct device *dev = &scom->fsi_dev->dev; >> + uint64_t val; >> + >> + if (len != sizeof(uint64_t)) >> + return -EINVAL; >> + >> + rc = copy_from_user(&val, buf, len); >> + if (rc) { >> + dev_dbg(dev, "copy from user failed:%d\n", rc); >> + return -EINVAL; >> + } > > You don't want to squash that return value; just return rc there. > OK, will change. >> + >> + rc = put_scom(scom, val, *offset); >> + if (rc) >> + dev_dbg(dev, "put_scom failed with:%d\n", rc); >> + >> + >> + return rc ? rc : len; >> +} >> + >> +static const struct file_operations scom_fops = { >> + .owner = THIS_MODULE, >> + .llseek = scom_llseek, >> + .read = scom_read, >> + .write = scom_write, >> +}; >> + >> +static int scom_probe(struct device *dev) >> +{ >> + struct fsi_device *fsi_dev = to_fsi_dev(dev); >> + struct scom_device *scom = NULL; > > No need to initialise this either. > Removing >> + >> + scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); >> + if (!scom) >> + return -ENOMEM; >> + >> + snprintf(scom->name, sizeof(scom->name), >> + "scom%d", atomic_inc_return(&scom_idx)); >> + scom->fsi_dev = fsi_dev; >> + scom->mdev.minor = MISC_DYNAMIC_MINOR; >> + scom->mdev.fops = &scom_fops; >> + scom->mdev.name = scom->name; >> + scom->mdev.parent = dev; >> + list_add(&scom->link, &scom_devices); >> + >> + return misc_register(&scom->mdev); >> +} >> + >> +static struct fsi_device_id scom_ids[] = { >> + { >> + .engine_type = FSI_ENGID_SCOM, >> + .version = FSI_VERSION_ANY, >> + }, >> + { 0 } >> +}; >> + >> +static struct fsi_driver scom_drv = { >> + .id_table = scom_ids, >> + .drv = { >> + .name = "scom", >> + .bus = &fsi_bus_type, >> + .probe = scom_probe, >> + } >> +}; >> + >> +static int scom_init(void) >> +{ >> + INIT_LIST_HEAD(&scom_devices); >> + return fsi_driver_register(&scom_drv); >> +} >> + >> +static void scom_exit(void) >> +{ >> + struct list_head *pos; >> + struct scom_device *scom = NULL; > > ... or this. > Will remove >> + >> + list_for_each(pos, &scom_devices) { >> + scom = list_entry(pos, struct scom_device, link); >> + misc_deregister(&scom->mdev); >> + devm_kfree(&scom->fsi_dev->dev, scom); >> + } >> + fsi_driver_unregister(&scom_drv); >> +} >> + >> +module_init(scom_init); >> +module_exit(scom_exit); >> +MODULE_LICENSE("GPL"); > Thanks, Chris > Cheers, > > > Jeremy
Hi Chris, Is there a similar device driver for the CFAM/FSI that I missed? That is will there be a chardev for read/writing FSI addresses directly (ie. a chardev to call fsi_device_read/write)? Probably not critical for an initial release but we will need one at some point as I'm pretty sure the cronus server for example has get/putcfam commands as some registers aren't available via SCOM. Regards, Alistair On Sun, 30 Oct 2016 05:09:20 PM christopher.lee.bostic@gmail.com wrote: > From: Chris Bostic <cbostic@us.ibm.com> > > Create a simple SCOM engine device driver that reads and writes > across an FSI bus. > > Signed-off-by: Chris Bostic <cbostic@us.ibm.com> > > --- > > V4 - Add put_scom and get_scom operations > > V5 - Add character device registration and fill in read/write > syscalls. > > V6 - Add multi scom engine support. Add list of devices. > Use file private data to store and hand off scom structures. > > - Add lseek, open, close > > - Remove data/address structure to pass from user space to > kernel. Use offset provided by read/write and pass data > > - Added list setup in init and so did not add the macro > module_fsi_driver > > - Global structs made static where possible. > > - Change from char dev to use misc device api as shown > in examples from Jeremy Kere. > --- > drivers/fsi/Kconfig | 6 ++ > drivers/fsi/Makefile | 1 + > drivers/fsi/fsi-scom.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 245 insertions(+) > create mode 100644 drivers/fsi/fsi-scom.c > > diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig > index b21fe3c..1fa9bc0 100644 > --- a/drivers/fsi/Kconfig > +++ b/drivers/fsi/Kconfig > @@ -23,6 +23,12 @@ config FSI_MASTER_GPIO > depends on FSI > ---help--- > This option enables a FSI master driver using GPIO lines. > + > +config FSI_SCOM > + tristate "SCOM FSI client" > + depends on FSI > + ---help--- > + This option enables the SCOM FSI client device driver. > endif > > endmenu > diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile > index 2021ce5..3e31d9a 100644 > --- a/drivers/fsi/Makefile > +++ b/drivers/fsi/Makefile > @@ -2,3 +2,4 @@ > obj-$(CONFIG_FSI) += fsi-core.o > obj-$(CONFIG_FSI_MASTER_FAKE) += fsi-master-fake.o > obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o > +obj-$(CONFIG_FSI_SCOM) += fsi-scom.o > diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c > new file mode 100644 > index 0000000..27be082 > --- /dev/null > +++ b/drivers/fsi/fsi-scom.c > @@ -0,0 +1,238 @@ > +/* > + * SCOM FSI Client device driver > + * > + * Copyright (C) IBM Corporation 2016 > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > + > +#include <linux/fsi.h> > +#include <linux/module.h> > +#include <linux/cdev.h> > +#include <linux/delay.h> > +#include <linux/fs.h> > +#include <linux/uaccess.h> > +#include <linux/slab.h> > +#include <linux/miscdevice.h> > +#include <linux/list.h> > + > +#define FSI_ENGID_SCOM 0x5 > + > +#define SCOM_FSI2PIB_DELAY 50 > + > +/* SCOM engine register set */ > +#define SCOM_DATA0_REG 0x00 > +#define SCOM_DATA1_REG 0x04 > +#define SCOM_CMD_REG 0x08 > +#define SCOM_RESET_REG 0x1C > + > +#define SCOM_RESET_CMD 0x80000000 > +#define SCOM_WRITE_CMD 0x80000000 > + > +struct scom_device { > + struct list_head link; > + struct fsi_device *fsi_dev; > + struct miscdevice mdev; > + char name[32]; > +}; > + > +#define to_scom_dev(x) container_of((x), struct scom_device, mdev) > + > +static struct list_head scom_devices; > +static atomic_t scom_idx = ATOMIC_INIT(0); > +static int scom_probe(struct device *); > + > +static int put_scom(struct scom_device *scom_dev, uint64_t value, > + uint32_t addr) > +{ > + int rc; > + uint32_t data = SCOM_RESET_CMD; > + > + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_RESET_REG, &data, > + sizeof(uint32_t)); > + if (rc) > + return rc; > + > + data = (value >> 32) & 0xffffffff; > + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, > + sizeof(uint32_t)); > + if (rc) > + return rc; > + > + data = value & 0xffffffff; > + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, > + sizeof(uint32_t)); > + if (rc) > + return rc; > + > + data = SCOM_WRITE_CMD | addr; > + return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, > + sizeof(uint32_t)); > +} > + > +static int get_scom(struct scom_device *scom_dev, uint64_t *value, > + uint32_t addr) > +{ > + uint32_t result, data; > + int rc; > + > + udelay(SCOM_FSI2PIB_DELAY); > + > + data = addr; > + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, > + sizeof(uint32_t)); > + if (rc) > + return rc; > + > + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, > + sizeof(uint32_t)); > + if (rc) > + return rc; > + > + *value |= (uint64_t) result << 32; > + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, > + sizeof(uint32_t)); > + if (rc) > + return rc; > + > + *value |= result; > + > + return 0; > +} > + > +static loff_t scom_llseek(struct file *filep, loff_t offset, int whence) > +{ > + if (whence != 0) /* SEEK_SET */ > + return -EINVAL; > + > + filep->f_pos = offset; > + return offset; > +} > + > +static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, > + loff_t *offset) > +{ > + int rc = 0; > + struct miscdevice *mdev = > + (struct miscdevice *)filep->private_data; > + struct scom_device *scom = to_scom_dev(mdev); > + struct device *dev = &scom->fsi_dev->dev; > + uint64_t val; > + > + if (len != sizeof(uint64_t)) > + return -EINVAL; > + > + rc = get_scom(scom, &val, *offset); > + if (rc) { > + dev_dbg(dev, "get_scom fail:%d\n", rc); > + return rc; > + } > + > + rc = copy_to_user(buf, &val, len); > + if (rc) > + dev_dbg(dev, "copy to user failed:%d\n", rc); > + > + return rc ? rc : len; > +} > + > +static ssize_t scom_write(struct file *filep, const char __user *buf, > + size_t len, loff_t *offset) > +{ > + int rc = 0; > + struct miscdevice *mdev = > + (struct miscdevice *)filep->private_data; > + struct scom_device *scom = to_scom_dev(mdev); > + struct device *dev = &scom->fsi_dev->dev; > + uint64_t val; > + > + if (len != sizeof(uint64_t)) > + return -EINVAL; > + > + rc = copy_from_user(&val, buf, len); > + if (rc) { > + dev_dbg(dev, "copy from user failed:%d\n", rc); > + return -EINVAL; > + } > + > + rc = put_scom(scom, val, *offset); > + if (rc) > + dev_dbg(dev, "put_scom failed with:%d\n", rc); > + > + > + return rc ? rc : len; > +} > + > +static const struct file_operations scom_fops = { > + .owner = THIS_MODULE, > + .llseek = scom_llseek, > + .read = scom_read, > + .write = scom_write, > +}; > + > +static int scom_probe(struct device *dev) > +{ > + struct fsi_device *fsi_dev = to_fsi_dev(dev); > + struct scom_device *scom = NULL; > + > + scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); > + if (!scom) > + return -ENOMEM; > + > + snprintf(scom->name, sizeof(scom->name), > + "scom%d", atomic_inc_return(&scom_idx)); > + scom->fsi_dev = fsi_dev; > + scom->mdev.minor = MISC_DYNAMIC_MINOR; > + scom->mdev.fops = &scom_fops; > + scom->mdev.name = scom->name; > + scom->mdev.parent = dev; > + list_add(&scom->link, &scom_devices); > + > + return misc_register(&scom->mdev); > +} > + > +static struct fsi_device_id scom_ids[] = { > + { > + .engine_type = FSI_ENGID_SCOM, > + .version = FSI_VERSION_ANY, > + }, > + { 0 } > +}; > + > +static struct fsi_driver scom_drv = { > + .id_table = scom_ids, > + .drv = { > + .name = "scom", > + .bus = &fsi_bus_type, > + .probe = scom_probe, > + } > +}; > + > +static int scom_init(void) > +{ > + INIT_LIST_HEAD(&scom_devices); > + return fsi_driver_register(&scom_drv); > +} > + > +static void scom_exit(void) > +{ > + struct list_head *pos; > + struct scom_device *scom = NULL; > + > + list_for_each(pos, &scom_devices) { > + scom = list_entry(pos, struct scom_device, link); > + misc_deregister(&scom->mdev); > + devm_kfree(&scom->fsi_dev->dev, scom); > + } > + fsi_driver_unregister(&scom_drv); > +} > + > +module_init(scom_init); > +module_exit(scom_exit); > +MODULE_LICENSE("GPL"); >
On Sun, Nov 13, 2016 at 5:44 PM, Alistair Popple <alistair@popple.id.au> wrote: > Hi Chris, > > Is there a similar device driver for the CFAM/FSI that I missed? That is will > there be a chardev for read/writing FSI addresses directly (ie. a chardev to > call fsi_device_read/write)? Probably not critical for an initial release but > we will need one at some point as I'm pretty sure the cronus server for > example has get/putcfam commands as some registers aren't available via SCOM. > > Regards, > > Alistair > Hi Alistair, No there isn't a driver like you describe in the set as it is now. I agree that would be important to have for general bringup debug. Once this set is accepted I'll add that. Thanks > On Sun, 30 Oct 2016 05:09:20 PM christopher.lee.bostic@gmail.com wrote: >> From: Chris Bostic <cbostic@us.ibm.com> >> >> Create a simple SCOM engine device driver that reads and writes >> across an FSI bus. >> >> Signed-off-by: Chris Bostic <cbostic@us.ibm.com> >> >> --- >> >> V4 - Add put_scom and get_scom operations >> >> V5 - Add character device registration and fill in read/write >> syscalls. >> >> V6 - Add multi scom engine support. Add list of devices. >> Use file private data to store and hand off scom structures. >> >> - Add lseek, open, close >> >> - Remove data/address structure to pass from user space to >> kernel. Use offset provided by read/write and pass data >> >> - Added list setup in init and so did not add the macro >> module_fsi_driver >> >> - Global structs made static where possible. >> >> - Change from char dev to use misc device api as shown >> in examples from Jeremy Kere. >> --- >> drivers/fsi/Kconfig | 6 ++ >> drivers/fsi/Makefile | 1 + >> drivers/fsi/fsi-scom.c | 238 > +++++++++++++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 245 insertions(+) >> create mode 100644 drivers/fsi/fsi-scom.c >> >> diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig >> index b21fe3c..1fa9bc0 100644 >> --- a/drivers/fsi/Kconfig >> +++ b/drivers/fsi/Kconfig >> @@ -23,6 +23,12 @@ config FSI_MASTER_GPIO >> depends on FSI >> ---help--- >> This option enables a FSI master driver using GPIO lines. >> + >> +config FSI_SCOM >> + tristate "SCOM FSI client" >> + depends on FSI >> + ---help--- >> + This option enables the SCOM FSI client device driver. >> endif >> >> endmenu >> diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile >> index 2021ce5..3e31d9a 100644 >> --- a/drivers/fsi/Makefile >> +++ b/drivers/fsi/Makefile >> @@ -2,3 +2,4 @@ >> obj-$(CONFIG_FSI) += fsi-core.o >> obj-$(CONFIG_FSI_MASTER_FAKE) += fsi-master-fake.o >> obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o >> +obj-$(CONFIG_FSI_SCOM) += fsi-scom.o >> diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c >> new file mode 100644 >> index 0000000..27be082 >> --- /dev/null >> +++ b/drivers/fsi/fsi-scom.c >> @@ -0,0 +1,238 @@ >> +/* >> + * SCOM FSI Client device driver >> + * >> + * Copyright (C) IBM Corporation 2016 >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License version 2 as >> + * published by the Free Software Foundation. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + * GNU General Public License for more details. >> + */ >> + >> +#include <linux/fsi.h> >> +#include <linux/module.h> >> +#include <linux/cdev.h> >> +#include <linux/delay.h> >> +#include <linux/fs.h> >> +#include <linux/uaccess.h> >> +#include <linux/slab.h> >> +#include <linux/miscdevice.h> >> +#include <linux/list.h> >> + >> +#define FSI_ENGID_SCOM 0x5 >> + >> +#define SCOM_FSI2PIB_DELAY 50 >> + >> +/* SCOM engine register set */ >> +#define SCOM_DATA0_REG 0x00 >> +#define SCOM_DATA1_REG 0x04 >> +#define SCOM_CMD_REG 0x08 >> +#define SCOM_RESET_REG 0x1C >> + >> +#define SCOM_RESET_CMD 0x80000000 >> +#define SCOM_WRITE_CMD 0x80000000 >> + >> +struct scom_device { >> + struct list_head link; >> + struct fsi_device *fsi_dev; >> + struct miscdevice mdev; >> + char name[32]; >> +}; >> + >> +#define to_scom_dev(x) container_of((x), struct scom_device, > mdev) >> + >> +static struct list_head scom_devices; >> +static atomic_t scom_idx = ATOMIC_INIT(0); >> +static int scom_probe(struct device *); >> + >> +static int put_scom(struct scom_device *scom_dev, uint64_t value, >> + uint32_t addr) >> +{ >> + int rc; >> + uint32_t data = SCOM_RESET_CMD; >> + >> + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_RESET_REG, &data, >> + sizeof(uint32_t)); >> + if (rc) >> + return rc; >> + >> + data = (value >> 32) & 0xffffffff; >> + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, >> + sizeof(uint32_t)); >> + if (rc) >> + return rc; >> + >> + data = value & 0xffffffff; >> + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, >> + sizeof(uint32_t)); >> + if (rc) >> + return rc; >> + >> + data = SCOM_WRITE_CMD | addr; >> + return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, >> + sizeof(uint32_t)); >> +} >> + >> +static int get_scom(struct scom_device *scom_dev, uint64_t *value, >> + uint32_t addr) >> +{ >> + uint32_t result, data; >> + int rc; >> + >> + udelay(SCOM_FSI2PIB_DELAY); >> + >> + data = addr; >> + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, >> + sizeof(uint32_t)); >> + if (rc) >> + return rc; >> + >> + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, >> + sizeof(uint32_t)); >> + if (rc) >> + return rc; >> + >> + *value |= (uint64_t) result << 32; >> + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, >> + sizeof(uint32_t)); >> + if (rc) >> + return rc; >> + >> + *value |= result; >> + >> + return 0; >> +} >> + >> +static loff_t scom_llseek(struct file *filep, loff_t offset, int whence) >> +{ >> + if (whence != 0) /* SEEK_SET */ >> + return -EINVAL; >> + >> + filep->f_pos = offset; >> + return offset; >> +} >> + >> +static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, >> + loff_t *offset) >> +{ >> + int rc = 0; >> + struct miscdevice *mdev = >> + (struct miscdevice *)filep->private_data; >> + struct scom_device *scom = to_scom_dev(mdev); >> + struct device *dev = &scom->fsi_dev->dev; >> + uint64_t val; >> + >> + if (len != sizeof(uint64_t)) >> + return -EINVAL; >> + >> + rc = get_scom(scom, &val, *offset); >> + if (rc) { >> + dev_dbg(dev, "get_scom fail:%d\n", rc); >> + return rc; >> + } >> + >> + rc = copy_to_user(buf, &val, len); >> + if (rc) >> + dev_dbg(dev, "copy to user failed:%d\n", rc); >> + >> + return rc ? rc : len; >> +} >> + >> +static ssize_t scom_write(struct file *filep, const char __user *buf, >> + size_t len, loff_t *offset) >> +{ >> + int rc = 0; >> + struct miscdevice *mdev = >> + (struct miscdevice *)filep->private_data; >> + struct scom_device *scom = to_scom_dev(mdev); >> + struct device *dev = &scom->fsi_dev->dev; >> + uint64_t val; >> + >> + if (len != sizeof(uint64_t)) >> + return -EINVAL; >> + >> + rc = copy_from_user(&val, buf, len); >> + if (rc) { >> + dev_dbg(dev, "copy from user failed:%d\n", rc); >> + return -EINVAL; >> + } >> + >> + rc = put_scom(scom, val, *offset); >> + if (rc) >> + dev_dbg(dev, "put_scom failed with:%d\n", rc); >> + >> + >> + return rc ? rc : len; >> +} >> + >> +static const struct file_operations scom_fops = { >> + .owner = THIS_MODULE, >> + .llseek = scom_llseek, >> + .read = scom_read, >> + .write = scom_write, >> +}; >> + >> +static int scom_probe(struct device *dev) >> +{ >> + struct fsi_device *fsi_dev = to_fsi_dev(dev); >> + struct scom_device *scom = NULL; >> + >> + scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); >> + if (!scom) >> + return -ENOMEM; >> + >> + snprintf(scom->name, sizeof(scom->name), >> + "scom%d", atomic_inc_return(&scom_idx)); >> + scom->fsi_dev = fsi_dev; >> + scom->mdev.minor = MISC_DYNAMIC_MINOR; >> + scom->mdev.fops = &scom_fops; >> + scom->mdev.name = scom->name; >> + scom->mdev.parent = dev; >> + list_add(&scom->link, &scom_devices); >> + >> + return misc_register(&scom->mdev); >> +} >> + >> +static struct fsi_device_id scom_ids[] = { >> + { >> + .engine_type = FSI_ENGID_SCOM, >> + .version = FSI_VERSION_ANY, >> + }, >> + { 0 } >> +}; >> + >> +static struct fsi_driver scom_drv = { >> + .id_table = scom_ids, >> + .drv = { >> + .name = "scom", >> + .bus = &fsi_bus_type, >> + .probe = scom_probe, >> + } >> +}; >> + >> +static int scom_init(void) >> +{ >> + INIT_LIST_HEAD(&scom_devices); >> + return fsi_driver_register(&scom_drv); >> +} >> + >> +static void scom_exit(void) >> +{ >> + struct list_head *pos; >> + struct scom_device *scom = NULL; >> + >> + list_for_each(pos, &scom_devices) { >> + scom = list_entry(pos, struct scom_device, link); >> + misc_deregister(&scom->mdev); >> + devm_kfree(&scom->fsi_dev->dev, scom); >> + } >> + fsi_driver_unregister(&scom_drv); >> +} >> + >> +module_init(scom_init); >> +module_exit(scom_exit); >> +MODULE_LICENSE("GPL"); >> >
On Mon, Nov 14, 2016 at 11:13 AM, Christopher Bostic <christopher.lee.bostic@gmail.com> wrote: > On Sun, Nov 13, 2016 at 5:44 PM, Alistair Popple <alistair@popple.id.au> wrote: >> Hi Chris, >> >> Is there a similar device driver for the CFAM/FSI that I missed? That is will >> there be a chardev for read/writing FSI addresses directly (ie. a chardev to >> call fsi_device_read/write)? Probably not critical for an initial release but >> we will need one at some point as I'm pretty sure the cronus server for >> example has get/putcfam commands as some registers aren't available via SCOM. >> >> Regards, >> >> Alistair >> > > Hi Alistair, > > No there isn't a driver like you describe in the set as it is now. I > agree that would be important to have for general bringup debug. > Once this set is accepted I'll add that. > To clarify, for each engine's address space in a cfam there will be a 'client' driver that is responsible for accessing that range. SCOM driver for scom space, etc... Right now there is only the SCOM client driver but I'll be filling in the others shortly. The idea though of having a general read/write driver in addition still makes sense to me for bringup purposes. This is I assume more in line with how you've structured pdbg. Regards, > Thanks > >> On Sun, 30 Oct 2016 05:09:20 PM christopher.lee.bostic@gmail.com wrote: >>> From: Chris Bostic <cbostic@us.ibm.com> >>> >>> Create a simple SCOM engine device driver that reads and writes >>> across an FSI bus. >>> >>> Signed-off-by: Chris Bostic <cbostic@us.ibm.com> >>> >>> --- >>> >>> V4 - Add put_scom and get_scom operations >>> >>> V5 - Add character device registration and fill in read/write >>> syscalls. >>> >>> V6 - Add multi scom engine support. Add list of devices. >>> Use file private data to store and hand off scom structures. >>> >>> - Add lseek, open, close >>> >>> - Remove data/address structure to pass from user space to >>> kernel. Use offset provided by read/write and pass data >>> >>> - Added list setup in init and so did not add the macro >>> module_fsi_driver >>> >>> - Global structs made static where possible. >>> >>> - Change from char dev to use misc device api as shown >>> in examples from Jeremy Kere. >>> --- >>> drivers/fsi/Kconfig | 6 ++ >>> drivers/fsi/Makefile | 1 + >>> drivers/fsi/fsi-scom.c | 238 >> +++++++++++++++++++++++++++++++++++++++++++++++++ >>> 3 files changed, 245 insertions(+) >>> create mode 100644 drivers/fsi/fsi-scom.c >>> >>> diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig >>> index b21fe3c..1fa9bc0 100644 >>> --- a/drivers/fsi/Kconfig >>> +++ b/drivers/fsi/Kconfig >>> @@ -23,6 +23,12 @@ config FSI_MASTER_GPIO >>> depends on FSI >>> ---help--- >>> This option enables a FSI master driver using GPIO lines. >>> + >>> +config FSI_SCOM >>> + tristate "SCOM FSI client" >>> + depends on FSI >>> + ---help--- >>> + This option enables the SCOM FSI client device driver. >>> endif >>> >>> endmenu >>> diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile >>> index 2021ce5..3e31d9a 100644 >>> --- a/drivers/fsi/Makefile >>> +++ b/drivers/fsi/Makefile >>> @@ -2,3 +2,4 @@ >>> obj-$(CONFIG_FSI) += fsi-core.o >>> obj-$(CONFIG_FSI_MASTER_FAKE) += fsi-master-fake.o >>> obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o >>> +obj-$(CONFIG_FSI_SCOM) += fsi-scom.o >>> diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c >>> new file mode 100644 >>> index 0000000..27be082 >>> --- /dev/null >>> +++ b/drivers/fsi/fsi-scom.c >>> @@ -0,0 +1,238 @@ >>> +/* >>> + * SCOM FSI Client device driver >>> + * >>> + * Copyright (C) IBM Corporation 2016 >>> + * >>> + * This program is free software; you can redistribute it and/or modify >>> + * it under the terms of the GNU General Public License version 2 as >>> + * published by the Free Software Foundation. >>> + * >>> + * This program is distributed in the hope that it will be useful, >>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >>> + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>> + * GNU General Public License for more details. >>> + */ >>> + >>> +#include <linux/fsi.h> >>> +#include <linux/module.h> >>> +#include <linux/cdev.h> >>> +#include <linux/delay.h> >>> +#include <linux/fs.h> >>> +#include <linux/uaccess.h> >>> +#include <linux/slab.h> >>> +#include <linux/miscdevice.h> >>> +#include <linux/list.h> >>> + >>> +#define FSI_ENGID_SCOM 0x5 >>> + >>> +#define SCOM_FSI2PIB_DELAY 50 >>> + >>> +/* SCOM engine register set */ >>> +#define SCOM_DATA0_REG 0x00 >>> +#define SCOM_DATA1_REG 0x04 >>> +#define SCOM_CMD_REG 0x08 >>> +#define SCOM_RESET_REG 0x1C >>> + >>> +#define SCOM_RESET_CMD 0x80000000 >>> +#define SCOM_WRITE_CMD 0x80000000 >>> + >>> +struct scom_device { >>> + struct list_head link; >>> + struct fsi_device *fsi_dev; >>> + struct miscdevice mdev; >>> + char name[32]; >>> +}; >>> + >>> +#define to_scom_dev(x) container_of((x), struct scom_device, >> mdev) >>> + >>> +static struct list_head scom_devices; >>> +static atomic_t scom_idx = ATOMIC_INIT(0); >>> +static int scom_probe(struct device *); >>> + >>> +static int put_scom(struct scom_device *scom_dev, uint64_t value, >>> + uint32_t addr) >>> +{ >>> + int rc; >>> + uint32_t data = SCOM_RESET_CMD; >>> + >>> + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_RESET_REG, &data, >>> + sizeof(uint32_t)); >>> + if (rc) >>> + return rc; >>> + >>> + data = (value >> 32) & 0xffffffff; >>> + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, >>> + sizeof(uint32_t)); >>> + if (rc) >>> + return rc; >>> + >>> + data = value & 0xffffffff; >>> + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, >>> + sizeof(uint32_t)); >>> + if (rc) >>> + return rc; >>> + >>> + data = SCOM_WRITE_CMD | addr; >>> + return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, >>> + sizeof(uint32_t)); >>> +} >>> + >>> +static int get_scom(struct scom_device *scom_dev, uint64_t *value, >>> + uint32_t addr) >>> +{ >>> + uint32_t result, data; >>> + int rc; >>> + >>> + udelay(SCOM_FSI2PIB_DELAY); >>> + >>> + data = addr; >>> + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, >>> + sizeof(uint32_t)); >>> + if (rc) >>> + return rc; >>> + >>> + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, >>> + sizeof(uint32_t)); >>> + if (rc) >>> + return rc; >>> + >>> + *value |= (uint64_t) result << 32; >>> + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, >>> + sizeof(uint32_t)); >>> + if (rc) >>> + return rc; >>> + >>> + *value |= result; >>> + >>> + return 0; >>> +} >>> + >>> +static loff_t scom_llseek(struct file *filep, loff_t offset, int whence) >>> +{ >>> + if (whence != 0) /* SEEK_SET */ >>> + return -EINVAL; >>> + >>> + filep->f_pos = offset; >>> + return offset; >>> +} >>> + >>> +static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, >>> + loff_t *offset) >>> +{ >>> + int rc = 0; >>> + struct miscdevice *mdev = >>> + (struct miscdevice *)filep->private_data; >>> + struct scom_device *scom = to_scom_dev(mdev); >>> + struct device *dev = &scom->fsi_dev->dev; >>> + uint64_t val; >>> + >>> + if (len != sizeof(uint64_t)) >>> + return -EINVAL; >>> + >>> + rc = get_scom(scom, &val, *offset); >>> + if (rc) { >>> + dev_dbg(dev, "get_scom fail:%d\n", rc); >>> + return rc; >>> + } >>> + >>> + rc = copy_to_user(buf, &val, len); >>> + if (rc) >>> + dev_dbg(dev, "copy to user failed:%d\n", rc); >>> + >>> + return rc ? rc : len; >>> +} >>> + >>> +static ssize_t scom_write(struct file *filep, const char __user *buf, >>> + size_t len, loff_t *offset) >>> +{ >>> + int rc = 0; >>> + struct miscdevice *mdev = >>> + (struct miscdevice *)filep->private_data; >>> + struct scom_device *scom = to_scom_dev(mdev); >>> + struct device *dev = &scom->fsi_dev->dev; >>> + uint64_t val; >>> + >>> + if (len != sizeof(uint64_t)) >>> + return -EINVAL; >>> + >>> + rc = copy_from_user(&val, buf, len); >>> + if (rc) { >>> + dev_dbg(dev, "copy from user failed:%d\n", rc); >>> + return -EINVAL; >>> + } >>> + >>> + rc = put_scom(scom, val, *offset); >>> + if (rc) >>> + dev_dbg(dev, "put_scom failed with:%d\n", rc); >>> + >>> + >>> + return rc ? rc : len; >>> +} >>> + >>> +static const struct file_operations scom_fops = { >>> + .owner = THIS_MODULE, >>> + .llseek = scom_llseek, >>> + .read = scom_read, >>> + .write = scom_write, >>> +}; >>> + >>> +static int scom_probe(struct device *dev) >>> +{ >>> + struct fsi_device *fsi_dev = to_fsi_dev(dev); >>> + struct scom_device *scom = NULL; >>> + >>> + scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); >>> + if (!scom) >>> + return -ENOMEM; >>> + >>> + snprintf(scom->name, sizeof(scom->name), >>> + "scom%d", atomic_inc_return(&scom_idx)); >>> + scom->fsi_dev = fsi_dev; >>> + scom->mdev.minor = MISC_DYNAMIC_MINOR; >>> + scom->mdev.fops = &scom_fops; >>> + scom->mdev.name = scom->name; >>> + scom->mdev.parent = dev; >>> + list_add(&scom->link, &scom_devices); >>> + >>> + return misc_register(&scom->mdev); >>> +} >>> + >>> +static struct fsi_device_id scom_ids[] = { >>> + { >>> + .engine_type = FSI_ENGID_SCOM, >>> + .version = FSI_VERSION_ANY, >>> + }, >>> + { 0 } >>> +}; >>> + >>> +static struct fsi_driver scom_drv = { >>> + .id_table = scom_ids, >>> + .drv = { >>> + .name = "scom", >>> + .bus = &fsi_bus_type, >>> + .probe = scom_probe, >>> + } >>> +}; >>> + >>> +static int scom_init(void) >>> +{ >>> + INIT_LIST_HEAD(&scom_devices); >>> + return fsi_driver_register(&scom_drv); >>> +} >>> + >>> +static void scom_exit(void) >>> +{ >>> + struct list_head *pos; >>> + struct scom_device *scom = NULL; >>> + >>> + list_for_each(pos, &scom_devices) { >>> + scom = list_entry(pos, struct scom_device, link); >>> + misc_deregister(&scom->mdev); >>> + devm_kfree(&scom->fsi_dev->dev, scom); >>> + } >>> + fsi_driver_unregister(&scom_drv); >>> +} >>> + >>> +module_init(scom_init); >>> +module_exit(scom_exit); >>> +MODULE_LICENSE("GPL"); >>> >>
diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index b21fe3c..1fa9bc0 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -23,6 +23,12 @@ config FSI_MASTER_GPIO depends on FSI ---help--- This option enables a FSI master driver using GPIO lines. + +config FSI_SCOM + tristate "SCOM FSI client" + depends on FSI + ---help--- + This option enables the SCOM FSI client device driver. endif endmenu diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index 2021ce5..3e31d9a 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_FSI) += fsi-core.o obj-$(CONFIG_FSI_MASTER_FAKE) += fsi-master-fake.o obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o +obj-$(CONFIG_FSI_SCOM) += fsi-scom.o diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c new file mode 100644 index 0000000..27be082 --- /dev/null +++ b/drivers/fsi/fsi-scom.c @@ -0,0 +1,238 @@ +/* + * SCOM FSI Client device driver + * + * Copyright (C) IBM Corporation 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/fsi.h> +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/miscdevice.h> +#include <linux/list.h> + +#define FSI_ENGID_SCOM 0x5 + +#define SCOM_FSI2PIB_DELAY 50 + +/* SCOM engine register set */ +#define SCOM_DATA0_REG 0x00 +#define SCOM_DATA1_REG 0x04 +#define SCOM_CMD_REG 0x08 +#define SCOM_RESET_REG 0x1C + +#define SCOM_RESET_CMD 0x80000000 +#define SCOM_WRITE_CMD 0x80000000 + +struct scom_device { + struct list_head link; + struct fsi_device *fsi_dev; + struct miscdevice mdev; + char name[32]; +}; + +#define to_scom_dev(x) container_of((x), struct scom_device, mdev) + +static struct list_head scom_devices; +static atomic_t scom_idx = ATOMIC_INIT(0); +static int scom_probe(struct device *); + +static int put_scom(struct scom_device *scom_dev, uint64_t value, + uint32_t addr) +{ + int rc; + uint32_t data = SCOM_RESET_CMD; + + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_RESET_REG, &data, + sizeof(uint32_t)); + if (rc) + return rc; + + data = (value >> 32) & 0xffffffff; + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, + sizeof(uint32_t)); + if (rc) + return rc; + + data = value & 0xffffffff; + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, + sizeof(uint32_t)); + if (rc) + return rc; + + data = SCOM_WRITE_CMD | addr; + return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, + sizeof(uint32_t)); +} + +static int get_scom(struct scom_device *scom_dev, uint64_t *value, + uint32_t addr) +{ + uint32_t result, data; + int rc; + + udelay(SCOM_FSI2PIB_DELAY); + + data = addr; + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, + sizeof(uint32_t)); + if (rc) + return rc; + + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, + sizeof(uint32_t)); + if (rc) + return rc; + + *value |= (uint64_t) result << 32; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, + sizeof(uint32_t)); + if (rc) + return rc; + + *value |= result; + + return 0; +} + +static loff_t scom_llseek(struct file *filep, loff_t offset, int whence) +{ + if (whence != 0) /* SEEK_SET */ + return -EINVAL; + + filep->f_pos = offset; + return offset; +} + +static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, + loff_t *offset) +{ + int rc = 0; + struct miscdevice *mdev = + (struct miscdevice *)filep->private_data; + struct scom_device *scom = to_scom_dev(mdev); + struct device *dev = &scom->fsi_dev->dev; + uint64_t val; + + if (len != sizeof(uint64_t)) + return -EINVAL; + + rc = get_scom(scom, &val, *offset); + if (rc) { + dev_dbg(dev, "get_scom fail:%d\n", rc); + return rc; + } + + rc = copy_to_user(buf, &val, len); + if (rc) + dev_dbg(dev, "copy to user failed:%d\n", rc); + + return rc ? rc : len; +} + +static ssize_t scom_write(struct file *filep, const char __user *buf, + size_t len, loff_t *offset) +{ + int rc = 0; + struct miscdevice *mdev = + (struct miscdevice *)filep->private_data; + struct scom_device *scom = to_scom_dev(mdev); + struct device *dev = &scom->fsi_dev->dev; + uint64_t val; + + if (len != sizeof(uint64_t)) + return -EINVAL; + + rc = copy_from_user(&val, buf, len); + if (rc) { + dev_dbg(dev, "copy from user failed:%d\n", rc); + return -EINVAL; + } + + rc = put_scom(scom, val, *offset); + if (rc) + dev_dbg(dev, "put_scom failed with:%d\n", rc); + + + return rc ? rc : len; +} + +static const struct file_operations scom_fops = { + .owner = THIS_MODULE, + .llseek = scom_llseek, + .read = scom_read, + .write = scom_write, +}; + +static int scom_probe(struct device *dev) +{ + struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct scom_device *scom = NULL; + + scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); + if (!scom) + return -ENOMEM; + + snprintf(scom->name, sizeof(scom->name), + "scom%d", atomic_inc_return(&scom_idx)); + scom->fsi_dev = fsi_dev; + scom->mdev.minor = MISC_DYNAMIC_MINOR; + scom->mdev.fops = &scom_fops; + scom->mdev.name = scom->name; + scom->mdev.parent = dev; + list_add(&scom->link, &scom_devices); + + return misc_register(&scom->mdev); +} + +static struct fsi_device_id scom_ids[] = { + { + .engine_type = FSI_ENGID_SCOM, + .version = FSI_VERSION_ANY, + }, + { 0 } +}; + +static struct fsi_driver scom_drv = { + .id_table = scom_ids, + .drv = { + .name = "scom", + .bus = &fsi_bus_type, + .probe = scom_probe, + } +}; + +static int scom_init(void) +{ + INIT_LIST_HEAD(&scom_devices); + return fsi_driver_register(&scom_drv); +} + +static void scom_exit(void) +{ + struct list_head *pos; + struct scom_device *scom = NULL; + + list_for_each(pos, &scom_devices) { + scom = list_entry(pos, struct scom_device, link); + misc_deregister(&scom->mdev); + devm_kfree(&scom->fsi_dev->dev, scom); + } + fsi_driver_unregister(&scom_drv); +} + +module_init(scom_init); +module_exit(scom_exit); +MODULE_LICENSE("GPL");