* [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client
@ 2017-02-02 23:25 eajames
2017-02-02 23:25 ` [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver eajames
` (8 more replies)
0 siblings, 9 replies; 16+ messages in thread
From: eajames @ 2017-02-02 23:25 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Edward A. James
From: "Edward A. James" <eajames@us.ibm.com>
First, this series add interrupt polling support to the FSI bus driver.
This implements a loop that constantly polls the FSI slave interrupt
status register and triggers any registered client interrupt handlers upon
detecting an interrupt.
Secondly, this series adds the I2C client driver for FSI bus. This driver
interacts with the IBM BOE engine on an FSI slave device.
Christopher Bostic (3):
drivers/fsi: Add slave interrupt polling
drivers/fsi: Add Client IRQ Enable / Disable
drivers/fsi: Add sysfs file to adjust i-poll period
Edward A. James (5):
drivers: fsi: Add i2c client driver
drivers: fsi: i2c: Add engine access wrappers
drivers: fsi: i2c: probe fsi device for i2c client
drivers: fsi: i2c: add driver file operations and bus locking
drivers: fsi: i2c: boe engine
drivers/fsi/Kconfig | 6 +
drivers/fsi/Makefile | 1 +
drivers/fsi/fsi-core.c | 170 +++
drivers/fsi/fsi-master-gpio.c | 1 +
drivers/fsi/fsi-master.h | 2 +
drivers/fsi/i2c/Makefile | 1 +
drivers/fsi/i2c/iic-boe.c | 1597 +++++++++++++++++++++++++++++
drivers/fsi/i2c/iic-boe.h | 180 ++++
drivers/fsi/i2c/iic-fsi.c | 715 +++++++++++++
drivers/fsi/i2c/iic-int.h | 395 +++++++
drivers/fsi/i2c/iic-lock.c | 439 ++++++++
drivers/fsi/i2c/iic-mstr.c | 2281 +++++++++++++++++++++++++++++++++++++++++
include/linux/fsi.h | 5 +
include/uapi/linux/Kbuild | 1 +
include/uapi/linux/i2cfsi.h | 136 +++
15 files changed, 5930 insertions(+)
create mode 100644 drivers/fsi/i2c/Makefile
create mode 100644 drivers/fsi/i2c/iic-boe.c
create mode 100644 drivers/fsi/i2c/iic-boe.h
create mode 100644 drivers/fsi/i2c/iic-fsi.c
create mode 100644 drivers/fsi/i2c/iic-int.h
create mode 100644 drivers/fsi/i2c/iic-lock.c
create mode 100644 drivers/fsi/i2c/iic-mstr.c
create mode 100644 include/uapi/linux/i2cfsi.h
--
1.8.3.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
@ 2017-02-02 23:25 ` eajames
2017-02-03 0:56 ` Alistair Popple
2017-02-02 23:25 ` [PATCH linux v1 5/8] drivers: fsi: i2c: Add engine access wrappers eajames
` (7 subsequent siblings)
8 siblings, 1 reply; 16+ messages in thread
From: eajames @ 2017-02-02 23:25 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Edward A. James
From: "Edward A. James" <eajames@us.ibm.com>
stub for I2C driver over FSI
Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
drivers/fsi/Kconfig | 6 ++
drivers/fsi/Makefile | 1 +
drivers/fsi/i2c/Makefile | 1 +
drivers/fsi/i2c/iic-fsi.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/fsi/i2c/iic-fsi.h | 29 ++++++++
drivers/fsi/i2c/iic-int.h | 80 ++++++++++++++++++++++
6 files changed, 283 insertions(+)
create mode 100644 drivers/fsi/i2c/Makefile
create mode 100644 drivers/fsi/i2c/iic-fsi.c
create mode 100644 drivers/fsi/i2c/iic-fsi.h
create mode 100644 drivers/fsi/i2c/iic-int.h
diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
index 0fa265c..c78b9b6e 100644
--- a/drivers/fsi/Kconfig
+++ b/drivers/fsi/Kconfig
@@ -24,6 +24,12 @@ config FSI_SCOM
---help---
This option enables an FSI based SCOM device driver.
+config FSI_I2C
+ tristate "I2C FSI client device driver"
+ depends on FSI
+ ---help---
+ This option enables an FSI based I2C device driver.
+
endif
endmenu
diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
index 3466f08..3a106ba 100644
--- a/drivers/fsi/Makefile
+++ b/drivers/fsi/Makefile
@@ -2,3 +2,4 @@
obj-$(CONFIG_FSI) += fsi-core.o
obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
+obj-$(CONFIG_FSI_I2C) += i2c/
diff --git a/drivers/fsi/i2c/Makefile b/drivers/fsi/i2c/Makefile
new file mode 100644
index 0000000..f9f9048
--- /dev/null
+++ b/drivers/fsi/i2c/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_FSI_I2C) += iic-fsi.o
diff --git a/drivers/fsi/i2c/iic-fsi.c b/drivers/fsi/i2c/iic-fsi.c
new file mode 100644
index 0000000..51a15a4
--- /dev/null
+++ b/drivers/fsi/i2c/iic-fsi.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2006, 2010, 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file contains the architecture independent IIC FSI code.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/fsi.h>
+#include <asm/io.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include "iic-int.h"
+#include "iic-fsi.h"
+
+#include <linux/delay.h>
+#include <linux/moduleparam.h>
+
+struct class* iic_fsi_class = 0;
+dev_t iic_devnum_start = 0;
+
+static const char iic_fsi_version[] = "3.0";
+
+int iic_fsi_probe(struct device *dev);
+int iic_fsi_remove(struct device *dev);
+
+struct iic_reg_access fsi_reg_access =
+{
+ .bus_readb = readb_wrap,
+ .bus_readh = readh_wrap,
+ .bus_readw = readw_wrap,
+ .bus_writeb = writeb_wrap,
+ .bus_writeh = writeh_wrap,
+ .bus_writew = writew_wrap,
+};
+
+static const struct fsi_device_id i2c_ids[] = {
+ {
+ .engine_type = FSI_ENGID_I2C,
+ .version = FSI_VERSION_ANY,
+ },
+ {
+ .engine_type = FSI_ENGID_I2C_BB,
+ .version = FSI_VERSION_ANY,
+ },
+ { 0 }
+};
+
+static const struct fsi_driver i2c_drv = {
+ .id_table = i2c_ids,
+ .drv = {
+ .name = "iic_fsi_dd",
+ .bus = &fsi_bus_type,
+ .probe = iic_fsi_probe,
+ .remove = iic_fsi_remove,
+ }
+};
+
+/*
+ * Called when an FSI IIC engine is plugged in.
+ * Causes creation of the /dev entry.
+ * Not allowed to access engine registers in this function.
+ *
+ */
+int iic_fsi_probe(struct device *dev)
+{
+ return 0;
+}
+
+/* This function is called when a link is removed or the driver is unloaded.
+ * It's job is to remove the device from the device hierarchy including
+ * removal from sysfs (this is where device files get removed).
+ */
+int iic_fsi_remove(struct device* dev)
+{
+ return 0;
+}
+
+/*
+ * Initialize this module. Creates a class for fsi connected iic devices and
+ * allocates device numbers for them.
+ */
+static int __init iic_fsi_init(void)
+{
+ int rc = 0;
+
+ IENTER();
+
+ rc = alloc_chrdev_region(&iic_devnum_start,
+ 0, /*starting minor number*/
+ IIC_FSI_MAX_DEVS,
+ "iic-fsi");
+ if(rc)
+ {
+ IFLDe(1, "master alloc_chrdev_region failed: rc = %d\n", rc);
+ return rc;
+ }
+
+ iic_fsi_class = class_create(THIS_MODULE, "iic-fsi");
+ if (IS_ERR(iic_fsi_class))
+ {
+ IFLDe(1, "class_create failed: rc=%ld",
+ PTR_ERR(iic_fsi_class));
+ goto exit_class_create;
+ }
+ /* Register this driver with the FSI infrastructure */
+ rc = fsi_driver_register(&i2c_drv);
+ if(rc)
+ {
+ IFLDe(1, "fsidrv_register failed: %d", rc);
+ goto exit_drv_register;
+ }
+
+ printk("IIC FSI support loaded, ver. %s\n", iic_fsi_version);
+
+ IEXIT(rc);
+ return rc;
+
+exit_drv_register:
+ class_destroy(iic_fsi_class);
+exit_class_create:
+ unregister_chrdev_region(iic_devnum_start, IIC_FSI_MAX_DEVS);
+ IEXIT(rc);
+ return rc;
+}
+
+static void __exit iic_fsi_exit(void)
+{
+ IENTER();
+ IDBGd(0, "fisdrv_unregister()\n");
+ fsi_driver_unregister(&i2c_drv);
+ IDBGd(0, "unregister_chrdev_region()\n");
+ unregister_chrdev_region(iic_devnum_start, IIC_FSI_MAX_DEVS);
+ if(iic_fsi_class)
+ {
+ IDBGd(0, "class_destroy\n");
+ class_destroy(iic_fsi_class);
+ }
+ printk("IIC FSI support unloaded.\n");
+ IEXIT(0);
+}
+
+module_init(iic_fsi_init);
+module_exit(iic_fsi_exit);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("IIC FSI Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/fsi/i2c/iic-fsi.h b/drivers/fsi/i2c/iic-fsi.h
new file mode 100644
index 0000000..4decb2c
--- /dev/null
+++ b/drivers/fsi/i2c/iic-fsi.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef IIC_FSI_H
+#define IIC_FSI_H
+
+/*
+ * we don't know ahead of time how many minors will be needed as this is based
+ * on platform type and the possibility of sparsely populated FRU's on special
+ * systems so picking a safely large number
+ */
+#define IIC_FSI_MAX_DEVS 1024
+
+#endif
diff --git a/drivers/fsi/i2c/iic-int.h b/drivers/fsi/i2c/iic-int.h
new file mode 100644
index 0000000..545b2c3
--- /dev/null
+++ b/drivers/fsi/i2c/iic-int.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2006, 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef IIC_INT_H
+#define IIC_INT_H
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/kobject.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/kdev_t.h>
+#include <linux/wait.h>
+#include <linux/i2cfsi.h>
+#include <linux/semaphore.h>
+#include <linux/fsi.h>
+#include <asm/atomic.h>
+
+#define FSI_ENGID_I2C 0x7
+#define FSI_ENGID_I2C_BB 0x17
+
+#ifdef FSI_I2C_DEBUG
+#define IDBGs(num, msg, args...) printk(msg, ## args)
+#define IDBGd(num, msg, args...) printk(msg, ## args)
+#define IDBGf(num, msg, args...) printk(msg, ## args)
+#define IDBGl(num, msg, args...) printk(msg, ## args)
+#else
+#define IDBGs(num, msg, args...)
+#define IDBGd(num, msg, args...)
+#define IDBGf(num, msg, args...)
+#define IDBGl(num, msg, args...)
+#endif
+
+#define IENTER()
+#define IEXIT(RC)
+
+/* IFLDx traces will not get compiled out */
+#define IFLDe(num, msg, args...)\
+ printk("ERR: "msg, ## args)
+#define IFLDi(num, msg, args...)\
+ printk(msg, ## args)
+
+#ifdef FSI_I2C_DEBUG
+#define IFLDs(num, msg, args...) printk(msg, ## args)
+#define IFLDd(num, msg, args...) printk(msg, ## args)
+#define IFLDf(num, msg, args...) printk(msg, ## args)
+#define IFLDl(num, msg, args...) printk(msg, ## args)
+#else
+#define IFLDs(num, msg, args...)
+#define IFLDd(num, msg, args...)
+#define IFLDf(num, msg, args...)
+#define IFLDl(num, msg, args...)
+#endif
+
+struct iic_reg_access
+{
+ int (*bus_readb)(iic_eng_t*, unsigned int, unsigned char*, iic_ffdc_t**);
+ int (*bus_readh)(iic_eng_t*, unsigned int, unsigned short*, iic_ffdc_t**);
+ int (*bus_readw)(iic_eng_t*, unsigned int, unsigned long*, iic_ffdc_t**);
+ int (*bus_writeb)(iic_eng_t*, unsigned int, unsigned char, iic_ffdc_t**);
+ int (*bus_writeh)(iic_eng_t*, unsigned int, unsigned short, iic_ffdc_t**);
+ int (*bus_writew)(iic_eng_t*, unsigned int, unsigned long, iic_ffdc_t**);
+};
+
+#endif
--
1.8.3.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH linux v1 5/8] drivers: fsi: i2c: Add engine access wrappers
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
2017-02-02 23:25 ` [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver eajames
@ 2017-02-02 23:25 ` eajames
2017-02-02 23:25 ` [PATCH linux v1 6/8] drivers: fsi: i2c: probe fsi device for i2c client eajames
` (6 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: eajames @ 2017-02-02 23:25 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Edward A. James
From: "Edward A. James" <eajames@us.ibm.com>
Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
drivers/fsi/i2c/iic-fsi.c | 84 +++++++++++++
drivers/fsi/i2c/iic-fsi.h | 87 +++++++++++++
drivers/fsi/i2c/iic-int.h | 315 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 486 insertions(+)
diff --git a/drivers/fsi/i2c/iic-fsi.c b/drivers/fsi/i2c/iic-fsi.c
index 51a15a4..53be538 100644
--- a/drivers/fsi/i2c/iic-fsi.c
+++ b/drivers/fsi/i2c/iic-fsi.c
@@ -41,6 +41,90 @@ static const char iic_fsi_version[] = "3.0";
int iic_fsi_probe(struct device *dev);
int iic_fsi_remove(struct device *dev);
+int readb_wrap(iic_eng_t* eng, unsigned int addr, unsigned char *val,
+ iic_ffdc_t** ffdc)
+{
+ int rc;
+ struct fsi_device *fsi_dev = to_fsi_dev(eng->dev);
+ rc = fsi_device_read(fsi_dev, addr, val, 1);
+ if(rc)
+ {
+ IFLDe(3, "eng[%08x]: fsi_readb_ffdc(%p)=%d\n", eng->id,
+ addr, rc);
+ }
+ return rc;
+};
+
+int readh_wrap(iic_eng_t* eng, unsigned int addr, unsigned short *val,
+ iic_ffdc_t** ffdc)
+{
+ int rc;
+ struct fsi_device *fsi_dev = to_fsi_dev(eng->dev);
+ rc = fsi_device_read(fsi_dev, addr, val, 2);
+ if(rc)
+ {
+ IFLDe(3, "eng[%08x]: fsi_readh_ffdc(%p)=%d\n", eng->id,
+ addr, rc);
+ }
+ return rc;
+};
+
+int readw_wrap(iic_eng_t* eng, unsigned int addr, unsigned long *val,
+ iic_ffdc_t** ffdc)
+{
+ int rc;
+ struct fsi_device *fsi_dev = to_fsi_dev(eng->dev);
+ rc = fsi_device_read(fsi_dev, addr, val, 4);
+ if(rc)
+ {
+ IFLDe(3, "eng[%08x]: fsi_readw_ffdc(%p)=%d\n", eng->id,
+ addr, rc);
+ }
+ return rc;
+};
+
+int writeb_wrap(iic_eng_t* eng, unsigned int addr, unsigned char val,
+ iic_ffdc_t** ffdc)
+{
+ int rc;
+ struct fsi_device *fsi_dev = to_fsi_dev(eng->dev);
+ rc = fsi_device_write(fsi_dev, addr, &val, 1);
+ if(rc)
+ {
+ IFLDe(3, "eng[%08x]: fsi_writeb_ffdc(%p)=%d\n", eng->id,
+ addr, rc);
+ }
+ return rc;
+};
+
+int writeh_wrap(iic_eng_t* eng, unsigned int addr, unsigned short val,
+ iic_ffdc_t** ffdc)
+{
+ int rc;
+ struct fsi_device *fsi_dev = to_fsi_dev(eng->dev);
+ rc = fsi_device_write(fsi_dev, addr, &val, 2);
+ if(rc)
+ {
+ IFLDe(3, "eng[%08x]: fsi_writeh_ffdc(%p)=%d\n", eng->id,
+ addr, rc);
+ }
+ return rc;
+};
+
+int writew_wrap(iic_eng_t* eng, unsigned int addr, unsigned long val,
+ iic_ffdc_t** ffdc)
+{
+ int rc;
+ struct fsi_device *fsi_dev = to_fsi_dev(eng->dev);
+ rc = fsi_device_write(fsi_dev, addr, &val, 4);
+ if(rc)
+ {
+ IFLDe(3, "eng[%08x]: fsi_writew_ffdc(%p)=%d\n", eng->id,
+ addr, rc);
+ }
+ return rc;
+};
+
struct iic_reg_access fsi_reg_access =
{
.bus_readb = readb_wrap,
diff --git a/drivers/fsi/i2c/iic-fsi.h b/drivers/fsi/i2c/iic-fsi.h
index 4decb2c..792803a 100644
--- a/drivers/fsi/i2c/iic-fsi.h
+++ b/drivers/fsi/i2c/iic-fsi.h
@@ -19,6 +19,31 @@
#ifndef IIC_FSI_H
#define IIC_FSI_H
+#define IIC_BUS_BITS 6
+#define IIC_ENG_BITS 5
+#define IIC_CFAM_BITS 2
+#define IIC_LINK_BITS 6
+#define IIC_PCFAM_BITS 2
+#define IIC_PLINK_BITS 6
+#define IIC_MAX_BUS (1U << IIC_BUS_BITS)
+#define IIC_MAX_ENG (1U << IIC_ENG_BITS)
+#define IIC_MAX_CFAM (1U << IIC_CFAM_BITS)
+#define IIC_MAX_LINK (1U << IIC_LINK_BITS)
+#define IIC_MAX_PCFAM (1U << IIC_PCFAM_BITS)
+#define IIC_MAX_PLINK (1U << IIC_PLINK_BITS)
+#define IIC_BUS_MASK ((1U << IIC_BUS_BITS) - 1U)
+#define IIC_ENG_MASK (((1U << IIC_ENG_BITS) - 1U) << IIC_BUS_BITS)
+#define IIC_CFAM_MASK (((1U << IIC_CFAM_BITS) - 1U) << \
+ (IIC_BUS_BITS + IIC_ENG_BITS))
+#define IIC_LINK_MASK (((1U << IIC_LINK_BITS) - 1U) << \
+ (IIC_BUS_BITS + IIC_ENG_BITS + IIC_CFAM_BITS))
+#define IIC_PCFAM_MASK (((1U << IIC_PCFAM_BITS) - 1U) << \
+ (IIC_BUS_BITS + IIC_ENG_BITS + \
+ IIC_CFAM_BITS + IIC_LINK_BITS))
+#define IIC_PLINK_MASK (((1U << IIC_PLINK_BITS) - 1U) << \
+ (IIC_BUS_BITS + IIC_ENG_BITS + IIC_CFAM_BITS + \
+ IIC_LINK_BITS + IIC_PCFAM_BITS))
+
/*
* we don't know ahead of time how many minors will be needed as this is based
* on platform type and the possibility of sparsely populated FRU's on special
@@ -26,4 +51,66 @@
*/
#define IIC_FSI_MAX_DEVS 1024
+#define IIC_SET_BUS(id, bus) \
+( \
+ (id & ~IIC_BUS_MASK) | ((unsigned long)bus & IIC_BUS_MASK) \
+)
+
+#define IIC_SET_ENG(id, eng) \
+( \
+ (id & ~IIC_ENG_MASK) | \
+ (((unsigned long)eng << IIC_BUS_BITS) & IIC_ENG_MASK) \
+)
+
+#define IIC_SET_CFAM(id, cfam) \
+( \
+ (id & ~IIC_CFAM_MASK) | \
+ (((unsigned long)cfam << (IIC_BUS_BITS + IIC_ENG_BITS)) & IIC_CFAM_MASK) \
+)
+
+#define IIC_SET_LINK(id, link) \
+( \
+ (id & ~IIC_LINK_MASK) | \
+ (((unsigned long)link << (IIC_BUS_BITS + IIC_ENG_BITS + IIC_CFAM_BITS)) & \
+ IIC_LINK_MASK) \
+)
+
+#define IIC_SET_PCFAM(id, pcfam) \
+( \
+ (id & ~IIC_PCFAM_MASK) | \
+ (((unsigned long)pcfam << \
+ (IIC_BUS_BITS + IIC_ENG_BITS + IIC_CFAM_BITS + IIC_LINK_BITS)) & \
+ IIC_PCFAM_MASK) \
+)
+
+#define IIC_SET_PLINK(id, plink) \
+( \
+ (id & ~IIC_PLINK_MASK) | \
+ (((unsigned long)plink << \
+ (IIC_BUS_BITS + IIC_ENG_BITS + IIC_CFAM_BITS + IIC_LINK_BITS + IIC_PCFAM_BITS)) & \
+ IIC_PLINK_MASK) \
+)
+
+#define IIC_MK_ENG_ID(plink, pcfam, link, cfam, eng) \
+(\
+ IIC_SET_BUS(IIC_SET_ENG(IIC_SET_CFAM(IIC_SET_LINK( \
+ IIC_SET_PCFAM(IIC_SET_PLINK(0U, plink), \
+ pcfam), \
+ link), \
+ cfam), \
+ eng), \
+ 0U) \
+)
+
+#define IIC_GET_BUS(id) (id & IIC_BUS_MASK)
+#define IIC_GET_ENG(id) ((id & IIC_ENG_MASK) >> IIC_BUS_BITS)
+#define IIC_GET_CFAM(id) ((id & IIC_CFAM_MASK) >> (IIC_BUS_BITS + IIC_ENG_BITS))
+#define IIC_GET_LINK(id) \
+((id & IIC_LINK_MASK) >> (IIC_BUS_BITS + IIC_ENG_BITS + IIC_CFAM_BITS))
+#define IIC_GET_PCFAM(id) \
+((id & IIC_PCFAM_MASK) >> (IIC_BUS_BITS + IIC_ENG_BITS + \
+ IIC_CFAM_BITS + IIC_LINK_BITS))
+#define IIC_GET_PLINK(id) \
+((id & IIC_PLINK_MASK) >> (IIC_BUS_BITS + IIC_ENG_BITS + \
+ IIC_CFAM_BITS + IIC_LINK_BITS + IIC_PCFAM_BITS))
#endif
diff --git a/drivers/fsi/i2c/iic-int.h b/drivers/fsi/i2c/iic-int.h
index 545b2c3..b29ece6 100644
--- a/drivers/fsi/i2c/iic-int.h
+++ b/drivers/fsi/i2c/iic-int.h
@@ -67,6 +67,102 @@
#define IFLDl(num, msg, args...)
#endif
+#define IIC_RESET_DELAY ( msecs_to_jiffies(100) ) /* 100ms in jiffies */
+
+struct iic_client;
+struct iic_bus;
+struct iic_eng;
+struct iic_xfr;
+struct iic_eng_ops;
+struct iic_reg_access;
+struct iic_lck;
+struct iic_lck_mgr;
+struct iic_slv;
+struct iicslv_client;
+struct iicslv_xfr;
+struct iic_ffdc {};
+struct iic_slv_ffdc;
+struct iic_mstr_ffdc;
+struct iic_eng_ffdc;
+struct dd_ffdc {}; //dummy to compile
+
+typedef struct iic_client iic_client_t;
+typedef struct iic_bus iic_bus_t;
+typedef struct iic_eng iic_eng_t;
+typedef struct iic_eng_ops iic_eng_ops_t;
+typedef struct iic_xfr iic_xfr_t;
+typedef struct iic_reg_access iic_reg_access_t;
+typedef struct iic_lck iic_lck_t;
+typedef struct iic_lck_mgr iic_lck_mgr_t;
+typedef struct iic_slv iicslv_t;
+typedef struct iicslv_client iicslv_client_t;
+typedef struct iicslv_xfr iicslv_xfr_t;
+typedef struct iic_ffdc iic_ffdc_t;
+typedef struct iic_slv_ffdc iic_slv_ffdc_t;
+typedef struct iic_mstr_ffdc iic_mstr_ffdc_t;
+typedef struct iic_eng_ffdc iic_eng_ffdc_t;
+typedef struct iic_trace_entry iic_trace_entry_t;
+typedef struct iicslv_zbuf_data iicslv_zbuf_data_t;
+typedef struct dd_ffdc dd_ffdc_t;
+
+struct iic_lck
+{
+ short addr; //bus & address
+ unsigned short mask; //bus & address mask
+ iic_client_t *client; //requesting/owning client
+ iic_xfr_t *cur_xfr; //transfer currently using this lock
+ unsigned long count; //lock count
+ struct list_head list; //node on locked or reqs list (below)
+};
+
+struct iic_lck_mgr
+{
+ struct list_head locked; //list of granted locks
+ struct list_head reqs; //list of yet-to-be granted locks
+};
+
+struct iic_xfr
+{
+ iic_client_t* client;
+ iic_lck_t* addr_lck; // lock handle for this xfr
+ struct timer_list delay; // pops when a write delay ends
+ struct list_head q_entry; // entry on engine q
+ struct kiocb *iocb;
+ char* buf; // Kernel space buffer
+ unsigned long num_pages;
+ unsigned long size; // size of the transfer
+ unsigned long bytes_xfrd; // bytes transfered so far
+ int status; // return status of this transfer
+ unsigned long flags; // miscellaneous transfer flags
+ iic_opts_t opts; // client defined attributes
+ struct timer_list timeout; // for xfr timeouts
+ unsigned short retry_count; // # of attempted retries
+ iic_ffdc_t* ffdc; // chain of ffdc
+ pid_t pid;
+ unsigned long offset_ffdc; // 1st 4 bytes of write data
+};
+
+/* transfer flags definitions */
+enum {
+ IIC_XFR_RD, // 1 = READ, 0 = WRITE
+ IIC_XFR_ASYNC, // transfer is asynchronous
+ IIC_XFR_FAST, // use 400khz for this transfer
+ IIC_XFR_CANCELLED, // transfer has been cancelled
+ IIC_XFR_ABORT, // transfer needs to be aborted
+ IIC_XFR_RECOVERY, // transfer is stuck, recovery in progress
+ IIC_XFR_STARTED, // transfer has been started at least once
+ IIC_XFR_DELAYED, // delay next transfer to xfr address
+ IIC_XFR_ENDED, // all processing for xfr has completed
+ IIC_XFR_ENG_COMPLETED, // xfr completed on the engine
+ IIC_XFR_DMA, // DMA will be used for this transfer
+ IIC_XFR_DMA_SUBMITTED, // DMA xfr has been submitted
+ IIC_XFR_DMA_COMPLETED, // DMA xfr has been completed
+ IIC_XFR_OFFSET_PHASE, // xfr is in offset phase
+ IIC_XFR_QUEUED, // xfr has been queued at least once
+ IIC_XFR_SPECIAL_PHASE, // xfr is in special phase (for pll/crc chips)
+ IIC_XFR_RETRY_IN_PROGRESS, // retry is in progress, (don't complete xfr)
+};
+
struct iic_reg_access
{
int (*bus_readb)(iic_eng_t*, unsigned int, unsigned char*, iic_ffdc_t**);
@@ -77,4 +173,223 @@ struct iic_reg_access
int (*bus_writew)(iic_eng_t*, unsigned int, unsigned long, iic_ffdc_t**);
};
+struct iic_eng_ops
+{
+ int (*use_dma)(iic_xfr_t*);
+ int (*start)(iic_xfr_t*);
+ int (*start_abort)(iic_eng_t*, iic_ffdc_t**);
+ int (*finish_abort)(iic_eng_t*, iic_ffdc_t**);
+ int (*start_rescue_timeout)(iic_eng_t*, iic_xfr_t*);
+ int (*finish_rescue_timeout)(iic_eng_t*, iic_xfr_t*);
+ int (*reset_bus)(iic_bus_t*, iic_ffdc_t**);
+ int (*reset_eng)(iic_eng_t*, iic_ffdc_t**);
+ int (*run_bat)(iic_eng_t*, iic_ffdc_t**);
+ int (*init)(iic_eng_t*, iic_ffdc_t**);
+ int (*enable_int)(iic_eng_t*, iic_ffdc_t**);
+ int (*disable_int)(iic_eng_t*, iic_ffdc_t**);
+ int (*int_handler)(int, void*);
+ int (*cleanup_eng)(iic_eng_t*);
+ int (*wait_for_idle)(iic_eng_t*, int, iic_ffdc_t**);
+ int (*slv_on)(iic_eng_t*, iic_ffdc_t**);
+ int (*slv_off)(iic_eng_t*, iic_ffdc_t**);
+ int (*slv_recv)(iic_eng_t*, char*, int*, iic_ffdc_t**);
+ int (*slv_cont)(iic_eng_t*, iic_ffdc_t**);
+ int (*slv_set_addr)(iic_eng_t*, unsigned long, iic_ffdc_t**);
+ int (*slv_get_addr)(iic_eng_t*, unsigned long*, iic_ffdc_t**);
+ void (*display_regs)(iic_eng_t*, iic_ffdc_t**);
+ int (*get_bus_state)(iic_bus_t*, unsigned long*, iic_ffdc_t**);
+ void (*get_ffdc)(iic_eng_t* eng, iic_ffdc_t* element);
+ int (*set_speed)(iic_bus_t* bus, int speed);
+ int (*get_speed)(iic_bus_t* bus);
+ int (*send)(iic_eng_t*, iic_ffdc_t**);
+};
+
+/*engine flags definitions */
+enum
+{
+ IIC_ENG_ABORT,
+ IIC_ENG_RESET,
+ IIC_ENG_BLOCK,
+ IIC_ENG_THROTTLED,
+ IIC_ENG_NEW_SEND_DATA,
+ IIC_ENG_INIT_FAILED, //engine initialization failed
+ IIC_ENG_CLEANUP_FAILED, //cleanup of previous xfr failed
+ IIC_NO_ACCESS,
+ IIC_ENG_RESUMED, // off = suspended
+ IIC_ENG_REMOVED,
+ IIC_ENG_Z7PLUS,
+ IIC_ENG_P8_Z8_CENTAUR,
+ IIC_ENG_OPB, // which parent bus we're on: opb/!fsi
+};
+
+struct iic_eng
+{
+ unsigned int id; //unique id for this engine
+ iic_reg_access_t* ra; //bus specific reg access methods
+ unsigned long base; //ioremapped base address of registers
+ int irq;
+ struct device* dev; //ldm structure from bus driver
+ iic_eng_ops_t* ops; //engine specific operations
+ spinlock_t lock;
+ struct list_head xfrq; //queue for xfrs waiting to run
+ iic_xfr_t* cur_xfr; //The currently running xfr
+ iic_bus_t* busses; //all busses attached to this engine
+ iic_bus_t* cur_bus;
+ struct iic_lck_mgr lck_mgr; //address lock management
+ struct work_struct work; //for freeing the bus
+ unsigned long flags; //misc engine flags
+ wait_queue_head_t waitq; //wait for engine events to occur
+ struct semaphore sem;
+ iicslv_t* slv; //pointer to a slave object
+ unsigned long type; //currently not used
+ void* private_data; //currently not used
+ unsigned long bus_speed; //parent bus speed in MHz
+ unsigned long fifo_size; //size of the engine fifo
+ iic_ffdc_t* ffdc; //ffdc data not associated with
+ //a process or bus(i.e., init errors)
+ struct kobject kobj; //for reference counting
+ unsigned long trace_sz; //number of trace entries
+ atomic_t xfr_num; //index to current trace entry
+ uint64_t enabled;
+};
+
+struct iic_bus
+{
+ unsigned char port; //the port number of this bus
+ unsigned long bus_id; //Unique ID for this bus
+ struct cdev cdev;
+ struct device* class_dev;
+ dev_t devnum;
+ iic_eng_t* eng; //the engine this bus is connected to
+ iic_bus_t* next; //the next bus connected to this engine
+ long i2c_hz; //the i2c clock speed for this bus.
+};
+
+struct iic_client
+{
+ iic_bus_t* bus;
+ wait_queue_head_t wait; //wait for locks and sync xfrs
+ iic_opts_t opts;
+ struct semaphore sem;
+ pid_t tgid;
+ char proc_name[16];
+ unsigned long flags;
+#define IIC_CLIENT_SOURCE_USER (1 << 0)
+#define IIC_CLIENT_SOURCE_KERN (1 << 1)
+#define IIC_CLIENT_EOD (1 << 2) /* End Of Data */
+};
+
+//--------------------------------------------------------------------
+// Specific header for repeated I/O
+//--------------------------------------------------------------------
+/*
+ * I2C Message - used for i2c transaction
+ */
+struct i2c_msg {
+ unsigned short addr; /* slave address */
+ unsigned short flags;
+#define I2C_M_TEN 0x10 /* we have a ten bit chip address */
+#define I2C_M_RD 0x01
+#define I2C_M_NOSTART 0x4000
+#define I2C_M_REV_DIR_ADDR 0x2000
+#define I2C_M_IGNORE_NAK 0x1000
+#define I2C_M_NO_RD_ACK 0x0800
+ unsigned short len; /* msg length */
+ unsigned char *buf; /* pointer to msg data */
+};
+/* This is the structure as used in the I2C_RDWR ioctl call */
+struct i2c_rdwr_ioctl_data {
+ struct i2c_msg *msgs; /* pointers to i2c_msgs */
+ unsigned int nmsgs; /* number of i2c_msgs */
+};
+
+int iic_register_eng_ops(iic_eng_ops_t* new_ops, unsigned long type);
+int iic_unregister_eng_ops(unsigned long type);
+iic_eng_ops_t* iic_get_eng_ops(unsigned long type);
+int iic_eng_ops_is_vaild(struct iic_eng_ops *ops);
+iic_bus_t* iic_create_bus(struct class* classp, iic_eng_t* eng,
+ dev_t devnum, char* name,
+ unsigned char port,
+ unsigned long bus_id);
+void iic_delete_bus(struct class* classp, iic_bus_t* bus);
+void iic_register_bus(iic_bus_t * bus, unsigned long type);
+void iic_unregister_bus(iic_bus_t * bus, unsigned long type);
+void iic_init_eng(iic_eng_t* eng);
+int iic_reset(iic_bus_t* bus, int timeout, iic_ffdc_t** ffdc); /* timeout is in milliseconds! */
+
+/* Engine specific code must call this function when a transfer completes
+ * and the engine is ready to start a new transfer
+ */
+void iic_xfr_complete(iic_xfr_t* xfr);
+
+void iic_delay_xfr(iic_xfr_t* xfr, unsigned long delay);
+void iic_abort_xfr(iic_xfr_t* xfr);
+int iic_abort_all(iic_eng_t* eng, iic_client_t* client, int status);
+int iic_start_next_xfr(iic_eng_t* eng);
+
+/* Name says it all!
+ */
+void iic_lck_mgr_init(iic_lck_mgr_t* mgr);
+
+/* Block waiting for an address lock to be granted until the timeout is
+ * reached.
+ *
+ * Return Values:
+ * SUCCESS: The requester owns the lock and the lock count is incremented.
+ * EINTR: The request was interrupted.
+ * ETIME: The request timed out, in jiffies.
+ */
+int iic_wait_lock(iic_lck_mgr_t *lm, short addr, short mask,
+ iic_client_t *client, unsigned long to);
+
+/* Try locking an address. Never block. If the address is already locked
+ * by another client, queue this request. After a request is queued,
+ * handle->count can be checked to determine when the lock has been granted.
+ * If handle->count > 0, the lock has been granted.
+ *
+ * Return Values:
+ * SUCCESS: The requester owns the lock and the lock count is incremented.
+ * QUEUED: The requester doesn't own the lock, but a lock request has been
+ * queued.
+ */
+#define IIC_REQ_QUEUED 1
+int iic_req_lock(iic_lck_mgr_t *lm, short addr, unsigned short mask,
+ iic_client_t *client, iic_lck_t **handle);
+
+iic_lck_t* iic_find_handle(iic_lck_mgr_t *lm, iic_client_t *client,
+ short addr, short mask);
+
+/* Unlock the specified address lock. If others are waiting for a lock, grant
+ * the next person in line.
+ */
+int iic_unlock(iic_lck_mgr_t *lm, iic_lck_t *lck);
+
+/* Unlock all locks associated with the specified client.
+ */
+int iic_unlock_all(iic_lck_mgr_t *lm, iic_client_t *client);
+
+
+#define iic_addr_is_locked(lck, xfr)\
+ (lck->count > 0 && (lck->cur_xfr == 0 || lck->cur_xfr == xfr))
+#define cdev_to_iic_bus(cdevp) container_of(cdevp, iic_bus_t, cdev)
+
+/*
+ * Exported for drivers needing 'sideways' kernel call access to IIC
+ * Currently only supports local OPB type IIC masters
+ */
+#define IIC_RESET_BUS 0
+#define IIC_RESET_ENG 1
+
+int iic_sideways_open(iic_client_t ** client, iic_bus_t * bus, int bus_num);
+int iic_sideways_read(iic_client_t * client, void * buf, size_t count,
+ loff_t * offset, dd_ffdc_t ** ffdc);
+int iic_sideways_write(iic_client_t * client, void * buf, size_t count,
+ loff_t * offset, dd_ffdc_t ** ffdc);
+int iic_sideways_release(iic_client_t * client);
+
+#define IIC_LOCK_ALL 0x7FF
+int iic_sideways_lock_bus(iic_client_t * client, unsigned short mask,
+ unsigned short addr, unsigned long timeout);
+int iic_sideways_unlock_bus(iic_client_t * client, unsigned short addr,
+ unsigned short mask);
#endif
--
1.8.3.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH linux v1 6/8] drivers: fsi: i2c: probe fsi device for i2c client
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
2017-02-02 23:25 ` [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver eajames
2017-02-02 23:25 ` [PATCH linux v1 5/8] drivers: fsi: i2c: Add engine access wrappers eajames
@ 2017-02-02 23:25 ` eajames
2017-02-02 23:26 ` [PATCH linux v1 7/8] drivers: fsi: i2c: add driver file operations and bus locking eajames
` (5 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: eajames @ 2017-02-02 23:25 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Edward A. James
From: "Edward A. James" <eajames@us.ibm.com>
Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
drivers/fsi/i2c/Makefile | 2 +-
drivers/fsi/i2c/iic-fsi.c | 464 ++++++++++++++++++++++++++++++++++++++++++++-
drivers/fsi/i2c/iic-mstr.c | 429 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 893 insertions(+), 2 deletions(-)
create mode 100644 drivers/fsi/i2c/iic-mstr.c
diff --git a/drivers/fsi/i2c/Makefile b/drivers/fsi/i2c/Makefile
index f9f9048..b1f28a1 100644
--- a/drivers/fsi/i2c/Makefile
+++ b/drivers/fsi/i2c/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_FSI_I2C) += iic-fsi.o
+obj-$(CONFIG_FSI_I2C) += iic-fsi.o iic-mstr.o
diff --git a/drivers/fsi/i2c/iic-fsi.c b/drivers/fsi/i2c/iic-fsi.c
index 53be538..3a9b25d 100644
--- a/drivers/fsi/i2c/iic-fsi.c
+++ b/drivers/fsi/i2c/iic-fsi.c
@@ -40,6 +40,56 @@ static const char iic_fsi_version[] = "3.0";
int iic_fsi_probe(struct device *dev);
int iic_fsi_remove(struct device *dev);
+void iic_fsi_shutdown(struct device *dev);
+int iic_fsi_suspend(struct device *dev);
+int iic_fsi_resume(struct device *dev);
+static void iic_eng_release(struct kobject* kobj);
+
+/* callback function for when the reference count for an engine reaches 0 */
+static void iic_eng_release(struct kobject* kobj)
+{
+ iic_eng_t* eng = container_of(kobj, iic_eng_t, kobj);
+ struct device* dev = eng->dev;
+ unsigned long flags;
+
+ IENTER();
+
+ /*remove all busses associated with the engine */
+ spin_lock_irqsave(&eng->lock, flags);
+ while(eng->busses){
+ iic_bus_t* temp = eng->busses;
+ eng->busses = temp->next;
+ iic_delete_bus(iic_fsi_class, temp);
+ }
+
+ eng->enabled = 0x0ULL;
+ spin_unlock_irqrestore(&eng->lock, flags);
+
+
+ /* providing an arch specific cleanup routine is optional.
+ * if not specified, use the default.
+ */
+ if (iic_eng_ops_is_vaild(eng->ops)) {
+ if(eng->ops->cleanup_eng)
+ {
+ eng->ops->cleanup_eng(eng);
+ }
+ else
+ {
+ IDBGd(0, "free engine\n");
+ kfree(eng);
+ }
+ }
+
+ dev_set_drvdata(dev, 0);
+ kobject_put(&dev->kobj);
+
+ IEXIT(0);
+}
+
+static struct kobj_type iic_eng_ktype = {
+ .release = iic_eng_release,
+};
int readb_wrap(iic_eng_t* eng, unsigned int addr, unsigned char *val,
iic_ffdc_t** ffdc)
@@ -157,6 +207,133 @@ static const struct fsi_driver i2c_drv = {
}
};
+/* Adds ports to the eng->buses SLL. Access to the SLL must be protected since
+ * iic_fsi_remove or iic_eng_release could be called asynchronously and they
+ * also traverse/modify the SLL.
+ */
+int iic_add_ports(iic_eng_t* eng, uint64_t ports)
+{
+ uint64_t ports_left;
+ int bus_num = 0;
+ unsigned long flags;
+ iic_bus_t* new_bus = 0;
+ int minor = 0;
+ char name[64];
+ int rc = 0;
+ struct fsi_device *fdev = to_fsi_dev(eng->dev);
+
+ IENTER();
+
+ IFLDi(3, "Adding ports[0x%08x%08x] to eng[0x%08x]",
+ (uint32_t)(ports >> 32),
+ (uint32_t)ports,
+ eng->id);
+
+ /* walk the mask adding master ports as needed */
+ for(ports_left = ports; ports_left; ports_left = ports >> ++bus_num)
+ {
+ if(!(ports_left & 0x1))
+ continue;
+
+
+ if( minor < 0 ) {
+ IFLDe(1, "bb_get_minor %d", minor);
+ rc = minor;
+ goto exit;
+ }
+
+ sprintf(name, "i2cfsi%02d", bus_num);
+
+ /* results in hotplug event for each master bus */
+ new_bus = iic_create_bus(iic_fsi_class, eng,
+ MKDEV(MAJOR(iic_devnum_start), minor),
+ name, bus_num, minor);
+ if(!new_bus)
+ {
+ IFLDe(1, "iic_create_bus failed on eng %d", eng->id);
+ rc = -ENODEV;
+ goto exit;
+ }
+
+ /* atomically insert the new bus into the SLL unless
+ * iic_fsi_remove has been started.
+ */
+ spin_lock_irqsave(&eng->lock, flags);
+ if(test_bit(IIC_ENG_REMOVED, &eng->flags))
+ {
+ /* if iic_fsi_remove has been started then
+ * don't add this bus to the SLL and delete it.
+ * Previously added buses will be removed by
+ * iic_eng_release
+ */
+ rc = -ENODEV;
+ iic_delete_bus(iic_fsi_class, new_bus);
+ spin_unlock_irqrestore(&eng->lock, flags);
+ goto exit;
+ }
+ else
+ {
+ new_bus->next = eng->busses;
+ eng->busses = new_bus;
+ eng->enabled |= 0x1ULL << bus_num;
+ }
+ spin_unlock_irqrestore(&eng->lock, flags);
+
+ minor++;
+ }
+
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* Removes ports from the eng->buses SLL. Access to the SLL must be protected
+ * since iic_fsi_remove or iic_eng_release could be called at same time as this
+ * and they also traverse/modify the SLL.
+ */
+int iic_del_ports(iic_eng_t* eng, uint64_t ports)
+{
+ unsigned long flags;
+ iic_bus_t* abusp;
+ iic_bus_t** p_abusp;
+
+ IENTER();
+
+ IFLDi(3, "removing ports[0x%08x%08x] from eng[0x%08x]",
+ (uint32_t)(ports >> 32),
+ (uint32_t)ports,
+ eng->id);
+
+ /* walk unordered SLL and delete bus if it is in the ports bit mask */
+ spin_lock_irqsave(&eng->lock, flags);
+ if(test_bit(IIC_ENG_REMOVED, &eng->flags))
+ goto exit;
+ p_abusp = &eng->busses;
+ abusp = *p_abusp;
+ while(abusp)
+ {
+ if(ports & (0x1ULL << abusp->port))
+ {
+ /* found a match, remove it */
+ *p_abusp = abusp->next;
+ eng->enabled &= ~(0x1ULL << abusp->port);
+ device_destroy(iic_fsi_class, abusp->devnum);
+ iic_delete_bus(iic_fsi_class, abusp);
+ }
+ else
+ {
+ /* not a match, skip to next one */
+ p_abusp = &abusp->next;
+ }
+ abusp = *p_abusp;
+ }
+
+exit:
+ spin_unlock_irqrestore(&eng->lock, flags);
+ IEXIT(0);
+ return 0;
+}
+
/*
* Called when an FSI IIC engine is plugged in.
* Causes creation of the /dev entry.
@@ -165,7 +342,86 @@ static const struct fsi_driver i2c_drv = {
*/
int iic_fsi_probe(struct device *dev)
{
- return 0;
+ uint64_t new_ports = 0;
+ int rc = 0;
+ struct fsi_device *dp = to_fsi_dev(dev);
+ iic_eng_t* eng = 0;
+
+ IENTER();
+
+ eng = (iic_eng_t*)kmalloc(sizeof(iic_eng_t), GFP_KERNEL);
+
+ if(!eng)
+ {
+ IFLDe(0, "Couldn't malloc engine\n");
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ memset(eng, 0, sizeof(*eng));
+ iic_init_eng(eng);
+ set_bit(IIC_ENG_BLOCK, &eng->flags); //block until resumed
+ eng->id = 0x00F5112C;
+ IFLDi(1, "PROBE eng[%08x]", eng->id);
+ eng->ra = &fsi_reg_access;
+ IFLDd(1, "vaddr=%#08lx\n", eng->base);
+ eng->dev = dev;
+ // The new kernel now requires 2 arguments
+ kobject_init(&eng->kobj, &iic_eng_ktype); //ref count = 1
+ eng->ops = iic_get_eng_ops(FSI_ENGID_I2C);
+ if(!eng->ops)
+ {
+ IFLDi(1, "support for engine type 0x%08x not loaded.\n",
+ FSI_ENGID_I2C);
+ rc = -ENODEV;
+ goto error;
+ }
+
+ /* Register interrupt handler with the kernel */
+ IDBGd(0, "requesting irq\n");
+ dp->irq_handler = eng->ops->int_handler;
+
+ IFLDd(1, "irq = %d\n", eng->irq);
+
+ new_ports = 0xFFFULL;
+ set_bit(IIC_ENG_P8_Z8_CENTAUR, &eng->flags);
+
+
+ /* Add master and slave ports to the engine */
+ rc = iic_add_ports(eng, new_ports);
+ if(rc)
+ goto error;
+
+ dev_set_drvdata(dev, eng);
+ eng->private_data = 0; //unused
+
+
+ /* set the callback function for when the eng ref count reaches 0 */
+ kobject_get(&eng->dev->kobj);
+
+ iic_fsi_resume(dev);
+
+error:
+ if(rc)
+ {
+ IFLDi(1, "IIC: iic_fsi_probe failed: %d\n", rc);
+ while(eng && eng->busses){
+ iic_bus_t* temp = eng->busses;
+ eng->busses = temp->next;
+ iic_delete_bus(iic_fsi_class, temp);
+ }
+ if(eng)
+ {
+ kfree(eng);
+ }
+ }
+ else
+ {
+ IFLDd(1, "engine 0x%08X probed.\n", eng->id);
+ }
+
+ IEXIT(rc);
+ return rc;
}
/* This function is called when a link is removed or the driver is unloaded.
@@ -174,9 +430,215 @@ int iic_fsi_probe(struct device *dev)
*/
int iic_fsi_remove(struct device* dev)
{
+ int rc = 0;
+ iic_bus_t* bus;
+ iic_eng_t* eng = (iic_eng_t*)dev_get_drvdata(dev);
+ unsigned long flags;
+
+ IENTER();
+
+ if(!eng)
+ {
+ IFLDe(1, "iic_fsi_remove called with bad dev: %p\n", dev);
+ rc = -1;
+ goto error;
+ }
+
+ /* set ENG_REMOVED flag so that aborted operations have status
+ * set to ENOLINK (lost fsi link) instead of ENODEV (no lbus).
+ */
+ set_bit(IIC_ENG_REMOVED, &eng->flags);
+
+ iic_fsi_suspend(dev); //ignore rc
+
+ IFLDi(1, "REMOVE eng[%08x]\n", eng->id);
+
+ /* Clean up device files immediately, don't wait for ref count */
+ spin_lock_irqsave(&eng->lock, flags);
+ bus = eng->busses;
+ while(bus)
+ {
+ /* causes hot unplug event */
+ device_destroy(iic_fsi_class, bus->devnum);
+ bus = bus->next;
+ }
+ spin_unlock_irqrestore(&eng->lock, flags);
+
+ /* cleans up engine and bus structures if ref count is zero */
+ kobject_put(&eng->kobj);
+
+error:
+ IEXIT(0);
return 0;
}
+/* This function is called when a link is removed or the driver is unloaded.
+ * It's job is to quiesce and disable the hardware if possible and unregister
+ * interrupts. It always precedes the remove function.
+ *
+ * The device may be in the resumed or suspended state when this function is
+ * called.
+ *
+ * This function is no longer called for mcp5
+ */
+void iic_fsi_shutdown(struct device *dev)
+{
+ int rc = 0;
+ iic_eng_t* eng = (iic_eng_t*)dev_get_drvdata(dev);
+ struct fsi_device* fsidev = to_fsi_dev(dev);
+
+ IENTER();
+ if(!eng || !eng->ops)
+ {
+ rc = -1;
+ goto error;
+ }
+ IFLDi(1, "SHUTDOWN eng[%08x]\n", eng->id);
+
+ /* set ENG_REMOVED flag so that aborted operations have status
+ * set to ENOLINK (lost fsi link) instead of ENODEV (no lbus).
+ */
+ set_bit(IIC_ENG_REMOVED, &eng->flags);
+
+ iic_fsi_suspend(dev);
+
+error:
+ if(rc)
+ {
+ IFLDe(1, "iic_fsi_shutdown failed: %d\n", rc);
+ }
+ IEXIT(0);
+ return;
+}
+
+/* This function is called when we loose ownership or are preparing to give
+ * up ownership of the local bus. If we still own lbus, then we try to
+ * gracefully halt any pending transfer. No hot unplug events are caused by
+ * this function.
+ *
+ * This function also is called from iic_fsi_remove.
+ *
+ * Note: In the case where we lose local bus and then loose the link or
+ * get rmmod'd, this function could be called twice without a resume
+ * in the middle!
+ */
+int iic_fsi_suspend(struct device *dev)
+{
+ int rc = 0;
+ iic_eng_t* eng = (iic_eng_t*)dev_get_drvdata(dev);
+ unsigned long xfr_status;
+
+ IENTER();
+ if(!eng)
+ {
+ rc = -1;
+ goto error;
+ }
+
+ IFLDi(2, "SUSPEND eng[%08x]\n", eng->id);
+
+ /* Prohibit new engine operations until resumed*/
+ set_bit(IIC_ENG_BLOCK, &eng->flags);
+
+
+ /* Terminate pending operations (set status to ENODEV)
+ * If we have access to the engine, halt any transfers
+ * and Disable engine interrupts.
+ */
+
+ /* lost lbus -> ENODEV, hot unplug -> ENOLINK */
+ xfr_status = test_bit(IIC_ENG_REMOVED, &eng->flags)? -ENOLINK: -ENODEV;
+ iic_abort_all(eng, 0, xfr_status);
+
+ /* disable slave transfers */
+ if (iic_eng_ops_is_vaild(eng->ops)) {
+ if(eng->ops->slv_off) {
+ eng->ops->slv_off(eng, 0);
+ }
+ }
+
+ /* prohibit hw access using this engine object */
+ set_bit(IIC_NO_ACCESS, &eng->flags);
+
+ /* disable interrupt handler if not already done */
+ if(test_and_clear_bit(IIC_ENG_RESUMED, &eng->flags))
+ {
+ fsi_disable_irq(to_fsi_dev(dev));
+ }
+
+error:
+ IEXIT(rc);
+ return rc;
+}
+
+/* This function is called when we are given (back) ownership of the local
+ * bus.
+ */
+int iic_fsi_resume(struct device *dev)
+{
+ iic_ffdc_t* ffdc = 0;
+ int rc = 0;
+ iic_eng_t* eng = 0;
+ struct fsi_device *dp = to_fsi_dev(dev);
+
+ IENTER();
+ // The device structure has changed for the new kernel.
+ // The member driver_data has been deprecated.
+ eng = (iic_eng_t*)dev_get_drvdata(dev);
+
+ if(!eng || !iic_eng_ops_is_vaild(eng->ops))
+ {
+ rc = -EIO;
+ goto error;
+ }
+
+ IFLDi(1, "RESUME eng[%08x]\n", eng->id);
+
+ eng->bus_speed = 20833333;
+ IFLDd(1, "eng->bus_speed=%ld\n", eng->bus_speed);
+
+ /* Reset the engine */
+ rc = eng->ops->reset_eng(eng, &ffdc);
+ if(rc)
+ {
+ goto error;
+ }
+
+ /* Give the engine time to determine the state of the bus before
+ * allowing transfers to begin after resetting the engine.
+ */
+ udelay(200);
+
+ /* Initialize the engine */
+ rc = eng->ops->init(eng, &ffdc);
+ if(rc)
+ {
+ goto error;
+ }
+
+ /* Enable interrupt handler in the kernel */
+ IDBGd(0, "enabling irq\n");
+ rc = fsi_enable_irq(dp);
+ if(rc)
+ {
+ IFLDe(1, "fsi_enable_irq failed rc=%d\n", rc);
+ goto error;
+ }
+
+ set_bit(IIC_ENG_RESUMED, &eng->flags);
+
+ /* allow operations to be submitted */
+ clear_bit(IIC_ENG_BLOCK, &eng->flags);
+ goto exit;
+
+error:
+ IFLDe(1, "iic_fsi_resume failed, rc=%d\n", rc);
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+
/*
* Initialize this module. Creates a class for fsi connected iic devices and
* allocates device numbers for them.
diff --git a/drivers/fsi/i2c/iic-mstr.c b/drivers/fsi/i2c/iic-mstr.c
new file mode 100644
index 0000000..de05e5d
--- /dev/null
+++ b/drivers/fsi/i2c/iic-mstr.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2006, 2009, 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/sysfs.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <asm/uaccess.h>
+#include <linux/delay.h>
+#include <linux/param.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <asm/page.h>
+#include <linux/pagemap.h>
+#include <linux/aio.h>
+#include "iic-int.h"
+#include <linux/fsi.h>
+#include <linux/time.h>
+#include <asm/io.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+
+typedef struct
+{
+ unsigned long type;
+ iic_eng_ops_t *ops;
+ struct list_head list;
+} iic_eng_type_t;
+
+typedef struct
+{
+ unsigned long type;
+ iic_bus_t * bus;
+ struct list_head list;
+} iic_bus_type_t;
+
+/* This is a DLL of engine ops for the engines that are supported */
+LIST_HEAD(iic_eng_type_list);
+
+/* DLL for bus ops for the busses that are supported */
+LIST_HEAD(iic_bus_type_list);
+
+iic_opts_t iic_dflt_opts =
+{
+ .xfr_opts =
+ {
+ .dev_addr = 0, /* 8 bits with LSB ignored */
+ .dev_width = 0, /* offset width in bytes */
+ .offset = 0, /* offset in bytes */
+ .inc_addr = 0, /* address increment mask */
+ .timeout = 5000, /* transfer timeout in milliseconds */
+ .wdelay = 0, /* write delay in milliseconds */
+ .rdelay = 0, /* read delay in milliseconds */
+ .wsplit = 0, /* write split chunk size (bytes) */
+ .rsplit = 0, /* read split chunk size (bytes) */
+ .flags = 0,
+ },
+ .recovery =
+ {
+ .redo_pol = 0,
+ .redo_delay = 0,
+ }
+};
+
+static const char iic_mstr_version[] = "3.0";
+
+/* save off the default cdev type pointer so we can call the default cdev
+ * release function in our own bus release function
+ */
+static struct kobj_type* cdev_dflt_type = 0;
+struct kobj_type iic_bus_type;
+
+/* funtion called when cdev object (embedded in bus object) ref count
+ * reaches zero. (prevents cdev memory from being freed to early)
+ */
+void iic_bus_release(struct kobject* kobj)
+{
+ struct cdev *p = container_of(kobj, struct cdev, kobj);
+ iic_bus_t* bus = container_of(p, iic_bus_t, cdev);
+
+ IFLDi(1, "deleting bus[%08lx]\n", bus->bus_id);
+ if(cdev_dflt_type && cdev_dflt_type->release)
+ cdev_dflt_type->release(kobj);
+ kfree(bus);
+}
+
+int iic_open(struct inode* inode, struct file* filp);
+int iic_release(struct inode* inode, struct file* filp);
+ssize_t iic_read(struct file *file, char __user *buf, size_t count,
+ loff_t *offset);
+ssize_t iic_write(struct file *file, const char __user *buf, size_t count,
+ loff_t *offset);
+ssize_t iic_aio_read(struct kiocb *iocb, const struct iovec *buf, unsigned long count, loff_t pos);
+ssize_t iic_aio_write(struct kiocb *iocb, const struct iovec *buf, unsigned long count, loff_t pos);
+long iic_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg);
+static int iic_mmap(struct file* file, struct vm_area_struct* vma);
+int iic_xfr(iic_client_t* client, char* buf, size_t count, loff_t* offset,
+ char read_flag);
+loff_t iic_llseek(struct file *filp, loff_t off, int whence);
+
+struct file_operations iic_fops = {
+ .owner = THIS_MODULE,
+ .open = iic_open,
+ .release = iic_release,
+ .read = iic_read,
+ .write = iic_write,
+ .unlocked_ioctl = iic_ioctl,
+ .llseek = iic_llseek,
+ .mmap = iic_mmap,
+};
+
+static iic_bus_t * iic_get_bus(unsigned long port, unsigned long type)
+{
+ iic_bus_type_t* iterator;
+ int found = 0;
+
+ IENTER();
+
+ list_for_each_entry(iterator, &iic_bus_type_list, list)
+ {
+ if((iterator->type == type) && (iterator->bus->port == port))
+ {
+ found = 1;
+ break;
+ }
+ }
+
+ IEXIT(0);
+ return (found == 1)? iterator->bus: NULL;
+}
+
+/* Abort all pending xfrs for a client, or if client is 0, abort all
+ * pending xfrs for the engine.
+ */
+int iic_abort_all(iic_eng_t* eng, iic_client_t* client, int status)
+{
+ return 0;
+}
+EXPORT_SYMBOL(iic_abort_all);
+
+int iic_register_eng_ops(iic_eng_ops_t* new_ops, unsigned long type)
+{
+ iic_eng_type_t* new_type = (iic_eng_type_t*)
+ kmalloc(sizeof(iic_eng_type_t), GFP_KERNEL);
+ IENTER();
+ if(!new_type)
+ {
+ return -ENOMEM;
+ }
+
+ new_type->type = type;
+ new_type->ops = new_ops;
+
+ /* Add this eng type object to beginning of engine type list*/
+ list_add(&new_type->list, &iic_eng_type_list);
+ IDBGd(1, "eng type %08lx registered\n", type);
+ IEXIT(0);
+ return 0;
+}
+EXPORT_SYMBOL(iic_register_eng_ops);
+
+int iic_unregister_eng_ops(unsigned long type)
+{
+ iic_eng_type_t *iterator, *found;
+ IENTER();
+ found = 0;
+
+ list_for_each_entry(iterator, &iic_eng_type_list, list)
+ {
+ if(iterator->type == type)
+ {
+ found = iterator;
+ break;
+ }
+ }
+ if(found)
+ {
+ list_del(&found->list);
+ kfree(found);
+ IDBGd(1, "engine type %08lx unregistered\n", type);
+ }
+ IEXIT(0);
+ return 0;
+}
+EXPORT_SYMBOL(iic_unregister_eng_ops);
+
+void iic_register_bus(iic_bus_t * new_bus, unsigned long type)
+{
+ iic_bus_type_t* new_type = (iic_bus_type_t*)
+ kmalloc(sizeof(iic_bus_type_t), GFP_KERNEL);
+
+ IENTER();
+
+ new_type->type = type;
+ new_type->bus = new_bus;
+ list_add(&new_type->list, &iic_bus_type_list);
+
+ IEXIT(0);
+}
+EXPORT_SYMBOL(iic_register_bus);
+
+void iic_unregister_bus(iic_bus_t *bus, unsigned long type)
+{
+ iic_bus_type_t *iterator, *temp;
+
+ IENTER();
+
+ list_for_each_entry_safe(iterator, temp, &iic_bus_type_list, list)
+ {
+ if((iterator->type == type) && (iterator->bus == bus))
+ {
+ list_del(&iterator->list);
+ kfree(iterator);
+ }
+ }
+ IEXIT(0);
+}
+EXPORT_SYMBOL(iic_unregister_bus);
+
+void iic_init_eng(iic_eng_t* eng)
+{
+ IENTER();
+ spin_lock_init(&eng->lock);
+ sema_init(&eng->sem, 1);
+ INIT_LIST_HEAD(&eng->xfrq);
+ eng->cur_xfr = 0;
+ iic_lck_mgr_init(&eng->lck_mgr);
+ init_waitqueue_head(&eng->waitq);
+ INIT_WORK(&eng->work, iic_finish_abort);
+ atomic_set(&eng->xfr_num, 0);
+ IEXIT(0);
+}
+EXPORT_SYMBOL(iic_init_eng);
+
+int iic_eng_ops_is_vaild(struct iic_eng_ops *ops)
+{
+ int found = 0;
+ iic_eng_type_t *iterator;
+
+ list_for_each_entry(iterator, &iic_eng_type_list, list)
+ {
+ if(iterator->ops == ops)
+ {
+ found = 1;
+ break;
+ }
+ }
+
+ return found;
+}
+EXPORT_SYMBOL(iic_eng_ops_is_vaild);
+
+struct iic_eng_ops* iic_get_eng_ops(unsigned long type)
+{
+ iic_eng_type_t *iterator;
+ iic_eng_ops_t *found = 0;
+ IENTER();
+
+ /* return the eng ops for the given type of engine */
+ list_for_each_entry(iterator, &iic_eng_type_list, list)
+ {
+ if(iterator->type == type)
+ {
+ found = iterator->ops;
+ break;
+ }
+ }
+ IEXIT((int)found);
+ return found;
+}
+EXPORT_SYMBOL(iic_get_eng_ops);
+
+/* called when an ffdc q for a bus is unlocked */
+void iic_ffdc_q_unlocked(int scope, void* data)
+{
+ iic_eng_t* eng = (iic_eng_t*)data;
+ unsigned long flags;
+ if(eng)
+ {
+ spin_lock_irqsave(&eng->lock, flags);
+ iic_start_next_xfr(eng);
+ spin_unlock_irqrestore(&eng->lock, flags);
+ }
+}
+
+/* Register this bus's minor number with the kernel and add
+ * it to the iic class in sysfs so that a hotplug event is
+ * sent to udev. The sysfs name needs to be unique because
+ * all entries are placed in the same directory. udev
+ * will take care of creating the correct /dev name.
+ */
+#define IIC_BUS_MAX_FFDC 4
+iic_bus_t* iic_create_bus(struct class* classp, iic_eng_t* eng,
+ dev_t devnum, char* name, unsigned char port,
+ unsigned long bus_id)
+{
+ int rc = 0;
+ iic_bus_t* bus = 0;
+
+ IENTER();
+
+ if(!eng)
+ {
+ goto exit;
+ }
+ bus = (iic_bus_t*)kmalloc(sizeof(iic_bus_t), GFP_KERNEL);
+ if(!bus)
+ {
+ goto exit;
+ }
+ memset(bus, 0, sizeof(iic_bus_t));
+ bus->port = port;
+ bus->bus_id = bus_id;
+ bus->eng = eng;
+ bus->devnum = devnum;
+ bus->i2c_hz = 400000;
+ cdev_init(&bus->cdev, &iic_fops); // ref count = 1
+ /* since cdev is embedded in our bus structure, override the cdev
+ * cleanup function with our own so that the bus object doesn't get
+ * freed until the cdev ref count goes to zero.
+ */
+ if(!cdev_dflt_type)
+ {
+ cdev_dflt_type = bus->cdev.kobj.ktype;
+ memcpy(&iic_bus_type, cdev_dflt_type, sizeof(iic_bus_type));
+ iic_bus_type.release = iic_bus_release;
+ }
+ bus->cdev.kobj.ktype = &iic_bus_type;
+ kobject_set_name(&bus->cdev.kobj, name);
+ rc = cdev_add(&bus->cdev, devnum, 1);
+ if(rc)
+ {
+ IFLDe(1, "cdev_add failed for bus %08lx\n", bus->bus_id);
+ kobject_put(&bus->cdev.kobj);
+ goto exit_cdev_add;
+ }
+
+ bus->class_dev = device_create(classp, NULL,
+ devnum,
+ bus->eng->dev,
+ (const char *)"%s",
+ name);
+ if(!bus->class_dev)
+ {
+ IFLDe(1, "device create failed, %08lx\n", bus->bus_id);
+ goto exit_class_add;
+ }
+
+ IFLDi(1, "bus[%08lx] created\n", bus->bus_id);
+ goto exit;
+
+exit_q_create:
+ device_destroy(classp, bus->devnum);
+exit_class_add:
+ cdev_del(&bus->cdev);
+exit_cdev_add:
+ bus = 0;
+exit:
+ IEXIT((int)bus);
+ return bus;
+}
+EXPORT_SYMBOL(iic_create_bus);
+
+void iic_delete_bus(struct class* classp, iic_bus_t* bus)
+{
+ IENTER();
+
+ if(!bus)
+ {
+ goto exit;
+ }
+ IFLDi(1, "cleanup bus[%08lx]\n", bus->bus_id);
+ cdev_del(&bus->cdev);
+exit:
+ IEXIT(0);
+ return;
+}
+EXPORT_SYMBOL(iic_delete_bus);
+
+static int __init iic_init(void)
+{
+ int rc = 0;
+
+ IENTER();
+ printk("IIC: base support loaded ver. %s\n", iic_mstr_version);
+ IEXIT(rc);
+ return rc;
+}
+
+static void __exit iic_exit(void)
+{
+ IENTER();
+ printk("IIC: base support unloaded.\n");
+ IEXIT(0);
+}
+
+static int iic_set_trc_sz(const char* val, struct kernel_param *kp)
+{
+ int rc = param_set_int(val, kp);
+ if(rc)
+ return rc;
+ return 0;
+}
+
+module_init(iic_init);
+module_exit(iic_exit);
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("Base IIC Driver");
+MODULE_LICENSE("GPL");
--
1.8.3.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH linux v1 7/8] drivers: fsi: i2c: add driver file operations and bus locking
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
` (2 preceding siblings ...)
2017-02-02 23:25 ` [PATCH linux v1 6/8] drivers: fsi: i2c: probe fsi device for i2c client eajames
@ 2017-02-02 23:26 ` eajames
2017-02-02 23:26 ` [PATCH linux v1 8/8] drivers: fsi: i2c: boe engine eajames
` (4 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: eajames @ 2017-02-02 23:26 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Edward A. James
From: "Edward A. James" <eajames@us.ibm.com>
Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
drivers/fsi/i2c/Makefile | 2 +-
drivers/fsi/i2c/iic-lock.c | 439 +++++++++++
drivers/fsi/i2c/iic-mstr.c | 1834 +++++++++++++++++++++++++++++++++++++++++++
include/uapi/linux/Kbuild | 1 +
include/uapi/linux/i2cfsi.h | 136 ++++
5 files changed, 2411 insertions(+), 1 deletion(-)
create mode 100644 drivers/fsi/i2c/iic-lock.c
create mode 100644 include/uapi/linux/i2cfsi.h
diff --git a/drivers/fsi/i2c/Makefile b/drivers/fsi/i2c/Makefile
index b1f28a1..4d04026 100644
--- a/drivers/fsi/i2c/Makefile
+++ b/drivers/fsi/i2c/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_FSI_I2C) += iic-fsi.o iic-mstr.o
+obj-$(CONFIG_FSI_I2C) += iic-fsi.o iic-mstr.o iic-lock.o
diff --git a/drivers/fsi/i2c/iic-lock.c b/drivers/fsi/i2c/iic-lock.c
new file mode 100644
index 0000000..ea5a42f8
--- /dev/null
+++ b/drivers/fsi/i2c/iic-lock.c
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2006, 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include "iic-int.h"
+
+#define IIC_NO_MATCH 0
+#define IIC_PARTIAL_MATCH 1
+#define IIC_EXACT_MATCH 2
+#define IIC_ADDR_MAX_BITS 10
+
+
+void iic_lck_mgr_init(iic_lck_mgr_t* mgr)
+{
+ IENTER();
+ INIT_LIST_HEAD(&mgr->locked);
+ INIT_LIST_HEAD(&mgr->reqs);
+ IEXIT(0);
+}
+EXPORT_SYMBOL(iic_lck_mgr_init);
+
+int iic_get_match_type(short a_addr, short a_mask, short b_addr, short b_mask)
+{
+ int rc = IIC_PARTIAL_MATCH;
+ short mask = a_mask | b_mask;
+
+ IENTER();
+
+ /* no match if the fixed bits aren't the same */
+ if((a_addr & ~mask) != (b_addr & ~mask))
+ {
+ rc = IIC_NO_MATCH;
+ }
+
+ /* exact match if the fixed bits and mask bits are the same */
+ else if(a_mask == b_mask)
+ {
+ rc = IIC_EXACT_MATCH;
+ }
+
+ /* everything else is partial */
+
+ IEXIT(rc);
+ return rc;
+}
+
+
+/* to must be in jiffies! */
+int iic_wait_lock(iic_lck_mgr_t *lm, short addr, short mask,
+ iic_client_t *client, unsigned long to)
+{
+ iic_lck_t *handle = 0;
+ int rc = 0;
+ unsigned long flags;
+
+ IENTER();
+
+ spin_lock_irqsave(&client->bus->eng->lock, flags);
+
+ /* try the lock and enqueue our request if locked by someone else */
+ rc = iic_req_lock(lm, addr, mask, client, &handle);
+
+ spin_unlock_irqrestore(&client->bus->eng->lock, flags);
+
+ if(rc == IIC_REQ_QUEUED)
+ {
+ /* wait for timeout, signal, or lock to be granted */
+ rc = wait_event_interruptible_timeout(
+ client->wait,
+ handle->count > 0,
+ to);
+ if(rc > 0)
+ {
+ rc = 0;
+ }
+ else
+ {
+ if(!rc)
+ {
+ rc = -ETIME;
+ IFLDe(4, "lock req timed out: client[%p] bus[%08lx] lock[%04x:%04x]\n",
+ client, client->bus->bus_id,
+ handle->addr, handle->mask);
+ }
+ else if(rc == -ERESTARTSYS)
+ {
+ rc = -EINTR;
+ IFLDe(4, "lock req interrupted: client[%p] bus[%08lx] lock[%04x:%04x]\n",
+ client, client->bus->bus_id,
+ handle->addr, handle->mask);
+ }
+
+ /* interrupted or timed out. delete request and
+ * return to caller.
+ */
+ spin_lock_irqsave(&client->bus->eng->lock, flags);
+ iic_unlock(lm, handle);
+ spin_unlock_irqrestore(&client->bus->eng->lock, flags);
+ }
+ }
+ IEXIT(rc);
+ return rc;
+}
+
+int iic_sideways_lock_bus(iic_client_t * client, unsigned short addr,
+ unsigned short mask, unsigned long timeout)
+{
+ int rc = 0;
+ iic_eng_t * eng = client->bus->eng;
+
+ rc = iic_wait_lock(&eng->lck_mgr, addr, mask >> 1, client,
+ msecs_to_jiffies(timeout));
+
+ return rc;
+}
+EXPORT_SYMBOL(iic_sideways_lock_bus);
+
+int iic_sideways_unlock_bus(iic_client_t * client, unsigned short addr,
+ unsigned short mask)
+{
+ int rc = 0;
+ iic_lck_t * klck;
+ iic_eng_t * eng = client->bus->eng;
+
+ spin_lock_irq(&eng->lock);
+ klck = iic_find_handle(&eng->lck_mgr, client, addr, mask >> 1);
+
+ if(klck)
+ {
+ rc = iic_unlock(&eng->lck_mgr, klck);
+ if(!rc)
+ iic_start_next_xfr(eng);
+ }
+ spin_unlock_irq(&eng->lock);
+
+ return rc;
+}
+EXPORT_SYMBOL(iic_sideways_unlock_bus);
+
+/* Engine must be locked before using this function!
+ *
+ * This function only requests a lock. It doesn't block waiting for the
+ * lock to be granted. Instead, a handle is returned and the caller can
+ * query this handle to determine if the lock was granted. If the lock
+ * count is not zero, the lock has been granted.
+ *
+ * returns IIC_REQ_QUEUED on success, negative value on failure.
+ *
+ * Locks from the same client can overlap each other, so a client could
+ * lock all addresses on a bus and still request a subset of locks within
+ * that range.
+ *
+ * Here's the algorithm in a nutshell:
+ *
+ * Iterate through lock list
+ * -If exact match and same client, stop iterating and increment the
+ * lock count of the existing lock.
+ * -If partial or exact match but different client, stop iterating and
+ * queue a new request on the request queue without granting the lock.
+ * -If partial and same client, keep iterating.
+ * -If we reach the end and none of the above apply,
+ * create a new lock and increment the count.
+ */
+int iic_req_lock(iic_lck_mgr_t *lm, short addr, unsigned short mask,
+ iic_client_t *client, iic_lck_t **handle)
+{
+ iic_lck_t *iterator, *found;
+ int mtype = IIC_NO_MATCH;
+ int rc = IIC_REQ_QUEUED;
+ iic_lck_t *new_req = 0;
+
+ IENTER();
+ found = 0;
+ addr = (client->bus->port << IIC_ADDR_MAX_BITS) | (addr >> 1);
+
+ IFLDs(4, "REQLOCK client[%p] bus[%08lx] lock[%04x:%04x]\n",
+ client, client->bus->bus_id, addr, mask);
+
+ /* Look for a partial or exact address match in the lock list */
+ list_for_each_entry(iterator, &lm->locked, list)
+ {
+ mtype = iic_get_match_type(iterator->addr, iterator->mask,
+ addr, mask);
+ if(mtype != IIC_NO_MATCH)
+ {
+ found = iterator;
+ if((found->client == client) &&
+ (mtype == IIC_EXACT_MATCH))
+ {
+ found->count++;
+ IDBGd(4, "bus[%08lx] lock[%04x:%04x] count=%ld\n",
+ client->bus->bus_id, addr, mask,
+ found->count);
+ *handle = found;
+ goto exit;
+ }
+ break;
+ }
+ }
+
+ /* Exact matches with same client id have already exited. Deal
+ * with the rest as follows:
+ * exact/partial matches w/diff client -> new lock requested
+ * no matches or partial w/same client -> new lock granted
+ */
+ new_req = (iic_lck_t*)kmalloc(sizeof(iic_lck_t), GFP_KERNEL);
+ if(!new_req)
+ {
+ rc = -ENOMEM;
+ goto exit;
+ }
+ new_req->client = client;
+ new_req->addr = addr;
+ new_req->mask = mask;
+ new_req->cur_xfr = 0;
+ *handle = new_req;
+
+ /* queue the request if someone else owns the lock */
+ if((mtype != IIC_NO_MATCH) && (found->client != client)){
+ IFLDi(5, "BLOCKED client[%p] bus[%08lx] lock[%04x:%04x] blocked by client[%p]\n",
+ client, client->bus->bus_id, addr, mask, found->client);
+ new_req->count = 0;
+ list_add_tail(&new_req->list, &lm->reqs);
+ }
+ /* if nobody owns the lock, then grant it to the requestor */
+ else
+ {
+ IDBGd(4, "bus[%08lx] lock[%04x:%04x] count=%d\n",
+ client->bus->bus_id, addr, mask, 1);
+ new_req->count = 1;
+ list_add_tail(&new_req->list, &lm->locked);
+ }
+
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* Engine must be locked before calling this function!
+ *
+ * If the lock pointed to by lck has been granted, the lock count is decremented
+ * and if it reaches zero, the lock is freed up, and the next in line (if any)
+ * is granted the lock. If lck points to a queued up lock request, the
+ * request is removed from the request queue and freed up.
+ *
+ * Here's the algorithm for finding the "next in line":
+ *
+ * Iterate through the request list
+ * -if exact or partial match of unlocked range found, search the
+ * lock list for partial matches to the requested lock that have
+ * different clients.
+ * -If not found, grant the lock request.
+ * -If found, don't grant the lock, continue iterating the
+ * request list for other potential candidates.
+ */
+int iic_unlock(iic_lck_mgr_t *lm, iic_lck_t *lck)
+{
+ iic_lck_t *req, *temp, *locked, *candidate;
+ int mtype = IIC_NO_MATCH;
+
+ IENTER();
+
+ IFLDs(5, "UNLOCK client[%p] bus[%08lx] lock[%04x:%04x] count=%ld\n",
+ lck->client, lck->client->bus->bus_id, lck->addr, lck->mask,
+ lck->count);
+ switch(lck->count)
+ {
+ /* lock count will decrement to 0, lock is to be unlocked and
+ * next in line gets the lock
+ */
+ case 1:
+ /*remove the lock from the locked list*/
+ list_del(&lck->list);
+
+ /* Look for a partial or exact address match in the
+ * request queue as a candidate for being granted
+ * the lock.
+ */
+ candidate = 0;
+ list_for_each_entry_safe(req, temp, &lm->reqs, list)
+ {
+ mtype = iic_get_match_type(req->addr, req->mask,
+ lck->addr, lck->mask);
+ if(mtype == IIC_NO_MATCH)
+ {
+ continue;
+ }
+
+ /* found someone waiting for this lock!
+ * Check if the requestors lock range
+ * is free
+ */
+ candidate = req;
+ list_for_each_entry(locked, &lm->locked, list)
+ {
+ mtype = iic_get_match_type(
+ req->addr,
+ req->mask,
+ locked->addr,
+ locked->mask);
+ if(mtype != IIC_NO_MATCH &&
+ req->client != locked->client)
+ {
+ candidate = 0;
+ break;
+ }
+ }
+ if(candidate)
+ {
+ /* if the candidate survived the test,
+ * grant the lock and discontinue search.
+ * Also, wakeup the client waiting for
+ * this lock to be granted.
+ */
+ candidate->count++;
+ list_del(&candidate->list);
+ list_add_tail(&candidate->list, &lm->locked);
+ wake_up_interruptible(&candidate->client->wait);
+ IFLDi(4, "UNBLOCK client[%p] bus[%08lx] lock[%04x:%04x]\n",
+ candidate->client,
+ candidate->client->bus->bus_id,
+ candidate->addr, candidate->mask);
+ }
+ }
+ kfree(lck);
+ break;
+
+ /* lock is unlocked (above case) or was waiting on the request q */
+ case 0:
+ /* Remove the old lock from the locked or request
+ * q and free it.
+ */
+ list_del(&lck->list);
+ kfree(lck);
+ break;
+
+ default: /* lock count is >= 2 */
+ lck->count--;
+ }
+ IEXIT(0);
+ return 0;
+}
+
+/* Find all locks and lock requests associated with the client_id and remove
+ * them all regardless of lock count. All associated transfers should have
+ * already been aborted before calling this function.
+ */
+int iic_unlock_all(iic_lck_mgr_t *lm, iic_client_t *client)
+{
+ iic_lck_t* iterator, *temp;
+
+ IENTER();
+
+ if(!lm || !client)
+ {
+ return 0;
+ }
+ /* first, search the request q */
+ list_for_each_entry_safe(iterator, temp, &lm->reqs, list)
+ {
+ if(iterator->client == client)
+ {
+ iic_unlock(lm, iterator);
+ }
+ }
+
+ /* then, search the lock q */
+ list_for_each_entry_safe(iterator, temp, &lm->locked, list)
+ {
+ if(iterator->client == client)
+ {
+ /* This removes all explicit locks and leaves a single
+ * implicit lock in case there is an active transfer
+ * associated with the lock.
+ */
+ iterator->count = 1;
+ /* if cur_xfr is set, it means that the xfr has
+ * been started and not completed. It is bad to
+ * forcefully unlock a lock associated with a
+ * xfr that hasn't completed because the implicit
+ * unlock will fail.
+ */
+ if(!iterator->cur_xfr)
+ iic_unlock(lm, iterator); //unlock now
+ }
+ }
+ IEXIT(0);
+ return 0;
+}
+
+/* Find the lock handle for a given client and address range */
+iic_lck_t* iic_find_handle(iic_lck_mgr_t *lm, iic_client_t *client,
+ short addr, short mask)
+{
+ iic_lck_t *iterator, *found;
+ IENTER();
+ addr = (client->bus->port << IIC_ADDR_MAX_BITS) | (addr >> 1);
+ found = 0;
+ list_for_each_entry(iterator, &lm->locked, list)
+ {
+ if((iterator->client == client) &&
+ (iic_get_match_type(addr, mask, iterator->addr,
+ iterator->mask) == IIC_EXACT_MATCH))
+ {
+ found = iterator;
+ goto exit;
+ }
+ }
+ list_for_each_entry(iterator, &lm->reqs, list)
+ {
+ if((iterator->client == client) &&
+ (iic_get_match_type(addr, mask, iterator->addr,
+ iterator->mask) == IIC_EXACT_MATCH))
+ {
+ found = iterator;
+ break;
+ }
+ }
+exit:
+ IEXIT((int)found);
+ return found;
+}
diff --git a/drivers/fsi/i2c/iic-mstr.c b/drivers/fsi/i2c/iic-mstr.c
index de05e5d..d7f029c 100644
--- a/drivers/fsi/i2c/iic-mstr.c
+++ b/drivers/fsi/i2c/iic-mstr.c
@@ -32,7 +32,9 @@
#include <asm/page.h>
#include <linux/pagemap.h>
#include <linux/aio.h>
+#include <linux/i2cfsi.h>
#include "iic-int.h"
+#include "iic-fsi.h"
#include <linux/fsi.h>
#include <linux/time.h>
#include <asm/io.h>
@@ -149,15 +151,1847 @@ static iic_bus_t * iic_get_bus(unsigned long port, unsigned long type)
return (found == 1)? iterator->bus: NULL;
}
+
+int iic_common_open(iic_client_t ** o_client, iic_bus_t * bus, int engine_num)
+{
+ int ret = 0;
+ iic_client_t * client;
+ IENTER();
+
+
+ BUG_ON(in_atomic());
+
+ /*
+ * Create a client with the default attributes and associate it
+ * with the file descriptor.
+ */
+ client = (iic_client_t*)kzalloc(sizeof(*client), GFP_KERNEL);
+ if(!client)
+ {
+ ret = -ENOMEM;
+ goto exit;
+ }
+ memcpy(&client->opts, &iic_dflt_opts, sizeof(iic_dflt_opts));
+
+ if(!bus)
+ {
+ ret = -ENOMEM;
+ kfree(client);
+ goto exit;
+ }
+ else
+ {
+ client->flags |= IIC_CLIENT_SOURCE_USER;
+ }
+
+ client->bus = bus;
+ client->tgid = current->tgid;
+ sema_init(&client->sem, 1);
+ init_waitqueue_head(&client->wait);
+ kobject_get(&bus->eng->kobj);
+ *o_client = client;
+
+exit:
+ IEXIT(0);
+ return ret;
+}
+
+int iic_sideways_open(iic_client_t ** o_client,
+ iic_bus_t * bus,
+ int engine_num)
+{
+ return iic_common_open(o_client, bus, engine_num);
+}
+EXPORT_SYMBOL(iic_sideways_open);
+
+int iic_open(struct inode* inode, struct file* filp)
+{
+ int ret = 0;
+ iic_client_t* client;
+ iic_bus_t* bus = container_of(inode->i_cdev,
+ iic_bus_t,
+ cdev);
+ IENTER();
+ if(!bus)
+ {
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ ret = iic_common_open(&client, bus, 0);
+ filp->private_data = client;
+ IFLDs(2, "OPEN client[%p] bus[%08lx]\n", client, bus->bus_id);
+
+exit:
+ IEXIT(ret);
+ return ret;
+}
+
/* Abort all pending xfrs for a client, or if client is 0, abort all
* pending xfrs for the engine.
*/
int iic_abort_all(iic_eng_t* eng, iic_client_t* client, int status)
{
+ unsigned long flags;
+ iic_xfr_t *iterator, *temp;
+
+ IENTER();
+ /* abort currently running xfr */
+ spin_lock_irqsave(&eng->lock, flags);
+ if(eng->cur_xfr && (!client || ( eng->cur_xfr->client == client)))
+ {
+ iic_xfr_t* cur_xfr = eng->cur_xfr;
+ cur_xfr->status = status;
+ iic_abort_xfr(cur_xfr);
+
+ iic_xfr_complete(cur_xfr);
+ }
+
+ /* abort queued xfrs */
+ list_for_each_entry_safe(iterator, temp, &eng->xfrq, q_entry)
+ {
+ if(!client || (iterator->client == client))
+ {
+ iterator->status = status;
+ iic_abort_xfr(iterator);
+ iic_xfr_complete(iterator);
+ }
+ }
+ spin_unlock_irqrestore(&eng->lock, flags);
+ IEXIT(0);
return 0;
}
EXPORT_SYMBOL(iic_abort_all);
+int iic_common_release(iic_client_t * client)
+{
+ int rc = 0;
+ iic_bus_t * bus = client->bus;
+
+ IENTER();
+
+ BUG_ON(in_atomic());
+
+ /* abort all pending transfers for this client */
+ iic_abort_all(bus->eng, client, -EPIPE);
+
+ /* unlock any address locks associated with this client */
+ spin_lock_irq(&bus->eng->lock);
+ iic_unlock_all(&bus->eng->lck_mgr, client);
+ iic_start_next_xfr(bus->eng);
+ spin_unlock_irq(&bus->eng->lock);
+
+ client->bus = 0;
+ kfree(client);
+ kobject_put(&bus->eng->kobj);
+
+ IEXIT(rc);
+ return rc;
+}
+
+int iic_sideways_release(iic_client_t * client)
+{
+ return iic_common_release(client);
+}
+EXPORT_SYMBOL(iic_sideways_release);
+
+int iic_release(struct inode* inode, struct file* filp)
+{
+ iic_client_t* client = (iic_client_t*)filp->private_data;
+ iic_bus_t* bus = container_of(inode->i_cdev,
+ iic_bus_t,
+ cdev);
+ IENTER();
+
+ IFLDs(2, "CLOSE client[%p] bus[%08lx]\n", client, bus->bus_id);
+
+ iic_common_release(client);
+
+ /* Delete the client object associated with the file descriptor */
+ filp->private_data = 0;
+
+ IEXIT(0);
+ return 0;
+}
+EXPORT_SYMBOL(iic_release);
+
+
+void iic_cleanup_xfr(iic_xfr_t* xfr, dd_ffdc_t ** o_ffdc)
+{
+ int i;
+ IENTER();
+
+ del_timer(&xfr->delay);
+ del_timer(&xfr->timeout);
+ kfree(xfr);
+ IEXIT(0);
+}
+
+#ifndef MSEC_PER_SEC
+#define MSEC_PER_SEC 1000
+#endif
+int iic_create_xfr(iic_client_t* client, struct kiocb* iocb,
+ void* buf, size_t len, unsigned long flags,
+ iic_xfr_t** new_xfr, dd_ffdc_t ** o_ffdc)
+{
+ int rc = 0;
+ iic_xfr_t *xfr;
+ iic_xfr_opts_t *t_opts;
+ iic_eng_t *eng = client->bus->eng;
+ int i;
+ unsigned short j = 0, count = 0, size = 0;
+
+ IENTER();
+
+ xfr = (iic_xfr_t*) kmalloc(sizeof(iic_xfr_t), GFP_KERNEL);
+ if(!xfr)
+ {
+ *new_xfr = 0;
+ rc = -ENOMEM;
+ IFLDe(0, "kmalloc xfr failed\n");
+ goto exit;
+ }
+
+ memset(xfr, 0, sizeof(iic_xfr_t));
+
+ /* Copy all client attributes neccesary for doing the transfer
+ * into the xfr struct.
+ */
+ memcpy(&xfr->opts, &client->opts, sizeof(iic_opts_t));
+ xfr->client = client;
+ xfr->iocb = iocb;
+ xfr->flags = flags;
+ xfr->buf = (char*)buf;
+ xfr->size = len;
+ xfr->pid = current->pid;
+
+ /* modify the xfr opts for ease of use in the device driver */
+ t_opts = &xfr->opts.xfr_opts;
+ t_opts->inc_addr = (t_opts->inc_addr >> 1) << (t_opts->dev_width * 8);
+
+ /* device driver code will only look at the rdelay and rsplit fields.
+ * wdelay and wsplit values will be copied to rdelay and rsplit if
+ * this is a write transfer.
+ */
+ if(test_bit(IIC_XFR_RD, &xfr->flags))
+ {
+ if(t_opts->rsplit)
+ {
+ if (t_opts->rsplit > 0x8000)
+ {
+ t_opts->rsplit = 0x7FFF;
+ }
+ else {
+ t_opts->rsplit = t_opts->rsplit - 1;
+ }
+ }
+ else {
+ t_opts->rsplit = 0x7FFF;
+ }
+ }
+ else
+ {
+ unsigned long data_sz = xfr->size;
+ unsigned long start;
+ char* data = (char*)&xfr->offset_ffdc;
+
+ if(t_opts->wsplit)
+ {
+ t_opts->rsplit = t_opts->wsplit - 1;
+ t_opts->rdelay = t_opts->wdelay;
+ }
+
+ /* store off the first 4 bytes of write transfers now
+ * for ffdc.
+ */
+ if(data_sz > sizeof(long))
+ data_sz = sizeof(long);
+ start = sizeof(long) - data_sz;
+ }
+
+ /* prevent split numbers that just have one bit set (0x800,
+ * 0x20, etc) to avoid problems with split calculation
+ * in engine
+ */
+ count = 0;
+ size = sizeof(t_opts->rsplit) * 8;
+ for (j = 0; j < size && count <= 1; j++) {
+ if (t_opts->rsplit & (1 << j))
+ count++;
+ }
+
+ if (count == 1 && t_opts->rsplit > 2)
+ t_opts->rsplit = t_opts->rsplit - 1;
+
+ if(test_bit(IIC_ENG_BLOCK, &eng->flags))
+ {
+ IFLDe(1, "eng[%08x] blocked\n", eng->id);
+ rc = -ENODEV;
+ if(test_bit(IIC_ENG_REMOVED, &eng->flags))
+ rc = -ENOLINK;
+ xfr->status = rc;
+ goto error;
+ }
+
+ rc = 0;
+
+ *new_xfr = xfr;
+ goto exit;
+
+error:
+ kfree(xfr);
+ *new_xfr = 0;
+
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* called within a timer context to continue a delayed transfer */
+void iic_continue_xfr(unsigned long data)
+{
+ iic_xfr_t *xfr = (iic_xfr_t*)data;
+ IENTER();
+ IFLDd(1, "CONTINUE xfr[%p]\n", xfr);
+ clear_bit(IIC_XFR_DELAYED, &xfr->flags);
+
+ iic_start_next_xfr(xfr->client->bus->eng);
+ IEXIT(0);
+}
+
+/* Called by the engine when a transfer should only be continued after
+ * a period of time has expired.
+ * This is needed for implementing write delays.
+ * Note: delay is in milliseconds!
+ */
+void iic_delay_xfr(iic_xfr_t* xfr, unsigned long delay)
+{
+ iic_eng_t *eng = xfr->client->bus->eng;
+ IENTER();
+ IFLDd(2, "DELAY xfr[%p] time[%ld]\n", xfr, delay);
+ eng->cur_xfr = 0;
+
+ /* Get the next xfr started (if any) */
+ iic_start_next_xfr(eng);
+
+ /* Make sure the delayed bit is set */
+ set_bit(IIC_XFR_DELAYED, &xfr->flags);
+
+ /* Place this xfr back at the beginning of the queue */
+ list_add(&xfr->q_entry, &eng->xfrq);
+
+ /* Start a timer that will allow the transfer to start back up
+ * when it pops.
+ */
+ xfr->delay.data = (unsigned long)xfr;
+ xfr->delay.function = iic_continue_xfr;
+ mod_timer(&xfr->delay, jiffies + msecs_to_jiffies( delay ) );
+ IEXIT(0);
+
+}
+EXPORT_SYMBOL(iic_delay_xfr);
+
+void iic_finish_complete(unsigned long data)
+{
+ iic_xfr_t *xfr = (iic_xfr_t*)data;
+ IENTER();
+ clear_bit(IIC_XFR_DELAYED, &xfr->flags);
+ iic_xfr_complete(xfr);
+ IEXIT(0);
+}
+
+#define NUM_RETRIES 15
+#define RETRY_DELAY 5
+#define BACKOFF_DELAY 500
+
+/* Retry timeout fails for relatively long timeout periods */
+static unsigned long allow_retry(iic_xfr_t* xfr)
+{
+ /* No retry allowed - use original timeout period */
+ return (xfr->opts.xfr_opts.timeout);
+}
+
+unsigned long error_match(int status, unsigned long policy, iic_xfr_t* xfr)
+{
+ unsigned long error_bit = 0;
+ unsigned long rc = 0;
+ IENTER();
+ switch(status)
+ {
+ case -ENXIO:
+ /* Allow one retry for addr NACK */
+ rc = 1;
+ error_bit = IIC_VAL_ADDR_NOACK;
+ break;
+ case -ENODATA:
+ error_bit = IIC_VAL_DATA_NOACK;
+ break;
+ case -ETIME:
+ /* Allow one retry for long timeout periods */
+ if (allow_retry(xfr) != xfr->opts.xfr_opts.timeout)
+ rc = 1;
+ error_bit = IIC_VAL_TIMEOUT;
+ break;
+ case -EALREADY:
+ error_bit = IIC_VAL_LOST_ARB;
+
+ /* More retries hardcoded for bus multimaster failure
+ This behavior can be overridden by user config
+ set delay to 5 ms */
+ if (!(error_bit & policy)) {
+ /* the return code should actually be the
+ number of retries. See comparison in
+ rec_retry */
+ rc = NUM_RETRIES;
+ xfr->opts.recovery.redo_delay = RETRY_DELAY;
+ goto exit;
+ }
+ break;
+ case -EIO:
+ /* Allow retries for bus errors */
+ rc = 3;
+ error_bit = IIC_VAL_BUS_ERR;
+ break;
+ default:
+ break;
+ }
+ if(error_bit & policy)
+ rc = policy & ~IIC_VAL_ALL_ERRS;
+exit:
+ IEXIT((int)rc);
+ return rc;
+}
+
+/* 1 means 1 retry, 0 means no retries. */
+unsigned short rec_retry(iic_xfr_t* xfr)
+{
+ unsigned short rc;
+ IENTER();
+ rc = error_match(xfr->status, xfr->opts.recovery.redo_pol, xfr);
+ if(rc <= xfr->retry_count)
+ rc = 0;
+ IEXIT(rc);
+ return 0;
+}
+
+/* Returns the delay needed prior to retry. If a read or write delay
+ * was specified and is larger than the retry delay or the error
+ * isn't a policy match, then the read/write delay will be returned.
+ */
+unsigned long rec_delay(iic_xfr_t* xfr)
+{
+ unsigned long rc;
+ IENTER();
+ rc = xfr->opts.recovery.redo_delay ;
+ if(rc < xfr->opts.xfr_opts.rdelay)
+ rc = xfr->opts.xfr_opts.rdelay;
+ IEXIT((int)rc);
+ return rc;
+}
+
+/* Keep IIC_XFR_RD, IIC_XFR_ASYNC, and IIC_XFR_FAST when retrying xfr. */
+#define IIC_XFR_RESET_MASK 0x00000007
+
+/* Called by the engine specific code to notify us that the transfer ended.
+ * If the transfer requires a delay before starting a new transfer,
+ * (i.e., the IIC_XFR_DELAYED bit is set), then unlocking the address,
+ * notifying caller of completion, and cleanup of transfer will be delayed
+ * using a kernel timer.
+ */
+void iic_xfr_complete(iic_xfr_t* xfr)
+{
+ unsigned short delay;
+ int rc = 0;
+ iic_eng_t *eng = 0;
+ iic_xfr_opts_t *opts;
+
+ IENTER();
+
+ if(!xfr)
+ {
+ IFLDe(0, "iic_xfr_complete called on null xfr!\n");
+ goto exit;
+ }
+
+ opts = &xfr->opts.xfr_opts;
+
+ if(test_bit(IIC_XFR_ENDED, &xfr->flags) ||
+ test_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags))
+ {
+ IFLDd(2, "iic_xfr_complete xfr[%p] flags[%08lx] no-op\n",
+ xfr, xfr->flags);
+ goto exit;
+ }
+
+ eng = xfr->client->bus->eng;
+
+ if(xfr->status == -ETIME && eng->ops->finish_rescue_timeout) {
+ xfr->status =
+ ((rc = eng->ops->finish_rescue_timeout(eng, xfr)) >=
+ (long int)xfr->size)
+ ? 0
+ : -ETIME;
+ if(xfr->status == 0 && xfr->bytes_xfrd < xfr->size)
+ xfr->client->flags |= IIC_CLIENT_EOD;
+ IFLDd(1, "xfr->status[%d]\n", xfr->status);
+ }
+
+ /* Check if we need to retry this transfer if it failed.
+ * Only the first failure's FFDC and status will be kept.
+ * If the transfer succeeds on a retry, the FFDC will be freed
+ * and no error will be reported.
+ */
+ if(rec_retry(xfr))
+ {
+
+ IFLDi(7, "RETRY client[%p], bus[%d.%d:%d.%d.%d.%d]\n",
+ xfr->client, IIC_GET_PLINK(eng->id), IIC_GET_PCFAM(eng->id),
+ IIC_GET_LINK(eng->id), IIC_GET_CFAM(eng->id),
+ IIC_GET_ENG(eng->id), xfr->client->bus->port);
+ IFLDi(3, " xfr[%p] count[%d] status[%d]\n",
+ xfr, xfr->retry_count + 1, xfr->status);
+
+ /* increment retry count */
+ xfr->retry_count++;
+
+ /* reset timeout timer */
+ if(xfr->opts.xfr_opts.timeout)
+ {
+ mod_timer(&xfr->timeout, jiffies +
+ msecs_to_jiffies( allow_retry(xfr) ) );
+ }
+
+ /* if xfr timed out before starting, just leave it
+ * on the queue and give it another chance to run.
+ */
+ if(!test_bit(IIC_XFR_STARTED, &xfr->flags))
+ {
+ goto exit;
+ }
+
+ /* reset xfr to start at the beginning
+ * Note: can't do DMA on a retry because
+ * dma_setup can't be called from a
+ * interrupt handler.
+ */
+ delay = rec_delay(xfr);
+
+ /* Multi-master - Backoff an extended period after every four retries */
+ if ((xfr->status == -EALREADY) && ((xfr->retry_count % 4) == 0))
+ {
+ delay += BACKOFF_DELAY;
+
+ /* adjust timeout timer to include backoff */
+ if(xfr->opts.xfr_opts.timeout)
+ {
+ mod_timer(&xfr->timeout, jiffies +
+ msecs_to_jiffies( xfr->opts.xfr_opts.timeout +
+ BACKOFF_DELAY ) );
+ }
+ }
+
+ xfr->status = 0;
+ xfr->flags &= IIC_XFR_RESET_MASK;
+
+ /* notify others that a retry is in progress, so don't
+ * call iic_xfr_complete until retry is attempted.
+ * This flag is cleared when an error occurs or the xfr
+ * completes successfully or is cancelled. (Problem
+ * noticed in timeout function when dma xfrs were retried.)
+ */
+ set_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags);
+
+ /* Always call iic_delay_xfr so that failed transfers
+ * are given time to be cleaned up before we try
+ * a new transfer. If the delay is 0, the transfer
+ * is placed back on the queue and started as soon as
+ * cleanup of the previous attempt completes
+ */
+ iic_delay_xfr(xfr, delay);
+ goto exit;
+ }
+
+
+#ifdef DELAYED_COMPLETION
+ if(!test_bit(IIC_XFR_DELAYED, &xfr->flags))
+ {
+#endif
+ /* if this xfr currently owned the address lock, release it */
+ if(xfr->addr_lck->cur_xfr == xfr)
+ {
+ xfr->addr_lck->cur_xfr = 0;
+ }
+
+ /* unlock this xfr's address lock or dequeue lock request */
+ IDBGd(1, "xfr[%p] releasing lock\n", xfr);
+ iic_unlock(&xfr->client->bus->eng->lck_mgr, xfr->addr_lck);
+#ifdef DELAYED_COMPLETION
+ }
+#endif
+
+ /* If a transfer isn't already running, check if one is ready and
+ * start it.
+ */
+ if(eng->cur_xfr == xfr)
+ {
+ eng->cur_xfr = 0;
+ }
+ iic_start_next_xfr(eng);
+
+ /* Once iic_xfr_complete is called, the timeout and delay timers are
+ * no longer needed.
+ */
+ del_timer(&xfr->timeout);
+
+#ifdef DELAYED_COMPLETION
+ /* For transfers that require a delay, take care of unlocking
+ * the address, completion notification, and cleanup later.
+ */
+ if(test_bit(IIC_XFR_DELAYED, &xfr->flags))
+ {
+ xfr->delay.data = (unsigned long)xfr;
+ xfr->delay.function = iic_finish_complete;
+ mod_timer(&xfr->delay, jiffies + msecs_to_jiffies( xfr->opts.xfr_opts.rdelay ) );
+ IFLDd(2, "DELAYCOMP xfr[%p] time[%d]\n", xfr,
+ xfr->opts.xfr_opts.rdelay);
+ goto exit;
+ }
+#endif
+ del_timer(&xfr->delay);
+
+ set_bit(IIC_XFR_ENDED, &xfr->flags);
+
+ IFLDi(7, "COMPLETE client[%p] bus[%d.%d:%d.%d.%d.%d]\n",
+ xfr->client, IIC_GET_PLINK(eng->id), IIC_GET_PCFAM(eng->id),
+ IIC_GET_LINK(eng->id), IIC_GET_CFAM(eng->id), IIC_GET_ENG(eng->id),
+ xfr->client->bus->port);
+ IFLDi(2, " xfr[%p] status[%d]\n", xfr, xfr->status);
+
+ /**
+ * defer queueing of ffdc to the calling thread
+ * or to iic_cleanup_xfr for async transfers.
+ */
+
+ /* for async transfers, just call aio_complete and then cleanup
+ * the xfr object.
+ */
+ if(test_bit(IIC_XFR_ASYNC, &xfr->flags))
+ {
+ xfr->status = (xfr->status)? xfr->status: xfr->bytes_xfrd;
+ IFLDd(1, "aio_complete xfr[%p]\n", xfr);
+// aio_complete(xfr->iocb, xfr->status, 0);
+ iic_cleanup_xfr(xfr, NULL);
+ }
+
+ /* for sync transfers, just wake up the calling thread. The
+ * calling thread will handle any necessary cleanup.
+ */
+ else
+ {
+ IFLDd(1, "wake xfr[%p] client\n", xfr);
+ wake_up_interruptible(&xfr->client->wait);
+ }
+exit:
+ IEXIT(0);
+ return;
+}
+EXPORT_SYMBOL(iic_xfr_complete);
+
+/* This function is either called within an interrupt context or when
+ * interrupts are disabled. This function is called as recovery from
+ * various types of failures. FFDC should already be filled in before
+ * calling this function. Failures caused by the abort are ignored.
+ */
+void iic_abort_xfr(iic_xfr_t* xfr)
+{
+ int rc = 0;
+ iic_eng_t *eng = xfr->client->bus->eng;
+ IENTER();
+ IFLDi(1, "ABORTREQ xfr[%p]\n", xfr);
+
+ if(test_bit(IIC_XFR_ABORT, &xfr->flags))
+ {
+ IDBGd(0, "abort already started!\n");
+ goto exit;
+ }
+
+ /* If this was a retry, the retry completed. clear flag so that
+ * iic_xfr_complete can do its work.
+ */
+ clear_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags);
+
+ set_bit(IIC_XFR_ABORT, &xfr->flags);
+ /* If the xfr is still waiting to run, remove it from the queue */
+ if(eng->cur_xfr != xfr)
+ {
+ list_del(&xfr->q_entry);
+ }
+ /* Otherwise, the xfr is running. Lock the engine, Signal the hw
+ * to halt the transfer.
+ */
+ else
+ {
+ /* lock the engine so we don't try to start a new transfer
+ * until the current transfer is aborted
+ */
+ set_bit(IIC_ENG_ABORT, &eng->flags);
+
+ /* once the IIC_ENG_ABORT flag is set, the interrupt
+ * handler will no longer access the xfr data structure
+ * and it's safe to set the IIC_XFR_ENG_COMPLETE flag.
+ */
+ set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+
+ /* don't access hw if failed due to a parent bus access error */
+ if(!test_bit(IIC_NO_ACCESS, &eng->flags))
+ {
+ /* start the abort procedure */
+ rc = eng->ops->start_abort(eng, 0/*ignore ffdc*/);
+ }
+
+ /* Finish off the abort inside a work queue context. When
+ * the abort is completed, the engine will get unlocked and
+ * iic_start_next_xfr will get called.
+ */
+ schedule_work(&eng->work);
+
+ }
+exit:
+ IEXIT(0);
+ return;
+}
+EXPORT_SYMBOL(iic_abort_xfr);
+
+/* Work queue function that finishes an abort operation */
+void iic_finish_abort(struct work_struct * work)
+{
+ unsigned long flags;
+ iic_eng_t* eng = container_of(work, iic_eng_t, work);
+ IENTER();
+ /* don't access hw if we lost engine access */
+ if(!test_bit(IIC_NO_ACCESS, &eng->flags))
+ {
+ eng->ops->finish_abort(eng, 0);
+ }
+ spin_lock_irqsave(&eng->lock, flags);
+ clear_bit(IIC_ENG_ABORT, &eng->flags);
+ iic_start_next_xfr(eng);
+ spin_unlock_irqrestore(&eng->lock, flags);
+ IFLDd(0, "ABORTREQ (completed)\n");
+ IEXIT(0);
+}
+
+
+/* Timer function that handles the case where a transfer or abort takes
+ * too long to complete.
+ */
+void iic_timeout(unsigned long data)
+{
+ iic_xfr_t *xfr = (iic_xfr_t*)data;
+ iic_eng_t *eng = xfr->client->bus->eng;
+ unsigned long flags;
+
+ spin_lock_irqsave(&eng->lock, flags);
+ IENTER();
+ IFLDi(1, "TIMEOUT xfr[%p]\n", xfr);
+ if(test_bit(IIC_XFR_ENDED, &xfr->flags))
+ goto exit;
+
+ if(eng->ops->start_rescue_timeout) {
+ int rc;
+ xfr->status =
+ ((rc = eng->ops->start_rescue_timeout(eng, xfr)) >=
+ (long int)xfr->size)
+ ? xfr->status
+ : -ETIME;
+ if(xfr->status == 0 && xfr->bytes_xfrd < xfr->size)
+ xfr->client->flags |= IIC_CLIENT_EOD;
+ } else
+ xfr->status = -ETIME;
+
+ IFLDd(1, "xfr->status[%d]\n", xfr->status);
+
+ /* makes sure xfr_complete gets called in dma callback function */
+ set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+
+ /* for DMA, this will cause dma_notify to get called which
+ * calls our callback function, which calls
+ * abort_xfr / xfr_complete.
+ */
+ iic_abort_xfr(xfr);
+
+ /* Don't force users to wait for the abort to complete */
+ iic_xfr_complete(xfr);
+exit:
+ spin_unlock_irqrestore(&eng->lock, flags);
+ IEXIT(0);
+}
+
+int iic_xfr_ready(iic_xfr_t* xfr)
+{
+ int rc = 0; //xfr not ready
+ iic_lck_t *lck = xfr->addr_lck;
+ IENTER();
+
+ /* If this xfr owns the lock and isn't write delayed, and isn't
+ * blacklisted, the transfer is ready to run.
+ */
+ if(!test_bit(IIC_XFR_DELAYED, &xfr->flags) &&
+ (lck->count > 0) &&
+ ((lck->cur_xfr == 0) || (lck->cur_xfr == xfr)))
+ {
+ IDBGf(1, "xfr[%p] good to go\n", xfr);
+ lck->cur_xfr = xfr;
+ rc = 1;
+ }
+
+ IEXIT(rc);
+ return rc;
+}
+
+int iic_start_next_xfr(iic_eng_t* eng)
+{
+ int rc = 0;
+ iic_xfr_t *iterator, *xfr;
+ IENTER();
+ xfr = 0;
+
+ /* if a xfr is already running, or there is an abort or reset then
+ * do nothing.
+ */
+ if(eng->cur_xfr ||
+ test_bit(IIC_ENG_ABORT, &eng->flags) ||
+ test_bit(IIC_ENG_RESET, &eng->flags) ||
+ test_bit(IIC_ENG_BLOCK, &eng->flags))
+ {
+ /* Notify thread waiting to do reset that the engine might
+ * be idle now.
+ */
+ if(test_bit(IIC_ENG_RESET, &eng->flags))
+ {
+ wake_up_interruptible(&eng->waitq);
+ }
+ goto exit;
+ }
+
+ IDBGl(0, "Looking for next xfr\n");
+ /* scan the queue from the beginning for a transfer that's ready */
+ /* if the process that submitted the xfr is black-listed, it will
+ * be skipped
+ */
+ list_for_each_entry(iterator, &eng->xfrq, q_entry)
+ {
+ if(iic_xfr_ready(iterator))
+ {
+ xfr = iterator;
+ break;
+ }
+ }
+
+ /* If a xfr is ready to go, start it */
+ if(xfr)
+ {
+ /* set the delay bit here if necessary so that if the transfer
+ * is aborted other transfers to the same address will be
+ * delayed appropriately in iic_xfr_complete.
+ */
+ if(xfr->opts.xfr_opts.rdelay)
+ {
+ set_bit(IIC_XFR_DELAYED, &xfr->flags);
+ }
+ eng->cur_xfr = xfr;
+ list_del(&xfr->q_entry);
+ if(!test_bit(IIC_XFR_STARTED, &xfr->flags))
+ IFLDs(3, "START client[%p] bus[%08lx] xfr[%p]\n",
+ xfr->client, xfr->client->bus->bus_id, xfr);
+ clear_bit(IIC_NO_ACCESS, &eng->flags);
+ set_bit(IIC_XFR_STARTED, &xfr->flags);
+ rc = eng->ops->start(xfr);
+ if(rc)
+ {
+ /* If this was a retry, the retry completed.
+ * clear flag so that
+ * iic_xfr_complete can do its work.
+ */
+ clear_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags);
+
+ IFLDe(2, "xfr[%p] start failed: %d\n", xfr, rc);
+ iic_abort_xfr(xfr);
+ set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+
+ iic_xfr_complete(xfr);
+ }
+ }
+
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* Adds a xfr to the end of the queue */
+int iic_enq_xfr(iic_xfr_t *xfr)
+{
+ int rc;
+ unsigned long flags;
+ iic_eng_t *eng = xfr->client->bus->eng;
+ iic_xfr_opts_t *opts = &xfr->opts.xfr_opts;
+ IENTER();
+ spin_lock_irqsave(&eng->lock, flags);
+
+ /* Submit a lock request for this xfr (non-blocking) */
+ rc = iic_req_lock(&eng->lck_mgr,
+ opts->dev_addr,
+ (opts->inc_addr >> (opts->dev_width * 8)),
+ xfr->client,
+ &xfr->addr_lck);
+ if(rc < 0)
+ {
+ goto exit;
+ }
+
+ /* enqueue this xfr */
+ IFLDi(7, "SUBMIT client[%p] bus[%d.%d:%d.%d.%d.%d]\n",
+ xfr->client, IIC_GET_PLINK(eng->id), IIC_GET_PCFAM(eng->id),
+ IIC_GET_LINK(eng->id), IIC_GET_CFAM(eng->id), IIC_GET_ENG(eng->id),
+ xfr->client->bus->port);
+ IFLDi(5, " xfr[%p] addr[%04x:%04x] sz[%08lx] timeout[%ld]\n",
+ xfr, opts->dev_addr + ((test_bit(IIC_XFR_RD, &xfr->flags))? 1:0),
+ opts->rsplit, xfr->size, opts->timeout);
+ list_add_tail(&xfr->q_entry, &eng->xfrq);
+ set_bit(IIC_XFR_QUEUED, &xfr->flags);
+
+ /* start a kernel timer that will abort the transfer
+ * if it takes too long.
+ */
+ init_timer(&xfr->timeout);
+ xfr->timeout.data = (unsigned long)xfr;
+ xfr->timeout.function = iic_timeout;
+ if(opts->timeout)
+ {
+ xfr->timeout.expires = jiffies +
+ msecs_to_jiffies( allow_retry(xfr) );
+ add_timer(&xfr->timeout);
+ }
+ init_timer(&xfr->delay);
+
+ /* If no transfers are currently active, scan the queue for the
+ * next transfer and start it
+ */
+ iic_start_next_xfr(eng);
+
+ rc = -EIOCBQUEUED;
+exit:
+ spin_unlock_irqrestore(&eng->lock, flags);
+ IEXIT(rc);
+
+ return rc;
+}
+
+int iic_wait_xfr(iic_xfr_t *xfr)
+{
+ int rc = 0;
+ unsigned long flags;
+
+ IENTER();
+ IFLDd(2, "WAIT xfr[%p] time[%ld]\n",
+ xfr, xfr->opts.xfr_opts.timeout);
+ rc = wait_event_interruptible(xfr->client->wait,
+ test_bit(IIC_XFR_ENDED, &xfr->flags));
+ if(rc < 0)
+ {
+ /* EINTR is always retried at the adal level. ADAL users
+ * will never see the EINTR errno and won't know to collect
+ * FFDC for it, so don't generate FFDC for EINTR but do
+ * trace it.
+ */
+ spin_lock_irqsave(&xfr->client->bus->eng->lock, flags);
+ if(!xfr->status)
+ xfr->status = -EINTR;
+ IFLDe(2, "aborting xfr[%p] due to signal. pid[%d]\n",
+ xfr, xfr->pid);
+ iic_abort_xfr(xfr);
+
+ /* Don't force users to wait for the abort to complete */
+ iic_xfr_complete(xfr);
+ spin_unlock_irqrestore(&xfr->client->bus->eng->lock, flags);
+ }
+ IEXIT(rc);
+ return rc;
+}
+
+/*
+ * Shared read method between user space applications and sideways kernel
+ * calls.
+ */
+ssize_t iic_common_read(iic_client_t * client, void * buf, size_t count,
+ loff_t *offset, dd_ffdc_t ** o_ffdc)
+{
+ ssize_t rc = count;
+ iic_xfr_t *xfr;
+
+ IENTER();
+
+ BUG_ON(in_atomic());
+
+ if(!count)
+ {
+ rc = -EINVAL;
+ goto no_up;
+ }
+
+ if(down_interruptible(&client->sem))
+ {
+ rc = -EINTR;
+ goto no_up;
+ }
+
+ rc = iic_create_xfr(client, 0, buf, count, (1 << IIC_XFR_RD), &xfr,
+ o_ffdc);
+ if(rc)
+ {
+ goto exit;
+ }
+
+ /* enqueue or start the xfr */
+ rc = iic_enq_xfr(xfr);
+ if(rc != -EIOCBQUEUED)
+ {
+ goto error;
+ }
+
+ /* wait for xfr to complete */
+ iic_wait_xfr(xfr);
+
+ /* set rc appropriately */
+ if(xfr->status)
+ {
+ rc = xfr->status;
+ }
+ else
+ {
+ rc = xfr->bytes_xfrd;
+ client->opts.xfr_opts.offset += rc;
+ }
+
+ /* Data is already in the user buffer at this point.
+ * Cleanup the transfer and return status to the user.
+ */
+
+error:
+ iic_cleanup_xfr(xfr, o_ffdc);
+exit:
+ up(&client->sem);
+no_up:
+ IEXIT(rc);
+ return rc;
+}
+
+ssize_t iic_sideways_read(iic_client_t * client, void * buf, size_t count,
+ loff_t *offset, dd_ffdc_t ** o_ffdc)
+{
+ client->opts.xfr_opts.offset = *offset;
+ return iic_common_read(client, buf, count, offset, o_ffdc);
+}
+EXPORT_SYMBOL(iic_sideways_read);
+
+ssize_t iic_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *offset)
+{
+ ssize_t rc = count;
+ char *kbuf;
+ iic_client_t *client = (iic_client_t*)filp->private_data;
+
+ IENTER();
+
+ if (client->flags & IIC_CLIENT_EOD) {
+ client->flags &= ~(IIC_CLIENT_EOD);
+ return 0;
+ }
+
+ if(filp->f_flags & O_NONBLOCK)
+ {
+ rc = -EAGAIN;
+ goto exit;
+ }
+
+ if(!access_ok(VERIFY_READ, buf, count))
+ {
+ rc = -EFAULT;
+ goto exit;
+ }
+
+ kbuf = kzalloc(count, GFP_KERNEL);
+ if (!kbuf) {
+ rc = -ENOMEM;
+ goto exit;
+ }
+
+ rc = iic_common_read(client, kbuf, count, offset, NULL);
+
+ copy_to_user(buf, kbuf, count);
+
+ kfree(kbuf);
+
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/*
+ * Shared write method between user space and kernel 'sideways' calls.
+ */
+ssize_t iic_common_write(iic_client_t * client, void * buf, size_t count,
+ loff_t * offset, dd_ffdc_t ** o_ffdc)
+{
+ ssize_t rc = count;
+ iic_xfr_t *xfr;
+
+ IENTER();
+
+ BUG_ON(in_atomic());
+
+ if(!count)
+ {
+ rc = -EINVAL;
+ goto no_up;
+ }
+
+ if(down_interruptible(&client->sem))
+ {
+ rc = -EINTR;
+ goto no_up;
+ }
+
+ rc = iic_create_xfr(client, 0, buf, count, 0, &xfr, o_ffdc);
+ if(rc)
+ {
+ goto exit;
+ }
+
+ /* enqueue or start the xfr */
+ rc = iic_enq_xfr(xfr);
+ if(rc != -EIOCBQUEUED)
+ {
+ goto error;
+ }
+
+ /* wait for xfr to complete */
+ iic_wait_xfr(xfr);
+
+ /* set rc appropriately */
+ if(xfr->status)
+ {
+ rc = xfr->status;
+ }
+ else
+ {
+ rc = xfr->bytes_xfrd;
+ client->opts.xfr_opts.offset += rc;
+ }
+
+error:
+ iic_cleanup_xfr(xfr, o_ffdc);
+exit:
+ up(&client->sem);
+no_up:
+ IEXIT(rc);
+ return rc;
+}
+
+ssize_t iic_sideways_write(iic_client_t * client, void * buf, size_t count,
+ loff_t * offset, dd_ffdc_t ** o_ffdc)
+{
+ client->opts.xfr_opts.offset = *offset;
+ return iic_common_write(client, buf, count, offset, o_ffdc);
+}
+EXPORT_SYMBOL(iic_sideways_write);
+
+ssize_t iic_write(struct file *filp, const char __user *buf, size_t count,
+ loff_t *offset)
+{
+ ssize_t rc = count;
+ char *kbuf;
+ iic_client_t *client = (iic_client_t*)filp->private_data;
+
+ IENTER();
+
+ if (client->flags & IIC_CLIENT_EOD) {
+ client->flags &= ~(IIC_CLIENT_EOD);
+ return 0;
+ }
+
+ /* don't support posted writes at this time */
+ if(filp->f_flags & O_NONBLOCK)
+ {
+ rc = -EAGAIN;
+ goto exit;
+ }
+
+ if(!access_ok(VERIFY_WRITE, buf, count))
+ {
+ rc = -EFAULT;
+ goto exit;
+ }
+
+ kbuf = kzalloc(count, GFP_KERNEL);
+ if (!kbuf) {
+ rc = -ENOMEM;
+ goto exit;
+ }
+
+ copy_from_user(kbuf, buf, count);
+
+ rc = iic_common_write(client, kbuf, count, offset, NULL);
+
+ kfree(kbuf);
+
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* timout is in milliseconds! */
+int iic_reset(iic_bus_t* bus, int timeout, iic_ffdc_t** ffdc)
+{
+ int rc;
+ IENTER();
+ //IFLDi(1, "bus[%08lx]: reset requested\n", bus->bus_id);
+ /* block new transfers from starting on the engine */
+ set_bit(IIC_ENG_RESET, &bus->eng->flags);
+
+ /* wait for any pending operations on the engine to complete */
+ /* Note - timeout must be in jiffies for wait_for_idle! */
+ rc = bus->eng->ops->wait_for_idle(bus->eng, msecs_to_jiffies( timeout ), ffdc);
+ if(!rc)
+ {
+ /* do the reset */
+ rc = bus->eng->ops->reset_bus(bus, ffdc);
+ if(!rc)
+ {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+
+ /* schedule_timeout requires its parameter in jiffies. */
+ rc = schedule_timeout(IIC_RESET_DELAY);
+ }
+ }
+ if(rc < 0)
+ {
+ IFLDe(2, "bus[%08lx] reset failed: %d\n", bus->bus_id, rc);
+ }
+ else
+ {
+ IFLDi(2, "bus[%08lx]: reset complete. stucked[%d]",
+ bus->bus_id, (rc == 1)? 1: 0);
+ }
+
+ /* restart processing of new transfers */
+ spin_lock_irq(&bus->eng->lock);
+ clear_bit(IIC_ENG_RESET, &bus->eng->flags);
+ iic_start_next_xfr(bus->eng);
+ spin_unlock_irq(&bus->eng->lock);
+
+ IEXIT(rc);
+ return rc;
+
+}
+EXPORT_SYMBOL(iic_reset);
+
+/* We need to make sure no transfers are in progress before reading the state
+ * of a bus in case we need to switch to a different bus.
+ * Note: timeout is in milliseconds!
+ */
+int iic_get_bus_state(iic_bus_t* bus, unsigned long* state, int timeout,
+ iic_ffdc_t** ffdc)
+{
+ int rc;
+
+ IENTER();
+ /* block new transfers from starting on the engine */
+ set_bit(IIC_ENG_RESET, &bus->eng->flags);
+
+ /* wait for any pending operations on the engine to complete
+ * Note: timeout must be in jiffies
+ */
+ rc = bus->eng->ops->wait_for_idle(bus->eng, msecs_to_jiffies( timeout ), ffdc);
+ if(!rc)
+ {
+ /* check bus state */
+ rc = bus->eng->ops->get_bus_state(bus, state, ffdc);
+ IDBGs(3, "get_bus_state[%08lx]: state=%08lx, rc=%d\n",
+ bus->bus_id, *state, rc);
+ }
+
+ /* restart processing of new transfers */
+ spin_lock_irq(&bus->eng->lock);
+ clear_bit(IIC_ENG_RESET, &bus->eng->flags);
+ iic_start_next_xfr(bus->eng);
+ spin_unlock_irq(&bus->eng->lock);
+
+ IEXIT(rc);
+ return rc;
+}
+
+#define IIC_W(a) _IOC(_IOC_WRITE, 0, a, 0)
+#define IIC_R(a) _IOC(_IOC_READ, 0, a, 0)
+
+/*
+During an I2C transfer there is often the need to first send a command
+and then read back an answer right away. This has to be done without the
+risk of another (multimaster) device interrupting this atomic operation.
+The I2C protocol defines a so-called repeated start condition. After
+having sent the address byte (address and read/write bit) the master may
+send any number of bytes followed by a stop condition. Instead of sending
+the stop condition it is also allowed to send another start condition
+again followed by an address (and of course including a read/write bit)
+and more data. This is defined recursively allowing any number of start
+conditions to be sent. The purpose of this is to allow combined
+write/read operations to one or more devices without releasing the bus
+and thus with the guarantee that the operation is not interrupted.
+
+Before reading data from the slave, you must tell it which of its
+internal address (offset) you want to read.
+So a read of the slave actually starts off by writing to it.
+This is the same as when you want to write to it: You send the
+start sequence, the I2C address of the slave with the R/W bit
+and the internal register number (i.e offset) you want to write to.
+Now you send another start sequence (sometimes called a restart)
+and the I2C address again - this time
+with the read bit set. You then read as many data bytes as you
+wish and terminate the transaction with a stop sequence.
+*/
+
+int iic_repeated_xfer(iic_client_t *client, struct i2c_msg msgs[], int num)
+{
+ struct i2c_msg *pmsg;
+ iic_xfr_t *xfr;
+ iic_opts_t* opts;
+ iic_xfr_opts_t* xfr_opts;
+ u8 __user **data_ptrs;
+ u8 *current_msg_buf_ptr;
+ int rc = 0;
+ int i;
+ unsigned long options;
+
+ opts = &client->opts;
+ xfr_opts = &opts->xfr_opts;
+ //unsigned long new_port = xfr->client->bus->port;
+
+ data_ptrs = kmalloc(num * sizeof(u8 __user *), GFP_KERNEL);
+ if (data_ptrs == NULL) {
+ rc = -ENOMEM;
+ goto exit;
+ }
+
+ // Get the offset from the configuration which is the default.
+ // The default will be overridden by the message.
+ for (i = 0; i < num; i++) {
+ pmsg = &msgs[i];
+ if (!pmsg->len) /* If length is zero */
+ continue; /* on to the next request. */
+ data_ptrs[i] = (u8 __user *)msgs[i].buf;
+ current_msg_buf_ptr = kmalloc(pmsg->len, GFP_KERNEL);
+ if (current_msg_buf_ptr == NULL)
+ {
+ rc = -ENOMEM;
+ goto error;
+ }
+ // Bring over the user space buffer in order to
+ // retrieve the offset.
+ // We need to set up the offset before calling
+ // iic_create_xfr.
+ // We still pass the user buffer to the iic_create_xfr because
+ // the function will do its own conversion.
+ if(copy_from_user(current_msg_buf_ptr,
+ data_ptrs[i],
+ msgs[i].len))
+ {
+ rc = -EFAULT;
+ kfree(current_msg_buf_ptr);
+ goto error;
+ }
+
+ options = 0;
+ if (i != num -1)
+ {
+ set_bit(IIC_REPEATED_START, &options);
+ xfr_opts->flags |= options;
+ }
+ else
+ {
+ clear_bit(IIC_REPEATED_START, &options);
+ xfr_opts->flags &= options;
+ }
+
+ // Need to set the slave address here
+ xfr_opts->dev_addr = pmsg->addr;
+ // The offset is passed down by the adal_iic_config using
+ // the ADAL_IIC_CFG_OFFSET parameter.
+ // Refer to the ioctl case IIC_W(IIC_IOC_OFFSET):
+ // xfr_opts->offset = val;
+ // The user should configure the dev_width for their specific
+ // slave device before calling the ioctl.
+ // If the dev_width = 0, then we set the option to default 2.
+ if (xfr_opts->dev_width == 0)
+ {
+ xfr_opts->dev_width = 2;
+ }
+
+
+ // This is the read command
+ if (pmsg->flags & I2C_M_RD)
+ {
+ //set_bit(IIC_XFR_RD, &options);
+ rc = iic_create_xfr(client, 0, (void*)pmsg->buf,
+ pmsg->len, (1 << IIC_XFR_RD), &xfr,
+ NULL);
+ }
+ else
+ {
+ if (num > 1)
+ {
+ // Multiple messages recieved
+ // The first msg contains the offset for
+ // the repeated start.
+ // Otherwise it's just a regular write.
+ if ( (i == 0) && (msgs[1].flags & I2C_M_RD) )
+ {
+ xfr_opts->offset = *current_msg_buf_ptr;
+ // Set the offset and don't do the write
+ continue;
+ }
+ }
+
+ // This is a regular write
+
+ rc = iic_create_xfr(client, 0, (void*)pmsg->buf,
+ pmsg->len, 0, &xfr, NULL);
+ }
+ if(rc)
+ {
+ goto exit;
+ }
+ /* enqueue or start the xfr */
+ rc = iic_enq_xfr(xfr);
+ if(rc != -EIOCBQUEUED)
+ {
+ goto error;
+ }
+
+ /* wait for xfr to complete */
+ iic_wait_xfr(xfr);
+ /* set rc appropriately */
+ rc = xfr->status;
+ kfree(current_msg_buf_ptr);
+ }
+ /* Data is already in the user buffer at this point.
+ * Cleanup the transfer and return status to the user.
+ */
+
+error:
+ iic_cleanup_xfr(xfr, NULL);
+exit:
+ kfree(data_ptrs);
+ return rc;
+}
+long iic_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ iic_ffdc_t* ffdc = 0;
+ int ret = 0;
+ unsigned long val = 0;
+ iic_client_t* client;
+ iic_eng_t* eng;
+ iic_opts_t* opts;
+ iic_xfr_opts_t* xfr_opts;
+ iic_rec_pol_t* recovery;
+ int ioc_nr = _IOC_NR(cmd);
+ struct i2c_msg *iic_msg_ptr;
+ struct i2c_rdwr_ioctl_data iic_msg_arg;
+
+ IENTER();
+
+ client = (iic_client_t*)file->private_data;
+ eng = client->bus->eng;
+
+ /* Allow address unlock to occur even if blacklisted or blocked */
+ if(ioc_nr == IIC_IOC_ULCK_ADDR)
+ goto skip_check;
+ if(test_bit(IIC_ENG_BLOCK, &eng->flags))
+ {
+ IFLDe(1, "IOCTL eng[%08x] blocked\n", eng->id);
+ ret = -ENODEV;
+ if(test_bit(IIC_ENG_REMOVED, &eng->flags))
+ ret = -ENOLINK;
+ goto exit;
+ }
+
+skip_check:
+ opts = &client->opts;
+ xfr_opts = &opts->xfr_opts;
+ recovery = &opts->recovery;
+
+ if(down_interruptible(&client->sem))
+ {
+ IEXIT(-EINTR);
+ return -EINTR;
+ }
+
+ if((_IOC_TYPE(cmd) != IIC_IOC_MAGIC) ||
+ (_IOC_NR(cmd) > IIC_IOC_MAXNR))
+ {
+ ret = -ENOTTY;
+ goto exit;
+ }
+
+ /* strip the magic and size info from the command that we don't care
+ * about.
+ */
+ cmd = _IOC(_IOC_DIR(cmd), 0, _IOC_NR(cmd), 0);
+
+ /* Check if no data needs to be transfered for this ioctl */
+ if(_IOC_DIR(cmd) == _IOC_NONE)
+ {
+ switch(_IOC_NR(cmd))
+ {
+ case IIC_IOC_RESET_FULL:
+ /* reset xfr opts to default values */
+ memcpy(opts, &iic_dflt_opts, sizeof(*opts));
+
+ case IIC_IOC_RESET_LIGHT:
+ /* only allow 1 user requested reset per engine
+ * at a time.
+ */
+ IFLDi(2, "RESET client[%p] bus[%08lx]\n",
+ client, client->bus->bus_id);
+ if(down_interruptible(&eng->sem))
+ {
+ ret = -EINTR;
+ break;
+ }
+ ret = iic_reset(client->bus, xfr_opts->timeout, &ffdc);
+ up(&eng->sem);
+
+ break;
+ case IIC_IOC_REPEATED_IO:
+ // The buffer pointer is stored in arg.
+ // Try to get the pointer out from the arg and
+ // send the request out one by one using the
+ // existing I/O method.
+ // Do not write "STOP" until the last I/O request
+ // is done.
+ ret = copy_from_user(&iic_msg_arg,
+ (struct i2c_rdwr_ioctl_data __user *)arg, sizeof(iic_msg_arg));
+ if (ret)
+ {
+ ret = -EFAULT;
+ break;
+ }
+ /* Put an arbitrary limit on the number of messages that can
+ * be sent at once */
+ if (iic_msg_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
+ {
+ ret = -EFAULT;
+ break;
+ }
+ iic_msg_ptr = (struct i2c_msg *)
+ kmalloc(iic_msg_arg.nmsgs * sizeof(struct i2c_msg),
+ GFP_KERNEL);
+ if (iic_msg_ptr == NULL)
+ {
+ ret = -ENOMEM;
+ break;
+ }
+
+ if (copy_from_user(iic_msg_ptr, iic_msg_arg.msgs,
+ iic_msg_arg.nmsgs * sizeof(struct i2c_msg))) {
+ kfree(iic_msg_ptr);
+ ret = -EFAULT;
+ break;
+ }
+
+ // We don't want to convert the data pointer here
+ // because the set_iic_xfr will do the conversion
+
+ ret = iic_repeated_xfer(client, iic_msg_ptr,
+ iic_msg_arg.nmsgs);
+ if (ret < 0)
+ {
+ ret = -EFAULT;
+ kfree(iic_msg_ptr);
+ break;
+ }
+ else
+ ret = 0;
+ kfree(iic_msg_ptr);
+
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ goto exit;
+ }
+
+ /* handle 4 byte args here */
+ if(ioc_nr <= IIC_IOC_4_BYTES)
+ {
+ if((_IOC_DIR(cmd) == _IOC_WRITE) &&
+ (ret = get_user(val, (unsigned long*)arg)))
+ {
+ goto exit;
+ }
+ switch(cmd)
+ {
+ case IIC_W(IIC_IOC_SPEED):
+ if((val < 1) || (val > 55))
+ {
+ ret = -EINVAL;
+ break;
+ }
+ ret = eng->ops->set_speed(client->bus,
+ val);
+ break;
+ case IIC_R(IIC_IOC_SPEED):
+ val = eng->ops->get_speed(client->bus);
+ break;
+ case IIC_W(IIC_IOC_DEV_ADDR):
+ xfr_opts->dev_addr = val;
+ xfr_opts->offset = 0;
+ break;
+ case IIC_R(IIC_IOC_DEV_ADDR):
+ val = xfr_opts->dev_addr;
+ break;
+ case IIC_W(IIC_IOC_DEV_WIDTH):
+ xfr_opts->dev_width = val;
+ xfr_opts->offset = 0;
+ break;
+ case IIC_R(IIC_IOC_DEV_WIDTH):
+ val = xfr_opts->dev_width;
+ break;
+ case IIC_W(IIC_IOC_OFFSET):
+ xfr_opts->offset = val;
+ break;
+ case IIC_R(IIC_IOC_OFFSET):
+ val = xfr_opts->offset;
+ break;
+ case IIC_W(IIC_IOC_INC_ADDR):
+ xfr_opts->inc_addr = val;
+ break;
+ case IIC_R(IIC_IOC_INC_ADDR):
+ val = xfr_opts->inc_addr;
+ break;
+ case IIC_W(IIC_IOC_TIMEOUT):
+ xfr_opts->timeout = val;
+ break;
+ case IIC_R(IIC_IOC_TIMEOUT):
+ val = xfr_opts->timeout;
+ break;
+ case IIC_W(IIC_IOC_RDELAY):
+ xfr_opts->rdelay = val;
+ break;
+ case IIC_R(IIC_IOC_RDELAY):
+ val = xfr_opts->rdelay;
+ break;
+ case IIC_W(IIC_IOC_WDELAY):
+ xfr_opts->wdelay = val;
+ break;
+ case IIC_R(IIC_IOC_WDELAY):
+ val = xfr_opts->wdelay;
+ break;
+ case IIC_W(IIC_IOC_RSPLIT):
+ xfr_opts->rsplit = val;
+ break;
+ case IIC_R(IIC_IOC_RSPLIT):
+ val = xfr_opts->rsplit;
+ break;
+ case IIC_W(IIC_IOC_WSPLIT):
+ xfr_opts->wsplit = val;
+ break;
+ case IIC_R(IIC_IOC_WSPLIT):
+ val = xfr_opts->wsplit;
+ break;
+ case IIC_W(IIC_IOC_REDO_POL):
+ recovery->redo_pol = val;
+ break;
+ case IIC_R(IIC_IOC_REDO_POL):
+ val = recovery->redo_pol;
+ break;
+ case IIC_W(IIC_IOC_REDO_DELAY):
+ recovery->redo_delay = val;
+ break;
+ case IIC_R(IIC_IOC_REDO_DELAY):
+ val = recovery->redo_delay;
+ break;
+ case IIC_R(IIC_IOC_BUS_STATE):
+ if(down_interruptible(&eng->sem))
+ {
+ ret = -EINTR;
+ break;
+ }
+ ret = iic_get_bus_state(client->bus, &val,
+ xfr_opts->timeout,
+ &ffdc);
+ up(&eng->sem);
+ break;
+ case IIC_W(IIC_IOC_FLAGS):
+ if(val & ~(IIC_FORCE_DMA | IIC_NO_DMA |
+ IIC_SPECIAL_RD))
+ {
+ ret = -EINVAL;
+ break;
+ }
+ xfr_opts->flags = val;
+ break;
+ case IIC_R(IIC_IOC_FLAGS):
+ val = xfr_opts->flags;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ if((_IOC_DIR(cmd) == _IOC_READ) && !ret)
+ {
+ ret = put_user(val, (unsigned long*)arg);
+ }
+ goto exit;
+ }
+
+ /* handle objects larger than 4 bytes here */
+ switch(cmd)
+ {
+ iic_lock_t ulck;
+ iic_lck_t *klck;
+
+ case IIC_W(IIC_IOC_LCK_ADDR):
+ case IIC_W(IIC_IOC_LCK_ENG):
+ if((ret = copy_from_user(&ulck, (void*)arg,
+ sizeof(ulck))))
+ {
+ ret = -EFAULT;
+ break;
+ }
+
+ ret = iic_wait_lock(&eng->lck_mgr, ulck.addr,
+ (cmd == IIC_W(IIC_IOC_LCK_ENG))
+ ? ulck.mask
+ : ulck.mask >> 1,
+ client,
+ msecs_to_jiffies( ulck.timeout));
+ break;
+ case IIC_W(IIC_IOC_ULCK_ADDR):
+ case IIC_W(IIC_IOC_ULCK_ENG):
+ if((ret = copy_from_user(&ulck, (void*)arg,
+ sizeof(ulck))))
+ {
+ ret = -EFAULT;
+ break;
+ }
+ spin_lock_irq(&eng->lock);
+ klck = iic_find_handle(&eng->lck_mgr, client,
+ ulck.addr,
+ (cmd == IIC_W(IIC_IOC_ULCK_ENG))
+ ? ulck.mask
+ : ulck.mask >> 1);
+ if(klck)
+ {
+ ret = iic_unlock(&eng->lck_mgr, klck);
+ if(!ret)
+ iic_start_next_xfr(eng);
+ }
+ spin_unlock_irq(&eng->lock);
+ break;
+ case IIC_W(IIC_IOC_ALL):
+ if((ret = copy_from_user(opts, (void*)arg,
+ sizeof(*opts))))
+ {
+ ret = -EFAULT;
+ }
+ break;
+ case IIC_R(IIC_IOC_ALL):
+ if((ret = copy_to_user((void*)arg, opts,
+ sizeof(*opts))))
+ {
+ ret = -EFAULT;
+ }
+ break;
+ case IIC_W(IIC_IOC_DISPLAY_REGS):
+ eng->ops->display_regs(eng, 0);
+ break;
+ default:
+ ret = -EINVAL;
+ goto exit;
+ }
+
+exit:
+ up(&client->sem);
+ IFLDd(5, "IOCTL client[%p] bus[%08lx] cmd[%08x] ptr[%08lx] val[%08lx]\n",
+ client, client->bus->bus_id, cmd, arg, val);
+ IEXIT(ret);
+ return ret;
+}
+
+loff_t iic_llseek(struct file *filp, loff_t off, int whence)
+{
+ iic_client_t* client;
+ iic_xfr_opts_t* xfr_opts;
+ loff_t new_pos;
+
+ IENTER();
+ client = (iic_client_t*)filp->private_data;
+ xfr_opts = &client->opts.xfr_opts;
+
+ if(down_interruptible(&client->sem))
+ {
+ new_pos = -EINTR;
+ goto exit;
+ }
+ switch(whence)
+ {
+ case 0: /* SEEK_SET */
+ new_pos = off;
+ break;
+ case 1: /* SEEK_CUR */
+ new_pos = xfr_opts->offset + off;
+ break;
+ case 2: /* SEEK_END, not supported */
+ default:
+ new_pos = -EINVAL;
+ }
+
+ if(new_pos >= 0)
+ {
+ xfr_opts->offset = new_pos;
+ }
+ up(&client->sem);
+exit:
+ IFLDd(2, "client[%p] seek: new_pos=%08lx\n", client,
+ (unsigned long)new_pos);
+ IEXIT((int)new_pos);
+ return new_pos;
+}
+
+static int iic_mmap(struct file* filp, struct vm_area_struct* vma)
+{
+ int rc = 0;
+ iic_client_t *client = (iic_client_t*)filp->private_data;
+ iic_eng_t* eng = client->bus->eng;
+ // iopa doesn't exist in MCP6 kernel
+ unsigned long phys_base_addr = virt_to_phys((unsigned long *)eng->base);
+ IENTER();
+ printk("mmap\n");
+
+ vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP | VM_IO;
+ printk(">>remap_page_range(%08lX, 0, %08lX, %08lX, )\n",
+ vma->vm_start, phys_base_addr, vma->vm_end - vma->vm_start);
+ rc = remap_pfn_range(vma, vma->vm_start, phys_base_addr,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot);
+ printk("<<remap_page_range = %d\n", rc);
+ if(rc){
+ return -EINVAL;
+ }
+ IEXIT(0);
+ return 0;
+}
+
int iic_register_eng_ops(iic_eng_ops_t* new_ops, unsigned long type)
{
iic_eng_type_t* new_type = (iic_eng_type_t*)
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index 9493842..aa4eb10 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -155,6 +155,7 @@ header-y += hyperv.h
header-y += hysdn_if.h
header-y += i2c-dev.h
header-y += i2c.h
+header-y += i2cfsi.h
header-y += i2o-dev.h
header-y += i8k.h
header-y += icmp.h
diff --git a/include/uapi/linux/i2cfsi.h b/include/uapi/linux/i2cfsi.h
new file mode 100644
index 0000000..78cab53
--- /dev/null
+++ b/include/uapi/linux/i2cfsi.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2006, 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _UAPI_I2CFSI_H
+#define _UAPI_I2CFSI_H
+
+#define I2C_RDRW_IOCTL_MAX_MSGS 42
+
+typedef struct iic_rec_pol
+{
+ unsigned long redo_pol;
+#define IIC_VAL_ADDR_NOACK 0x00010000
+#define IIC_VAL_DATA_NOACK 0x00020000
+#define IIC_VAL_TIMEOUT 0x00040000
+#define IIC_VAL_LOST_ARB 0x00080000
+#define IIC_VAL_BUS_ERR 0x00100000
+#define IIC_VAL_ALL_ERRS 0xffff0000
+ unsigned long rsvd;
+ unsigned long redo_delay;
+} iic_rec_pol_t;
+
+#define IIC_VAL_100KHZ 100
+#define IIC_VAL_400KHZ 400
+typedef struct iic_xfr_opts
+{
+ unsigned short rsvd;
+ unsigned short dev_addr; // address of end device
+ unsigned short dev_width; // number of bytes for offset (1-4)
+ unsigned long inc_addr; // mask of address bits to increment
+ // for devices that span multiple
+ // addresses.
+ unsigned long timeout; // operation timeout (msec)
+ unsigned short wdelay; // delay between write xfrs (msec)
+ unsigned short rdelay; // delay between read xfrs (msec)
+ unsigned short wsplit; // splits writes into smaller chunks
+ unsigned short rsplit; // splits reads into smaller chunks
+ unsigned long offset; // offset from beginning of device
+ unsigned long flags; // flags defined below
+} iic_xfr_opts_t;
+
+enum
+{
+ IIC_FORCE_DMA = 0x01, // use dma regardless of xfr size
+ IIC_NO_DMA = 0x02, // disallow dma
+ IIC_SPECIAL_RD = 0x04, // workaround for PLL/CRC chips
+ IIC_REPEATED_START = 0x08, // repeated start
+};
+
+typedef struct iic_opts
+{
+ iic_xfr_opts_t xfr_opts;
+ iic_rec_pol_t recovery;
+} iic_opts_t;
+
+typedef struct iic_lock
+{
+ unsigned short mask;
+ unsigned short addr;
+ unsigned long timeout;
+} iic_lock_t;
+
+typedef struct iicslv_opts
+{
+ unsigned long addr;
+ unsigned long timeout;
+} iicslv_opts_t;
+
+#define IICSLV_ZBUF_MAX_SZ 256
+
+/* external master access mode of local slave shared buffer */
+enum
+{
+ IICSLV_BUF_MODE_EXT_R = 1,
+ IICSLV_BUF_MODE_EXT_RW = 2,
+};
+
+/* Master IOCTL Ordinal Numbers */
+#define IIC_IOC_MAGIC 0x07
+enum
+{
+ /* 0 bytes */
+ IIC_IOC_RESET_LIGHT,
+ IIC_IOC_RESET_FULL,
+
+ IIC_IOC_0_BYTES = IIC_IOC_RESET_FULL,
+
+ /* 4 bytes */
+ IIC_IOC_SPEED,
+ IIC_IOC_DEV_ADDR,
+ IIC_IOC_DEV_WIDTH,
+ IIC_IOC_OFFSET,
+ IIC_IOC_INC_ADDR,
+ IIC_IOC_TIMEOUT,
+ IIC_IOC_RDELAY,
+ IIC_IOC_WDELAY,
+ IIC_IOC_RSPLIT,
+ IIC_IOC_WSPLIT,
+ IIC_IOC_REDO_POL,
+ IIC_IOC_SPD_POL,
+ IIC_IOC_REDO_DELAY,
+ IIC_IOC_BUS_STATE,
+#define IIC_VAL_BOTH_LO 0x00
+#define IIC_VAL_SDA_LO 0x01
+#define IIC_VAL_SCL_LO 0x02
+#define IIC_VAL_BOTH_HI 0x03
+ IIC_IOC_FLAGS,
+
+ IIC_IOC_4_BYTES = IIC_IOC_FLAGS,
+
+ /* Objects */
+ IIC_IOC_LCK_ADDR,
+ IIC_IOC_ULCK_ADDR,
+ IIC_IOC_LCK_ENG,
+ IIC_IOC_ULCK_ENG,
+ IIC_IOC_ALL,
+ IIC_IOC_DISPLAY_REGS,
+ IIC_IOC_REPEATED_IO,
+ IIC_IOC_MAXNR = IIC_IOC_REPEATED_IO,
+};
+
+#endif
--
1.8.3.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH linux v1 8/8] drivers: fsi: i2c: boe engine
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
` (3 preceding siblings ...)
2017-02-02 23:26 ` [PATCH linux v1 7/8] drivers: fsi: i2c: add driver file operations and bus locking eajames
@ 2017-02-02 23:26 ` eajames
2017-02-02 23:26 ` [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling eajames
` (3 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: eajames @ 2017-02-02 23:26 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Edward A. James
From: "Edward A. James" <eajames@us.ibm.com>
Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
drivers/fsi/i2c/Makefile | 2 +-
drivers/fsi/i2c/iic-boe.c | 1597 +++++++++++++++++++++++++++++++++++++++++++++
drivers/fsi/i2c/iic-boe.h | 180 +++++
3 files changed, 1778 insertions(+), 1 deletion(-)
create mode 100644 drivers/fsi/i2c/iic-boe.c
create mode 100644 drivers/fsi/i2c/iic-boe.h
diff --git a/drivers/fsi/i2c/Makefile b/drivers/fsi/i2c/Makefile
index 4d04026..59ecf36 100644
--- a/drivers/fsi/i2c/Makefile
+++ b/drivers/fsi/i2c/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_FSI_I2C) += iic-fsi.o iic-mstr.o iic-lock.o
+obj-$(CONFIG_FSI_I2C) += iic-fsi.o iic-mstr.o iic-lock.o iic-boe.o
diff --git a/drivers/fsi/i2c/iic-boe.c b/drivers/fsi/i2c/iic-boe.c
new file mode 100644
index 0000000..27dc8c1
--- /dev/null
+++ b/drivers/fsi/i2c/iic-boe.c
@@ -0,0 +1,1597 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2006, 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+#include <linux/mm.h>
+#include <linux/highmem.h>
+#include "iic-int.h"
+#include "iic-boe.h"
+#include <linux/fsi.h>
+#include <asm/unaligned.h>
+
+/* Wrappers around the register access functions allow for access
+ * over different types of busses (i.e. FSI or OPB).
+ */
+#define iic_readb(eng, reg, val, ffdc)\
+ (eng->ra->bus_readb(eng, reg * 4, val, ffdc))
+#define iic_readh(eng, reg, val, ffdc)\
+ (eng->ra->bus_readh(eng, reg * 4, val, ffdc))
+#define iic_readw(eng, reg, val, ffdc)\
+ (eng->ra->bus_readw(eng, reg * 4, val, ffdc))
+
+#define iic_writeb(eng, reg, val, ffdc)\
+ (eng->ra->bus_writeb(eng, reg * 4, val, ffdc))
+#define iic_writeh(eng, reg, val, ffdc)\
+ (eng->ra->bus_writeh(eng, reg * 4, val, ffdc))
+#define iic_writew(eng, reg, val, ffdc)\
+ (eng->ra->bus_writew(eng, reg * 4, val, ffdc))
+
+/* Actual equation for the clock divider is
+ * (((lb_hz / (iic_hz)) - 1) / 4) - 1), but 1 is added in
+ * order to compensate for truncation errors -- It's better to be a little
+ * slow than to be a little too fast.
+ */
+#define IIC_BOE_HZ2DIV(lb_hz, iic_hz) _clock_divider
+#define IIC_BOE_DIV2HZ(lb_hz, d) \
+ ((lb_hz) / ((4 * ((d) + 1)) + 1))
+
+/* Bus address calc for larger SEEPROM devices
+ */
+#define IIC_BOE_CALC_BUS_ADDR(opts, xfr) \
+ opts->dev_addr + (((opts->offset + xfr->bytes_xfrd) & opts->inc_addr) \
+ >> ((opts->dev_width * 8) - 1));
+
+/* Externalized Master Functions */
+int iic_boe_use_dma(iic_xfr_t* xfr);
+int iic_boe_start(iic_xfr_t* xfr);
+int iic_boe_start_abort(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_finish_abort(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_rescue_timeout(iic_eng_t* eng, iic_xfr_t* xfr);
+int iic_boe_reset_bus(iic_bus_t* bus, iic_ffdc_t**);
+int iic_boe_reset_eng(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_run_bat(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_eng_init(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_enable_int(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_disable_int(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_cleanup_eng(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_int_handler(int, void*);
+int iic_boe_wait_for_idle(iic_eng_t* eng, int timeout, iic_ffdc_t**);
+void iic_boe_display_regs(iic_eng_t* eng, iic_ffdc_t**);
+int iic_boe_get_bus_state(iic_bus_t* bus, unsigned long* state, iic_ffdc_t**);
+int iic_boe_set_speed(iic_bus_t* bus, int speed);
+int iic_boe_get_speed(iic_bus_t* bus);
+
+#define IIC_BOE_BUS_RESET 1
+#define IIC_BOE_ENG_RESET 0
+int iic_boe_reset(iic_eng_t* eng, int type, iic_ffdc_t** ffdc);
+void iic_boe_dma_callback(int dma_rc, void* ffdc, void* data);
+int iic_boe_check_ddr4_nack(iic_xfr_t *xfr);
+
+static const char iic_boe_version[] = "3.0";
+static unsigned int _clock_divider = 6;
+
+static iic_eng_ops_t eng_ops = {
+ .use_dma = &iic_boe_use_dma,
+ .start = &iic_boe_start,
+ .start_abort = &iic_boe_start_abort,
+ .finish_abort = &iic_boe_finish_abort,
+ .start_rescue_timeout = &iic_boe_rescue_timeout,
+ .finish_rescue_timeout = &iic_boe_rescue_timeout,
+ .reset_bus = &iic_boe_reset_bus,
+ .reset_eng = &iic_boe_reset_eng,
+ .run_bat = &iic_boe_run_bat,
+ .init = &iic_boe_eng_init,
+ .enable_int = &iic_boe_enable_int,
+ .disable_int = &iic_boe_disable_int,
+ .int_handler = &iic_boe_int_handler,
+ .wait_for_idle = &iic_boe_wait_for_idle,
+ .cleanup_eng = 0,
+ .display_regs = iic_boe_display_regs,
+ .slv_on = 0,
+ .slv_off = 0,
+ .slv_recv = 0,
+ .slv_cont = 0,
+ .slv_set_addr = 0,
+ .slv_get_addr = 0,
+ .get_bus_state = iic_boe_get_bus_state,
+ .set_speed = iic_boe_set_speed,
+ .get_speed = iic_boe_get_speed,
+ .send = 0,
+};
+
+#define IIC_BOE_MAX_CLKDIV 0x0FFF
+int iic_boe_set_speed(iic_bus_t* bus, int i2c_hz)
+{
+ _clock_divider = i2c_hz;
+}
+
+int iic_boe_get_speed(iic_bus_t* bus)
+{
+ return _clock_divider;
+}
+
+void iic_boe_display_regs(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int i;
+ unsigned long reg;
+ IENTER();
+ for(i = 1; i < IIC_BOE_MAX_OFFSET; i++)
+ {
+ if(eng->ra->bus_readw(eng, i * 4, ®, 0))
+ break;
+ printk("%02x: %08lx\n", i, reg);
+ }
+ IEXIT(0);
+}
+
+/* rc > 0 means use dma
+ * rc = 0 means don't use dma
+ * rc < 0 means dma setup failed
+ */
+#define IIC_BOE_MIN_DMA_SIZE 129
+int iic_boe_use_dma(iic_xfr_t* xfr)
+{
+ int rc = 0;
+ IEXIT(rc);
+ return rc;
+}
+
+#define IIC_BOE_SCL_SHIFT 11
+#define IIC_BOE_SDA_SHIFT 9
+/* Returns the logical state of the clock and data lines */
+int iic_boe_get_bus_state(iic_bus_t* bus, unsigned long* state, iic_ffdc_t** ffdc)
+{
+ unsigned long stat = 0;
+ unsigned long mode;
+ int rc = 0;
+
+ IENTER();
+ *state = 0;
+ rc = iic_readw(bus->eng, IIC_BOE_MODE, &mode, ffdc);
+ if(rc)
+ goto exit;
+
+ if(test_bit(IIC_ENG_Z7PLUS, &bus->eng->flags) ||
+ test_bit(IIC_ENG_P8_Z8_CENTAUR, &bus->eng->flags))
+ {
+ IFLDi(0, "iic_boe_get_bus_state: P8/Z7PLUS mode\n");
+ mode = (mode & ~IIC_BOE_Z7_PORT) | IIC_BOE_Z7_MK_PORT(bus->port);
+ }
+ else
+ {
+ IFLDi(0, "iic_boe_get_bus_state: Normal mode\n");
+ mode = (mode & ~IIC_BOE_PORT) | IIC_BOE_MK_PORT(bus->port);
+ }
+ rc = iic_writew(bus->eng, IIC_BOE_MODE, mode, ffdc);
+ if(rc)
+ goto exit;
+ rc = iic_readw(bus->eng, IIC_BOE_STAT, &stat, ffdc);
+ if(rc)
+ goto exit;
+
+ *state = ((stat & IIC_BOE_SCL_IN) >> IIC_BOE_SCL_SHIFT) |
+ ((stat & IIC_BOE_SDA_IN) >> IIC_BOE_SDA_SHIFT);
+ IDBGd(1, "bus state = %08lx\n", *state);
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* Translate BOE status bits into our standardized error codes.
+ */
+int iic_boe_get_error(iic_eng_t* eng, unsigned long stat, iic_ffdc_t** ffdc)
+{
+ int ret;
+ IENTER();
+ /* Use priority scheme in case multiple bits are enabled */
+ if(stat & IIC_BOE_S_BE_ERRS)
+ {
+ ret = -EIO;
+ }
+ else if(stat & IIC_BOE_S_NACK)
+ {
+ unsigned long cmd_len;
+ unsigned long fe_len;
+ /* compare cmd length and front end len to determine
+ * if we were sending the address or data. If the cmd
+ * len is the same as the front end len, then no data
+ * has been transfered and we must have been in the
+ * address phase, otherwise, we're in the data phase.
+ */
+ ret = iic_readw(eng, IIC_BOE_CMD, &cmd_len, ffdc);
+ if(ret)
+ goto exit;
+ ret = iic_readw(eng, IIC_BOE_RESID_LEN, &fe_len, ffdc);
+ if(ret)
+ goto exit;
+ cmd_len &= IIC_BOE_XFR_LEN;
+ fe_len = IIC_BOE_GET_FE(fe_len);
+ if(cmd_len == fe_len)
+ {
+ ret = -ENXIO;
+ }
+ else
+ {
+ if (!iic_boe_check_ddr4_nack(eng->cur_xfr))
+ ret = -ENODATA;
+ }
+ }
+ else if((stat & IIC_BOE_S_LOST_ARB) || (stat & IIC_BOE_PORT_BUSY))
+ {
+ ret = -EALREADY;
+ }
+ else
+ {
+ ret = -EIO;
+ }
+exit:
+ IEXIT(ret);
+ return ret;
+}
+
+int iic_boe_fifo_to_usr(iic_eng_t* eng, iic_xfr_t* xfr, unsigned long bytes)
+{
+ int rc = 0;
+ char* uptr;
+ unsigned long bytes_left;
+ unsigned long end = xfr->bytes_xfrd + bytes;
+ char dummy[4];
+
+ IENTER();
+ while((xfr->bytes_xfrd < end) && !rc)
+ {
+ uptr = &xfr->buf[xfr->bytes_xfrd];
+
+ bytes_left = end - xfr->bytes_xfrd;
+ if (xfr->bytes_xfrd >= xfr->size) {
+ IFLDe(2, "buffer is full, but fifo still has data\n");
+ uptr = dummy;
+ }
+ if(bytes_left >= 4) {
+ rc = iic_readw(eng, IIC_BOE_FIFO, (long *)dummy, &xfr->ffdc);
+ uptr[0] = dummy[3];
+ uptr[1] = dummy[2];
+ uptr[2] = dummy[1];
+ uptr[3] = dummy[0];
+ xfr->bytes_xfrd += 4;
+ } else if(bytes_left >= 2) {
+ iic_readh(eng, IIC_BOE_FIFO, (short *)dummy, &xfr->ffdc);
+ uptr[0] = dummy[1];
+ uptr[1] = dummy[0];
+ xfr->bytes_xfrd += 2;
+ } else {
+ iic_readb(eng, IIC_BOE_FIFO, uptr, &xfr->ffdc);
+ xfr->bytes_xfrd++;
+ }
+ }
+ if (xfr->bytes_xfrd > xfr->size)
+ xfr->bytes_xfrd = xfr->size;
+
+ IEXIT(rc);
+ return rc;
+}
+
+int iic_boe_usr_to_fifo(iic_eng_t* eng, iic_xfr_t* xfr, unsigned long bytes)
+{
+ int rc = 0;
+ u16 half;
+ u32 word;
+ char* uptr;
+ unsigned long bytes_left;
+ unsigned long end = xfr->bytes_xfrd + bytes;
+
+ IENTER();
+ while((xfr->bytes_xfrd < end) && !rc)
+ {
+ uptr = &xfr->buf[xfr->bytes_xfrd];
+
+ bytes_left = end - xfr->bytes_xfrd;
+ if(bytes_left >= 4) {
+ word = cpu_to_be32(get_unaligned((long *)uptr));
+ rc = iic_writew(eng, IIC_BOE_FIFO, word, &xfr->ffdc);
+ xfr->bytes_xfrd += 4;
+ } else if(bytes_left >= 2) {
+ half = cpu_to_be16(get_unaligned((short *)uptr));
+ iic_writeh(eng, IIC_BOE_FIFO, half, &xfr->ffdc);
+ xfr->bytes_xfrd += 2;
+ } else {
+ iic_writeb(eng, IIC_BOE_FIFO, *uptr, &xfr->ffdc);
+ xfr->bytes_xfrd++;
+ }
+ }
+
+ IEXIT(rc);
+ return rc;
+}
+
+/* Interrupt Handler
+ *
+ * Handles the following general cases:
+ *
+ * DATA REQUEST - This services the fifo
+ * CMD COMPLETE - Determines the next command to run for a transfer
+ * or notifies base code that transfer completed
+ * successfully.
+ * ERRORS - Gathers FFDC, cleans up bus, and notifies base code that
+ * transfer completed with errors.
+ */
+int iic_boe_int_handler(int irq,
+ void* device_data)
+{
+ int rc;
+ iic_ffdc_t* ffdc = 0;
+ unsigned long stat;
+ unsigned long cur_dev_addr;
+ struct device *dev = device_data;
+ iic_eng_t* eng = dev_get_drvdata(dev);
+ iic_xfr_t* xfr = eng->cur_xfr;
+ iic_xfr_opts_t* opts;
+ unsigned long cmd_left = 0;
+ unsigned long fifo_left = 0;
+
+ IENTER();
+
+ /* Mask all IIC interrupts for this engine */
+ rc = iic_writew(eng, IIC_BOE_INT_MASK, 0, &ffdc);
+ if(rc)
+ {
+ if(xfr)
+ {
+ goto xfr_err;
+ }
+ goto exit;
+ }
+
+ /* If the engine is locked and we get an interrupt,
+ * then we are in the process of aborting the transfer.
+ * Access of the xfr from the interrupt handler is forbidden
+ * as long as this flag is set.
+ * Wake up blocked threads waiting for the abort to complete
+ */
+ if(test_bit(IIC_ENG_ABORT, &eng->flags))
+ {
+ IDBGd(1, "xfr[%p] abort!\n", eng->cur_xfr);
+ wake_up_interruptible(&eng->waitq);
+ goto exit;
+ }
+
+ if(!xfr)
+ {
+ goto exit;
+ }
+
+ rc = iic_readw(eng, IIC_BOE_STAT, &stat, &ffdc);
+ if(rc)
+ goto xfr_err;
+
+ IDBGl(2, "eng[%08x]: status[%08lx]\n", eng->id, stat);
+
+ opts = &xfr->opts.xfr_opts;
+ cur_dev_addr = opts->dev_addr;
+
+ /* Check for errors
+ */
+ if(stat & IIC_BOE_S_ANY_ERR)
+ {
+ IDBGd(0, "error\n");
+
+ /* If status hasn't been set yet, Gather FFDC, & set status */
+ if(!xfr->status)
+ {
+ xfr->status = iic_boe_get_error(eng, stat, &xfr->ffdc);
+// iic_fill_xfr_ffdc(xfr, &xfr->ffdc);
+// iic_ffdc_loc(&xfr->ffdc);
+ }
+
+ /* clears error conditions and causes a stop
+ * command to be issued, which causes
+ * an interrupt when it completes.
+ * Also initiates cleanup of DMA transfer if
+ * one is submitted.
+ */
+ iic_abort_xfr(xfr);
+
+ set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+ /* call xfr complete immediately if non-dma or completed
+ * dma xfr
+ */
+ iic_xfr_complete(xfr); // q ffdc
+ goto exit;
+ }
+
+ /* Check for cmd complete */
+ if(stat & IIC_BOE_S_CMD_COMP)
+ {
+ unsigned long cmd = 0;
+ unsigned long next_xfr;
+ unsigned long enable_ints = 0;
+
+ IDBGd(0, "cmd complete\n");
+
+ /* Special read handling in the offset phase for PLL/CRC chips*/
+ if(test_bit(IIC_XFR_SPECIAL_PHASE, &xfr->flags))
+ {
+ /* exit the special phase and enter the offset phase */
+ clear_bit(IIC_XFR_SPECIAL_PHASE, &xfr->flags);
+
+ /* Start a "write with no stop" command which will
+ * result in a data request interrupt.
+ */
+ cmd = opts->dev_width;
+ IFLDd(1, "cmd[%08lx]\n", cmd);
+ rc = iic_writew(eng, IIC_BOE_CMD, cmd, &xfr->ffdc);
+ if(rc)
+ goto xfr_err;
+
+ /* unmask interrupts and exit handler*/
+ rc = iic_writew(eng, IIC_BOE_INT_MASK,
+ IIC_BOE_ANY_ERR | IIC_BOE_CMD_COMP |
+ IIC_BOE_DAT_REQ, &xfr->ffdc);
+ if(rc)
+ goto xfr_err;
+ goto exit;
+ }
+
+ /* Check for xfr completion */
+ if((xfr->bytes_xfrd >= xfr->size) || xfr->status)
+ {
+ /* If this was a retry, the retry completed.
+ * clear flag so that
+ * iic_xfr_complete can do its work.
+ */
+ clear_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags);
+
+ set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+ iic_xfr_complete(xfr); // q ffdc
+ goto exit;
+ }
+
+ /* If there are bytes left to transfer and the last
+ * transfer sucessfully ended (with a stop), then
+ * this must be a split transfer, unless we are still
+ * in offset phase, in which case self_busy is inaccurate
+ */
+ if(!(stat & IIC_BOE_SELF_BUSY) &&
+ !test_bit(IIC_XFR_OFFSET_PHASE, &xfr->flags))
+ {
+ if(opts->rdelay)
+ {
+ iic_delay_xfr(xfr, opts->rdelay);
+ }
+ else
+ {
+ /* bus access errors are only possible
+ * failure
+ */
+ rc = iic_boe_start(xfr);
+ if(rc)
+ goto xfr_err;
+ }
+ goto exit;
+ }
+
+ /* Otherwise, we just need to issue a command to continue
+ * the current read or write transfer.
+ */
+ next_xfr = xfr->size - xfr->bytes_xfrd;
+
+ /* Calculate bytes left in this 'page' if xfr splits are
+ * enabled
+ */
+ if(opts->rsplit)
+ {
+ unsigned long pg_offset = (xfr->bytes_xfrd +
+ opts->offset) & opts->rsplit;
+ unsigned long page_bytes = opts->rsplit + 1;
+ if(pg_offset)
+ {
+ page_bytes -= pg_offset;
+ }
+ if(page_bytes < next_xfr)
+ {
+ next_xfr = page_bytes;
+ }
+ }
+ /* If the transfer is longer than supported by a single
+ * command (64k), set the 'read continue' bit so that
+ * the last byte in the command will get acked. Also,
+ * don't issue a stop.
+ */
+ if(next_xfr & ~IIC_BOE_XFR_LEN)
+ {
+ next_xfr = IIC_BOE_XFR_LEN;
+ cmd |= IIC_BOE_RD_CONT;
+ }
+ /* otherwise, issue a stop after the command completes */
+ else
+ {
+ if (!test_bit(IIC_REPEATED_START, &opts->flags))
+ {
+ cmd |= IIC_BOE_WITH_STOP;
+ }
+ else // Issue NO STOP
+ {
+ cmd |= IIC_BOE_RD_CONT;
+ }
+ }
+ cmd |= next_xfr;
+
+ /* set the read/!write bit */
+ if(test_bit(IIC_XFR_RD, &xfr->flags))
+ {
+ cmd |= IIC_BOE_READ;
+ }
+
+ /* special handling if the previous command was for
+ * setting a device offset pointer
+ */
+ if(test_bit(IIC_XFR_OFFSET_PHASE, &xfr->flags))
+ {
+ if(opts->dev_width && opts->inc_addr)
+ {
+ cur_dev_addr = IIC_BOE_CALC_BUS_ADDR(opts, xfr);
+ }
+
+ clear_bit(IIC_XFR_OFFSET_PHASE, &xfr->flags);
+ if(test_bit(IIC_XFR_RD, &xfr->flags) &&
+ !(opts->flags & IIC_SPECIAL_RD))
+ {
+ cmd |= IIC_BOE_WITH_START;
+ cmd |= IIC_BOE_WITH_ADDR;
+ cmd |= IIC_BOE_MK_ADDR(cur_dev_addr);
+ }
+ }
+
+ /* Issue cmd to iic engine prior to submitting dma */
+ IFLDd(1, "cmd[%08lx]\n", cmd);
+ rc = iic_writew(eng, IIC_BOE_CMD, cmd, &xfr->ffdc);
+ if(rc)
+ goto xfr_err;
+
+ enable_ints = IIC_BOE_DAT_REQ;
+ enable_ints |= IIC_BOE_CMD_COMP | IIC_BOE_ANY_ERR;
+
+
+ /* unmask interrupts */
+ IDBGd(1, "unmask ints[%08lx]\n", enable_ints);
+ rc = iic_writew(eng, IIC_BOE_INT_MASK, enable_ints, &xfr->ffdc);
+ if(rc)
+ goto xfr_err;
+
+ goto exit;
+ }
+
+ /* Check for data request, (this bit is masked for DMA transfers) */
+ if(stat & IIC_BOE_S_DAT_REQ)
+ {
+ unsigned long fifo_cnt = stat & IIC_BOE_FIFO_COUNT;
+
+ IDBGl(0, "data request\n");
+
+ /* Data request during the offset phase means we need to
+ * write the fifo with the offset address.
+ */
+ if(test_bit(IIC_XFR_OFFSET_PHASE, &xfr->flags))
+ {
+ unsigned long cur_offset = opts->offset +
+ xfr->bytes_xfrd;
+ IDBGl(1, "offset: %d\n", cur_offset);
+ fifo_cnt += opts->dev_width;
+
+ if(opts->dev_width == sizeof(long))
+ {
+ rc = iic_writew(eng, IIC_BOE_FIFO,
+ cur_offset, &xfr->ffdc);
+ }
+ else if(opts->dev_width == sizeof(short))
+ {
+ rc = iic_writeh(eng, IIC_BOE_FIFO,
+ cur_offset, &xfr->ffdc);
+ }
+ else
+ {
+ int i;
+ for(i = 0; !rc && (i < opts->dev_width); i++)
+ {
+ rc = iic_writeb(eng, IIC_BOE_FIFO,
+ cur_offset, &xfr->ffdc);
+ }
+ }
+ if(rc)
+ goto xfr_err;
+
+ /* also write data to fifo if this is a write */
+ if (test_bit(IIC_XFR_RD, &xfr->flags))
+ goto enable_interrupts;
+ else
+ clear_bit(IIC_XFR_OFFSET_PHASE, &xfr->flags);
+ }
+ else if(test_bit(IIC_XFR_RD, &xfr->flags)) /*read from fifo */
+ {
+ rc = iic_boe_fifo_to_usr(eng, xfr, fifo_cnt);
+ if(rc)
+ goto xfr_err;
+ goto enable_interrupts;
+ }
+
+ /* write to fifo */
+ fifo_left = eng->fifo_size - fifo_cnt;
+
+ /* 1st, determine how many bytes to write to the fifo,
+ * which is a minimum of the space left in the fifo
+ * and a maximum of the bytes left in the command.
+ */
+ rc = iic_readw(eng, IIC_BOE_RESID_LEN, &cmd_left, &xfr->ffdc);
+ if(rc)
+ goto xfr_err;
+ cmd_left = IIC_BOE_GET_BE(cmd_left);
+
+ if(cmd_left < fifo_left)
+ fifo_left = cmd_left;
+ rc = iic_boe_usr_to_fifo(eng, xfr, fifo_left);
+ if(rc)
+ goto xfr_err;
+
+enable_interrupts:
+ /* enable interrupts */
+ rc = iic_writew(eng, IIC_BOE_INT_MASK, IIC_BOE_ANY_ERR |
+ IIC_BOE_CMD_COMP |
+ IIC_BOE_DAT_REQ, &xfr->ffdc);
+ if(rc)
+ goto xfr_err;
+ }
+ goto exit;
+xfr_err:
+ IFLDe(2, "xfr[%p] rc=%d\n", xfr, rc);
+ xfr->status = rc;
+ set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+
+ iic_xfr_complete(xfr);
+
+exit:
+ IEXIT(IRQ_HANDLED);
+ return IRQ_HANDLED;
+}
+
+/* Initialize the engine. This should only be called after the engine has
+ * been reset */
+#define IIC_BOE_LO_LVL 4
+#define IIC_BOE_HI_LVL 4
+#define IIC_BOE_DFLT_SPEED 100000
+#define IIC_BOE_CFAM_FIFO_SZ 8
+int iic_boe_eng_init(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int rc = 0;
+ unsigned long clk_div = 0;
+ unsigned long water_mark = 0;
+ unsigned long mode;
+
+ IENTER();
+ /* Set up the clock divider, set port to 0, disable enhanced,
+ * diagnostic, pacing allow, and wrap modes
+ * Set to default speed of 400khz.
+ */
+ clk_div = IIC_BOE_HZ2DIV(eng->bus_speed, IIC_BOE_DFLT_SPEED);
+ if(!clk_div)
+ {
+ IFLDi(2, "eng[%08x], max speed = %ld\n",
+ eng->id, eng->bus_speed / 5);
+ }
+ if(clk_div & ~IIC_BOE_MAX_CLKDIV)
+ clk_div = IIC_BOE_MAX_CLKDIV;
+ IFLDd(3, "eng[%08x] speed[%ld] divisor[%ld]\n", eng->id,
+ IIC_BOE_DIV2HZ(eng->bus_speed, clk_div), clk_div);
+
+ if(test_bit(IIC_ENG_Z7PLUS, &eng->flags) ||
+ test_bit(IIC_ENG_P8_Z8_CENTAUR, &eng->flags))
+ {
+ IFLDi(0, "iic_boe_eng_init: P8/Z7PLUS mode\n");
+ mode = IIC_BOE_Z7_MK_CLKDIV(clk_div);
+ }
+ else
+ {
+ IFLDi(0, "iic_boe_eng_init: Normal mode\n");
+ mode = IIC_BOE_MK_CLKDIV(clk_div);
+ }
+
+ /* Set up the Water Mark register according to the fifo size */
+ rc = iic_readw(eng, IIC_BOE_ESTAT, &eng->fifo_size, ffdc);
+ if(rc)
+ goto exit;
+ eng->fifo_size = eng->fifo_size >> IIC_BOE_FIFO_SZ_SHIFT;
+
+ /* workaround for bad fifo size reported on CFAM dd1.0 */
+ if(eng->fifo_size == 16)
+ eng->fifo_size = IIC_BOE_CFAM_FIFO_SZ;
+
+ water_mark = IIC_BOE_MK_WATER_MRK(eng->fifo_size - IIC_BOE_HI_LVL,
+ IIC_BOE_LO_LVL);
+
+ /* handle differences between IOU and CFAM engines */
+ if(eng->fifo_size == IIC_BOE_CFAM_FIFO_SZ)
+ {
+ /* workaround for watermark mismatch between IOU and CFAM
+ * register layout
+ */
+ water_mark = water_mark << 4;
+
+ /* workaround for enhanced mode bit having opposite
+ * meaning in CFAM engines. (bug 37245)
+ */
+ mode |= IIC_BOE_ENHANCED;
+ }
+
+ rc = iic_writew(eng, IIC_BOE_MODE, mode, ffdc);
+ if(rc)
+ goto exit;
+
+ rc = iic_writew(eng, IIC_BOE_WATER_MARK, water_mark, ffdc);
+
+ /* all interrupts are masked coming out of a reset (which is what
+ * we want)
+ */
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+
+/* if dma_rc is -ECONNRESET then we notified DMA of an error.
+ * if dma_rc is -EIO then DMA discovered an error.
+ *
+ * It's assumed that this function will ALWAYS get called before
+ * the IIC_BOE_CMD_COMP interrupt. If an IIC error occurs in the
+ * middle of a transfer, the IIC interrupt handler will disable interrupts,
+ * collect ffdc, set xfr status, and then call dma_notify. Error recovery
+ * is started from here.
+ *
+ * In the good path case, this function is called prior to the
+ * IIC_BOE_CMD_COMP bit getting set. In this case this function
+ * does nothing but set the IIC_XFR_DMA_COMPLETED bit. iic_xfr_complete
+ * is still called from the IIC interrupt handler when the IIC_BOE_CMD_COMP
+ * bit gets set.
+ *
+ * Handles the following FFDC cases:
+ *
+ * 1) DMA ffdc, no IIC ffdc
+ * Create IIC ffdc element, add to existing ffdc chain
+ * 2) IIC ffdc, no DMA ffdc
+ * do nothing
+ * 3) both DMA and IIC ffdc
+ * combine both chains
+ */
+void iic_boe_dma_callback(int dma_rc, void* ffdc, void* data)
+{
+ iic_xfr_t* xfr = (iic_xfr_t*)data;
+
+ IENTER();
+ set_bit(IIC_XFR_DMA_COMPLETED, &xfr->flags);
+
+ /* Check for DMA/IIC failure */
+ if(dma_rc < 0 || dma_rc != xfr->size)
+ {
+ IFLDe(2, "dma_callback xfr[%p] dma_rc = %d\n", xfr, dma_rc);
+ if(!xfr->status)
+ {
+ xfr->status = -EIO;
+ }
+
+ iic_abort_xfr(xfr);
+ }
+ /* don't complete the xfr until both DMA and IIC operations have
+ * stopped, otherwise, xfr structure could still be in use.
+ */
+ if(test_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags))
+ {
+ /* If this was a retry, the retry completed.
+ * clear flag so that
+ * iic_xfr_complete can do its work.
+ */
+ clear_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags);
+
+ iic_xfr_complete(xfr);
+ }
+
+ IEXIT(0);
+ return;
+}
+
+/* Start a transfer from the beginning or from where it left off.
+ *
+ * xfr->bytes_xfrd is updated with each interrupt.
+ *
+ */
+int iic_boe_start(iic_xfr_t* xfr)
+{
+ int rc = -ENODEV;
+ iic_eng_t* eng = 0;
+ iic_xfr_opts_t* opts;
+ unsigned long cur_dev_addr;
+ unsigned long cmd = 0;
+ unsigned long enable_ints = 0;
+ unsigned long next_xfr = 0;
+ unsigned long mode;
+ unsigned long new_port = xfr->client->bus->port;
+ unsigned long new_clkdiv = 0;
+ unsigned long port = 0;
+ unsigned long clkdiv_hi = 0;
+ unsigned long clkdiv_lo = 0;
+ unsigned long port_mask = 0;
+ unsigned long port_encode = 0;
+ unsigned long clkdiv_encode = 0;
+ unsigned long clkdiv = 0;
+
+ IENTER();
+ opts = &xfr->opts.xfr_opts;
+ eng = xfr->client->bus->eng;
+ cur_dev_addr = opts->dev_addr;
+ new_clkdiv = IIC_BOE_HZ2DIV(eng->bus_speed, xfr->client->bus->i2c_hz);
+ if(new_clkdiv & ~IIC_BOE_MAX_CLKDIV)
+ new_clkdiv = IIC_BOE_MAX_CLKDIV;
+
+ /* update the target address if requested by user */
+ if(opts->dev_width && opts->inc_addr)
+ {
+ cur_dev_addr = IIC_BOE_CALC_BUS_ADDR(opts, xfr);
+ }
+
+ /* adjust the clock divider and set the port if necessary */
+ rc = iic_readw(eng, IIC_BOE_MODE, &mode, &xfr->ffdc);
+ if(rc)
+ goto error1;
+
+ if(test_bit(IIC_ENG_Z7PLUS, &eng->flags) ||
+ test_bit(IIC_ENG_P8_Z8_CENTAUR, &eng->flags))
+ {
+ IFLDi(0, "iic_boe_start: P8/Z7PLUS mode\n");
+ port = IIC_BOE_Z7_GET_PORT(mode);
+ clkdiv = IIC_BOE_Z7_GET_CLKDIV(mode);
+ clkdiv_hi = IIC_BOE_Z7_CLKDIV;
+ clkdiv_lo = 0;
+ port_mask = IIC_BOE_Z7_PORT;
+ port_encode = IIC_BOE_Z7_MK_PORT(new_port);
+ clkdiv_encode = IIC_BOE_Z7_MK_CLKDIV(new_clkdiv);
+ }
+ else
+ {
+ IFLDi(0, "iic_boe_start: Normal mode\n");
+ port = IIC_BOE_GET_PORT(mode);
+ clkdiv = IIC_BOE_GET_CLKDIV(mode);
+ clkdiv_hi = IIC_BOE_CLKDIV_HI;
+ clkdiv_lo = IIC_BOE_CLKDIV_LO;
+ port_mask = IIC_BOE_PORT;
+ port_encode = IIC_BOE_MK_PORT(new_port);
+ clkdiv_encode = IIC_BOE_MK_CLKDIV(new_clkdiv);
+ }
+
+ if((port != new_port) || (clkdiv != new_clkdiv))
+ {
+ /* clear out old clkdiv and port values */
+ mode &= ~(port_mask | clkdiv_hi | clkdiv_lo);
+
+ IDBGd(2, "new_port = %08lx, new_clkdiv = %08lx\n", new_port,
+ new_clkdiv);
+ /* set new values */
+ mode |= port_encode | clkdiv_encode;
+ rc = iic_writew(eng, IIC_BOE_MODE, mode, &xfr->ffdc);
+ if(rc)
+ goto error1;
+
+ /* reset the engine whenever the port is changed */
+ rc = iic_writew(eng, IIC_BOE_RESET_ERR, 0, &xfr->ffdc);
+ if(rc)
+ goto error1;
+ }
+
+ /* regardless of read or write, if offset is required, always
+ * start the xfr with a write of 1-4 bytes. Read transfers require
+ * a repeated start.
+ */
+ if(opts->dev_width)
+ {
+ /* This tells the interrupt handler that the last
+ * command was for setting the device offset
+ * (don't count the bytes as part of the actual
+ * transfer and reads require special handling)
+ */
+ IDBGd(0, "offset phase\n");
+ set_bit(IIC_XFR_OFFSET_PHASE, &xfr->flags);
+
+ /* writes must combine the dev_width xfr with real xfr */
+ if (test_bit(IIC_XFR_RD, &xfr->flags)) {
+ /* do a write command with no stop */
+ cmd |= IIC_BOE_WITH_START | IIC_BOE_WITH_ADDR |
+ IIC_BOE_MK_ADDR(cur_dev_addr) |
+ opts->dev_width;
+ if(test_bit(IIC_REPEATED_START, &opts->flags))
+ {
+ // Repeated start sent by the ioctl
+ cmd |= IIC_BOE_RD_CONT;
+ }
+
+ /* PLL/CRC chip workaround */
+ if (opts->flags & IIC_SPECIAL_RD)
+ {
+ set_bit(IIC_XFR_SPECIAL_PHASE, &xfr->flags);
+ cmd |= IIC_BOE_READ;
+ cmd &= ~IIC_BOE_XFR_LEN;
+ }
+
+ IFLDd(1, "cmd[%08lx]\n", cmd);
+ rc = iic_writew(eng, IIC_BOE_CMD, cmd, &xfr->ffdc);
+ if(rc)
+ goto error1;
+
+ /* Enable interrupts */
+ enable_ints |= IIC_BOE_CMD_COMP | IIC_BOE_ANY_ERR |
+ IIC_BOE_DAT_REQ;
+ IDBGd(1, "enable ints[%08lx]\n", enable_ints);
+ rc = iic_writew(eng, IIC_BOE_INT_MASK, enable_ints,
+ &xfr->ffdc);
+ if(rc)
+ goto error1;
+
+ goto exit;
+ }
+
+ }
+
+ /* otherwise, do a normal transfer */
+ cmd |= IIC_BOE_WITH_START | IIC_BOE_WITH_ADDR |
+ IIC_BOE_MK_ADDR(cur_dev_addr);
+ if(test_bit(IIC_XFR_RD, &xfr->flags))
+ {
+ cmd |= IIC_BOE_READ;
+ }
+ if(test_bit(IIC_REPEATED_START, &opts->flags))
+ {
+ // Repeated start sent by the ioctl
+ cmd |= IIC_BOE_RD_CONT;
+ }
+ next_xfr = xfr->size - xfr->bytes_xfrd;
+
+ /* Calculate bytes left in this 'page' if xfr splits are
+ * enabled
+ */
+ if(opts->rsplit)
+ {
+ unsigned long pg_offset = (xfr->bytes_xfrd +
+ opts->offset) & opts->rsplit;
+ unsigned long page_bytes = opts->rsplit + 1;
+ if(pg_offset)
+ {
+ page_bytes -= pg_offset;
+ }
+ if(page_bytes < next_xfr)
+ {
+ next_xfr = page_bytes;
+ }
+ }
+
+ next_xfr += opts->dev_width;
+
+ if(next_xfr & ~IIC_BOE_XFR_LEN)
+ {
+ next_xfr = IIC_BOE_XFR_LEN;
+ cmd |= IIC_BOE_RD_CONT;
+ }
+ else
+ {
+ if (!test_bit(IIC_REPEATED_START, &opts->flags))
+ {
+ cmd |= IIC_BOE_WITH_STOP;
+ }
+ }
+ cmd |= next_xfr;
+
+ /* Start the command prior to submitting dma */
+ IFLDd(1, "cmd[%08lx]\n", cmd);
+ rc = iic_writew(eng, IIC_BOE_CMD, cmd, &xfr->ffdc);
+ if(rc)
+ goto error1;
+
+ enable_ints = IIC_BOE_DAT_REQ;
+ enable_ints |= IIC_BOE_CMD_COMP | IIC_BOE_ANY_ERR;
+
+ /* unmask interrupts */
+ IDBGd(1, "enable ints[%08lx]\n", enable_ints);
+ rc = iic_writew(eng, IIC_BOE_INT_MASK, enable_ints, &xfr->ffdc);
+ if(rc)
+ goto error1;
+
+ goto exit;
+error1:
+ xfr->status = rc;
+
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* This function starts the process of aborting a xfr. It must be
+ * interrupt safe in case it gets called from a timer interrupt for a
+ * timeout.
+ */
+int iic_boe_start_abort(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int rc;
+ unsigned long stat;
+ unsigned long cmd = IIC_BOE_WITH_STOP;
+ unsigned long additional_check = 0;
+
+ IENTER();
+
+ /* mask interrupts so that we don't get spurious interrupts.
+ * If we don't mask interrupts, the following 'reset errors'
+ * command can cause a 'CMD_COMPLETE' interrupt to be raised
+ * but then we issue the abort command which clears the
+ * CMD_COMPLETE interrupt bit before it can be
+ * handled.*/
+ rc = iic_writew(eng, IIC_BOE_INT_MASK, 0, ffdc);
+ if(rc)
+ goto exit;
+
+ rc = iic_readw(eng, IIC_BOE_STAT, &stat, ffdc);
+ if(rc)
+ goto exit;
+
+ IFLDi(1, "pre-abort status[%08lx]\n", stat);
+
+ if(test_bit(IIC_ENG_P8_Z8_CENTAUR, &eng->flags)) {
+ additional_check = IIC_BOE_S_LOST_ARB;
+
+ if (eng->cur_xfr && eng->cur_xfr->client && eng->cur_xfr->client->bus)
+ rc = iic_boe_reset_bus(eng->cur_xfr->client->bus, ffdc);
+ else
+ rc = iic_boe_reset_eng(eng, ffdc);
+
+ if(rc)
+ goto exit;
+
+ /* In order to find the potential defect in new IIC engiene,
+ * track stat for sure. */
+ {
+ unsigned long stat_tmp;
+ rc = iic_readw(eng, IIC_BOE_STAT, &stat_tmp, ffdc);
+ if(rc)
+ goto exit;
+ IFLDi(1, "after reset (P8,Z8,Centaur) status[%08lx]\n",
+ stat_tmp);
+ }
+ } else {
+ if (eng->cur_xfr && eng->cur_xfr->client && eng->cur_xfr->client->bus &&
+ ((eng->cur_xfr->retry_count % 4) == 0) &&
+ ((eng->cur_xfr->status != -ENODATA) ||
+ (eng->cur_xfr->retry_count > 0))) {
+ rc = iic_boe_reset_bus(eng->cur_xfr->client->bus, ffdc);
+ } else {
+ cmd |= IIC_BOE_FORCE_LAUNCH;
+
+ /* reset the engine (forces CMD_COMPLETE condition) */
+ rc = iic_boe_reset_eng(eng, ffdc);
+ }
+ if(rc)
+ goto exit;
+ }
+
+ /* don't bother issuing a stop command if we're recovering from
+ * a stop error, a parity error or arbitration lost. Otherwise,
+ * we can end up in a loop.
+ */
+ if(stat & (IIC_BOE_S_STOP_ERR | IIC_BOE_S_PARITY | additional_check))
+ {
+ IFLDi(1, "don't bother issuing a stop\n");
+ rc = -EIO;
+ goto exit;
+ }
+
+ /* issue a stop only command (clears CMD_COMPLETE condtion)*/
+ IFLDd(1, "cmd[%08x]\n", cmd);
+ rc = iic_writew(eng, IIC_BOE_CMD, cmd, ffdc);
+ if(rc)
+ goto exit;
+
+ /* unmask interrupts while stop command runs */
+ IDBGd(1, "enable ints[%08x]\n", IIC_BOE_CMD_COMP | IIC_BOE_ANY_ERR);
+ rc = iic_writew(eng, IIC_BOE_INT_MASK, IIC_BOE_CMD_COMP |
+ IIC_BOE_ANY_ERR, ffdc);
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* returns true if no transfer is pending on the specified engine.
+ */
+int iic_boe_xfr_not_pending(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int rc;
+ unsigned long stat;
+ IENTER();
+ /* Note: if engine access fails, this function will indicate
+ * that no transfer is pending and wake up the waiter.
+ */
+ rc = iic_readw(eng, IIC_BOE_STAT, &stat, ffdc);
+ if(!rc)
+ rc = stat & IIC_BOE_S_CMD_COMP;
+ IDBGd(1, "status[%08lx]\n", stat);
+ IEXIT(rc);
+ return rc;
+}
+
+/* This function is not interrupt safe, it may require long delays!
+ *
+ * When we enter this function the engine is in a locked state and xfr_complete
+ * has already been called on the current xfr object. Any state information
+ * we need must be obtained from the engine object or the engine registers.
+ * If the transfer was a write operation, the halt command has been issued.
+ * Nothing has been done yet for read operations. Any master
+ * interrupts that occur after xfr_complete was called will not get handled
+ * except to clear the interrupt bit (i.e., status bits will not get cleared).
+ */
+#define IIC_BOE_ABORT_TIMEOUT ( msecs_to_jiffies(200) ) /* 200ms */
+int iic_boe_finish_abort(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int rc = 0;
+ int rc2 = 0;
+
+ IENTER();
+ rc = wait_event_interruptible_timeout(eng->waitq,
+ (rc2 = iic_boe_xfr_not_pending(eng, ffdc)),
+ IIC_BOE_ABORT_TIMEOUT);
+ if(rc2 < 0)
+ {
+ rc = rc2;
+ goto exit;
+ }
+
+ if(rc <= 0)
+ {
+ if(!rc)
+ {
+ IFLDi(0, "abort timed out\n");
+ }
+ else
+ IFLDi(0, "abort interrupted?\n");
+
+ /* We were unable to abort an operation. Try
+ * Resetting the engine as a last attempt to leave
+ * the engine in a good state.
+ */
+ IFLDi(1, "Resetting eng[%08x]\n", eng->id);
+ rc = iic_boe_reset_eng(eng, ffdc);
+ }
+exit:
+ /* wake up threads waiting for engine to be available */
+ wake_up_interruptible(&eng->waitq);
+ IEXIT(rc);
+ return rc;
+
+}
+
+/* This function rescue the timeout event when the xfr is not really timeout
+ * Return "0" when this event is not really timeout event
+ * Return negative value when this event is really timeout event, or it
+ * is DMA transfer.
+ */
+int iic_boe_rescue_timeout(iic_eng_t *eng, iic_xfr_t* xfr)
+{
+ int retval = -1;
+ unsigned long stat;
+ unsigned long fifo_cnt;
+ int rc;
+
+ IENTER();
+
+ rc = iic_readw(eng, IIC_BOE_WATER_MARK, &stat, 0);
+ if(rc)
+ goto exit;
+ IDBGl(2, "eng[%08x]: Water mark status[%08lx]\n", eng->id, stat);
+
+ rc = iic_readw(eng, IIC_BOE_STAT, &stat, 0);
+ if(rc)
+ goto exit;
+ IFLDi(2, "eng[%08x]: status[%08lx]\n", eng->id, stat);
+
+ if(stat & IIC_BOE_S_ANY_ERR) {
+ IFLDd(0, "IIC_BOE_S_ANY_ERR\n");
+ goto exit;
+ }
+
+
+ if((fifo_cnt = stat & IIC_BOE_FIFO_COUNT) > 0) {
+ IFLDi(1, "fifo_cnt[%d]\n", fifo_cnt);
+
+ if(test_bit(IIC_XFR_RD, &xfr->flags))
+ { /* read operation */
+ unsigned long to_user = fifo_cnt;
+ if (xfr->size < to_user)
+ to_user = xfr->size;
+ rc = iic_boe_fifo_to_usr(eng, xfr, to_user);
+ if(rc)
+ goto exit;
+ retval = xfr->bytes_xfrd;
+ } else { /* write operation */
+ retval = xfr->bytes_xfrd;
+ }
+ }
+
+exit:
+ IEXIT(retval);
+ return retval;
+}
+
+/* Wait for pending transfers and abort operations to complete on
+ * the engine.
+ *
+ * NOTE: timeout must be in jiffies
+ */
+int iic_boe_wait_for_idle(iic_eng_t* eng, int timeout, iic_ffdc_t** ffdc)
+{
+ int rc;
+ int rc2 = 0;
+ IENTER();
+ rc = wait_event_interruptible_timeout(eng->waitq,
+ ((rc2 = iic_boe_xfr_not_pending(eng, ffdc)) &&
+ (!test_bit(IIC_ENG_ABORT, &eng->flags) || (rc2 < 0)) &&
+ !eng->cur_xfr),
+ timeout);
+ if(rc2 < 0)
+ {
+ rc = rc2;
+ goto exit;
+ }
+ if(!rc)
+ {
+ rc = -ETIME;
+ }
+ else if(rc < 0)
+ {
+ rc = -EINTR;
+ }
+ else if(rc > 0)
+ {
+ rc = 0;
+ }
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+/* This function is only called when SCL is still alive.
+ * it is a low level bus reset mechanism */
+int iic_boe_ll_bus_reset(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int i, rc = 0;
+ iic_bus_t* bus = eng->cur_bus;
+ unsigned long stat = 0, mode = 0;
+
+ if (!bus) {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Enter diagnostic mode to enable the manual control of SDL & SCL */
+ rc = iic_readw(eng, IIC_BOE_MODE, &mode, ffdc);
+ if(rc)
+ goto exit;
+
+ mode |= IIC_BOE_DIAG;
+ rc = iic_writew(eng, IIC_BOE_MODE, mode, ffdc);
+ if(rc)
+ goto exit;
+ IFLDi(1, "iic_boe_ll_bus_reset: current mode[%08lx]\n", mode);
+
+ /* Send 9 clock cycle */
+ for (i = 0; i < 9; i++) {
+ /* SCL = 0 */
+ rc = iic_writew(eng, IIC_BOE_RESET_SCL, 0, ffdc);
+ if(rc)
+ goto exit;
+ /* SCL = 1 */
+ rc = iic_writew(eng, IIC_BOE_SET_SCL, 0, ffdc);
+ if(rc)
+ goto exit;
+ }
+ /* Send stop */
+ /* SCL = 0 */
+ rc = iic_writew(eng, IIC_BOE_RESET_SCL, 0, ffdc);
+ if(rc)
+ goto exit;
+ /* SDL = 0 */
+ rc = iic_writew(eng, IIC_BOE_RESET_SDA, 0, ffdc);
+ if(rc)
+ goto exit;
+ /* SCL = 1 */
+ rc = iic_writew(eng, IIC_BOE_SET_SCL, 0, ffdc);
+ if(rc)
+ goto exit;
+ /* SDL = 1 */
+ rc = iic_writew(eng, IIC_BOE_SET_SDA, 0, ffdc);
+ if(rc)
+ goto exit;
+
+ /* disable diagnostic mode */
+ mode &= ~IIC_BOE_DIAG;
+ rc = iic_writew(eng, IIC_BOE_MODE, mode, ffdc);
+ if(rc)
+ goto exit;
+
+ if((rc = iic_readw(eng, IIC_BOE_STAT, &stat, ffdc)))
+ goto exit;
+
+ IFLDi(1, "iic_boe_ll_bus_reset: current stat[%08lx]\n", stat);
+exit:
+ return rc;
+}
+
+/* Reset the engine and restore to a known state. Also attempts to free
+ * the bus if requested.
+ */
+#define IIC_BOE_BOTH_HI (IIC_BOE_SCL_IN | IIC_BOE_SDA_IN)
+int iic_boe_reset(iic_eng_t* eng, int type, iic_ffdc_t** ffdc)
+{
+ int rc = 0;
+ int i;
+ unsigned long stat;
+ int sample_count = 10; //0.1ms sample time
+ unsigned long bus_state = 0;
+ unsigned long mode = 0;
+
+ IENTER();
+ /* issue an immediate reset i2c command */
+ IFLDi(0, "iic_boe_reset\n");
+ rc = iic_writew(eng, IIC_BOE_RESET_I2C, 0, ffdc);
+ if(rc)
+ goto exit;
+
+ /* reinit the engine */
+ rc = iic_boe_eng_init(eng, ffdc);
+ if(rc)
+ goto exit;
+
+ /* write the bus port */
+ if (eng->cur_bus) {
+ rc = iic_readw(eng, IIC_BOE_MODE, &mode, ffdc);
+ if(rc)
+ goto exit;
+
+ /* encode the port number */
+ if (test_bit(IIC_ENG_Z7PLUS, &eng->flags) ||
+ test_bit(IIC_ENG_P8_Z8_CENTAUR, &eng->flags)) {
+ mode = (mode & ~IIC_BOE_Z7_PORT) |
+ IIC_BOE_Z7_MK_PORT(eng->cur_bus->port);
+ }
+ else {
+ mode = (mode & ~IIC_BOE_PORT) |
+ IIC_BOE_MK_PORT(eng->cur_bus->port);
+ }
+
+ rc = iic_writew(eng, IIC_BOE_MODE, mode, ffdc);
+ if(rc)
+ goto exit;
+ }
+
+ /* Workaround for HW237041 and SW243504 */
+ if(test_bit(IIC_ENG_P8_Z8_CENTAUR, &eng->flags)) {
+ unsigned long port_busy;
+
+ rc = iic_readw(eng, IIC_BOE_PORTBUSY, &port_busy, ffdc);
+ if(rc)
+ goto exit;
+ /* A start by anyone will assert i2c_busy.
+ * A start by itself will also asseret i2c_self_busy.
+ * A stop will deassert i2c_busy.
+ * A stop by itself will deassert i2c_self_busy.
+ * if self_busy is not 1 and i2c_busy is 1 then we cannot
+ * send STOP also.
+ * that's why we clear the I2C_busy from the PORT_BUSY_REGISTER.
+ * */
+ rc = iic_writew(eng, IIC_BOE_PORTBUSY, IIC_BOE_PORT_BUSY_REST,
+ ffdc);
+ if (rc)
+ goto exit;
+ IFLDi(1, "clean port busy port_busy[%08lx]\n", port_busy);
+ }
+
+ /* Test for stuck lines */
+ for(i = 0; ((i < sample_count) && (bus_state != IIC_BOE_BOTH_HI)); i++)
+ {
+ if((rc = iic_readw(eng, IIC_BOE_STAT, &stat, ffdc)))
+ goto exit;
+ bus_state |= stat & IIC_BOE_BOTH_HI;
+ udelay(100);
+ }
+
+ /* if both lines went high at least once during the sample time
+ * then the bus isn't stuck but we still can do bus reset.
+ * The reason is:
+ * In some case/iic mult-master case, the iic transfer is
+ * interrupted in mid of some operation, it will cause the
+ * iic salve in bad state machine/state. For example, the
+ * iic slave will keep SDA HIGH(wait ACK) or will keep SDA LOW
+ * (stuck BUS). So in order to recover iic slave back to normal,
+ * it is also make sense to send "STOP" on this bus.
+ */
+ if( bus_state != IIC_BOE_BOTH_HI)
+ {
+ IFLDi(1, "bus stuck, state[%08lx]\n", bus_state);
+ }
+
+ /* issue a stop command to reset the bus if requested and the
+ * clock line was sampled to be high.
+ */
+ if(type == IIC_BOE_BUS_RESET)
+ {
+ if(!(bus_state & IIC_BOE_SCL_IN) &&
+ (!eng->cur_xfr || eng->cur_xfr->retry_count))
+ {
+ IFLDi(0, "Clock stuck low. recovery not possible.\n");
+ goto exit;
+ }
+
+ IFLDi(0, "attempting bus reset\n");
+ rc = iic_boe_ll_bus_reset(eng, ffdc);
+ if(rc)
+ goto exit;
+
+ /* reset errors after low level bus reset */
+ rc = iic_writew(eng, IIC_BOE_RESET_ERR, 0, ffdc);
+ if (rc)
+ goto exit;
+
+ /* allow 1ms for the cmd to complete */
+ udelay(1000);
+
+ rc = iic_readw(eng, IIC_BOE_STAT, &stat, ffdc);
+ if(rc)
+ goto exit;
+ if((stat & IIC_BOE_S_CMD_COMP) && (stat & IIC_BOE_BOTH_HI))
+ {
+ IFLDi(0, "Bus Freed!\n");
+ goto exit;
+ }
+
+ /* if the stop didn't happen after 1ms,
+ * reset the engine
+ */
+ IFLDi(0, "Bus still stuck!\n");
+ rc = iic_writew(eng, IIC_BOE_RESET_I2C, 0, ffdc);
+ if(rc)
+ goto exit;
+ udelay(1);
+ rc = iic_boe_eng_init(eng, ffdc);
+ }
+exit:
+ IEXIT(rc);
+ return rc;
+}
+
+int iic_boe_reset_bus(iic_bus_t* bus, iic_ffdc_t** ffdc)
+{
+ int ret;
+ iic_eng_t* eng = bus->eng;
+
+ IENTER();
+ eng->cur_bus = bus;
+ ret = iic_boe_reset(eng, IIC_BOE_BUS_RESET, ffdc);
+ eng->cur_bus = NULL;
+ IEXIT(ret);
+ return ret;
+}
+
+int iic_boe_reset_eng(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int ret;
+ IENTER();
+ ret = iic_boe_reset(eng, IIC_BOE_ENG_RESET, ffdc);
+ IEXIT(ret);
+ return ret;
+}
+
+/* This should be called when the engine is resumed */
+int iic_boe_run_bat(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int rc = 0;
+
+ IENTER();
+ /* check DATA REQUEST bit is set properly after water mark
+ * register is set
+ */
+
+ /* check the 'and' and 'or' register functionality against the
+ * 'invalid command' bit.
+ */
+
+ /* force an invalid command and check the invalid command bit */
+
+ /* Verify that the 'reset err' command clears the invalid command
+ * bit.
+ */
+ IEXIT(rc);
+ return rc;
+}
+
+int iic_boe_enable_int(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int rc = 0;
+ IENTER();
+ /* only enable interrupts when a command is in progress */
+
+ IEXIT(rc);
+ return rc;
+}
+
+int iic_boe_disable_int(iic_eng_t* eng, iic_ffdc_t** ffdc)
+{
+ int rc = 0;
+
+ IENTER();
+ rc = iic_writew(eng, IIC_BOE_INT_MASK, 0, ffdc);
+ IEXIT(rc);
+ return rc;
+}
+
+#define DDR4_NACK_DEV_ADDR 0x6C
+#define DDR4_NACK_REDO_POL 0x0002DD40
+int iic_boe_check_ddr4_nack(iic_xfr_t *xfr) {
+ int rc = 0;
+
+ if (xfr->opts.xfr_opts.dev_addr == DDR4_NACK_DEV_ADDR &&
+ xfr->opts.recovery.redo_pol == DDR4_NACK_REDO_POL &&
+ !test_bit(IIC_XFR_RD, &xfr->flags))
+ rc = 1;
+
+ return rc;
+}
+
+int __init iic_boe_init(void)
+{
+ int rc = 0;
+
+ IENTER();
+ /* Register the boe functions with the base driver */
+ iic_register_eng_ops(&eng_ops, FSI_ENGID_I2C);
+ iic_register_eng_ops(&eng_ops, FSI_ENGID_I2C_BB);
+
+ printk("IIC BOE engine support loaded. ver. %s\n", iic_boe_version);
+ IEXIT(rc);
+ return rc;
+}
+
+void __exit iic_boe_exit(void)
+{
+ IENTER();
+ iic_unregister_eng_ops(FSI_ENGID_I2C);
+ iic_unregister_eng_ops(FSI_ENGID_I2C_BB);
+ printk("IIC BOE engine support unloaded.\n");
+ IEXIT(0);
+}
+
+module_init(iic_boe_init);
+module_exit(iic_boe_exit);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("FSP BOE IIC Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/fsi/i2c/iic-boe.h b/drivers/fsi/i2c/iic-boe.h
new file mode 100644
index 0000000..81b7783
--- /dev/null
+++ b/drivers/fsi/i2c/iic-boe.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/************************ BOE Engine offsets *********************************/
+#define IIC_BOE_FIFO 0x00
+#define IIC_BOE_CMD 0x01
+#define IIC_BOE_MODE 0x02
+#define IIC_BOE_WATER_MARK 0x03
+#define IIC_BOE_INT_MASK 0x04
+#define IIC_BOE_INT_COND 0x05
+#define IIC_BOE_OR_INT_MASK 0x05
+#define IIC_BOE_INTS 0x06
+#define IIC_BOE_AND_INT_MASK 0x06
+#define IIC_BOE_STAT 0x07
+#define IIC_BOE_RESET_I2C 0x07
+#define IIC_BOE_ESTAT 0x08
+#define IIC_BOE_RESET_ERR 0x08
+#define IIC_BOE_RESID_LEN 0x09
+#define IIC_BOE_SET_SCL 0x09
+#define IIC_BOE_RESERVED 0x0a
+#define IIC_BOE_PORTBUSY 0x0a
+#define IIC_BOE_RESET_SCL 0x0b
+#define IIC_BOE_SET_SDA 0x0c
+#define IIC_BOE_RESET_SDA 0x0d
+#define IIC_BOE_MAX_OFFSET IIC_BOE_RESET_SDA
+
+/************************ BOE Bit Definitions ********************************/
+
+#define IIC_BOE_MAX_BUSES 16
+#define IIC_BOE_MAX_FIFO_SIZE 32
+#define IIC_BOE_DFLT_FIFO_SIZE 8
+
+/* cmd register bits */
+#define IIC_BOE_WITH_START 0x80000000
+#define IIC_BOE_WITH_ADDR 0x40000000
+#define IIC_BOE_RD_CONT 0x20000000
+#define IIC_BOE_WITH_STOP 0x10000000
+#define IIC_BOE_FORCE_LAUNCH 0x08000000
+#define IIC_BOE_ADDR 0x00fe0000
+#define IIC_BOE_ADDR_SHIFT 16
+#define IIC_BOE_READ 0x00010000
+#define IIC_BOE_XFR_LEN 0x0000ffff
+
+#define IIC_BOE_MK_ADDR(addr) ((addr << IIC_BOE_ADDR_SHIFT) & IIC_BOE_ADDR)
+#define IIC_BOE_GET_ADDR(r) ((r & IIC_BOE_ADDR) >> IIC_BOE_ADDR_SHIFT)
+
+/* mode register bits */
+#define IIC_BOE_CLKDIV_LO 0xff000000
+#define IIC_BOE_CLKDIV_LO_SHIFT 24
+#define IIC_BOE_PORT 0x00ff0000
+#define IIC_BOE_PORT_SHIFT 16
+#define IIC_BOE_MODE_RSVD 0x0000ff00
+#define IIC_BOE_CLKDIV_HI 0x000000f0
+#define IIC_BOE_CLKDIV_HI_SHIFT 4
+#define IIC_BOE_ENHANCED 0x00000008
+#define IIC_BOE_DIAG 0x00000004
+#define IIC_BOE_PACE_ALLOW 0x00000002
+#define IIC_BOE_WRAP 0x00000001
+
+#define IIC_BOE_GET_PORT(r) ((r & IIC_BOE_PORT) >> IIC_BOE_PORT_SHIFT)
+#define IIC_BOE_MK_PORT(p) ((p << IIC_BOE_PORT_SHIFT) & IIC_BOE_PORT)
+
+#define IIC_BOE_MK_CLKDIV(val) \
+((val << IIC_BOE_CLKDIV_LO_SHIFT) | \
+((val >> IIC_BOE_CLKDIV_HI_SHIFT) & IIC_BOE_CLKDIV_HI))
+
+#define IIC_BOE_GET_CLKDIV(r) \
+(((r & IIC_BOE_CLKDIV_LO) >> IIC_BOE_CLKDIV_LO_SHIFT) | \
+((r & IIC_BOE_CLKDIV_HI) << IIC_BOE_CLKDIV_HI_SHIFT))
+
+/* Z7 plus mode register */
+#define IIC_BOE_Z7_CLKDIV 0xffff0000
+#define IIC_BOE_Z7_CLKDIV_SHIFT 16
+#define IIC_BOE_Z7_PORT 0x0000fc00
+#define IIC_BOE_Z7_PORT_SHIFT 10
+
+#define IIC_BOE_Z7_GET_PORT(r) ((r & IIC_BOE_Z7_PORT) >> IIC_BOE_Z7_PORT_SHIFT)
+#define IIC_BOE_Z7_MK_PORT(p) ((p << IIC_BOE_Z7_PORT_SHIFT) & IIC_BOE_Z7_PORT)
+
+#define IIC_BOE_Z7_MK_CLKDIV(val) ((val << IIC_BOE_Z7_CLKDIV_SHIFT))
+#define IIC_BOE_Z7_GET_CLKDIV(r) ((r & IIC_BOE_Z7_CLKDIV) >> IIC_BOE_Z7_CLKDIV_SHIFT)
+
+/* water mark register bits */
+#define IIC_BOE_HI_MRK 0x0000ff00
+#define IIC_BOE_HI_MRK_SHIFT 8
+#define IIC_BOE_LO_MRK 0x000000ff
+#define IIC_BOE_LO_MRK_SHIFT 0
+#define IIC_BOE_GET_LO_MRK(e) ((e->regs[IIC_BOE_WATER_MARK] & \
+ IIC_BOE_LO_MRK) >> \
+ IIC_BOE_LO_MRK_SHIFT)
+#define IIC_BOE_MK_WATER_MRK(hi, lo)\
+ ((((hi) << IIC_BOE_HI_MRK_SHIFT) & IIC_BOE_HI_MRK) |\
+ (((lo) << IIC_BOE_LO_MRK_SHIFT) & IIC_BOE_LO_MRK))
+#define IIC_BOE_GET_HI_MRK(e) ((e->regs[IIC_BOE_WATER_MARK] & \
+ IIC_BOE_HI_MRK) >> \
+ IIC_BOE_HI_MRK_SHIFT)
+
+/* interrupt, interrupt mask, interrupt condition bits */
+#define IIC_BOE_INVALID_CMD 0x00008000
+#define IIC_BOE_PARITY 0x00004000
+#define IIC_BOE_BE_OVERRUN 0x00002000
+#define IIC_BOE_BE_ACCESS 0x00001000
+#define IIC_BOE_LOST_ARB 0x00000800
+#define IIC_BOE_NACK 0x00000400
+#define IIC_BOE_DAT_REQ 0x00000200
+#define IIC_BOE_CMD_COMP 0x00000100
+#define IIC_BOE_STOP_ERR 0x00000080
+#define IIC_BOE_BUSY 0x00000040
+#define IIC_BOE_IDLE 0x00000020
+#define IIC_BOE_ALL_INTS 0x0000ffff
+#define IIC_BOE_FE_ERRS (IIC_BOE_LOST_ARB | IIC_BOE_NACK | \
+ IIC_BOE_STOP_ERR)
+#define IIC_BOE_BE_ERRS (IIC_BOE_INVALID_CMD | IIC_BOE_PARITY | \
+ IIC_BOE_BE_OVERRUN | IIC_BOE_BE_ACCESS)
+#define IIC_BOE_ANY_ERR (IIC_BOE_BE_ERRS | IIC_BOE_FE_ERRS)
+
+/* status register bits */
+#define IIC_BOE_S_INVALID_CMD 0x80000000
+#define IIC_BOE_S_PARITY 0x40000000
+#define IIC_BOE_S_BE_OVERRUN 0x20000000
+#define IIC_BOE_S_BE_ACCESS 0x10000000
+#define IIC_BOE_S_LOST_ARB 0x08000000
+#define IIC_BOE_S_NACK 0x04000000
+#define IIC_BOE_S_DAT_REQ 0x02000000
+#define IIC_BOE_S_CMD_COMP 0x01000000
+#define IIC_BOE_S_STOP_ERR 0x00800000
+#define IIC_BOE_STAT_SHIFT 16
+#define IIC_BOE_MAX_PORT 0x000f0000
+#define IIC_BOE_MAX_PORT_SHIFT 16
+#define IIC_BOE_ANY_INT 0x00008000
+#define IIC_BOE_SCL_IN 0x00000800
+#define IIC_BOE_SDA_IN 0x00000400
+#define IIC_BOE_PORT_BUSY 0x00000200
+#define IIC_BOE_SELF_BUSY 0x00000100
+#define IIC_BOE_FIFO_COUNT 0x000000ff
+
+#define IIC_BOE_S_BE_ERRS (IIC_BOE_BE_ERRS << IIC_BOE_STAT_SHIFT)
+#define IIC_BOE_S_FE_ERRS (IIC_BOE_FE_ERRS << IIC_BOE_STAT_SHIFT)
+#define IIC_BOE_S_ANY_ERR (IIC_BOE_ANY_ERR << IIC_BOE_STAT_SHIFT)
+
+/* extended status register bits */
+#define IIC_BOE_FIFO_SZ 0xff000000
+#define IIC_BOE_FIFO_SZ_SHIFT 24
+#define IIC_BOE_SCL_IN_SYN 0x00008000
+#define IIC_BOE_SDA_IN_SYN 0x00004000
+#define IIC_BOE_S_SCL 0x00002000
+#define IIC_BOE_S_SDA 0x00001000
+#define IIC_BOE_M_SCL 0x00000800
+#define IIC_BOE_M_SDA 0x00000400
+#define IIC_BOE_HI_WATER 0x00000200
+#define IIC_BOE_LO_WATER 0x00000100
+#define IIC_BOE_ES_PORT_BUSY 0x00000080
+#define IIC_BOE_ES_SELF_BUSY 0x00000040
+#define IIC_BOE_VERSION 0x0000001f
+
+/* residual length register bits */
+#define IIC_BOE_FE_LEN 0xffff0000
+#define IIC_BOE_FE_LEN_SHIFT 16
+#define IIC_BOE_BE_LEN 0x0000ffff
+#define IIC_BOE_GET_BE(r) (r & IIC_BOE_BE_LEN)
+#define IIC_BOE_GET_FE(r) (r >> IIC_BOE_FE_LEN_SHIFT)
+
+/* Murano/Centaur port busy register */
+#define IIC_BOE_PORT_BUSY_REST 0x80000000
+
--
1.8.3.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
` (4 preceding siblings ...)
2017-02-02 23:26 ` [PATCH linux v1 8/8] drivers: fsi: i2c: boe engine eajames
@ 2017-02-02 23:26 ` eajames
2017-02-03 1:11 ` Alistair Popple
2017-02-02 23:26 ` [PATCH v2 2/3] drivers/fsi: Add Client IRQ Enable / Disable eajames
` (2 subsequent siblings)
8 siblings, 1 reply; 16+ messages in thread
From: eajames @ 2017-02-02 23:26 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Christopher Bostic, Eddie James
From: Christopher Bostic <cbostic@linux.vnet.ibm.com>
Scan slaves present for asserting interrupt signals in the si1s
register and call a registered client's interrupt handler as
appropriate.
Signed-off-by: Eddie James <eajames@us.ibm.com>
Signed-off-by: Christopher Bostic <cbostic@linux.vnet.ibm.com>
---
drivers/fsi/fsi-core.c | 99 +++++++++++++++++++++++++++++++++++++++++++
drivers/fsi/fsi-master-gpio.c | 1 +
drivers/fsi/fsi-master.h | 2 +
include/linux/fsi.h | 5 +++
4 files changed, 107 insertions(+)
diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c
index 72e0ca3..4df7218 100644
--- a/drivers/fsi/fsi-core.c
+++ b/drivers/fsi/fsi-core.c
@@ -15,11 +15,15 @@
#include <linux/device.h>
#include <linux/fsi.h>
+#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/slab.h>
+#include <linux/jiffies.h>
#include "fsi-master.h"
+#define DEBUG
+
#define FSI_N_SLAVES 4
#define FSI_SLAVE_CONF_NEXT_MASK 0x80000000
@@ -36,7 +40,11 @@
#define FSI_PEEK_BASE 0x410
#define FSI_SLAVE_BASE 0x800
+#define FSI_IPOLL_PERIOD msecs_to_jiffies(fsi_ipoll_period_ms)
+
static const int engine_page_size = 0x400;
+static struct task_struct *master_ipoll;
+static unsigned int fsi_ipoll_period_ms = 100;
static atomic_t master_idx = ATOMIC_INIT(-1);
@@ -60,6 +68,8 @@ static int fsi_slave_write(struct fsi_slave *slave, uint32_t addr,
* FSI slave engine control register offsets
*/
#define FSI_SMODE 0x0 /* R/W: Mode register */
+#define FSI_SI1M 0x18 /* R/W: IRQ mask */
+#define FSI_SI1S 0x1C /* R: IRQ status */
/*
* SMODE fields
@@ -197,6 +207,7 @@ static int fsi_slave_scan(struct fsi_slave *slave)
uint32_t engine_addr;
uint32_t conf;
int rc, i;
+ uint8_t si1s_bit = 1;
INIT_LIST_HEAD(&slave->my_engines);
@@ -253,6 +264,7 @@ static int fsi_slave_scan(struct fsi_slave *slave)
dev->unit = i;
dev->addr = engine_addr;
dev->size = slots * engine_page_size;
+ dev->si1s_bit = si1s_bit++;
dev_info(&slave->dev,
"engine[%i]: type %x, version %x, addr %x size %x\n",
@@ -507,6 +519,56 @@ static void fsi_master_unscan(struct fsi_master *master)
master->slave_list = false;
}
+static void fsi_master_irq(struct fsi_master *master, int link, uint32_t si1s)
+{
+ struct fsi_slave *slave;
+ struct fsi_device *fsi_dev;
+
+ if (list_empty(&master->my_slaves))
+ return;
+
+ slave = list_first_entry(&master->my_slaves, struct fsi_slave,
+ list_link);
+
+ list_for_each_entry(fsi_dev, &slave->my_engines, link) {
+ if (si1s & (0x80000000 >> fsi_dev->si1s_bit) &&
+ fsi_dev->irq_handler)
+ fsi_dev->irq_handler(0, &fsi_dev->dev);
+ }
+}
+
+static int fsi_master_ipoll(void *data)
+{
+ int rc;
+ uint32_t si1s;
+ unsigned long elapsed = 0;
+ unsigned long previous_jiffies = jiffies;
+ struct fsi_master *master = data;
+
+ while (!kthread_should_stop()) {
+ if (!master->ipoll)
+ goto done;
+
+ /* Ignore errors for now */
+ rc = master->read(master, 0, 0, FSI_SLAVE_BASE + FSI_SI1S,
+ &si1s, sizeof(uint32_t));
+ if (rc)
+ goto done;
+
+ if (si1s & master->ipoll)
+ fsi_master_irq(master, 0, si1s);
+done:
+ elapsed = jiffies - previous_jiffies;
+ if (elapsed < FSI_IPOLL_PERIOD) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(FSI_IPOLL_PERIOD - elapsed);
+ }
+ previous_jiffies = jiffies;
+ }
+
+ return 0;
+}
+
int fsi_master_register(struct fsi_master *master)
{
if (!master || !master->dev)
@@ -524,9 +586,36 @@ void fsi_master_unregister(struct fsi_master *master)
{
fsi_master_unscan(master);
put_device(master->dev);
+ if (master_ipoll) {
+ kthread_stop(master_ipoll);
+ master_ipoll = NULL;
+ }
}
EXPORT_SYMBOL_GPL(fsi_master_unregister);
+/*
+ * TODO: move this to master->start_ipoll( ) -each master may have its
+ * own way of doing this
+ */
+int fsi_master_start_ipoll(struct fsi_master *master)
+{
+ if (master_ipoll) {
+ dev_err(master->dev, "Already polling for irqs\n");
+ return -EALREADY;
+ }
+ master_ipoll = kthread_create(fsi_master_ipoll, master,
+ "fsi_master_ipoll");
+ if (IS_ERR(master_ipoll)) {
+ dev_err(master->dev, "Couldn't create ipoll thread rc:%d\n",
+ (int)PTR_ERR(master_ipoll));
+ return PTR_ERR(master_ipoll);
+ }
+ wake_up_process(master_ipoll);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fsi_master_start_ipoll);
+
/* FSI core & Linux bus type definitions */
static int fsi_bus_match(struct device *dev, struct device_driver *drv)
@@ -566,6 +655,16 @@ void fsi_driver_unregister(struct fsi_driver *fsi_drv)
}
EXPORT_SYMBOL_GPL(fsi_driver_unregister);
+int fsi_enable_irq(struct fsi_device *dev)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fsi_enable_irq);
+
+void fsi_disable_irq(struct fsi_device *dev)
+{
+}
+
struct bus_type fsi_bus_type = {
.name = "fsi",
.match = fsi_bus_match,
diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c
index 83c7cf0..73d9985 100644
--- a/drivers/fsi/fsi-master-gpio.c
+++ b/drivers/fsi/fsi-master-gpio.c
@@ -457,6 +457,7 @@ static ssize_t store_scan(struct device *dev,
/* clear out any old scan data if present */
fsi_master_unregister(&master->master);
fsi_master_register(&master->master);
+ fsi_master_start_ipoll(&master->master);
return count;
}
diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h
index 454af2b..3737404 100644
--- a/drivers/fsi/fsi-master.h
+++ b/drivers/fsi/fsi-master.h
@@ -25,6 +25,7 @@ struct fsi_master {
struct device *dev;
int idx;
int n_links;
+ uint32_t ipoll;
int (*read)(struct fsi_master *, int link,
uint8_t slave, uint32_t addr,
void *val, size_t size);
@@ -37,6 +38,7 @@ struct fsi_master {
extern int fsi_master_register(struct fsi_master *master);
extern void fsi_master_unregister(struct fsi_master *master);
+extern int fsi_master_start_ipoll(struct fsi_master *master);
/**
* crc4 helper: Given a starting crc4 state @c, calculate the crc4 vaue of @x,
diff --git a/include/linux/fsi.h b/include/linux/fsi.h
index 2721255..d22d0c5 100644
--- a/include/linux/fsi.h
+++ b/include/linux/fsi.h
@@ -23,9 +23,11 @@ struct fsi_device {
u8 engine_type;
u8 version;
u8 unit;
+ u8 si1s_bit;
struct fsi_slave *slave;
uint32_t addr;
uint32_t size;
+ int (*irq_handler)(int, void *);
};
extern int fsi_device_read(struct fsi_device *dev, uint32_t addr,
@@ -69,4 +71,7 @@ extern void fsi_driver_unregister(struct fsi_driver *);
extern struct bus_type fsi_bus_type;
+extern int fsi_enable_irq(struct fsi_device *dev);
+extern void fsi_disable_irq(struct fsi_device *dev);
+
#endif /* LINUX_FSI_H */
--
1.8.2.2
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 2/3] drivers/fsi: Add Client IRQ Enable / Disable
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
` (5 preceding siblings ...)
2017-02-02 23:26 ` [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling eajames
@ 2017-02-02 23:26 ` eajames
2017-02-02 23:26 ` [PATCH v2 3/3] drivers/fsi: Add sysfs file to adjust i-poll period eajames
2017-02-03 0:55 ` [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client Joel Stanley
8 siblings, 0 replies; 16+ messages in thread
From: eajames @ 2017-02-02 23:26 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Christopher Bostic, Eddie James
From: Christopher Bostic <cbostic@linux.vnet.ibm.com>
Allow FSI client drivers to enable and disable their engine
IRQ's via the exported interfaces fsi_enable_irq and fsi_disable_irq
Signed-off-by: Eddie James <eajames@us.ibm.com>
Signed-off-by: Christopher Bostic <cbostic@linux.vnet.ibm.com>
---
drivers/fsi/fsi-core.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c
index 4df7218..2f6fde9 100644
--- a/drivers/fsi/fsi-core.c
+++ b/drivers/fsi/fsi-core.c
@@ -657,12 +657,57 @@ EXPORT_SYMBOL_GPL(fsi_driver_unregister);
int fsi_enable_irq(struct fsi_device *dev)
{
+ int rc;
+ u32 si1m;
+ u32 bit = 0x80000000 >> dev->si1s_bit;
+ struct fsi_master *master = dev->slave->master;
+
+ if (!dev->irq_handler)
+ return -EINVAL;
+
+ rc = master->read(master, 0, 0, FSI_SLAVE_BASE + FSI_SI1M, &si1m,
+ sizeof(u32));
+ if (rc) {
+ dev_err(master->dev, "couldn't read si1m:%d\n", rc);
+ return rc;
+ }
+
+ si1m |= bit;
+ rc = master->write(master, 0, 0, FSI_SLAVE_BASE + FSI_SI1M, &si1m,
+ sizeof(u32));
+ if (rc) {
+ dev_err(master->dev, "couldn't write si1m:%d\n", rc);
+ return rc;
+ }
+
+ master->ipoll |= bit;
return 0;
}
EXPORT_SYMBOL_GPL(fsi_enable_irq);
void fsi_disable_irq(struct fsi_device *dev)
{
+ int rc;
+ u32 si1m;
+ u32 bits = ~(0x80000000 >> dev->si1s_bit);
+ struct fsi_master *master = dev->slave->master;
+
+ master->ipoll &= bits;
+
+ rc = master->read(master, 0, 0, FSI_SLAVE_BASE + FSI_SI1M, &si1m,
+ sizeof(u32));
+ if (rc) {
+ dev_err(master->dev, "couldn't read si1m:%d\n", rc);
+ return;
+ }
+
+ si1m &= bits;
+ rc = master->write(master, 0, 0, FSI_SLAVE_BASE + FSI_SI1M, &si1m,
+ sizeof(u32));
+ if (rc) {
+ dev_err(master->dev, "couldn't write si1m:%d\n", rc);
+ return;
+ }
}
struct bus_type fsi_bus_type = {
--
1.8.2.2
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 3/3] drivers/fsi: Add sysfs file to adjust i-poll period
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
` (6 preceding siblings ...)
2017-02-02 23:26 ` [PATCH v2 2/3] drivers/fsi: Add Client IRQ Enable / Disable eajames
@ 2017-02-02 23:26 ` eajames
2017-02-03 0:55 ` [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client Joel Stanley
8 siblings, 0 replies; 16+ messages in thread
From: eajames @ 2017-02-02 23:26 UTC (permalink / raw)
To: openbmc; +Cc: joel, alistair, benh, Christopher Bostic, Eddie James
From: Christopher Bostic <cbostic@linux.vnet.ibm.com>
Create a sysfs file that can read and modify the period for
interrupt polling of connected CFAMs.
Signed-off-by: Eddie James <eajames@us.ibm.com>
Signed-off-by: Christopher Bostic <cbostic@linux.vnet.ibm.com>
---
drivers/fsi/fsi-core.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c
index 2f6fde9..1e9c5a2 100644
--- a/drivers/fsi/fsi-core.c
+++ b/drivers/fsi/fsi-core.c
@@ -569,6 +569,30 @@ done:
return 0;
}
+static ssize_t fsi_ipoll_period_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE - 1, "%u\n", fsi_ipoll_period_ms);
+}
+
+static ssize_t fsi_ipoll_period_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int rc;
+ unsigned long val = 0;
+
+ rc = kstrtoul(buf, 0, &val);
+
+ if (val > 1 && val < 10000)
+ fsi_ipoll_period_ms = val;
+
+ return count;
+}
+
+DEVICE_ATTR(fsi_ipoll_period, S_IRUGO | S_IWUSR, fsi_ipoll_period_show,
+ fsi_ipoll_period_store);
+
int fsi_master_register(struct fsi_master *master)
{
if (!master || !master->dev)
@@ -578,12 +602,14 @@ int fsi_master_register(struct fsi_master *master)
master->slave_list = false;
get_device(master->dev);
fsi_master_scan(master);
+ device_create_file(master->dev, &dev_attr_fsi_ipoll_period);
return 0;
}
EXPORT_SYMBOL_GPL(fsi_master_register);
void fsi_master_unregister(struct fsi_master *master)
{
+ device_remove_file(master->dev, &dev_attr_fsi_ipoll_period);
fsi_master_unscan(master);
put_device(master->dev);
if (master_ipoll) {
--
1.8.2.2
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
` (7 preceding siblings ...)
2017-02-02 23:26 ` [PATCH v2 3/3] drivers/fsi: Add sysfs file to adjust i-poll period eajames
@ 2017-02-03 0:55 ` Joel Stanley
2017-02-03 16:36 ` eajames
8 siblings, 1 reply; 16+ messages in thread
From: Joel Stanley @ 2017-02-03 0:55 UTC (permalink / raw)
To: eajames
Cc: OpenBMC Maillist, Alistair Popple, Benjamin Herrenschmidt,
Edward A. James
On Fri, Feb 3, 2017 at 9:55 AM, <eajames@linux.vnet.ibm.com> wrote:
> From: "Edward A. James" <eajames@us.ibm.com>
>
> First, this series add interrupt polling support to the FSI bus driver.
> This implements a loop that constantly polls the FSI slave interrupt
> status register and triggers any registered client interrupt handlers upon
> detecting an interrupt.
>
> Secondly, this series adds the I2C client driver for FSI bus. This driver
> interacts with the IBM BOE engine on an FSI slave device.
>
> Christopher Bostic (3):
> drivers/fsi: Add slave interrupt polling
> drivers/fsi: Add Client IRQ Enable / Disable
> drivers/fsi: Add sysfs file to adjust i-poll period
>
> Edward A. James (5):
> drivers: fsi: Add i2c client driver
> drivers: fsi: i2c: Add engine access wrappers
> drivers: fsi: i2c: probe fsi device for i2c client
> drivers: fsi: i2c: add driver file operations and bus locking
> drivers: fsi: i2c: boe engine
This series was a bit of a mess. The second 5 patches all had trailing
whitespace. They didn't build cleanly. These patches had different
subject prefxies too. You can generate the subject line like this:
git format-patch --subject-prefix "PATCH linux dev-4.7" and it will
spit out patch files with the same subject prefix on all patches.
If you're doing v2, you can do:
git format-patch -v 2 --subject-prefix "PATCH linux dev-4.7" and it
will add the v2 or v3 or v100 to your subject line.
The full incarnation might look like this:
git format-patch -v 2 --subject-prefix "PATCH linux dev-4.7"
--to=joel@jms.id.au --cc=openbmc@lists.ozlabs.org origin/dev-4.7 -o
fsi-patches-v2
And then once you've edited the cover letter, I do this:
git send-email fsi-patches-v2/*
I fixed the mess this time, but please make sure you're sending
patches that cleanly apply and don't introduce warnings next time.
Cheers,
Joel
>
> drivers/fsi/Kconfig | 6 +
> drivers/fsi/Makefile | 1 +
> drivers/fsi/fsi-core.c | 170 +++
> drivers/fsi/fsi-master-gpio.c | 1 +
> drivers/fsi/fsi-master.h | 2 +
> drivers/fsi/i2c/Makefile | 1 +
> drivers/fsi/i2c/iic-boe.c | 1597 +++++++++++++++++++++++++++++
> drivers/fsi/i2c/iic-boe.h | 180 ++++
> drivers/fsi/i2c/iic-fsi.c | 715 +++++++++++++
> drivers/fsi/i2c/iic-int.h | 395 +++++++
> drivers/fsi/i2c/iic-lock.c | 439 ++++++++
> drivers/fsi/i2c/iic-mstr.c | 2281 +++++++++++++++++++++++++++++++++++++++++
> include/linux/fsi.h | 5 +
> include/uapi/linux/Kbuild | 1 +
> include/uapi/linux/i2cfsi.h | 136 +++
> 15 files changed, 5930 insertions(+)
> create mode 100644 drivers/fsi/i2c/Makefile
> create mode 100644 drivers/fsi/i2c/iic-boe.c
> create mode 100644 drivers/fsi/i2c/iic-boe.h
> create mode 100644 drivers/fsi/i2c/iic-fsi.c
> create mode 100644 drivers/fsi/i2c/iic-int.h
> create mode 100644 drivers/fsi/i2c/iic-lock.c
> create mode 100644 drivers/fsi/i2c/iic-mstr.c
> create mode 100644 include/uapi/linux/i2cfsi.h
>
> --
> 1.8.3.1
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver
2017-02-02 23:25 ` [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver eajames
@ 2017-02-03 0:56 ` Alistair Popple
2017-02-03 0:58 ` Joel Stanley
0 siblings, 1 reply; 16+ messages in thread
From: Alistair Popple @ 2017-02-03 0:56 UTC (permalink / raw)
To: eajames; +Cc: openbmc, joel, benh, Edward A. James
Hi Edward,
On Thu, 2 Feb 2017 05:25:57 PM eajames@linux.vnet.ibm.com wrote:
> From: "Edward A. James" <eajames@us.ibm.com>
>
> stub for I2C driver over FSI
Unless I've missed something it doesn't look like this uses any of the
existing I2C infrastructure in the kernel. This should live in
drivers/i2c/busses and register the I2C bus adapter using
i2c_add_adapter(). This way all the existing kernel infrastructe for
probing and assiging I2C device drivers can be utilised.
For an example see how OPAL currently exposes I2C busses on the host
in drivers/i2c/busses/i2c-opal.c.
Regards,
Alistair
> Signed-off-by: Edward A. James <eajames@us.ibm.com>
> ---
> drivers/fsi/Kconfig | 6 ++
> drivers/fsi/Makefile | 1 +
> drivers/fsi/i2c/Makefile | 1 +
> drivers/fsi/i2c/iic-fsi.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++
> drivers/fsi/i2c/iic-fsi.h | 29 ++++++++
> drivers/fsi/i2c/iic-int.h | 80 ++++++++++++++++++++++
> 6 files changed, 283 insertions(+)
> create mode 100644 drivers/fsi/i2c/Makefile
> create mode 100644 drivers/fsi/i2c/iic-fsi.c
> create mode 100644 drivers/fsi/i2c/iic-fsi.h
> create mode 100644 drivers/fsi/i2c/iic-int.h
>
> diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
> index 0fa265c..c78b9b6e 100644
> --- a/drivers/fsi/Kconfig
> +++ b/drivers/fsi/Kconfig
> @@ -24,6 +24,12 @@ config FSI_SCOM
> ---help---
> This option enables an FSI based SCOM device driver.
>
> +config FSI_I2C
> + tristate "I2C FSI client device driver"
> + depends on FSI
> + ---help---
> + This option enables an FSI based I2C device driver.
> +
> endif
>
> endmenu
> diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
> index 3466f08..3a106ba 100644
> --- a/drivers/fsi/Makefile
> +++ b/drivers/fsi/Makefile
> @@ -2,3 +2,4 @@
> obj-$(CONFIG_FSI) += fsi-core.o
> obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
> obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
> +obj-$(CONFIG_FSI_I2C) += i2c/
> diff --git a/drivers/fsi/i2c/Makefile b/drivers/fsi/i2c/Makefile
> new file mode 100644
> index 0000000..f9f9048
> --- /dev/null
> +++ b/drivers/fsi/i2c/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_FSI_I2C) += iic-fsi.o
> diff --git a/drivers/fsi/i2c/iic-fsi.c b/drivers/fsi/i2c/iic-fsi.c
> new file mode 100644
> index 0000000..51a15a4
> --- /dev/null
> +++ b/drivers/fsi/i2c/iic-fsi.c
> @@ -0,0 +1,166 @@
> +/*
> + * Copyright (c) International Business Machines Corp., 2006, 2010, 2012
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
> + * the GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
> + */
> +
> +/*
> + * This file contains the architecture independent IIC FSI code.
> + */
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/fsi.h>
> +#include <asm/io.h>
> +#include <linux/mm.h>
> +#include <linux/interrupt.h>
> +#include "iic-int.h"
> +#include "iic-fsi.h"
> +
> +#include <linux/delay.h>
> +#include <linux/moduleparam.h>
> +
> +struct class* iic_fsi_class = 0;
> +dev_t iic_devnum_start = 0;
> +
> +static const char iic_fsi_version[] = "3.0";
> +
> +int iic_fsi_probe(struct device *dev);
> +int iic_fsi_remove(struct device *dev);
> +
> +struct iic_reg_access fsi_reg_access =
> +{
> + .bus_readb = readb_wrap,
> + .bus_readh = readh_wrap,
> + .bus_readw = readw_wrap,
> + .bus_writeb = writeb_wrap,
> + .bus_writeh = writeh_wrap,
> + .bus_writew = writew_wrap,
> +};
> +
> +static const struct fsi_device_id i2c_ids[] = {
> + {
> + .engine_type = FSI_ENGID_I2C,
> + .version = FSI_VERSION_ANY,
> + },
> + {
> + .engine_type = FSI_ENGID_I2C_BB,
> + .version = FSI_VERSION_ANY,
> + },
> + { 0 }
> +};
> +
> +static const struct fsi_driver i2c_drv = {
> + .id_table = i2c_ids,
> + .drv = {
> + .name = "iic_fsi_dd",
> + .bus = &fsi_bus_type,
> + .probe = iic_fsi_probe,
> + .remove = iic_fsi_remove,
> + }
> +};
> +
> +/*
> + * Called when an FSI IIC engine is plugged in.
> + * Causes creation of the /dev entry.
> + * Not allowed to access engine registers in this function.
> + *
> + */
> +int iic_fsi_probe(struct device *dev)
> +{
> + return 0;
> +}
> +
> +/* This function is called when a link is removed or the driver is unloaded.
> + * It's job is to remove the device from the device hierarchy including
> + * removal from sysfs (this is where device files get removed).
> + */
> +int iic_fsi_remove(struct device* dev)
> +{
> + return 0;
> +}
> +
> +/*
> + * Initialize this module. Creates a class for fsi connected iic devices and
> + * allocates device numbers for them.
> + */
> +static int __init iic_fsi_init(void)
> +{
> + int rc = 0;
> +
> + IENTER();
> +
> + rc = alloc_chrdev_region(&iic_devnum_start,
> + 0, /*starting minor number*/
> + IIC_FSI_MAX_DEVS,
> + "iic-fsi");
> + if(rc)
> + {
> + IFLDe(1, "master alloc_chrdev_region failed: rc = %d\n", rc);
> + return rc;
> + }
> +
> + iic_fsi_class = class_create(THIS_MODULE, "iic-fsi");
> + if (IS_ERR(iic_fsi_class))
> + {
> + IFLDe(1, "class_create failed: rc=%ld",
> + PTR_ERR(iic_fsi_class));
> + goto exit_class_create;
> + }
> + /* Register this driver with the FSI infrastructure */
> + rc = fsi_driver_register(&i2c_drv);
> + if(rc)
> + {
> + IFLDe(1, "fsidrv_register failed: %d", rc);
> + goto exit_drv_register;
> + }
> +
> + printk("IIC FSI support loaded, ver. %s\n", iic_fsi_version);
> +
> + IEXIT(rc);
> + return rc;
> +
> +exit_drv_register:
> + class_destroy(iic_fsi_class);
> +exit_class_create:
> + unregister_chrdev_region(iic_devnum_start, IIC_FSI_MAX_DEVS);
> + IEXIT(rc);
> + return rc;
> +}
> +
> +static void __exit iic_fsi_exit(void)
> +{
> + IENTER();
> + IDBGd(0, "fisdrv_unregister()\n");
> + fsi_driver_unregister(&i2c_drv);
> + IDBGd(0, "unregister_chrdev_region()\n");
> + unregister_chrdev_region(iic_devnum_start, IIC_FSI_MAX_DEVS);
> + if(iic_fsi_class)
> + {
> + IDBGd(0, "class_destroy\n");
> + class_destroy(iic_fsi_class);
> + }
> + printk("IIC FSI support unloaded.\n");
> + IEXIT(0);
> +}
> +
> +module_init(iic_fsi_init);
> +module_exit(iic_fsi_exit);
> +
> +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
> +MODULE_DESCRIPTION("IIC FSI Driver");
> +MODULE_LICENSE("GPL");
> +
> diff --git a/drivers/fsi/i2c/iic-fsi.h b/drivers/fsi/i2c/iic-fsi.h
> new file mode 100644
> index 0000000..4decb2c
> --- /dev/null
> +++ b/drivers/fsi/i2c/iic-fsi.h
> @@ -0,0 +1,29 @@
> +/*
> + * Copyright (c) International Business Machines Corp., 2006
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
> + * the GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
> + */
> +
> +#ifndef IIC_FSI_H
> +#define IIC_FSI_H
> +
> +/*
> + * we don't know ahead of time how many minors will be needed as this is based
> + * on platform type and the possibility of sparsely populated FRU's on special
> + * systems so picking a safely large number
> + */
> +#define IIC_FSI_MAX_DEVS 1024
> +
> +#endif
> diff --git a/drivers/fsi/i2c/iic-int.h b/drivers/fsi/i2c/iic-int.h
> new file mode 100644
> index 0000000..545b2c3
> --- /dev/null
> +++ b/drivers/fsi/i2c/iic-int.h
> @@ -0,0 +1,80 @@
> +/*
> + * Copyright (c) International Business Machines Corp., 2006, 2012
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
> + * the GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
> + */
> +
> +#ifndef IIC_INT_H
> +#define IIC_INT_H
> +#include <linux/list.h>
> +#include <linux/spinlock.h>
> +#include <linux/fs.h>
> +#include <linux/kobject.h>
> +#include <linux/cdev.h>
> +#include <linux/device.h>
> +#include <linux/kdev_t.h>
> +#include <linux/wait.h>
> +#include <linux/i2cfsi.h>
> +#include <linux/semaphore.h>
> +#include <linux/fsi.h>
> +#include <asm/atomic.h>
> +
> +#define FSI_ENGID_I2C 0x7
> +#define FSI_ENGID_I2C_BB 0x17
> +
> +#ifdef FSI_I2C_DEBUG
> +#define IDBGs(num, msg, args...) printk(msg, ## args)
> +#define IDBGd(num, msg, args...) printk(msg, ## args)
> +#define IDBGf(num, msg, args...) printk(msg, ## args)
> +#define IDBGl(num, msg, args...) printk(msg, ## args)
> +#else
> +#define IDBGs(num, msg, args...)
> +#define IDBGd(num, msg, args...)
> +#define IDBGf(num, msg, args...)
> +#define IDBGl(num, msg, args...)
> +#endif
> +
> +#define IENTER()
> +#define IEXIT(RC)
> +
> +/* IFLDx traces will not get compiled out */
> +#define IFLDe(num, msg, args...)\
> + printk("ERR: "msg, ## args)
> +#define IFLDi(num, msg, args...)\
> + printk(msg, ## args)
> +
> +#ifdef FSI_I2C_DEBUG
> +#define IFLDs(num, msg, args...) printk(msg, ## args)
> +#define IFLDd(num, msg, args...) printk(msg, ## args)
> +#define IFLDf(num, msg, args...) printk(msg, ## args)
> +#define IFLDl(num, msg, args...) printk(msg, ## args)
> +#else
> +#define IFLDs(num, msg, args...)
> +#define IFLDd(num, msg, args...)
> +#define IFLDf(num, msg, args...)
> +#define IFLDl(num, msg, args...)
> +#endif
> +
> +struct iic_reg_access
> +{
> + int (*bus_readb)(iic_eng_t*, unsigned int, unsigned char*, iic_ffdc_t**);
> + int (*bus_readh)(iic_eng_t*, unsigned int, unsigned short*, iic_ffdc_t**);
> + int (*bus_readw)(iic_eng_t*, unsigned int, unsigned long*, iic_ffdc_t**);
> + int (*bus_writeb)(iic_eng_t*, unsigned int, unsigned char, iic_ffdc_t**);
> + int (*bus_writeh)(iic_eng_t*, unsigned int, unsigned short, iic_ffdc_t**);
> + int (*bus_writew)(iic_eng_t*, unsigned int, unsigned long, iic_ffdc_t**);
> +};
> +
> +#endif
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver
2017-02-03 0:56 ` Alistair Popple
@ 2017-02-03 0:58 ` Joel Stanley
0 siblings, 0 replies; 16+ messages in thread
From: Joel Stanley @ 2017-02-03 0:58 UTC (permalink / raw)
To: Alistair Popple
Cc: eajames, OpenBMC Maillist, Benjamin Herrenschmidt,
Edward A. James
On Fri, Feb 3, 2017 at 11:26 AM, Alistair Popple <alistair@popple.id.au> wrote:
> Hi Edward,
>
> On Thu, 2 Feb 2017 05:25:57 PM eajames@linux.vnet.ibm.com wrote:
>> From: "Edward A. James" <eajames@us.ibm.com>
>>
>> stub for I2C driver over FSI
>
> Unless I've missed something it doesn't look like this uses any of the
> existing I2C infrastructure in the kernel. This should live in
> drivers/i2c/busses and register the I2C bus adapter using
> i2c_add_adapter(). This way all the existing kernel infrastructe for
> probing and assiging I2C device drivers can be utilised.
>
> For an example see how OPAL currently exposes I2C busses on the host
> in drivers/i2c/busses/i2c-opal.c.
Alistair is right on the money. This should have never seen the light of day.
Alistair, and others: we did agree that due to this functionality
being required in short order, and a proper driver not being started
yet, we will merge these patches temporarily.
Eddie and Chris will be working with me to write a proper i2c driver on Monday.
Cheers,
Joel
>
> Regards,
>
> Alistair
>
>> Signed-off-by: Edward A. James <eajames@us.ibm.com>
>> ---
>> drivers/fsi/Kconfig | 6 ++
>> drivers/fsi/Makefile | 1 +
>> drivers/fsi/i2c/Makefile | 1 +
>> drivers/fsi/i2c/iic-fsi.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++
>> drivers/fsi/i2c/iic-fsi.h | 29 ++++++++
>> drivers/fsi/i2c/iic-int.h | 80 ++++++++++++++++++++++
>> 6 files changed, 283 insertions(+)
>> create mode 100644 drivers/fsi/i2c/Makefile
>> create mode 100644 drivers/fsi/i2c/iic-fsi.c
>> create mode 100644 drivers/fsi/i2c/iic-fsi.h
>> create mode 100644 drivers/fsi/i2c/iic-int.h
>>
>> diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
>> index 0fa265c..c78b9b6e 100644
>> --- a/drivers/fsi/Kconfig
>> +++ b/drivers/fsi/Kconfig
>> @@ -24,6 +24,12 @@ config FSI_SCOM
>> ---help---
>> This option enables an FSI based SCOM device driver.
>>
>> +config FSI_I2C
>> + tristate "I2C FSI client device driver"
>> + depends on FSI
>> + ---help---
>> + This option enables an FSI based I2C device driver.
>> +
>> endif
>>
>> endmenu
>> diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
>> index 3466f08..3a106ba 100644
>> --- a/drivers/fsi/Makefile
>> +++ b/drivers/fsi/Makefile
>> @@ -2,3 +2,4 @@
>> obj-$(CONFIG_FSI) += fsi-core.o
>> obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
>> obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
>> +obj-$(CONFIG_FSI_I2C) += i2c/
>> diff --git a/drivers/fsi/i2c/Makefile b/drivers/fsi/i2c/Makefile
>> new file mode 100644
>> index 0000000..f9f9048
>> --- /dev/null
>> +++ b/drivers/fsi/i2c/Makefile
>> @@ -0,0 +1 @@
>> +obj-$(CONFIG_FSI_I2C) += iic-fsi.o
>> diff --git a/drivers/fsi/i2c/iic-fsi.c b/drivers/fsi/i2c/iic-fsi.c
>> new file mode 100644
>> index 0000000..51a15a4
>> --- /dev/null
>> +++ b/drivers/fsi/i2c/iic-fsi.c
>> @@ -0,0 +1,166 @@
>> +/*
>> + * Copyright (c) International Business Machines Corp., 2006, 2010, 2012
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
>> + * the GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program; if not, write to the Free Software
>> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>> + */
>> +
>> +/*
>> + * This file contains the architecture independent IIC FSI code.
>> + */
>> +#include <linux/init.h>
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/slab.h>
>> +#include <linux/fsi.h>
>> +#include <asm/io.h>
>> +#include <linux/mm.h>
>> +#include <linux/interrupt.h>
>> +#include "iic-int.h"
>> +#include "iic-fsi.h"
>> +
>> +#include <linux/delay.h>
>> +#include <linux/moduleparam.h>
>> +
>> +struct class* iic_fsi_class = 0;
>> +dev_t iic_devnum_start = 0;
>> +
>> +static const char iic_fsi_version[] = "3.0";
>> +
>> +int iic_fsi_probe(struct device *dev);
>> +int iic_fsi_remove(struct device *dev);
>> +
>> +struct iic_reg_access fsi_reg_access =
>> +{
>> + .bus_readb = readb_wrap,
>> + .bus_readh = readh_wrap,
>> + .bus_readw = readw_wrap,
>> + .bus_writeb = writeb_wrap,
>> + .bus_writeh = writeh_wrap,
>> + .bus_writew = writew_wrap,
>> +};
>> +
>> +static const struct fsi_device_id i2c_ids[] = {
>> + {
>> + .engine_type = FSI_ENGID_I2C,
>> + .version = FSI_VERSION_ANY,
>> + },
>> + {
>> + .engine_type = FSI_ENGID_I2C_BB,
>> + .version = FSI_VERSION_ANY,
>> + },
>> + { 0 }
>> +};
>> +
>> +static const struct fsi_driver i2c_drv = {
>> + .id_table = i2c_ids,
>> + .drv = {
>> + .name = "iic_fsi_dd",
>> + .bus = &fsi_bus_type,
>> + .probe = iic_fsi_probe,
>> + .remove = iic_fsi_remove,
>> + }
>> +};
>> +
>> +/*
>> + * Called when an FSI IIC engine is plugged in.
>> + * Causes creation of the /dev entry.
>> + * Not allowed to access engine registers in this function.
>> + *
>> + */
>> +int iic_fsi_probe(struct device *dev)
>> +{
>> + return 0;
>> +}
>> +
>> +/* This function is called when a link is removed or the driver is unloaded.
>> + * It's job is to remove the device from the device hierarchy including
>> + * removal from sysfs (this is where device files get removed).
>> + */
>> +int iic_fsi_remove(struct device* dev)
>> +{
>> + return 0;
>> +}
>> +
>> +/*
>> + * Initialize this module. Creates a class for fsi connected iic devices and
>> + * allocates device numbers for them.
>> + */
>> +static int __init iic_fsi_init(void)
>> +{
>> + int rc = 0;
>> +
>> + IENTER();
>> +
>> + rc = alloc_chrdev_region(&iic_devnum_start,
>> + 0, /*starting minor number*/
>> + IIC_FSI_MAX_DEVS,
>> + "iic-fsi");
>> + if(rc)
>> + {
>> + IFLDe(1, "master alloc_chrdev_region failed: rc = %d\n", rc);
>> + return rc;
>> + }
>> +
>> + iic_fsi_class = class_create(THIS_MODULE, "iic-fsi");
>> + if (IS_ERR(iic_fsi_class))
>> + {
>> + IFLDe(1, "class_create failed: rc=%ld",
>> + PTR_ERR(iic_fsi_class));
>> + goto exit_class_create;
>> + }
>> + /* Register this driver with the FSI infrastructure */
>> + rc = fsi_driver_register(&i2c_drv);
>> + if(rc)
>> + {
>> + IFLDe(1, "fsidrv_register failed: %d", rc);
>> + goto exit_drv_register;
>> + }
>> +
>> + printk("IIC FSI support loaded, ver. %s\n", iic_fsi_version);
>> +
>> + IEXIT(rc);
>> + return rc;
>> +
>> +exit_drv_register:
>> + class_destroy(iic_fsi_class);
>> +exit_class_create:
>> + unregister_chrdev_region(iic_devnum_start, IIC_FSI_MAX_DEVS);
>> + IEXIT(rc);
>> + return rc;
>> +}
>> +
>> +static void __exit iic_fsi_exit(void)
>> +{
>> + IENTER();
>> + IDBGd(0, "fisdrv_unregister()\n");
>> + fsi_driver_unregister(&i2c_drv);
>> + IDBGd(0, "unregister_chrdev_region()\n");
>> + unregister_chrdev_region(iic_devnum_start, IIC_FSI_MAX_DEVS);
>> + if(iic_fsi_class)
>> + {
>> + IDBGd(0, "class_destroy\n");
>> + class_destroy(iic_fsi_class);
>> + }
>> + printk("IIC FSI support unloaded.\n");
>> + IEXIT(0);
>> +}
>> +
>> +module_init(iic_fsi_init);
>> +module_exit(iic_fsi_exit);
>> +
>> +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
>> +MODULE_DESCRIPTION("IIC FSI Driver");
>> +MODULE_LICENSE("GPL");
>> +
>> diff --git a/drivers/fsi/i2c/iic-fsi.h b/drivers/fsi/i2c/iic-fsi.h
>> new file mode 100644
>> index 0000000..4decb2c
>> --- /dev/null
>> +++ b/drivers/fsi/i2c/iic-fsi.h
>> @@ -0,0 +1,29 @@
>> +/*
>> + * Copyright (c) International Business Machines Corp., 2006
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
>> + * the GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program; if not, write to the Free Software
>> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>> + */
>> +
>> +#ifndef IIC_FSI_H
>> +#define IIC_FSI_H
>> +
>> +/*
>> + * we don't know ahead of time how many minors will be needed as this is based
>> + * on platform type and the possibility of sparsely populated FRU's on special
>> + * systems so picking a safely large number
>> + */
>> +#define IIC_FSI_MAX_DEVS 1024
>> +
>> +#endif
>> diff --git a/drivers/fsi/i2c/iic-int.h b/drivers/fsi/i2c/iic-int.h
>> new file mode 100644
>> index 0000000..545b2c3
>> --- /dev/null
>> +++ b/drivers/fsi/i2c/iic-int.h
>> @@ -0,0 +1,80 @@
>> +/*
>> + * Copyright (c) International Business Machines Corp., 2006, 2012
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
>> + * the GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program; if not, write to the Free Software
>> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>> + */
>> +
>> +#ifndef IIC_INT_H
>> +#define IIC_INT_H
>> +#include <linux/list.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/fs.h>
>> +#include <linux/kobject.h>
>> +#include <linux/cdev.h>
>> +#include <linux/device.h>
>> +#include <linux/kdev_t.h>
>> +#include <linux/wait.h>
>> +#include <linux/i2cfsi.h>
>> +#include <linux/semaphore.h>
>> +#include <linux/fsi.h>
>> +#include <asm/atomic.h>
>> +
>> +#define FSI_ENGID_I2C 0x7
>> +#define FSI_ENGID_I2C_BB 0x17
>> +
>> +#ifdef FSI_I2C_DEBUG
>> +#define IDBGs(num, msg, args...) printk(msg, ## args)
>> +#define IDBGd(num, msg, args...) printk(msg, ## args)
>> +#define IDBGf(num, msg, args...) printk(msg, ## args)
>> +#define IDBGl(num, msg, args...) printk(msg, ## args)
>> +#else
>> +#define IDBGs(num, msg, args...)
>> +#define IDBGd(num, msg, args...)
>> +#define IDBGf(num, msg, args...)
>> +#define IDBGl(num, msg, args...)
>> +#endif
>> +
>> +#define IENTER()
>> +#define IEXIT(RC)
>> +
>> +/* IFLDx traces will not get compiled out */
>> +#define IFLDe(num, msg, args...)\
>> + printk("ERR: "msg, ## args)
>> +#define IFLDi(num, msg, args...)\
>> + printk(msg, ## args)
>> +
>> +#ifdef FSI_I2C_DEBUG
>> +#define IFLDs(num, msg, args...) printk(msg, ## args)
>> +#define IFLDd(num, msg, args...) printk(msg, ## args)
>> +#define IFLDf(num, msg, args...) printk(msg, ## args)
>> +#define IFLDl(num, msg, args...) printk(msg, ## args)
>> +#else
>> +#define IFLDs(num, msg, args...)
>> +#define IFLDd(num, msg, args...)
>> +#define IFLDf(num, msg, args...)
>> +#define IFLDl(num, msg, args...)
>> +#endif
>> +
>> +struct iic_reg_access
>> +{
>> + int (*bus_readb)(iic_eng_t*, unsigned int, unsigned char*, iic_ffdc_t**);
>> + int (*bus_readh)(iic_eng_t*, unsigned int, unsigned short*, iic_ffdc_t**);
>> + int (*bus_readw)(iic_eng_t*, unsigned int, unsigned long*, iic_ffdc_t**);
>> + int (*bus_writeb)(iic_eng_t*, unsigned int, unsigned char, iic_ffdc_t**);
>> + int (*bus_writeh)(iic_eng_t*, unsigned int, unsigned short, iic_ffdc_t**);
>> + int (*bus_writew)(iic_eng_t*, unsigned int, unsigned long, iic_ffdc_t**);
>> +};
>> +
>> +#endif
>>
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling
2017-02-02 23:26 ` [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling eajames
@ 2017-02-03 1:11 ` Alistair Popple
2017-02-03 16:18 ` eajames
0 siblings, 1 reply; 16+ messages in thread
From: Alistair Popple @ 2017-02-03 1:11 UTC (permalink / raw)
To: eajames; +Cc: openbmc, joel, benh, Christopher Bostic, Eddie James
Hi Edward,
Ideally this should also make use of as much existing kernel
infrastructure as possible to avoid remiplementing interfaces that
already exist. In the case of IRQs the kernel already has functions
implementing interfaces like request_irq() to assign interrupt
handlers, etc.
To hook into these calls you need to implement an irqchip. For example
arch/powerpc/platforms/powernv/opal-irqchip.c has a resonable example,
although there are plenty others in the kernel including for I2C
busses. Searching for "struct irq_chip" should turn up more examples.
Regards,
Alistair
On Thu, 2 Feb 2017 05:26:02 PM eajames@linux.vnet.ibm.com wrote:
> From: Christopher Bostic <cbostic@linux.vnet.ibm.com>
>
> Scan slaves present for asserting interrupt signals in the si1s
> register and call a registered client's interrupt handler as
> appropriate.
>
> Signed-off-by: Eddie James <eajames@us.ibm.com>
> Signed-off-by: Christopher Bostic <cbostic@linux.vnet.ibm.com>
> ---
> drivers/fsi/fsi-core.c | 99 +++++++++++++++++++++++++++++++++++++++++++
> drivers/fsi/fsi-master-gpio.c | 1 +
> drivers/fsi/fsi-master.h | 2 +
> include/linux/fsi.h | 5 +++
> 4 files changed, 107 insertions(+)
>
> diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c
> index 72e0ca3..4df7218 100644
> --- a/drivers/fsi/fsi-core.c
> +++ b/drivers/fsi/fsi-core.c
> @@ -15,11 +15,15 @@
>
> #include <linux/device.h>
> #include <linux/fsi.h>
> +#include <linux/kthread.h>
> #include <linux/module.h>
> #include <linux/slab.h>
> +#include <linux/jiffies.h>
>
> #include "fsi-master.h"
>
> +#define DEBUG
> +
> #define FSI_N_SLAVES 4
>
> #define FSI_SLAVE_CONF_NEXT_MASK 0x80000000
> @@ -36,7 +40,11 @@
> #define FSI_PEEK_BASE 0x410
> #define FSI_SLAVE_BASE 0x800
>
> +#define FSI_IPOLL_PERIOD msecs_to_jiffies(fsi_ipoll_period_ms)
> +
> static const int engine_page_size = 0x400;
> +static struct task_struct *master_ipoll;
> +static unsigned int fsi_ipoll_period_ms = 100;
>
> static atomic_t master_idx = ATOMIC_INIT(-1);
>
> @@ -60,6 +68,8 @@ static int fsi_slave_write(struct fsi_slave *slave, uint32_t addr,
> * FSI slave engine control register offsets
> */
> #define FSI_SMODE 0x0 /* R/W: Mode register */
> +#define FSI_SI1M 0x18 /* R/W: IRQ mask */
> +#define FSI_SI1S 0x1C /* R: IRQ status */
>
> /*
> * SMODE fields
> @@ -197,6 +207,7 @@ static int fsi_slave_scan(struct fsi_slave *slave)
> uint32_t engine_addr;
> uint32_t conf;
> int rc, i;
> + uint8_t si1s_bit = 1;
>
> INIT_LIST_HEAD(&slave->my_engines);
>
> @@ -253,6 +264,7 @@ static int fsi_slave_scan(struct fsi_slave *slave)
> dev->unit = i;
> dev->addr = engine_addr;
> dev->size = slots * engine_page_size;
> + dev->si1s_bit = si1s_bit++;
>
> dev_info(&slave->dev,
> "engine[%i]: type %x, version %x, addr %x size %x\n",
> @@ -507,6 +519,56 @@ static void fsi_master_unscan(struct fsi_master *master)
> master->slave_list = false;
> }
>
> +static void fsi_master_irq(struct fsi_master *master, int link, uint32_t si1s)
> +{
> + struct fsi_slave *slave;
> + struct fsi_device *fsi_dev;
> +
> + if (list_empty(&master->my_slaves))
> + return;
> +
> + slave = list_first_entry(&master->my_slaves, struct fsi_slave,
> + list_link);
> +
> + list_for_each_entry(fsi_dev, &slave->my_engines, link) {
> + if (si1s & (0x80000000 >> fsi_dev->si1s_bit) &&
> + fsi_dev->irq_handler)
> + fsi_dev->irq_handler(0, &fsi_dev->dev);
> + }
> +}
> +
> +static int fsi_master_ipoll(void *data)
> +{
> + int rc;
> + uint32_t si1s;
> + unsigned long elapsed = 0;
> + unsigned long previous_jiffies = jiffies;
> + struct fsi_master *master = data;
> +
> + while (!kthread_should_stop()) {
> + if (!master->ipoll)
> + goto done;
> +
> + /* Ignore errors for now */
> + rc = master->read(master, 0, 0, FSI_SLAVE_BASE + FSI_SI1S,
> + &si1s, sizeof(uint32_t));
> + if (rc)
> + goto done;
> +
> + if (si1s & master->ipoll)
> + fsi_master_irq(master, 0, si1s);
> +done:
> + elapsed = jiffies - previous_jiffies;
> + if (elapsed < FSI_IPOLL_PERIOD) {
> + set_current_state(TASK_UNINTERRUPTIBLE);
> + schedule_timeout(FSI_IPOLL_PERIOD - elapsed);
> + }
> + previous_jiffies = jiffies;
> + }
> +
> + return 0;
> +}
> +
> int fsi_master_register(struct fsi_master *master)
> {
> if (!master || !master->dev)
> @@ -524,9 +586,36 @@ void fsi_master_unregister(struct fsi_master *master)
> {
> fsi_master_unscan(master);
> put_device(master->dev);
> + if (master_ipoll) {
> + kthread_stop(master_ipoll);
> + master_ipoll = NULL;
> + }
> }
> EXPORT_SYMBOL_GPL(fsi_master_unregister);
>
> +/*
> + * TODO: move this to master->start_ipoll( ) -each master may have its
> + * own way of doing this
> + */
> +int fsi_master_start_ipoll(struct fsi_master *master)
> +{
> + if (master_ipoll) {
> + dev_err(master->dev, "Already polling for irqs\n");
> + return -EALREADY;
> + }
> + master_ipoll = kthread_create(fsi_master_ipoll, master,
> + "fsi_master_ipoll");
> + if (IS_ERR(master_ipoll)) {
> + dev_err(master->dev, "Couldn't create ipoll thread rc:%d\n",
> + (int)PTR_ERR(master_ipoll));
> + return PTR_ERR(master_ipoll);
> + }
> + wake_up_process(master_ipoll);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(fsi_master_start_ipoll);
> +
> /* FSI core & Linux bus type definitions */
>
> static int fsi_bus_match(struct device *dev, struct device_driver *drv)
> @@ -566,6 +655,16 @@ void fsi_driver_unregister(struct fsi_driver *fsi_drv)
> }
> EXPORT_SYMBOL_GPL(fsi_driver_unregister);
>
> +int fsi_enable_irq(struct fsi_device *dev)
> +{
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(fsi_enable_irq);
> +
> +void fsi_disable_irq(struct fsi_device *dev)
> +{
> +}
> +
> struct bus_type fsi_bus_type = {
> .name = "fsi",
> .match = fsi_bus_match,
> diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c
> index 83c7cf0..73d9985 100644
> --- a/drivers/fsi/fsi-master-gpio.c
> +++ b/drivers/fsi/fsi-master-gpio.c
> @@ -457,6 +457,7 @@ static ssize_t store_scan(struct device *dev,
> /* clear out any old scan data if present */
> fsi_master_unregister(&master->master);
> fsi_master_register(&master->master);
> + fsi_master_start_ipoll(&master->master);
>
> return count;
> }
> diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h
> index 454af2b..3737404 100644
> --- a/drivers/fsi/fsi-master.h
> +++ b/drivers/fsi/fsi-master.h
> @@ -25,6 +25,7 @@ struct fsi_master {
> struct device *dev;
> int idx;
> int n_links;
> + uint32_t ipoll;
> int (*read)(struct fsi_master *, int link,
> uint8_t slave, uint32_t addr,
> void *val, size_t size);
> @@ -37,6 +38,7 @@ struct fsi_master {
>
> extern int fsi_master_register(struct fsi_master *master);
> extern void fsi_master_unregister(struct fsi_master *master);
> +extern int fsi_master_start_ipoll(struct fsi_master *master);
>
> /**
> * crc4 helper: Given a starting crc4 state @c, calculate the crc4 vaue of @x,
> diff --git a/include/linux/fsi.h b/include/linux/fsi.h
> index 2721255..d22d0c5 100644
> --- a/include/linux/fsi.h
> +++ b/include/linux/fsi.h
> @@ -23,9 +23,11 @@ struct fsi_device {
> u8 engine_type;
> u8 version;
> u8 unit;
> + u8 si1s_bit;
> struct fsi_slave *slave;
> uint32_t addr;
> uint32_t size;
> + int (*irq_handler)(int, void *);
> };
>
> extern int fsi_device_read(struct fsi_device *dev, uint32_t addr,
> @@ -69,4 +71,7 @@ extern void fsi_driver_unregister(struct fsi_driver *);
>
> extern struct bus_type fsi_bus_type;
>
> +extern int fsi_enable_irq(struct fsi_device *dev);
> +extern void fsi_disable_irq(struct fsi_device *dev);
> +
> #endif /* LINUX_FSI_H */
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling
2017-02-03 1:11 ` Alistair Popple
@ 2017-02-03 16:18 ` eajames
2017-02-03 20:33 ` Benjamin Herrenschmidt
0 siblings, 1 reply; 16+ messages in thread
From: eajames @ 2017-02-03 16:18 UTC (permalink / raw)
To: Alistair Popple; +Cc: openbmc, joel, benh, Christopher Bostic, Eddie James
On 2017-02-02 19:11, Alistair Popple wrote:
> Hi Edward,
>
> Ideally this should also make use of as much existing kernel
> infrastructure as possible to avoid remiplementing interfaces that
> already exist. In the case of IRQs the kernel already has functions
> implementing interfaces like request_irq() to assign interrupt
> handlers, etc.
Hi,
The problem here is that there is no hardware irq. FSI is using GPIO
lines to access slave registers. The only indication that we have an
interrupt is a bit set in the "slave interrupt status register". So we
have to poll that register and handle the results. I'm not aware of any
way to turn that into a kernel irq and use the existing kernel
framework. But if it can be done, agreed, we should implement that!
Thanks,
Eddie
>
> To hook into these calls you need to implement an irqchip. For example
> arch/powerpc/platforms/powernv/opal-irqchip.c has a resonable example,
> although there are plenty others in the kernel including for I2C
> busses. Searching for "struct irq_chip" should turn up more examples.
>
> Regards,
>
> Alistair
>
> On Thu, 2 Feb 2017 05:26:02 PM eajames@linux.vnet.ibm.com wrote:
>> From: Christopher Bostic <cbostic@linux.vnet.ibm.com>
>>
>> Scan slaves present for asserting interrupt signals in the si1s
>> register and call a registered client's interrupt handler as
>> appropriate.
>>
>> Signed-off-by: Eddie James <eajames@us.ibm.com>
>> Signed-off-by: Christopher Bostic <cbostic@linux.vnet.ibm.com>
>> ---
>> drivers/fsi/fsi-core.c | 99
>> +++++++++++++++++++++++++++++++++++++++++++
>> drivers/fsi/fsi-master-gpio.c | 1 +
>> drivers/fsi/fsi-master.h | 2 +
>> include/linux/fsi.h | 5 +++
>> 4 files changed, 107 insertions(+)
>>
>> diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c
>> index 72e0ca3..4df7218 100644
>> --- a/drivers/fsi/fsi-core.c
>> +++ b/drivers/fsi/fsi-core.c
>> @@ -15,11 +15,15 @@
>>
>> #include <linux/device.h>
>> #include <linux/fsi.h>
>> +#include <linux/kthread.h>
>> #include <linux/module.h>
>> #include <linux/slab.h>
>> +#include <linux/jiffies.h>
>>
>> #include "fsi-master.h"
>>
>> +#define DEBUG
>> +
>> #define FSI_N_SLAVES 4
>>
>> #define FSI_SLAVE_CONF_NEXT_MASK 0x80000000
>> @@ -36,7 +40,11 @@
>> #define FSI_PEEK_BASE 0x410
>> #define FSI_SLAVE_BASE 0x800
>>
>> +#define FSI_IPOLL_PERIOD msecs_to_jiffies(fsi_ipoll_period_ms)
>> +
>> static const int engine_page_size = 0x400;
>> +static struct task_struct *master_ipoll;
>> +static unsigned int fsi_ipoll_period_ms = 100;
>>
>> static atomic_t master_idx = ATOMIC_INIT(-1);
>>
>> @@ -60,6 +68,8 @@ static int fsi_slave_write(struct fsi_slave *slave,
>> uint32_t addr,
>> * FSI slave engine control register offsets
>> */
>> #define FSI_SMODE 0x0 /* R/W: Mode register */
>> +#define FSI_SI1M 0x18 /* R/W: IRQ mask */
>> +#define FSI_SI1S 0x1C /* R: IRQ status */
>>
>> /*
>> * SMODE fields
>> @@ -197,6 +207,7 @@ static int fsi_slave_scan(struct fsi_slave *slave)
>> uint32_t engine_addr;
>> uint32_t conf;
>> int rc, i;
>> + uint8_t si1s_bit = 1;
>>
>> INIT_LIST_HEAD(&slave->my_engines);
>>
>> @@ -253,6 +264,7 @@ static int fsi_slave_scan(struct fsi_slave *slave)
>> dev->unit = i;
>> dev->addr = engine_addr;
>> dev->size = slots * engine_page_size;
>> + dev->si1s_bit = si1s_bit++;
>>
>> dev_info(&slave->dev,
>> "engine[%i]: type %x, version %x, addr %x size %x\n",
>> @@ -507,6 +519,56 @@ static void fsi_master_unscan(struct fsi_master
>> *master)
>> master->slave_list = false;
>> }
>>
>> +static void fsi_master_irq(struct fsi_master *master, int link,
>> uint32_t si1s)
>> +{
>> + struct fsi_slave *slave;
>> + struct fsi_device *fsi_dev;
>> +
>> + if (list_empty(&master->my_slaves))
>> + return;
>> +
>> + slave = list_first_entry(&master->my_slaves, struct fsi_slave,
>> + list_link);
>> +
>> + list_for_each_entry(fsi_dev, &slave->my_engines, link) {
>> + if (si1s & (0x80000000 >> fsi_dev->si1s_bit) &&
>> + fsi_dev->irq_handler)
>> + fsi_dev->irq_handler(0, &fsi_dev->dev);
>> + }
>> +}
>> +
>> +static int fsi_master_ipoll(void *data)
>> +{
>> + int rc;
>> + uint32_t si1s;
>> + unsigned long elapsed = 0;
>> + unsigned long previous_jiffies = jiffies;
>> + struct fsi_master *master = data;
>> +
>> + while (!kthread_should_stop()) {
>> + if (!master->ipoll)
>> + goto done;
>> +
>> + /* Ignore errors for now */
>> + rc = master->read(master, 0, 0, FSI_SLAVE_BASE + FSI_SI1S,
>> + &si1s, sizeof(uint32_t));
>> + if (rc)
>> + goto done;
>> +
>> + if (si1s & master->ipoll)
>> + fsi_master_irq(master, 0, si1s);
>> +done:
>> + elapsed = jiffies - previous_jiffies;
>> + if (elapsed < FSI_IPOLL_PERIOD) {
>> + set_current_state(TASK_UNINTERRUPTIBLE);
>> + schedule_timeout(FSI_IPOLL_PERIOD - elapsed);
>> + }
>> + previous_jiffies = jiffies;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> int fsi_master_register(struct fsi_master *master)
>> {
>> if (!master || !master->dev)
>> @@ -524,9 +586,36 @@ void fsi_master_unregister(struct fsi_master
>> *master)
>> {
>> fsi_master_unscan(master);
>> put_device(master->dev);
>> + if (master_ipoll) {
>> + kthread_stop(master_ipoll);
>> + master_ipoll = NULL;
>> + }
>> }
>> EXPORT_SYMBOL_GPL(fsi_master_unregister);
>>
>> +/*
>> + * TODO: move this to master->start_ipoll( ) -each master may have
>> its
>> + * own way of doing this
>> + */
>> +int fsi_master_start_ipoll(struct fsi_master *master)
>> +{
>> + if (master_ipoll) {
>> + dev_err(master->dev, "Already polling for irqs\n");
>> + return -EALREADY;
>> + }
>> + master_ipoll = kthread_create(fsi_master_ipoll, master,
>> + "fsi_master_ipoll");
>> + if (IS_ERR(master_ipoll)) {
>> + dev_err(master->dev, "Couldn't create ipoll thread rc:%d\n",
>> + (int)PTR_ERR(master_ipoll));
>> + return PTR_ERR(master_ipoll);
>> + }
>> + wake_up_process(master_ipoll);
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(fsi_master_start_ipoll);
>> +
>> /* FSI core & Linux bus type definitions */
>>
>> static int fsi_bus_match(struct device *dev, struct device_driver
>> *drv)
>> @@ -566,6 +655,16 @@ void fsi_driver_unregister(struct fsi_driver
>> *fsi_drv)
>> }
>> EXPORT_SYMBOL_GPL(fsi_driver_unregister);
>>
>> +int fsi_enable_irq(struct fsi_device *dev)
>> +{
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(fsi_enable_irq);
>> +
>> +void fsi_disable_irq(struct fsi_device *dev)
>> +{
>> +}
>> +
>> struct bus_type fsi_bus_type = {
>> .name = "fsi",
>> .match = fsi_bus_match,
>> diff --git a/drivers/fsi/fsi-master-gpio.c
>> b/drivers/fsi/fsi-master-gpio.c
>> index 83c7cf0..73d9985 100644
>> --- a/drivers/fsi/fsi-master-gpio.c
>> +++ b/drivers/fsi/fsi-master-gpio.c
>> @@ -457,6 +457,7 @@ static ssize_t store_scan(struct device *dev,
>> /* clear out any old scan data if present */
>> fsi_master_unregister(&master->master);
>> fsi_master_register(&master->master);
>> + fsi_master_start_ipoll(&master->master);
>>
>> return count;
>> }
>> diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h
>> index 454af2b..3737404 100644
>> --- a/drivers/fsi/fsi-master.h
>> +++ b/drivers/fsi/fsi-master.h
>> @@ -25,6 +25,7 @@ struct fsi_master {
>> struct device *dev;
>> int idx;
>> int n_links;
>> + uint32_t ipoll;
>> int (*read)(struct fsi_master *, int link,
>> uint8_t slave, uint32_t addr,
>> void *val, size_t size);
>> @@ -37,6 +38,7 @@ struct fsi_master {
>>
>> extern int fsi_master_register(struct fsi_master *master);
>> extern void fsi_master_unregister(struct fsi_master *master);
>> +extern int fsi_master_start_ipoll(struct fsi_master *master);
>>
>> /**
>> * crc4 helper: Given a starting crc4 state @c, calculate the crc4
>> vaue of @x,
>> diff --git a/include/linux/fsi.h b/include/linux/fsi.h
>> index 2721255..d22d0c5 100644
>> --- a/include/linux/fsi.h
>> +++ b/include/linux/fsi.h
>> @@ -23,9 +23,11 @@ struct fsi_device {
>> u8 engine_type;
>> u8 version;
>> u8 unit;
>> + u8 si1s_bit;
>> struct fsi_slave *slave;
>> uint32_t addr;
>> uint32_t size;
>> + int (*irq_handler)(int, void *);
>> };
>>
>> extern int fsi_device_read(struct fsi_device *dev, uint32_t addr,
>> @@ -69,4 +71,7 @@ extern void fsi_driver_unregister(struct fsi_driver
>> *);
>>
>> extern struct bus_type fsi_bus_type;
>>
>> +extern int fsi_enable_irq(struct fsi_device *dev);
>> +extern void fsi_disable_irq(struct fsi_device *dev);
>> +
>> #endif /* LINUX_FSI_H */
>>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client
2017-02-03 0:55 ` [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client Joel Stanley
@ 2017-02-03 16:36 ` eajames
0 siblings, 0 replies; 16+ messages in thread
From: eajames @ 2017-02-03 16:36 UTC (permalink / raw)
To: Joel Stanley
Cc: OpenBMC Maillist, Alistair Popple, Benjamin Herrenschmidt,
Edward A. James, joel.stan
On 2017-02-02 18:55, Joel Stanley wrote:
> On Fri, Feb 3, 2017 at 9:55 AM, <eajames@linux.vnet.ibm.com> wrote:
>> From: "Edward A. James" <eajames@us.ibm.com>
>>
>> First, this series add interrupt polling support to the FSI bus
>> driver.
>> This implements a loop that constantly polls the FSI slave interrupt
>> status register and triggers any registered client interrupt handlers
>> upon
>> detecting an interrupt.
>>
>> Secondly, this series adds the I2C client driver for FSI bus. This
>> driver
>> interacts with the IBM BOE engine on an FSI slave device.
>>
>> Christopher Bostic (3):
>> drivers/fsi: Add slave interrupt polling
>> drivers/fsi: Add Client IRQ Enable / Disable
>> drivers/fsi: Add sysfs file to adjust i-poll period
>>
>> Edward A. James (5):
>> drivers: fsi: Add i2c client driver
>> drivers: fsi: i2c: Add engine access wrappers
>> drivers: fsi: i2c: probe fsi device for i2c client
>> drivers: fsi: i2c: add driver file operations and bus locking
>> drivers: fsi: i2c: boe engine
>
> This series was a bit of a mess. The second 5 patches all had trailing
> whitespace. They didn't build cleanly. These patches had different
> subject prefxies too. You can generate the subject line like this:
>
> git format-patch --subject-prefix "PATCH linux dev-4.7" and it will
> spit out patch files with the same subject prefix on all patches.
>
> If you're doing v2, you can do:
>
> git format-patch -v 2 --subject-prefix "PATCH linux dev-4.7" and it
> will add the v2 or v3 or v100 to your subject line.
>
> The full incarnation might look like this:
>
> git format-patch -v 2 --subject-prefix "PATCH linux dev-4.7"
> --to=joel@jms.id.au --cc=openbmc@lists.ozlabs.org origin/dev-4.7 -o
> fsi-patches-v2
>
> And then once you've edited the cover letter, I do this:
>
> git send-email fsi-patches-v2/*
>
> I fixed the mess this time, but please make sure you're sending
> patches that cleanly apply and don't introduce warnings next time.
>
> Cheers,
>
> Joel
Thanks Joel, my apologies for the dirty patch set. I spent no effort
cleaning anything up from the FSP code since this is a temporary
measure. As for the subject prefixes, Chris generated the first three
and I generated the last 5, so I didn't realize they had different
subjects before I sent them out. Won't happen again. Looking forward to
working on the new i2c driver.
Thanks,
Eddie
>
>>
>> drivers/fsi/Kconfig | 6 +
>> drivers/fsi/Makefile | 1 +
>> drivers/fsi/fsi-core.c | 170 +++
>> drivers/fsi/fsi-master-gpio.c | 1 +
>> drivers/fsi/fsi-master.h | 2 +
>> drivers/fsi/i2c/Makefile | 1 +
>> drivers/fsi/i2c/iic-boe.c | 1597 +++++++++++++++++++++++++++++
>> drivers/fsi/i2c/iic-boe.h | 180 ++++
>> drivers/fsi/i2c/iic-fsi.c | 715 +++++++++++++
>> drivers/fsi/i2c/iic-int.h | 395 +++++++
>> drivers/fsi/i2c/iic-lock.c | 439 ++++++++
>> drivers/fsi/i2c/iic-mstr.c | 2281
>> +++++++++++++++++++++++++++++++++++++++++
>> include/linux/fsi.h | 5 +
>> include/uapi/linux/Kbuild | 1 +
>> include/uapi/linux/i2cfsi.h | 136 +++
>> 15 files changed, 5930 insertions(+)
>> create mode 100644 drivers/fsi/i2c/Makefile
>> create mode 100644 drivers/fsi/i2c/iic-boe.c
>> create mode 100644 drivers/fsi/i2c/iic-boe.h
>> create mode 100644 drivers/fsi/i2c/iic-fsi.c
>> create mode 100644 drivers/fsi/i2c/iic-int.h
>> create mode 100644 drivers/fsi/i2c/iic-lock.c
>> create mode 100644 drivers/fsi/i2c/iic-mstr.c
>> create mode 100644 include/uapi/linux/i2cfsi.h
>>
>> --
>> 1.8.3.1
>>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling
2017-02-03 16:18 ` eajames
@ 2017-02-03 20:33 ` Benjamin Herrenschmidt
0 siblings, 0 replies; 16+ messages in thread
From: Benjamin Herrenschmidt @ 2017-02-03 20:33 UTC (permalink / raw)
To: eajames, Alistair Popple; +Cc: openbmc, joel, Christopher Bostic, Eddie James
On Fri, 2017-02-03 at 10:18 -0600, eajames wrote:
> The problem here is that there is no hardware irq. FSI is using GPIO
> lines to access slave registers. The only indication that we have an
> interrupt is a bit set in the "slave interrupt status register". So
> we
> have to poll that register and handle the results. I'm not aware of
> any
> way to turn that into a kernel irq and use the existing kernel
> framework. But if it can be done, agreed, we should implement that!
The existing kernel i2c framework doesn't require IRQs.
You can poll, which sucks, but you can. With delays of course.
Also, if you know your i2c bit freq, you can always use a timer.
Cheers,
Ben.
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2017-02-03 20:34 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
2017-02-02 23:25 ` [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver eajames
2017-02-03 0:56 ` Alistair Popple
2017-02-03 0:58 ` Joel Stanley
2017-02-02 23:25 ` [PATCH linux v1 5/8] drivers: fsi: i2c: Add engine access wrappers eajames
2017-02-02 23:25 ` [PATCH linux v1 6/8] drivers: fsi: i2c: probe fsi device for i2c client eajames
2017-02-02 23:26 ` [PATCH linux v1 7/8] drivers: fsi: i2c: add driver file operations and bus locking eajames
2017-02-02 23:26 ` [PATCH linux v1 8/8] drivers: fsi: i2c: boe engine eajames
2017-02-02 23:26 ` [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling eajames
2017-02-03 1:11 ` Alistair Popple
2017-02-03 16:18 ` eajames
2017-02-03 20:33 ` Benjamin Herrenschmidt
2017-02-02 23:26 ` [PATCH v2 2/3] drivers/fsi: Add Client IRQ Enable / Disable eajames
2017-02-02 23:26 ` [PATCH v2 3/3] drivers/fsi: Add sysfs file to adjust i-poll period eajames
2017-02-03 0:55 ` [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client Joel Stanley
2017-02-03 16:36 ` eajames
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.