diff mbox

[linux,v6,18/18] drivers/fsi: Add SCOM FSI client device driver

Message ID 1477865379-11566-19-git-send-email-christopher.lee.bostic@gmail.com
State Changes Requested, archived
Headers show

Commit Message

christopher.lee.bostic@gmail.com Oct. 30, 2016, 10:09 p.m. UTC
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

Comments

Jeremy Kerr Oct. 31, 2016, 1:22 a.m. UTC | #1
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
christopher.lee.bostic@gmail.com Nov. 3, 2016, 4:47 p.m. UTC | #2
>> +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
Alistair Popple Nov. 13, 2016, 11:44 p.m. UTC | #3
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");
>
christopher.lee.bostic@gmail.com Nov. 14, 2016, 5:13 p.m. UTC | #4
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");
>>
>
christopher.lee.bostic@gmail.com Nov. 14, 2016, 10:43 p.m. UTC | #5
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 mbox

Patch

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");