[PATCH linux v1 6/8] drivers: fsi: i2c: probe fsi device for i2c client
eajames at linux.vnet.ibm.com
eajames at linux.vnet.ibm.com
Fri Feb 3 10:25:59 AEDT 2017
From: "Edward A. James" <eajames at us.ibm.com>
Signed-off-by: Edward A. James <eajames at 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 at us.ibm.com>");
+MODULE_DESCRIPTION("Base IIC Driver");
+MODULE_LICENSE("GPL");
--
1.8.3.1
More information about the openbmc
mailing list