Adding an async I2C interface
Subsystems (3)
I have an IPMI interface driver that sits on top of the I2C code. I'd like to get it into the mainstream kernel, but I have a few problems to solve first before I can do that. The I2C code is synchronous and must run from a task context. The IPMI driver has certain operations that occur at panic time, including:
* Storing panic information in IPMI's system event log
* Extending the watchdog timer so it doesn't go off during panic operations (like kernel coredumps).
* Powering the system off
I can't really put the IPMI SMB interface into the kernel until I can do those operations. Also, I understand that some vendors put RTC chips onto the I2C bus and this must be accessed outside task context, too. I would really like add asynchronous interface to the I2C bus drivers. I propose:
* Adding an async send interface to the busses that does a callback when the operation is complete.
* Adding a poll interface to the busses. The I2C core code could call this if a synchronous call is made from task context (much like all the current drivers do right now). For asyncronous operation, the I2C core code would call it from a timer interrupt. If the driver supported interrupts, polling from the timer interrupt would not be necessary.
* Add async operations for the user to call, including access to the polling code.
* If the driver didn't support an async send, it would work as it does today and the async calls would return ENOSYS.
This way, the bus drivers on I2C could be converted on a driver-by-driver basis. The IPMI code could query to see if the driver supported async operations. And the RTC code could use it, too.
Is this ok with the I2C community? I would do the base work and convert over a few drivers.
Thanks,
-Corey
is there a way to do this solely in i2c-core without having to add support to all the drivers?
Quoted 43 lines · Corey Minyard
I have an IPMI interface driver that sits on top of the I2C code. I'd like to get it into the mainstream kernel, but I have a few problems to solve first before I can do that. The I2C code is synchronous and must run from a task context. The IPMI driver has certain operations that occur at panic time, including:
* Storing panic information in IPMI's system event log
* Extending the watchdog timer so it doesn't go off during panic operations (like kernel coredumps).
* Powering the system offI can't really put the IPMI SMB interface into the kernel until I can do those operations. Also, I understand that some vendors put RTC chips onto the I2C bus and this must be accessed outside task context, too. I would really like add asynchronous interface to the I2C bus drivers. I propose:
* Adding an async send interface to the busses that does a callback when the operation is complete.
* Adding a poll interface to the busses. The I2C core code could call this if a synchronous call is made from task context (much like all the current drivers do right now). For asyncronous operation, the I2C core code would call it from a timer interrupt. If the driver supported interrupts, polling from the timer interrupt would not be necessary.
* Add async operations for the user to call, including access to the polling code.
* If the driver didn't support an async send, it would work as it does today and the async calls would return ENOSYS.This way, the bus drivers on I2C could be converted on a driver-by-driver basis. The IPMI code could query to see if the driver supported async operations. And the RTC code could use it, too.
Is this ok with the I2C community? I would do the base work and convert over a few drivers.
Thanks,
-Corey
Quoted 2 lines · Mark Studebaker
is there a way to do this solely in i2c-core without having to add support to all the drivers?
Yes and no. In order to support this async operation, the driver cannot block and do things like msleep() or schedule(). It has to start the operation, return, and either let polling or an interrupt drive the continued operation. Thus for async operations the driver has to be modified. However, if async operation is not required, the driver can stay as is.
I've been working on this and will probably have a patch tomorrow. I've modified the piix4 and the i801 drivers, I probably won't do any more myself unless the need arises, since I can't test any others. Note that this still supports the old driver interface, so no drivers need to be rewritten. That way, they only need to be modified if something needs the async interface. So drivers that have an RTC on them or that support IPMI BMCs could be rewritten, but nothing else needs to be done.
I've also noticed a somewhat cavalier attitude in this code with respect to return values. I've cleaned some of that up so return values are not just -1 on error, but are proper errno values. However, I've only fixed the core code and the drivers I've worked on.
Thanks,
-Corey
Quoted 46 lines
Quoted 43 lines · Corey Minyard
I have an IPMI interface driver that sits on top of the I2C code. I'd like to get it into the mainstream kernel, but I have a few problems to solve first before I can do that. The I2C code is synchronous and must run from a task context. The IPMI driver has certain operations that occur at panic time, including:
* Storing panic information in IPMI's system event log
* Extending the watchdog timer so it doesn't go off during panic operations (like kernel coredumps).
* Powering the system offI can't really put the IPMI SMB interface into the kernel until I can do those operations. Also, I understand that some vendors put RTC chips onto the I2C bus and this must be accessed outside task context, too. I would really like add asynchronous interface to the I2C bus drivers. I propose:
* Adding an async send interface to the busses that does a callback when the operation is complete.
* Adding a poll interface to the busses. The I2C core code could call this if a synchronous call is made from task context (much like all the current drivers do right now). For asyncronous operation, the I2C core code would call it from a timer interrupt. If the driver supported interrupts, polling from the timer interrupt would not be necessary.
* Add async operations for the user to call, including access to the polling code.
* If the driver didn't support an async send, it would work as it does today and the async calls would return ENOSYS.This way, the bus drivers on I2C could be converted on a driver-by-driver basis. The IPMI code could query to see if the driver supported async operations. And the RTC code could use it, too.
Is this ok with the I2C community? I would do the base work and convert over a few drivers.
Thanks,
-Corey
I will be glad to work with on this, I have some exposure to the BMC. See text below in blue.
bukie
Quoted 7 lines · Corey Minyard
Quoted 2 lines · Mark Studebaker
is there a way to do this solely in i2c-core without having to add support to all the drivers?
Yes and no. In order to support this async operation, the driver cannot block and do things like
msleep()orschedule().
The Interface driver must be a user space driver.
Quoted 14 lines
It has to start the
operation, return, and either let polling or an interrupt drive the continued operation. Thus for async operations the driver has to be modified. However, if async operation is not required, the driver can stay as is.I've been working on this and will probably have a patch tomorrow. I've modified the piix4 and the i801 drivers, I probably won't do any more myself unless the need arises, since I can't test any others. Note that this still supports the old driver interface, so no drivers need to be rewritten. That way, they only need to be modified if something needs the async interface. So drivers that have an RTC on them or that support IPMI BMCs could be rewritten, but nothing else needs to be done.
Quoted 16 lines
I've also noticed a somewhat cavalier attitude in this code with respect to return values. I've cleaned some of that up so return values are not just -1 on error, but are proper errno values. However, I've only fixed the core code and the drivers I've worked on.
Thanks,
-Corey
Quoted 7 lines
Quoted 4 lines · Corey Minyard
I have an IPMI interface driver that sits on top of the I2C code. I'd like to get it into the mainstream kernel, but I have a few problems to solve first before I can do that. The I2C code is synchronous and must run from a task context.
I am not sure what you mean that the I2C code is synchronous. I2C bus is Asynchronous which means that the data clock is not included in the data. The Sender and Receiver agrees on the timing parameters prior to the bus transaction.
Quoted 8 lines
The IPMI driver has certain
Quoted 7 lines
Quoted 7 lines
operations that occur at panic time, including:
* Storing panic information in IPMI's system event log
* Extending the watchdog timer so it doesn't go off during panic operations (like kernel coredumps).
* Powering the system off
Is this driver compliant with the IPMI spec? Because the above should be a sensor that must be enable or disable. A driver should not make sure a decision by itself.
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
I can't really put the IPMI SMB interface into the kernel until I can do those operations. Also, I understand that some vendors put RTC chips onto the I2C bus and this must be accessed outside task context, too.
What the vendor put on the board doesn't matter with respect to IPMI. What matter is that you have access to the Master where the slave you talking to is connect on the I2C bus.
Quoted 2 lines
I would really like add asynchronous interface to the I2C bus
Quoted 1 line
Quoted 1 line
drivers.
Do you mean a blocking and non blocking I/O?
Quoted 4 lines
I propose:
Quoted 3 lines
Quoted 3 lines
* Adding an async send interface to the busses that does a callback when the operation is complete.
Okay, you are doing a non-blocking I/O. So what happens when another process tries to access the I2C bus before the bus transaction is completed. Some thing (mainly the app) needs to tell the driver not to share the resource while a transaction is in progress.
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
* Adding a poll interface to the busses. The I2C core code could call this if a synchronous call is made from task context (much like all the current drivers do right now). For asyncronous operation, the I2C core code would call it from a timer interrupt. If the driver supported interrupts, polling from the timer interrupt would not be necessary.
I think this should be done in the Interface code because the Interface code will be running in the user space and have access to the operating system facility.
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
* Add async operations for the user to call, including access to the polling code.
The driver can make itself blocking and non blocking
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
* If the driver didn't support an async send, it would work as it does today and the async calls would return ENOSYS.
Not needed, it will be addressed by the blocking and non-block implementation of the driver.
Quoted 16 lines
Quoted 7 lines
Quoted 7 lines
This way, the bus drivers on I2C could be converted on a driver-by-driver basis. The IPMI code could query to see if the driver supported async operations. And the RTC code could use it, too.
Quoted 8 lines
Quoted 8 lines
Is this ok with the I2C community? I would do the base work and convert over a few drivers.
Thanks,
-Corey
Quoted 5 lines
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.lm-sensors.org/pipermail/lm-sensors/attachments/20050127/eb6c4af0/attachment.html
Quoted 124 lines
I will be glad to work with on this, I have some exposure to the BMC. See text below in blue.
bukie
Quoted 7 lines · Corey Minyard
Quoted 2 lines · Mark Studebaker
is there a way to do this solely in i2c-core without having to add support to all the drivers?
Yes and no. In order to support this async operation, the driver cannot block and do things like
msleep()orschedule().The Interface driver must be a user space driver.
Quoted 14 lines
It has to start the
operation, return, and either let polling or an interrupt drive the continued operation. Thus for async operations the driver has to be modified. However, if async operation is not required, the driver can stay as is.I've been working on this and will probably have a patch tomorrow. I've modified the piix4 and the i801 drivers, I probably won't do any more myself unless the need arises, since I can't test any others. Note that this still supports the old driver interface, so no drivers need to be rewritten. That way, they only need to be modified if something needs the async interface. So drivers that have an RTC on them or that support IPMI BMCs could be rewritten, but nothing else needs to be done.
Quoted 16 lines
I've also noticed a somewhat cavalier attitude in this code with respect to return values. I've cleaned some of that up so return values are not just -1 on error, but are proper errno values. However, I've only fixed the core code and the drivers I've worked on.
Thanks,
-Corey
Quoted 7 lines
Quoted 4 lines · Corey Minyard
I have an IPMI interface driver that sits on top of the I2C code. I'd like to get it into the mainstream kernel, but I have a few problems to solve first before I can do that. The I2C code is synchronous and must run from a task context.
I am not sure what you mean that the I2C code is synchronous. I2C bus is Asynchronous which means that the data clock is not included in the data. The Sender and Receiver agrees on the timing parameters prior to the bus transaction.
Quoted 8 lines
The IPMI driver has certain
Quoted 7 lines
Quoted 7 lines
operations that occur at panic time, including:
* Storing panic information in IPMI's system event log
* Extending the watchdog timer so it doesn't go off during panic operations (like kernel coredumps).
* Powering the system offIs this driver compliant with the IPMI spec? Because the above should be a sensor that must be enable or disable. A driver should not make sure a decision by itself.
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
I can't really put the IPMI SMB interface into the kernel until I can do those operations. Also, I understand that some vendors put RTC chips onto the I2C bus and this must be accessed outside task context, too.
What the vendor put on the board doesn't matter with respect to IPMI. What matter is that you have access to the Master where the slave you talking to is connect on the I2C bus.
Quoted 2 lines
I would really like add asynchronous interface to the I2C bus
Quoted 1 line
Quoted 1 line
drivers.
Do you mean a blocking and non blocking I/O?
Quoted 4 lines
I propose:
Quoted 3 lines
Quoted 3 lines
* Adding an async send interface to the busses that does a callback when the operation is complete.
Okay, you are doing a non-blocking I/O. So what happens when another process tries to access the I2C bus before the bus transaction is completed. Some thing (mainly the app) needs to tell the driver not to share the resource while a transaction is in progress.
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
* Adding a poll interface to the busses. The I2C core code could call this if a synchronous call is made from task context (much like all the current drivers do right now). For asyncronous operation, the I2C core code would call it from a timer interrupt. If the driver supported interrupts, polling from the timer interrupt would not be necessary.
I think this should be done in the Interface code because the Interface code will be running in the user space and have access to the operating system facility.
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
* Add async operations for the user to call, including access to the polling code.
The driver can make itself blocking and non blocking
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
* If the driver didn't support an async send, it would work as it does today and the async calls would return ENOSYS.
Not needed, it will be addressed by the blocking and non-block implementation of the driver.
Quoted 21 lines
Quoted 7 lines
Quoted 7 lines
This way, the bus drivers on I2C could be converted on a driver-by-driver basis. The IPMI code could query to see if the driver supported async operations. And the RTC code could use it, too.
Quoted 8 lines
Quoted 8 lines
Is this ok with the I2C community? I would do the base work and convert over a few drivers.
Thanks,
-Corey
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Quoted 8 lines · Bukie Mabayoje
I will be glad to work with on this, I have some exposure to the BMC. See text below in blue.
bukie
Quoted 2 lines · Corey Minyard
Mark Studebaker wrote:
Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
is there a way to do this solely in i2c-core without having to add support to all the drivers?
Yes and no. In order to support this async operation, the driver cannot block and do things like
msleep()orschedule().The Interface driver must be a user space driver.
That is precisely the problem. What happens if you need to get to the i2c bus but you are not running from a task context?
Quoted 26 lines
Quoted 26 lines
It has to start the
operation, return, and either let polling or an interrupt drive the continued operation. Thus for async operations the driver has to be modified. However, if async operation is not required, the driver can stay as is.I've been working on this and will probably have a patch tomorrow. I've modified the piix4 and the i801 drivers, I probably won't do any more myself unless the need arises, since I can't test any others. Note that this still supports the old driver interface, so no drivers need to be rewritten. That way, they only need to be modified if something needs the async interface. So drivers that have an RTC on them or that support IPMI BMCs could be rewritten, but nothing else needs to be done.
I've also noticed a somewhat cavalier attitude in this code with respect to return values. I've cleaned some of that up so return values are not just -1 on error, but are proper errno values. However, I've only fixed the core code and the drivers I've worked on.
Thanks,
-Corey
Quoted 3 lines
Corey Minyard wrote:
Quoted 7 lines
Quoted 6 lines
Quoted 1 line
Quoted 1 line
I have an IPMI interface driver that sits on top of the I2C code.
I'd
Quoted 3 lines
Quoted 3 lines
like to get it into the mainstream kernel, but I have a few problems to solve first before I can do that. The I2C code is synchronous and must run from a task context.
I am not sure what you mean that the I2C code is synchronous. I2C bus is Asynchronous which means that the data clock is not included in the data. The Sender and Receiver agrees on the timing parameters prior to the bus transaction.
I mean the driver, not the hardware. By the I2C driver being synchronous, I mean that you call a function, the whole operation occurs in the function, and then the function returns the result. By asynchronous, I mean that you tell the driver to start an operation and the driver starts it and immediately returns. It then uses interrupts, timers, or polling calls to drive the operation of the interface. When the operation is done, the results are reported back through a callback function provided when the driver started.
Plus there are SMB alerts which the hardware can generate asynchronously. The I2C driver has no concept of that right now.
Quoted 10 lines
Quoted 8 lines
The IPMI driver has certain
Quoted 7 lines
Quoted 7 lines
operations that occur at panic time, including:
* Storing panic information in IPMI's system event log
* Extending the watchdog timer so it doesn't go off during panic operations (like kernel coredumps).
* Powering the system offIs this driver compliant with the IPMI spec? Because the above should be a sensor that must be enable or disable. A driver should not make sure a decision by itself.
There are two parts here. There is the I2C driver and the IPMI driver that sits on top of it. I'm really only taking about the I2C driver here, I need asynchronous operation from the I2C driver to do those things in the IPMI driver.
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
Quoted 4 lines
I can't really put the IPMI SMB interface into the kernel until I can do those operations. Also, I understand that some vendors put RTC chips onto the I2C bus and this must be accessed outside task
context,
Quoted 8 lines
Quoted 1 line
Quoted 1 line
Quoted 1 line
too.
What the vendor put on the board doesn't matter with respect to IPMI. What matter is that you have access to the Master where the slave you talking to is connect on the I2C bus.
Quoted 2 lines
I would really like add asynchronous interface to the I2C bus
Quoted 1 line
Quoted 1 line
drivers.
Do you mean a blocking and non blocking I/O?
Yes, that is a better term. I'll switch to using that.
-Corey
PATCH 1/1 Add a non-blocking I2C interface
In reply to Bukie MabayojeHere's the code that I have so far for adding a non-blocking interface to the I2C interface. I've debated whether to do this as a patch or just post the files, because the patch is about half the size of the files. I've decided on the diff for now, it seems to be fairly readable. This is relative to 2.6.11-rc2.
I have not extensively tested this patch. I've done eeprom operations on the piix4 and i801 chips both with and without updating the those interface for non-blocking operation. I'm going to test the block transfers with the IPMI driver when I get access to a system (this weekend). This is posted for comment.
This patch doesn't actually change much. It adds a queue entry that is used to pass around the message data (and is obviously used for queuing :). But this mostly is using the queue entry for data, breaking the operations into smaller functions, and doing the polling in the i2c-core code instead of the drivers themselves (for non-blocking capable drivers). Function for unchanged drivers should be unchanged.
This does not implement SMB Alert, which should improve the performace of the IPMI SMB driver. I'll do that in the future and just poll the interface for now.
I'll post the changes to the i801 driver next.
This patch requires the fixes to the completion code that Mike Waychison posted a few days ago (http://marc.theaimsgroup.com/?l=linux-kernel&m=110669761400454&w=2). Otherwise the wait_for_completion_interruptible() and wait_for_completion_timeout() are broken.
In case you missed the previous discussion, I need a non-blocking interface in the I2C code for use by the IPMI SMB driver. The driver needs to be able to do things like store panic information in the SEL, ping the watchdog timer while in a panic, and power the system off. The current I2C driver requires a task context, but you can't exactly do things at panic time with a task context.
-Corey
drivers/i2c/i2c-core.c +620 -156
@@ -30,8 +30,14 @@#include <linux/init.h> #include <linux/idr.h> #include <linux/seq_file.h> +#include <linux/completion.h> #include <asm/uaccess.h> +static int i2c_stop_timer (struct i2c_adapter * adap); +static void i2c_start_timer (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry); + +#define USEC_PER_JIFFIE (1000000 / HZ)static LIST_HEAD (adapters);static LIST_HEAD (drivers);
@@ -134,11 +140,26 @@ } adap->nr = id &MAX_ID_MASK ; +spin_lock_init (&adap->q_lock); +INIT_LIST_HEAD (&adap->q);init_MUTEX (&adap->bus_lock);init_MUTEX (&adap->clist_lock);list_add_tail (&adap->list,&adapters);INIT_LIST_HEAD (&adap->clients); + adap->timer =kmalloc (sizeof (*adap->timer),GFP_KERNEL ); +if (!adap->timer) { + res = -ENOMEM ; + goto out_unlock; + } + +init_timer (&adap->timer->timer); +spin_lock_init (&adap->timer->lock); + adap->timer->deleted =0 ; + adap->timer->running =0 ; + adap->timer->next_call_time =0 ; + adap->timer->adapter = adap; +/* Add the adapter to the driver core. * If the parent pointer is not set up, * we add this adapter to the host bus.
@@ -181,6 +202,7 @@struct i2c_driver *driver;struct i2c_client *client;int res =0 ; +unsigned long flags;down (&core_lists);
@@ -233,6 +255,17 @@device_unregister (&adap->dev);list_del (&adap->list); +/* Stop the timer and free its memory */ +spin_lock_irqsave (&adap->timer->lock, flags); +if (i2c_stop_timer (adap)) { +spin_unlock_irqrestore (&adap->timer->lock, flags); +kfree (adap->timer); + }else { + adap->timer->deleted =1 ; +spin_unlock_irqrestore (&adap->timer->lock, flags); + } + adap->timer =NULL ; +/* wait for sysfs to drop all references */ wait_for_completion (&adap->dev_released);wait_for_completion (&adap->class_dev_released);
@@ -583,15 +616,283 @@* ---------------------------------------------------- */ -int i2c_transfer (struct i2c_adapter * adap,struct i2c_msg msgs[],int num) +/* Must be called with the q_lock held. */ +static void i2c_start_entry (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) +{ + entry->started =1 ; +switch (entry->xfer_type) { +case I2C_OP_I2C : + adap->algo->master_start (adap, entry); +break ; +case I2C_OP_SMBUS : + adap->algo->smbus_start (adap, entry); +break ; +default : + entry->result = -EINVAL ; +i2c_op_done (adap, entry); + } + +if (!entry->completed && entry->use_timer) +i2c_start_timer (adap, entry); +} + +/* Must be called with q lock held. */ +static void i2c_entry_inc (struct i2c_adapter * adapter, +struct i2c_op_q_entry * entry) { -int ret; +atomic_inc (&entry->usecount); +} + +/* Get the first entry off the head of the queue and lock it there. +The entry is guaranteed to remain first in the list and the handler +not be called until i2c_entry_put() is called. */ +static struct i2c_op_q_entry *_i2c_entry_get (struct i2c_adapter * adap) +{ +struct i2c_op_q_entry * entry =NULL ; -if (adap->algo->master_xfer) { -dev_dbg (&adap->dev,"master_xfer: with %d msgs.\n" , num); +if (!list_empty (&adap->q)) { +struct list_head * link = adap->q.next; + entry = list_entry(link, struct i2c_op_q_entry, link); +if (entry->completed) + entry =NULL ; +else +i2c_entry_inc (adap, entry); + } +pr_debug ("_i2c_entry_get %p %p\n" , adap, entry); +return entry; +} + +struct i2c_op_q_entry *i2c_entry_get (struct i2c_adapter * adap) +{ +unsigned long flags; +struct i2c_op_q_entry * entry; + +spin_lock_irqsave (&adap->q_lock, flags); + entry =_i2c_entry_get (adap); +spin_unlock_irqrestore (&adap->q_lock, flags); +return entry; +} + +void i2c_entry_put (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) +{ +unsigned long flags; +struct i2c_op_q_entry * new_entry =NULL ; + + restart: +pr_debug ("i2c_put %p %p\n" , adap, entry); +if (atomic_dec_and_test (&entry->usecount)) { +spin_lock_irqsave (&adap->q_lock, flags); +list_del (&entry->link); + +/* Get the next entry to start. */ + new_entry =_i2c_entry_get (adap); +spin_unlock_irqrestore (&adap->q_lock, flags); + + entry->handler (entry); + + +if (new_entry) { +i2c_start_entry (adap, new_entry); +if (new_entry->start) +complete (new_entry->start); +/* Do tail recursion ourself. */ + entry = new_entry; + goto restart; + } + } +} + +static void i2c_handle_timer (unsigned long data); + +static void i2c_start_timer (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) +{ +unsigned int wait_jiffies; +struct i2c_timer *t = adap->timer; +unsigned long flags; + + wait_jiffies = ((entry->call_again_us +USEC_PER_JIFFIE -1 ) + /USEC_PER_JIFFIE ); +if (wait_jiffies ==0 ) + wait_jiffies =1 ; +/* This won't be polled from the user code, so +start a timer to poll it. */ +spin_lock_irqsave (&t->lock, flags); +if (! t->running) { + t->timer.expires = jiffies + wait_jiffies; + t->timer.data = (unsigned long ) adap; + t->timer.function = i2c_handle_timer; + t->running =1 ; + t->next_call_time = wait_jiffies *USEC_PER_JIFFIE ; +add_timer (&t->timer); + t->sequence = adap->timer_sequence; + } +spin_unlock_irqrestore (&t->lock, flags); +} + +/* Returns true if the timer is stopped (or was not running), false if +not. Must be called with the timer lock held. */ +static int i2c_stop_timer (struct i2c_adapter * adap) +{ +return (!adap->timer->running ||del_timer (&adap->timer->timer)); +} + +static void i2c_handle_timer (unsigned long data) +{ +struct i2c_timer * t = (void *) data; +struct i2c_adapter * adap; +unsigned long flags; +struct i2c_op_q_entry * entry; +unsigned int sequence_match; + +spin_lock_irqsave (&t->lock, flags); +if (t->deleted) { +spin_unlock_irqrestore (&t->lock, flags); +kfree (t); +return ; + } + + adap = t->adapter; + t->running =0 ; + sequence_match = adap->timer_sequence == t->sequence; +spin_unlock_irqrestore (&t->lock, flags); + + entry =i2c_entry_get (adap); +pr_debug ("i2c_handle_timer: %p %p\n" , adap, entry); +if (!entry) +return ; + +if (sequence_match) { +/* This is the one we expected, call the poll routine. */ + adap->algo->poll (adap, entry, t->next_call_time); + +if (!entry->completed) +i2c_start_timer (adap, entry); + }else if (entry->use_timer) +/* We raced in timer deletion, just restart the +timer if necessary. */ +i2c_start_timer (adap, entry); + +i2c_entry_put (adap, entry); +} + +void i2c_op_done (struct i2c_adapter *adap,struct i2c_op_q_entry *e) +{ +unsigned long flags; +int did_complete =0 ; + +pr_debug ("i2c_op_done: %p %p\n" , adap, e); +spin_lock_irqsave (&adap->q_lock, flags); +if (! e->completed) { + e->completed =1 ; + did_complete =1 ; + } +spin_unlock_irqrestore (&adap->q_lock, flags); + +if (did_complete) { +if (e->use_timer) { +struct i2c_timer *t = adap->timer; +spin_lock_irqsave (&t->lock, flags); +if (!i2c_stop_timer (adap)) +/* If we are unable to stop the timer, that +means the timer has gone off but has not +yet run the first part of the handler call. +Increment the sequence so the timer handler +can detect this. */ + adap->timer_sequence++; +spin_unlock_irqrestore (&t->lock, flags); + } +if (e->complete) + e->complete (adap, e); + } + +i2c_entry_put (adap, e); +} + +static void i2c_wait_complete (struct i2c_op_q_entry * entry) +{ +struct completion *done = entry->handler_data; +pr_debug ("i2c_wait_complete %p\n" , entry); +complete (done); +} + +static void i2c_perform_op_wait (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) +{ +struct completion done; +unsigned long flags; +struct i2c_algorithm *algo = adap->algo; + +pr_debug ("i2c_perform_op_wait %p %p\n" , adap, entry); +init_completion (&done); + entry->start =NULL ; + entry->handler = i2c_wait_complete; + entry->handler_data = &done; + entry->started =0 ; + entry->completed =0 ; + entry->result =0 ; + entry->use_timer =0 ;/* We poll it directly. */ + entry->data =NULL ; +atomic_set (&entry->usecount,1 ); +spin_lock_irqsave (&adap->q_lock, flags); +list_add_tail (&entry->link, &adap->q); +if (adap->q.next == &entry->link) { +/* Added to the list head, start it */ +spin_unlock_irqrestore (&adap->q_lock, flags); +i2c_start_entry (adap, entry); + }else { +struct completion start; +init_completion (&start); + entry->start = &start; +spin_unlock_irqrestore (&adap->q_lock, flags); + +wait_for_completion_interruptible (&start); + +spin_lock_irqsave (&adap->q_lock, flags); +if (!entry->started) { +/* Operation was interrupted. There +is a race, we can't use the +wait_for_completion return code. */ + entry->result = -ERESTARTSYS ; + entry->completed =1 ; +list_del (&entry->link); + } +spin_unlock_irqrestore (&adap->q_lock, flags); + } + +/* Once the operation is started, we will not +interrupt it. */ +while (!entry->completed) { +unsigned int timeout = entry->call_again_us; + timeout += (USEC_PER_JIFFIE -1 ); + timeout /=USEC_PER_JIFFIE ; +if (timeout ==0 ) + timeout =1 ; +wait_for_completion_timeout (&done, timeout); +if (entry->completed) +break ; + algo->poll (adap, entry, timeout *USEC_PER_JIFFIE ); + } +} + +static int i2c_transfer_entry (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) +{ + entry->xfer_type =I2C_OP_I2C ; + entry->complete =NULL ; +if (adap->algo->master_start) { +i2c_perform_op_wait (adap, entry); +return entry->result; + }else if (adap->algo->master_xfer) { +int ret; +dev_dbg (&adap->dev,"master_xfer: with %d msgs.\n" , + entry->i2c.num);down (&adap->bus_lock); - ret = adap->algo->master_xfer (adap,msgs,num); + ret = adap->algo->master_xfer (adap, entry->i2c.msgs, + entry->i2c.num);up (&adap->bus_lock);return ret;
@@ -601,33 +902,45 @@ } } +int i2c_transfer (struct i2c_adapter * adap,struct i2c_msg msgs[],int num) +{ +struct i2c_op_q_entry *entry; +int rv; + + entry =kmalloc (sizeof (*entry),GFP_KERNEL ); +if (!entry) +return -ENOMEM ; + + entry->i2c.msgs = msgs; + entry->i2c.num = num; + + rv =i2c_transfer_entry (adap, entry); +kfree (entry); +return rv; +} +int i2c_master_send (struct i2c_client *client,const char *buf ,int count) {int ret;struct i2c_adapter *adap=client->adapter;struct i2c_msg msg; -if (client->adapter->algo->master_xfer) { - msg.addr = client->addr; - msg.flags = client->flags &I2C_M_TEN ; - msg.len = count; - msg.buf = (char *)buf; - -dev_dbg (&client->adapter->dev,"master_send: writing %d bytes.\n" , - count); + msg.addr = client->addr; + msg.flags = client->flags &I2C_M_TEN ; + msg.len = count; + msg.buf = (char *)buf; -down (&adap->bus_lock); - ret = adap->algo->master_xfer (adap,&msg,1 ); -up (&adap->bus_lock); +dev_dbg (&client->adapter->dev,"master_send: writing %d bytes.\n" , + count); -/* if everything went ok (i.e. 1 msg transmitted), return #bytes -* transmitted, else error code. -*/ -return (ret ==1 )? count : ret; - }else { -dev_err (&client->adapter->dev,"I2C level transfers not supported\n" ); -return -ENOSYS ; - } + ret =i2c_transfer (adap, &msg,1 ); +if (ret <0 ) +return ret; + +/* if everything went ok (i.e. 1 msg transmitted), return #bytes +* transmitted, else error code. +*/ +return (ret ==1 )? count : ret; }int i2c_master_recv (struct i2c_client *client,char *buf ,int count)
@@ -635,31 +948,27 @@struct i2c_adapter *adap=client->adapter;struct i2c_msg msg;int ret; -if (client->adapter->algo->master_xfer) { - msg.addr = client->addr; - msg.flags = client->flags &I2C_M_TEN ; - msg.flags |=I2C_M_RD ; - msg.len = count; - msg.buf = buf; -dev_dbg (&client->adapter->dev,"master_recv: reading %d bytes.\n" , - count); - -down (&adap->bus_lock); - ret = adap->algo->master_xfer (adap,&msg,1 ); -up (&adap->bus_lock); + msg.addr = client->addr; + msg.flags = client->flags &I2C_M_TEN ; + msg.flags |=I2C_M_RD ; + msg.len = count; + msg.buf = buf; + +dev_dbg (&client->adapter->dev,"master_recv: reading %d bytes.\n" , + count); -dev_dbg (&client->adapter->dev,"master_recv: return:%d (count:%d, addr:0x%02x)\n" , - ret, count, client->addr); + ret =i2c_transfer (adap, &msg,1 ); +if (ret <0 ) +return ret; + +dev_dbg (&client->adapter->dev,"master_recv: return:%d (count:%d, addr:0x%02x)\n" , + ret, count, client->addr); -/* if everything went ok (i.e. 1 msg transmitted), return #bytes -* transmitted, else error code. -*/ -return (ret ==1 )? count : ret; - }else { -dev_err (&client->adapter->dev,"I2C level transfers not supported\n" ); -return -ENOSYS ; - } +/* if everything went ok (i.e. 1 msg transmitted), return #bytes +* transmitted, else error code. +*/ +return (ret ==1 )? count : ret; }
@@ -1037,7 +1346,8 @@ }/* Returns the number of read bytes */ -s32 i2c_smbus_read_i2c_block_data (struct i2c_client *client,u8 command,u8 *values) +s32 i2c_smbus_read_i2c_block_data (struct i2c_client *client,u8 command, +u8 *values) {union i2c_smbus_data data;int i;
@@ -1052,184 +1362,333 @@ } } -/* Simulate a SMBus command using the i2c protocol -No checking of parameters is done! */ -static s32 i2c_smbus_xfer_emulated (struct i2c_adapter * adapter,u16 addr, -unsigned short flags, -char read_write,u8 command,int size, -union i2c_smbus_data * data) -{ -/* So we need to generate a series of msgs. In the case of writing, we -need to use only one message; when reading, we need two. We initialize -most things with sane defaults, to keep the code below somewhat -simpler. */ -unsigned char msgbuf0[34 ]; -unsigned char msgbuf1[34 ]; -int num = read_write ==I2C_SMBUS_READ ?2 :1 ; -struct i2c_msg msg[2 ] = { { addr, flags,1 , msgbuf0 }, - { addr, flags |I2C_M_RD ,0 , msgbuf1 } - }; + +static void i2c_smbus_complete_entry (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) +{ +if (entry->result <0 ) +return ; + +if (entry->result >=0 && entry->swpec && + entry->smbus.size !=I2C_SMBUS_QUICK && + entry->smbus.size !=I2C_SMBUS_I2C_BLOCK_DATA && + (entry->smbus.read_write ==I2C_SMBUS_READ || + entry->smbus.size ==I2C_SMBUS_PROC_CALL_PEC || + entry->smbus.size ==I2C_SMBUS_BLOCK_PROC_CALL_PEC )) { +if (i2c_smbus_check_pec (entry->smbus.addr, + entry->smbus.command, + entry->smbus.size, + entry->partial, + entry->smbus.data)) + entry->result = -EINVAL ; + } +} + +static void i2c_smbus_format_entry (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) +{ + entry->swpec =0 ; + entry->partial =0 ; + entry->smbus.flags &=I2C_M_TEN |I2C_CLIENT_PEC ; +if ((entry->smbus.flags &I2C_CLIENT_PEC ) && + !(i2c_check_functionality (adap,I2C_FUNC_SMBUS_HWPEC_CALC ))) { + entry->swpec =1 ; +if (entry->smbus.read_write ==I2C_SMBUS_READ && + entry->smbus.size ==I2C_SMBUS_BLOCK_DATA ) + entry->smbus.size =I2C_SMBUS_BLOCK_DATA_PEC ; +else if (entry->smbus.size ==I2C_SMBUS_PROC_CALL ) + entry->smbus.size =I2C_SMBUS_PROC_CALL_PEC ; +else if (entry->smbus.size ==I2C_SMBUS_BLOCK_PROC_CALL ) { +unsigned char *data = entry->smbus.data->block; +i2c_smbus_add_pec (entry->smbus.addr, + entry->smbus.command, +I2C_SMBUS_BLOCK_DATA , + entry->smbus.data); + entry->partial = data[data[0 ] +1 ]; + entry->smbus.size =I2C_SMBUS_BLOCK_PROC_CALL_PEC ; + }else if (entry->smbus.read_write ==I2C_SMBUS_WRITE && + entry->smbus.size !=I2C_SMBUS_QUICK && + entry->smbus.size !=I2C_SMBUS_I2C_BLOCK_DATA ) + entry->smbus.size = +i2c_smbus_add_pec (entry->smbus.addr, + entry->smbus.command, + entry->smbus.size, + entry->smbus.data); + } + + entry->complete = i2c_smbus_complete_entry; +} + +static void i2c_smbus_emu_complete (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) +{ +unsigned char *msgbuf0 = entry->i2c.msgs[0 ].buf; +unsigned char *msgbuf1 = entry->i2c.msgs[1 ].buf;int i; - msgbuf0[0 ] = command; -switch (size) { +if (entry->smbus.read_write !=I2C_SMBUS_READ ) +return ; + +switch (entry->smbus.size) { +case I2C_SMBUS_BYTE : + entry->smbus.data->byte = msgbuf0[0 ]; +break ; +case I2C_SMBUS_BYTE_DATA : + entry->smbus.data->byte = msgbuf1[0 ]; +break ; +case I2C_SMBUS_WORD_DATA : +case I2C_SMBUS_PROC_CALL : + entry->smbus.data->word = msgbuf1[0 ]|(msgbuf1[1 ] <<8 ); +break ; +case I2C_SMBUS_I2C_BLOCK_DATA : +/* fixed at 32 for now */ + entry->smbus.data->block[0 ] =I2C_SMBUS_I2C_BLOCK_MAX ; +for (i =0 ; i <I2C_SMBUS_I2C_BLOCK_MAX ; i++) + entry->smbus.data->block[i+1 ] = msgbuf1[i]; +break ; + } + + entry->xfer_type =I2C_OP_SMBUS ; +i2c_smbus_complete_entry (adap, entry); +} + +static int i2c_smbus_emu_format (struct i2c_adapter *adap, +struct i2c_op_q_entry * entry) +{ +/* So we need to generate a series of msgs. In the case of +writing, we need to use only one message; when reading, we +need two. We initialize most things with sane defaults, to +keep the code below somewhat simpler. */ +unsigned char *msgbuf0 = entry->msgbuf0; +unsigned char *msgbuf1 = entry->msgbuf1; +int num = entry->smbus.read_write ==I2C_SMBUS_READ ?2 :1 ; +struct i2c_msg *msg = entry->msg; +int i; + + entry->i2c.msgs = msg; + entry->i2c.msgs[0 ].buf = msgbuf0; + entry->i2c.msgs[1 ].buf = msgbuf1; + + msg[0 ].addr = entry->smbus.addr; + msg[0 ].flags = entry->smbus.flags; + msg[0 ].len =1 ; + msg[1 ].addr = entry->smbus.addr; + msg[1 ].flags = entry->smbus.flags |I2C_M_RD ; + msg[1 ].len =1 ; + + msgbuf0[0 ] = entry->smbus.command; +switch (entry->smbus.size) {case I2C_SMBUS_QUICK : msg[0 ].len =0 ;/* Special case: The read/write field is used as data */ - msg[0 ].flags = flags | (read_write==I2C_SMBUS_READ )?I2C_M_RD :0 ; + msg[0 ].flags = (entry->smbus.flags | + ((entry->smbus.read_write==I2C_SMBUS_READ ) + ?I2C_M_RD :0 )); num =1 ;break ;case I2C_SMBUS_BYTE : -if (read_write ==I2C_SMBUS_READ ) { +if (entry->smbus.read_write ==I2C_SMBUS_READ ) {/* Special case: only a read! */ - msg[0 ].flags =I2C_M_RD | flags; + msg[0 ].flags =I2C_M_RD | entry->smbus.flags; num =1 ; }break ;case I2C_SMBUS_BYTE_DATA : -if (read_write ==I2C_SMBUS_READ ) +if (entry->smbus.read_write ==I2C_SMBUS_READ ) msg[1 ].len =1 ;else { msg[0 ].len =2 ; - msgbuf0[1 ] = data->byte; + msgbuf0[1 ] = entry->smbus.data->byte; }break ;case I2C_SMBUS_WORD_DATA : -if (read_write ==I2C_SMBUS_READ ) +if (entry->smbus.read_write ==I2C_SMBUS_READ ) msg[1 ].len =2 ;else { msg[0 ].len=3 ; - msgbuf0[1 ] = data->word &0xff ; - msgbuf0[2 ] = (data->word >>8 ) &0xff ; + msgbuf0[1 ] = entry->smbus.data->word &0xff ; + msgbuf0[2 ] = (entry->smbus.data->word >>8 ) &0xff ; }break ;case I2C_SMBUS_PROC_CALL : num =2 ;/* Special case */ - read_write =I2C_SMBUS_READ ; + entry->smbus.read_write =I2C_SMBUS_READ ; msg[0 ].len =3 ; msg[1 ].len =2 ; - msgbuf0[1 ] = data->word &0xff ; - msgbuf0[2 ] = (data->word >>8 ) &0xff ; + msgbuf0[1 ] = entry->smbus.data->word &0xff ; + msgbuf0[2 ] = (entry->smbus.data->word >>8 ) &0xff ;break ;case I2C_SMBUS_BLOCK_DATA :case I2C_SMBUS_BLOCK_DATA_PEC : -if (read_write ==I2C_SMBUS_READ ) { -dev_err (&adapter->dev,"Block read not supported " +if (entry->smbus.read_write ==I2C_SMBUS_READ ) { +dev_err (&adap->dev,"Block read not supported " "under I2C emulation!\n" );return -1 ; }else { - msg[0 ].len = data->block[0 ] +2 ; + msg[0 ].len = entry->smbus.data->block[0 ] +2 ;if (msg[0 ].len >I2C_SMBUS_BLOCK_MAX +2 ) { -dev_err (&adapter->dev,"smbus_access called with " +dev_err (&adap->dev, +"smbus_access called with " "invalid block write size (%d)\n" , - data->block[0 ]); + entry->smbus.data->block[0 ]);return -1 ; } -if (size ==I2C_SMBUS_BLOCK_DATA_PEC ) +if (entry->smbus.size ==I2C_SMBUS_BLOCK_DATA_PEC ) (msg[0 ].len)++;for (i =1 ; i <= msg[0 ].len; i++) - msgbuf0[i] = data->block[i-1 ]; + msgbuf0[i] = entry->smbus.data->block[i-1 ]; }break ;case I2C_SMBUS_BLOCK_PROC_CALL :case I2C_SMBUS_BLOCK_PROC_CALL_PEC : -dev_dbg (&adapter->dev,"Block process call not supported " +dev_dbg (&adap->dev,"Block process call not supported " "under I2C emulation!\n" );return -1 ;case I2C_SMBUS_I2C_BLOCK_DATA : -if (read_write ==I2C_SMBUS_READ ) { +if (entry->smbus.read_write ==I2C_SMBUS_READ ) { msg[1 ].len =I2C_SMBUS_I2C_BLOCK_MAX ; }else { - msg[0 ].len = data->block[0 ] +1 ; + msg[0 ].len = entry->smbus.data->block[0 ] +1 ;if (msg[0 ].len >I2C_SMBUS_I2C_BLOCK_MAX +1 ) { -dev_err (&adapter->dev,"i2c_smbus_xfer_emulated called with " +dev_err (&adap->dev, +"i2c_smbus_xfer_emulated called with " "invalid block write size (%d)\n" , - data->block[0 ]); + entry->smbus.data->block[0 ]);return -1 ; } -for (i =1 ; i <= data->block[0 ]; i++) - msgbuf0[i] = data->block[i]; +for (i =1 ; i <= entry->smbus.data->block[0 ]; i++) + msgbuf0[i] = entry->smbus.data->block[i]; }break ;default : -dev_err (&adapter->dev,"smbus_access called with invalid size (%d)\n" , - size); +dev_err (&adap->dev, +"smbus_access called with invalid size (%d)\n" , + entry->smbus.size);return -1 ; } -if (i2c_transfer (adapter, msg, num) <0 ) -return -1 ; - -if (read_write ==I2C_SMBUS_READ ) -switch (size) { -case I2C_SMBUS_BYTE : - data->byte = msgbuf0[0 ]; -break ; -case I2C_SMBUS_BYTE_DATA : - data->byte = msgbuf1[0 ]; -break ; -case I2C_SMBUS_WORD_DATA : -case I2C_SMBUS_PROC_CALL : - data->word = msgbuf1[0 ] | (msgbuf1[1 ] <<8 ); -break ; -case I2C_SMBUS_I2C_BLOCK_DATA : -/* fixed at 32 for now */ - data->block[0 ] =I2C_SMBUS_I2C_BLOCK_MAX ; -for (i =0 ; i <I2C_SMBUS_I2C_BLOCK_MAX ; i++) - data->block[i+1 ] = msgbuf1[i]; -break ; - } + entry->xfer_type =I2C_OP_I2C ; + entry->i2c.msgs = msg; + entry->i2c.num = num; + entry->complete = i2c_smbus_emu_complete;return 0 ; } +/* Simulate a SMBus command using the i2c protocol +No checking of parameters is done! */ +static s32 i2c_smbus_xfer_emulated (struct i2c_adapter * adap, +struct i2c_op_q_entry * entry) + +{ +if (i2c_smbus_emu_format (adap, entry)) +return -EINVAL ; + +if (i2c_transfer_entry (adap, entry) <0 ) +return -EINVAL ; -s32 i2c_smbus_xfer (struct i2c_adapter * adapter,u16 addr,unsigned short flags, +return entry->result; +} + +s32 i2c_smbus_xfer (struct i2c_adapter * adap,u16 addr,unsigned short flags,char read_write,u8 command,int size,union i2c_smbus_data * data) { -s32 res; -int swpec =0 ; -u8 partial =0 ; - - flags &=I2C_M_TEN |I2C_CLIENT_PEC ; -if ((flags &I2C_CLIENT_PEC ) && - !(i2c_check_functionality (adapter,I2C_FUNC_SMBUS_HWPEC_CALC ))) { - swpec =1 ; -if (read_write ==I2C_SMBUS_READ && - size ==I2C_SMBUS_BLOCK_DATA ) - size =I2C_SMBUS_BLOCK_DATA_PEC ; -else if (size ==I2C_SMBUS_PROC_CALL ) - size =I2C_SMBUS_PROC_CALL_PEC ; -else if (size ==I2C_SMBUS_BLOCK_PROC_CALL ) { -i2c_smbus_add_pec (addr, command, -I2C_SMBUS_BLOCK_DATA , data); - partial = data->block[data->block[0 ] +1 ]; - size =I2C_SMBUS_BLOCK_PROC_CALL_PEC ; - }else if (read_write ==I2C_SMBUS_WRITE && - size !=I2C_SMBUS_QUICK && - size !=I2C_SMBUS_I2C_BLOCK_DATA ) - size =i2c_smbus_add_pec (addr, command, size, data); - } - -if (adapter->algo->smbus_xfer) { -down (&adapter->bus_lock); - res = adapter->algo->smbus_xfer (adapter,addr,flags,read_write, - command,size,data); -up (&adapter->bus_lock); - }else - res =i2c_smbus_xfer_emulated (adapter,addr,flags,read_write, - command,size,data); - -if (res >=0 && swpec && - size !=I2C_SMBUS_QUICK && size !=I2C_SMBUS_I2C_BLOCK_DATA && - (read_write ==I2C_SMBUS_READ || size ==I2C_SMBUS_PROC_CALL_PEC || - size ==I2C_SMBUS_BLOCK_PROC_CALL_PEC )) { -if (i2c_smbus_check_pec (addr, command, size, partial, data)) -return -1 ; +struct i2c_op_q_entry *entry; +struct i2c_algorithm *algo = adap->algo; +int result; + + + entry =kmalloc (sizeof (*entry),GFP_KERNEL ); +if (!entry) +return -ENOMEM ; + + entry->xfer_type =I2C_OP_SMBUS ; + entry->smbus.addr = addr; + entry->smbus.flags = flags; + entry->smbus.read_write = read_write; + entry->smbus.command = command; + entry->smbus.size = size; + entry->smbus.data = data; + +i2c_smbus_format_entry (adap, entry); + +if (algo->smbus_start) { +i2c_perform_op_wait (adap, entry); + }else if (algo->smbus_xfer) { +down (&adap->bus_lock); + entry->result = adap->algo->smbus_xfer (adap, + entry->smbus.addr, + entry->smbus.flags, + entry->smbus.read_write, + entry->smbus.command, + entry->smbus.size, + entry->smbus.data); +up (&adap->bus_lock); + }else { +i2c_smbus_xfer_emulated (adap, entry); } -return res; + + result = entry->result; +kfree (entry); +return result; } +int i2c_non_blocking_capable (struct i2c_adapter *adap) +{ +return adap->algo->poll !=NULL ; +} + +void i2c_poll (struct i2c_client *client, +unsigned int us_since_last_call) +{ +struct i2c_adapter *adap = client->adapter; +struct i2c_op_q_entry *entry; + + entry =i2c_entry_get (adap); +if (!entry) +return ; + adap->algo->poll (adap, entry, us_since_last_call); +i2c_entry_put (adap, entry); +} + +int i2c_non_blocking_op (struct i2c_client *client, +struct i2c_op_q_entry *entry) +{ +unsigned long flags; +struct i2c_adapter *adap = client->adapter; + +if (!i2c_non_blocking_capable (adap)) +return -ENOSYS ; + + entry->smbus.addr = client->addr; + entry->smbus.flags = client->flags; + +if (entry->xfer_type ==I2C_OP_SMBUS ) { +i2c_smbus_format_entry (adap, entry); +if (!adap->algo->smbus_start) { +if (i2c_smbus_emu_format (adap, entry)) +return -EINVAL ; + } + } + + entry->start =NULL ; + entry->started =0 ; + entry->completed =0 ; + entry->result =0 ; + entry->use_timer =1 ;/* Let the timer code poll it. */ + entry->data =NULL ; +atomic_set (&entry->usecount,1 ); + +spin_lock_irqsave (&adap->q_lock, flags); +list_add_tail (&entry->link, &adap->q); +if (adap->q.next == &entry->link) { +/* Added to the list head, start it */ +spin_unlock_irqrestore (&adap->q_lock, flags); +i2c_start_entry (adap, entry); + }else +spin_unlock_irqrestore (&adap->q_lock, flags); +return 0 ; +}/* You should always define `functionality'; the 'else' is just for backward compatibility. */
@@ -1258,6 +1717,7 @@EXPORT_SYMBOL (i2c_clients_command);EXPORT_SYMBOL (i2c_check_addr); +EXPORT_SYMBOL (i2c_op_done);EXPORT_SYMBOL (i2c_master_send);EXPORT_SYMBOL (i2c_master_recv);EXPORT_SYMBOL (i2c_control);
@@ -1278,6 +1738,10 @@EXPORT_SYMBOL (i2c_smbus_write_block_data);EXPORT_SYMBOL (i2c_smbus_read_i2c_block_data); +EXPORT_SYMBOL (i2c_non_blocking_capable); +EXPORT_SYMBOL (i2c_poll); +EXPORT_SYMBOL (i2c_non_blocking_op); +EXPORT_SYMBOL (i2c_get_functionality);EXPORT_SYMBOL (i2c_check_functionality);
include/linux/i2c.h +219 -32
331 lines not on this page · raw message
PATCH 1/1 Updates for the i801 driver to support the I2C non-blocking interface
In reply to Bukie MabayojeHere's the changes required for the i801 driver. See the previous post for the patch to add a non-blocking interface to the I2C driver.
Like the core I2C changes, this is mostly breaking the functions into smaller pieces and calling them from the appropriate places.
-Corey
drivers/i2c/busses/i2c-i801.c +368 -257
@@ -79,7 +79,8 @@#define SMBHSTCFG_I2C_EN 4/* Other settings */ -#define MAX_TIMEOUT 100 +#define MAX_TIMEOUT_US 100000 +#define RETRY_TIME_US 500/* Retry minimum is 500us */ #define ENABLE_INT9 0/* set to 0x01 to enable - untested */ /* I801 command constants */
@@ -105,21 +106,31 @@"Forcibly enable the I801 at the given address. " "EXTREMELY DANGEROUS!"); -static int i801_transaction (void ); -static int i801_block_transaction (union i2c_smbus_data *data, -char read_write,int command); -static unsigned short i801_smba;static struct pci_dev *I801_dev;static int isich4; +struct i801_i2c_data +{ +int i; +int len; +unsigned char hostc; +int block; +int hwpec; +int xact; +int hststs; +int wait_intr; +int finished; +}; +struct i801_i2c_data i801_data; +static int i801_setup (struct pci_dev *dev) {int error_return =0 ;unsigned char temp;/* Note: we keep on searching until we have found 'function 3' */ -if (PCI_FUNC (dev->devfn) !=3 ) +if (PCI_FUNC (dev->devfn) !=3 )return -ENODEV ; I801_dev = dev;
@@ -136,7 +147,7 @@ }else {pci_read_config_word (I801_dev,SMBBA , &i801_smba); i801_smba &=0xfff0 ; -if (i801_smba ==0 ) { +if (i801_smba ==0 ) {dev_err (&dev->dev,"SMB base address uninitialized" "- upgrade BIOS or use force_addr=0xaddr\n" );return -ENODEV ;
@@ -180,12 +191,93 @@return error_return; } -static int i801_transaction (void ) +static void i801_check_hststs (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) +{ +if (d->hststs &0x10 ) { + entry->result = -EIO ; +dev_dbg (&I801_dev->dev, +"Error: Failed bus transaction\n" ); + }else if (d->hststs &0x08 ) { + entry->result = -EIO ; +dev_err (&I801_dev->dev,"Bus collision!\n" ); +/* Clock stops and slave is stuck in mid-transmission */ + }else if (d->hststs &0x04 ) { + entry->result = -EIO ; +dev_dbg (&I801_dev->dev,"Error: no response!\n" ); + } +} + +static void i801_finish (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) +{ + d->finished =1 ; + +#ifdef HAVE_PEC +if (isich4 && d->hwpec) { +if (entry->smbus.size !=I2C_SMBUS_QUICK && + entry->smbus.size !=I2C_SMBUS_I2C_BLOCK_DATA ) +outb_p (0 ,SMBAUXCTL ); + } +#endif + +if (d->block || (entry->result <0 ) || + ((entry->smbus.read_write ==I2C_SMBUS_WRITE ) + || (d->xact ==I801_QUICK ))) +return ; + +switch (d->xact &0x7f ) { +case I801_BYTE :/* Result put in SMBHSTDAT0 */ +case I801_BYTE_DATA : + entry->smbus.data->byte =inb_p (SMBHSTDAT0 ); +break ; +case I801_WORD_DATA : + entry->smbus.data->word =inb_p (SMBHSTDAT0 ) + + (inb_p (SMBHSTDAT1 ) <<8 ); +break ; + } +} + +static void i801_transaction_final_check (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) +{ +i801_check_hststs (adap, entry, d); + +if ((inb_p (SMBHSTSTS ) &0x1f ) !=0x00 ) +outb_p (inb (SMBHSTSTS ),SMBHSTSTS ); + +if ((d->hststs = (0x1f &inb_p (SMBHSTSTS ))) !=0x00 ) { +dev_dbg (&I801_dev->dev,"Failed reset at end of transaction" +"(%02x)\n" , d->hststs); + } +dev_dbg (&I801_dev->dev,"Transaction (post): CNT=%02x, CMD=%02x, " +"ADD=%02x, DAT0=%02x, DAT1=%02x\n" ,inb_p (SMBHSTCNT ), +inb_p (SMBHSTCMD ),inb_p (SMBHSTADD ),inb_p (SMBHSTDAT0 ), +inb_p (SMBHSTDAT1 )); +} + +static void i801_transaction_poll (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) { -int temp; -int result =0 ; -int timeout =0 ; + d->hststs =inb_p (SMBHSTSTS ); +if (!(d->hststs &0x01 )) { +i801_transaction_final_check (adap, entry, d); +i801_finish (adap, entry, d); + }else if (entry->time_left <=0 ) { +dev_dbg (&I801_dev->dev,"SMBus Timeout!\n" ); + entry->result = -EIO ; +i801_transaction_final_check (adap, entry, d); + } +} +static void i801_transaction_start (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) +{ dev_dbg(&I801_dev->dev, "Transaction (pre): CNT=%02x, CMD=%02x," "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0),
@@ -193,331 +285,347 @@/* Make sure the SMBus host is ready to start transmitting */ /* 0x1f = Failed, Bus_Err, Dev_Err, Intr, Host_Busy */ -if ((temp = (0x1f &inb_p (SMBHSTSTS ))) !=0x00 ) { +if ((d->hststs = (0x1f &inb_p (SMBHSTSTS ))) !=0x00 ) {dev_dbg (&I801_dev->dev,"SMBus busy (%02x). Resetting... \n" , - temp); -outb_p (temp,SMBHSTSTS ); -if ((temp = (0x1f &inb_p (SMBHSTSTS ))) !=0x00 ) { -dev_dbg (&I801_dev->dev,"Failed! (%02x)\n" , temp); -return -1 ; + d->hststs); +outb_p (d->hststs,SMBHSTSTS ); +if ((d->hststs = (0x1f &inb_p (SMBHSTSTS ))) !=0x00 ) { +dev_dbg (&I801_dev->dev,"Failed! (%02x)\n" , d->hststs); + entry->result = -EIO ; +return ; }else {dev_dbg (&I801_dev->dev,"Successfull!\n" ); } }outb_p (inb (SMBHSTCNT ) |I801_START ,SMBHSTCNT ); +} -/* We will always wait for a fraction of a second! */ -do { -msleep (1 ); - temp =inb_p (SMBHSTSTS ); - }while ((temp &0x01 ) && (timeout++ <MAX_TIMEOUT )); - -/* If the SMBus is still busy, we give up */ -if (timeout >=MAX_TIMEOUT ) { -dev_dbg (&I801_dev->dev,"SMBus Timeout!\n" ); - result =-1 ; +static void i801_block_finish (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) +{ +if (entry->smbus.command ==I2C_SMBUS_I2C_BLOCK_DATA ) { +/* restore saved configuration register value */ +pci_write_config_byte (I801_dev,SMBHSTCFG , d->hostc); } -if (temp &0x10 ) { - result =-1 ; -dev_dbg (&I801_dev->dev,"Error: Failed bus transaction\n" ); - } +i801_finish (adap, entry, d); +} -if (temp &0x08 ) { - result =-1 ; -dev_err (&I801_dev->dev,"Bus collision! SMBus may be locked " -"until next hard reset. (sorry!)\n" ); -/* Clock stops and slave is stuck in mid-transmission */ - } +static void i801_block_poll_wait_intr (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) +{ +#ifdef HAVE_PEC +if (entry->result >=0 && + isich4 && + entry->smbus.command ==I2C_SMBUS_BLOCK_DATA_PEC ) + { +/* wait for INTR bit as advised by Intel */ + d->hststs =inb_p (SMBHSTSTS ); +if (d->hststs &0x02 ) { +outb_p (d->hststs,SMBHSTSTS ); +i801_block_finish (adap, entry, d); + }else if (entry->time_left <=0 ) { +/* Timed out */ +outb_p (d->hststs,SMBHSTSTS ); + entry->result = -EIO ; +dev_dbg (&I801_dev->dev,"PEC Timeout!\n" ); + } + } else +#endif +i801_block_finish (adap, entry, d); +} -if (temp &0x04 ) { - result =-1 ; -dev_dbg (&I801_dev->dev,"Error: no response!\n" ); +static void i801_block_next_byte (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) +{ +int smbcmd; +unsigned char errmask; + +if (d->i > d->len) { + d->wait_intr =1 ; + entry->time_left =MAX_TIMEOUT_US ; +i801_block_poll_wait_intr (adap, entry, d); +return ; } -if ((inb_p (SMBHSTSTS ) &0x1f ) !=0x00 ) -outb_p (inb (SMBHSTSTS ),SMBHSTSTS ); +if (d->i == d->len && entry->smbus.read_write ==I2C_SMBUS_READ ) + smbcmd =I801_BLOCK_LAST ; +else + smbcmd =I801_BLOCK_DATA ; +outb_p (smbcmd |ENABLE_INT9 ,SMBHSTCNT ); -if ((temp = (0x1f &inb_p (SMBHSTSTS ))) !=0x00 ) { -dev_dbg (&I801_dev->dev,"Failed reset at end of transaction" -"(%02x)\n" , temp); +dev_dbg (&I801_dev->dev,"Block (pre %d): CNT=%02x, CMD=%02x, " +"ADD=%02x, DAT0=%02x, BLKDAT=%02x\n" , d->i, +inb_p (SMBHSTCNT ),inb_p (SMBHSTCMD ),inb_p (SMBHSTADD ), +inb_p (SMBHSTDAT0 ),inb_p (SMBBLKDAT )); + +/* Make sure the SMBus host is ready to start transmitting */ + d->hststs =inb_p (SMBHSTSTS ); +if (d->i ==1 ) { +/* Erronenous conditions before transaction: +* Byte_Done, Failed, Bus_Err, Dev_Err, Intr, Host_Busy */ + errmask=0x9f ; + }else { +/* Erronenous conditions during transaction: +* Failed, Bus_Err, Dev_Err, Intr */ + errmask=0x1e ; + } +if (d->hststs & errmask) { +dev_dbg (&I801_dev->dev,"SMBus busy (%02x). " +"Resetting... \n" , d->hststs); +outb_p (d->hststs,SMBHSTSTS ); +if (((d->hststs =inb_p (SMBHSTSTS )) & errmask) !=0x00 ) { +dev_err (&I801_dev->dev, +"Reset failed! (%02x)\n" , d->hststs); + entry->result = -EIO ; +return ; + } +if (d->i !=1 ) { +/* if die in middle of block transaction, fail */ + entry->result = -EIO ; +return ; + } } -dev_dbg (&I801_dev->dev,"Transaction (post): CNT=%02x, CMD=%02x, " -"ADD=%02x, DAT0=%02x, DAT1=%02x\n" ,inb_p (SMBHSTCNT ), -inb_p (SMBHSTCMD ),inb_p (SMBHSTADD ),inb_p (SMBHSTDAT0 ), -inb_p (SMBHSTDAT1 )); -return result; + +if (d->i ==1 ) +outb_p (inb (SMBHSTCNT ) |I801_START ,SMBHSTCNT ); } -/* All-inclusive block transaction function */ -static int i801_block_transaction (union i2c_smbus_data *data,char read_write, -int command) +/* Called after a timeout. This checks the result of the +transaction */ +static void i801_block_poll (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) { -int i, len; -int smbcmd; -int temp; -int result =0 ; -int timeout; -unsigned char hostc, errmask; + d->hststs =inb_p (SMBHSTSTS ); +if (!(d->hststs &0x80 )) { +/* Not ready yet */ +if (entry->time_left <=0 ) { +dev_dbg (&I801_dev->dev,"SMBus Timeout!\n" ); + entry->result = -EIO ; + } +return ; + } -if (command ==I2C_SMBUS_I2C_BLOCK_DATA ) { -if (read_write ==I2C_SMBUS_WRITE ) { +i801_check_hststs (adap, entry, d); +if (entry->result) +return ; + +if (d->i ==1 && entry->smbus.read_write ==I2C_SMBUS_READ ) { + d->len =inb_p (SMBHSTDAT0 ); +if (d->len <1 ) + d->len =1 ; +if (d->len >32 ) + d->len =32 ; + entry->smbus.data->block[0 ] = d->len; + } + +/* Retrieve/store value in SMBBLKDAT */ +if (entry->smbus.read_write ==I2C_SMBUS_READ ) + entry->smbus.data->block[d->i] =inb_p (SMBBLKDAT ); +if (entry->smbus.read_write ==I2C_SMBUS_WRITE && d->i+1 <= d->len) +outb_p (entry->smbus.data->block[d->i+1 ],SMBBLKDAT ); +if ((d->hststs &0x9e ) !=0x00 ) +outb_p (d->hststs,SMBHSTSTS );/* signals SMBBLKDAT ready */ + +if ((d->hststs = (0x1e &inb_p (SMBHSTSTS ))) !=0x00 ) { +dev_dbg (&I801_dev->dev, +"Bad status (%02x) at end of transaction\n" , + d->hststs); + } +dev_dbg (&I801_dev->dev,"Block (post %d): CNT=%02x, CMD=%02x, " +"ADD=%02x, DAT0=%02x, BLKDAT=%02x\n" , d->i, +inb_p (SMBHSTCNT ),inb_p (SMBHSTCMD ),inb_p (SMBHSTADD ), +inb_p (SMBHSTDAT0 ),inb_p (SMBBLKDAT )); + + d->i++; +i801_block_next_byte (adap, entry, d); +} + +static void i801_block_start (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +struct i801_i2c_data *d) +{ +if (entry->smbus.command ==I2C_SMBUS_I2C_BLOCK_DATA ) { +if (entry->smbus.read_write ==I2C_SMBUS_WRITE ) {/* set I2C_EN bit in configuration register */ -pci_read_config_byte (I801_dev,SMBHSTCFG , &hostc); +pci_read_config_byte (I801_dev,SMBHSTCFG , &d->hostc);pci_write_config_byte (I801_dev,SMBHSTCFG , - hostc |SMBHSTCFG_I2C_EN ); + d->hostc |SMBHSTCFG_I2C_EN ); }else {dev_err (&I801_dev->dev,"I2C_SMBUS_I2C_BLOCK_READ not DB!\n" ); -return -1 ; + entry->result = -EINVAL ; +return ; } } -if (read_write ==I2C_SMBUS_WRITE ) { - len = data->block[0 ]; -if (len <1 ) - len =1 ; -if (len >32 ) - len =32 ; -outb_p (len,SMBHSTDAT0 ); -outb_p (data->block[1 ],SMBBLKDAT ); +if (entry->smbus.read_write ==I2C_SMBUS_WRITE ) { + d->len = entry->smbus.data->block[0 ]; +if (d->len <1 ) + d->len =1 ; +if (d->len >32 ) + d->len =32 ; +outb_p (d->len,SMBHSTDAT0 ); +outb_p (entry->smbus.data->block[1 ],SMBBLKDAT ); }else { - len =32 ;/* max for reads */ + d->len =32 ;/* max for reads */ } -if (isich4 && command !=I2C_SMBUS_I2C_BLOCK_DATA ) { +if (isich4 && entry->smbus.command !=I2C_SMBUS_I2C_BLOCK_DATA ) {/* set 32 byte buffer */ } -for (i =1 ; i <= len; i++) { -if (i == len && read_write ==I2C_SMBUS_READ ) - smbcmd =I801_BLOCK_LAST ; -else - smbcmd =I801_BLOCK_DATA ; -outb_p (smbcmd |ENABLE_INT9 ,SMBHSTCNT ); - -dev_dbg (&I801_dev->dev,"Block (pre %d): CNT=%02x, CMD=%02x, " -"ADD=%02x, DAT0=%02x, BLKDAT=%02x\n" , i, -inb_p (SMBHSTCNT ),inb_p (SMBHSTCMD ),inb_p (SMBHSTADD ), -inb_p (SMBHSTDAT0 ),inb_p (SMBBLKDAT )); - -/* Make sure the SMBus host is ready to start transmitting */ - temp =inb_p (SMBHSTSTS ); -if (i ==1 ) { -/* Erronenous conditions before transaction: -* Byte_Done, Failed, Bus_Err, Dev_Err, Intr, Host_Busy */ - errmask=0x9f ; - }else { -/* Erronenous conditions during transaction: -* Failed, Bus_Err, Dev_Err, Intr */ - errmask=0x1e ; - } -if (temp & errmask) { -dev_dbg (&I801_dev->dev,"SMBus busy (%02x). " -"Resetting... \n" , temp); -outb_p (temp,SMBHSTSTS ); -if (((temp =inb_p (SMBHSTSTS )) & errmask) !=0x00 ) { -dev_err (&I801_dev->dev, -"Reset failed! (%02x)\n" , temp); - result =-1 ; - goto END; - } -if (i !=1 ) { -/* if die in middle of block transaction, fail */ - result =-1 ; - goto END; - } - } + d->i =1 ; +i801_block_next_byte (adap, entry, d); +} -if (i ==1 ) -outb_p (inb (SMBHSTCNT ) |I801_START ,SMBHSTCNT ); +/* General poll routine. Called periodically by the i2c code. */ +static void i801_poll (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry, +unsigned int us_since_last_poll) +{ +struct i801_i2c_data *d = entry->data; -/* We will always wait for a fraction of a second! */ - timeout =0 ; -do { - temp =inb_p (SMBHSTSTS ); -msleep (1 ); - } -while ((!(temp &0x80 )) - && (timeout++ <MAX_TIMEOUT )); +dev_dbg (&I801_dev->dev,"Poll call for %p %p at %ld\n" , adap, entry, + jiffies); -/* If the SMBus is still busy, we give up */ -if (timeout >=MAX_TIMEOUT ) { - result =-1 ; -dev_dbg (&I801_dev->dev,"SMBus Timeout!\n" ); - } +if (!d) +/* The entry hasn't been started yet. */ +return ; -if (temp &0x10 ) { - result =-1 ; -dev_dbg (&I801_dev->dev, -"Error: Failed bus transaction\n" ); - }else if (temp &0x08 ) { - result =-1 ; -dev_err (&I801_dev->dev,"Bus collision!\n" ); - }else if (temp &0x04 ) { - result =-1 ; -dev_dbg (&I801_dev->dev,"Error: no response!\n" ); - } +/* Decrement timeout */ + entry->time_left -= us_since_last_poll; -if (i ==1 && read_write ==I2C_SMBUS_READ ) { - len =inb_p (SMBHSTDAT0 ); -if (len <1 ) - len =1 ; -if (len >32 ) - len =32 ; - data->block[0 ] = len; - } +/* Wait a jiffie normally. */ + entry->call_again_us =RETRY_TIME_US ; -/* Retrieve/store value in SMBBLKDAT */ -if (read_write ==I2C_SMBUS_READ ) - data->block[i] =inb_p (SMBBLKDAT ); -if (read_write ==I2C_SMBUS_WRITE && i+1 <= len) -outb_p (data->block[i+1 ],SMBBLKDAT ); -if ((temp &0x9e ) !=0x00 ) -outb_p (temp,SMBHSTSTS );/* signals SMBBLKDAT ready */ - -if ((temp = (0x1e &inb_p (SMBHSTSTS ))) !=0x00 ) { -dev_dbg (&I801_dev->dev, -"Bad status (%02x) at end of transaction\n" , - temp); +if (d->block) { +if (d->wait_intr) { +i801_block_poll_wait_intr (adap, entry, d); + }else { +i801_block_poll (adap, entry, d); } -dev_dbg (&I801_dev->dev,"Block (post %d): CNT=%02x, CMD=%02x, " -"ADD=%02x, DAT0=%02x, BLKDAT=%02x\n" , i, -inb_p (SMBHSTCNT ),inb_p (SMBHSTCMD ),inb_p (SMBHSTADD ), -inb_p (SMBHSTDAT0 ),inb_p (SMBBLKDAT )); - -if (result <0 ) - goto END; +if (entry->result <0 ) +/* Error, finish the transaction */ +i801_block_finish (adap, entry, d); + }else { +i801_transaction_poll (adap, entry, d); +if (entry->result <0 ) +/* Error, finish the transaction */ +i801_finish (adap, entry, d); } -#ifdef HAVE_PEC -if (isich4 && command ==I2C_SMBUS_BLOCK_DATA_PEC ) { -/* wait for INTR bit as advised by Intel */ - timeout =0 ; -do { - temp =inb_p (SMBHSTSTS ); -msleep (1 ); - }while ((!(temp &0x02 )) - && (timeout++ <MAX_TIMEOUT )); - -if (timeout >=MAX_TIMEOUT ) { -dev_dbg (&I801_dev->dev,"PEC Timeout!\n" ); - } -outb_p (temp,SMBHSTSTS ); - } -#endif - result =0 ; -END: -if (command ==I2C_SMBUS_I2C_BLOCK_DATA ) { -/* restore saved configuration register value */ -pci_write_config_byte (I801_dev,SMBHSTCFG , hostc); - } -return result; +if (d->finished) +i2c_op_done (adap, entry); } -/* Return -1 on error. */ -static s32 i801_access (struct i2c_adapter * adap,u16 addr, -unsigned short flags,char read_write,u8 command, -int size,union i2c_smbus_data * data) +/* Start a general SMBUS transaction on the i801. Figure out what +kind of transaction it is, set it up, and start it. */ +static void i801_start (struct i2c_adapter *adap, +struct i2c_op_q_entry *entry) { -int hwpec =0 ; -int block =0 ; -int ret, xact =0 ; +struct i801_i2c_data *d = adap->algo_data; + + d->block =0 ; + d->hwpec =0 ; + d->xact =0 ; + d->wait_intr =0 ; + d->finished =0 ;#ifdef HAVE_PEC -if (isich4) - hwpec = (flags &I2C_CLIENT_PEC ) !=0 ; +if (isich4) + d->hwpec = (entry->smbus.flags &I2C_CLIENT_PEC ) !=0 ;#endif -switch (size) { +switch (entry->smbus.size) {case I2C_SMBUS_QUICK : -outb_p (((addr &0x7f ) <<1 ) | (read_write &0x01 ), +outb_p (((entry->smbus.addr &0x7f ) <<1 ) + | (entry->smbus.read_write &0x01 ),SMBHSTADD ); - xact =I801_QUICK ; + d->xact =I801_QUICK ;break ;case I2C_SMBUS_BYTE : -outb_p (((addr &0x7f ) <<1 ) | (read_write &0x01 ), +outb_p (((entry->smbus.addr &0x7f ) <<1 ) + | (entry->smbus.read_write &0x01 ),SMBHSTADD ); -if (read_write ==I2C_SMBUS_WRITE ) -outb_p (command,SMBHSTCMD ); - xact =I801_BYTE ; +if (entry->smbus.read_write ==I2C_SMBUS_WRITE ) +outb_p (entry->smbus.command,SMBHSTCMD ); + d->xact =I801_BYTE ;break ;case I2C_SMBUS_BYTE_DATA : -outb_p (((addr &0x7f ) <<1 ) | (read_write &0x01 ), +outb_p (((entry->smbus.addr &0x7f ) <<1 ) + | (entry->smbus.read_write &0x01 ),SMBHSTADD ); -outb_p (command,SMBHSTCMD ); -if (read_write ==I2C_SMBUS_WRITE ) -outb_p (data->byte,SMBHSTDAT0 ); - xact =I801_BYTE_DATA ; +outb_p (entry->smbus.command,SMBHSTCMD ); +if (entry->smbus.read_write ==I2C_SMBUS_WRITE ) +outb_p (entry->smbus.data->byte,SMBHSTDAT0 ); + d->xact =I801_BYTE_DATA ;break ;case I2C_SMBUS_WORD_DATA : -outb_p (((addr &0x7f ) <<1 ) | (read_write &0x01 ), +outb_p (((entry->smbus.addr &0x7f ) <<1 ) + | (entry->smbus.read_write &0x01 ),SMBHSTADD ); -outb_p (command,SMBHSTCMD ); -if (read_write ==I2C_SMBUS_WRITE ) { -outb_p (data->word &0xff ,SMBHSTDAT0 ); -outb_p ((data->word &0xff00 ) >>8 ,SMBHSTDAT1 ); +outb_p (entry->smbus.command,SMBHSTCMD ); +if (entry->smbus.read_write ==I2C_SMBUS_WRITE ) { +outb_p (entry->smbus.data->word &0xff ,SMBHSTDAT0 ); +outb_p ((entry->smbus.data->word &0xff00 ) >>8 , +SMBHSTDAT1 ); } - xact =I801_WORD_DATA ; + d->xact =I801_WORD_DATA ;break ;case I2C_SMBUS_BLOCK_DATA :case I2C_SMBUS_I2C_BLOCK_DATA :#ifdef HAVE_PEC case I2C_SMBUS_BLOCK_DATA_PEC : -if (hwpec && size ==I2C_SMBUS_BLOCK_DATA ) - size =I2C_SMBUS_BLOCK_DATA_PEC ; +if (d->hwpec && entry->smbus.size ==I2C_SMBUS_BLOCK_DATA ) + entry->smbus.size =I2C_SMBUS_BLOCK_DATA_PEC ;#endif -outb_p (((addr &0x7f ) <<1 ) | (read_write &0x01 ), +outb_p (((entry->smbus.addr &0x7f ) <<1 ) + | (entry->smbus.read_write &0x01 ),SMBHSTADD ); -outb_p (command,SMBHSTCMD ); - block =1 ; +outb_p (entry->smbus.command,SMBHSTCMD ); + d->block =1 ;break ;case I2C_SMBUS_PROC_CALL :default : -dev_err (&I801_dev->dev,"Unsupported transaction %d\n" , size); -return -1 ; +dev_err (&I801_dev->dev,"Unsupported transaction %d\n" , + entry->smbus.size); + entry->result = -EINVAL ; +return ; }#ifdef HAVE_PEC -if (isich4 && hwpec) { -if (size !=I2C_SMBUS_QUICK && - size !=I2C_SMBUS_I2C_BLOCK_DATA ) +if (isich4 && d->hwpec) { +if (entry->smbus.size !=I2C_SMBUS_QUICK && + entry->smbus.size !=I2C_SMBUS_I2C_BLOCK_DATA )outb_p (1 ,SMBAUXCTL );/* enable HW PEC */ }#endif -if (block) - ret =i801_block_transaction (data, read_write, size); -else { -outb_p (xact |ENABLE_INT9 ,SMBHSTCNT ); - ret =i801_transaction (); - } - -#ifdef HAVE_PEC -if (isich4 && hwpec) { -if (size !=I2C_SMBUS_QUICK && - size !=I2C_SMBUS_I2C_BLOCK_DATA ) -outb_p (0 ,SMBAUXCTL ); +if (d->block) { +i801_block_start (adap, entry, d); +if (entry->result <0 ) +/* Error, finish the transaction */ +i801_block_finish (adap, entry, d); + }else { +outb_p (d->xact |ENABLE_INT9 ,SMBHSTCNT ); +i801_transaction_start (adap, entry, d); +if (entry->result <0 ) +/* Error, finish the transaction */ +i801_finish (adap, entry, d); } -#endif -if (block) -return ret; -if (ret) -return -1 ; -if ((read_write ==I2C_SMBUS_WRITE ) || (xact ==I801_QUICK )) -return 0 ; + entry->call_again_us =RETRY_TIME_US ; + entry->time_left =MAX_TIMEOUT_US ; -switch (xact &0x7f ) { -case I801_BYTE :/* Result put in SMBHSTDAT0 */ -case I801_BYTE_DATA : - data->byte =inb_p (SMBHSTDAT0 ); -break ; -case I801_WORD_DATA : - data->word =inb_p (SMBHSTDAT0 ) + (inb_p (SMBHSTDAT1 ) <<8 ); -break ; - } -return 0 ; +if (d->finished) +i2c_op_done (adap, entry); +else + entry->data = d; }
@@ -537,7 +645,8 @@static struct i2c_algorithm smbus_algorithm = { .name ="Non-I2C SMBus adapter" , .id =I2C_ALGO_SMBUS , - .smbus_xfer = i801_access, + .smbus_start = i801_start, + .poll = i801_poll, .functionality = i801_func, };
@@ -545,6 +654,7 @@ .owner = THIS_MODULE, .class = I2C_CLASS_HWMON, .algo = &smbus_algorithm, + .algo_data = &i801_data, .name = "unset", };
@@ -563,7 +673,8 @@MODULE_DEVICE_TABLE (pci, i801_ids); -static int __devinit i801_probe (struct pci_dev *dev,const struct pci_device_id *id) +static int __devinit i801_probe (struct pci_dev *dev, +const struct pci_device_id *id) {if (i801_setup (dev)) {