-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Christopher Bostic
authored and
Greg Kroah-Hartman
committed
Jun 9, 2017
1 parent
1247cf7
commit 680ca6d
Showing
3 changed files
with
269 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
|
||
obj-$(CONFIG_FSI) += fsi-core.o | ||
obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o | ||
obj-$(CONFIG_FSI_SCOM) += fsi-scom.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
/* | ||
* 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> | ||
#include <linux/idr.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]; | ||
int idx; | ||
}; | ||
|
||
#define to_scom_dev(x) container_of((x), struct scom_device, mdev) | ||
|
||
static struct list_head scom_devices; | ||
|
||
static DEFINE_IDA(scom_ida); | ||
|
||
static int put_scom(struct scom_device *scom_dev, uint64_t value, | ||
uint32_t addr) | ||
{ | ||
int rc; | ||
uint32_t data; | ||
|
||
data = cpu_to_be32(SCOM_RESET_CMD); | ||
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_RESET_REG, &data, | ||
sizeof(uint32_t)); | ||
if (rc) | ||
return rc; | ||
|
||
data = cpu_to_be32((value >> 32) & 0xffffffff); | ||
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, | ||
sizeof(uint32_t)); | ||
if (rc) | ||
return rc; | ||
|
||
data = cpu_to_be32(value & 0xffffffff); | ||
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, | ||
sizeof(uint32_t)); | ||
if (rc) | ||
return rc; | ||
|
||
data = cpu_to_be32(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; | ||
|
||
*value = 0ULL; | ||
data = cpu_to_be32(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)cpu_to_be32(result) << 32; | ||
rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, | ||
sizeof(uint32_t)); | ||
if (rc) | ||
return rc; | ||
|
||
*value |= cpu_to_be32(result); | ||
|
||
return 0; | ||
} | ||
|
||
static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, | ||
loff_t *offset) | ||
{ | ||
int rc; | ||
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; | ||
struct miscdevice *mdev = 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; | ||
} | ||
|
||
return len; | ||
} | ||
|
||
static loff_t scom_llseek(struct file *file, loff_t offset, int whence) | ||
{ | ||
switch (whence) { | ||
case SEEK_CUR: | ||
break; | ||
case SEEK_SET: | ||
file->f_pos = offset; | ||
break; | ||
default: | ||
return -EINVAL; | ||
} | ||
|
||
return offset; | ||
} | ||
|
||
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; | ||
|
||
scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); | ||
if (!scom) | ||
return -ENOMEM; | ||
|
||
scom->idx = ida_simple_get(&scom_ida, 1, INT_MAX, GFP_KERNEL); | ||
snprintf(scom->name, sizeof(scom->name), "scom%d", 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 int scom_remove(struct device *dev) | ||
{ | ||
struct scom_device *scom, *scom_tmp; | ||
struct fsi_device *fsi_dev = to_fsi_dev(dev); | ||
|
||
list_for_each_entry_safe(scom, scom_tmp, &scom_devices, link) { | ||
if (scom->fsi_dev == fsi_dev) { | ||
list_del(&scom->link); | ||
ida_simple_remove(&scom_ida, scom->idx); | ||
misc_deregister(&scom->mdev); | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
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, | ||
.remove = scom_remove, | ||
} | ||
}; | ||
|
||
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; | ||
|
||
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"); |