Skip to contentSkip to conversation

Add a non-blocking interface to the I2C code, part 5

PATCH New Corey Minyard 1 participant 1 message Mar 2, 2005 I2C

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Add a non-blocking interface to the I2C code, part 5

See part 1 for details on what this does...

This patch adds a non-blocking interface to the I2C code. This is needed by some RTC drivers on the I2C bus and is needed by the IPMI code so it can do things at panic time.

The non-blocking interface requires changes to the driver below it. The current drivers will work as-is, but will not be able to use the non-blocking interfaces.

drivers/i2c/i2c-core.c +157 -4
Index: linux-2.6.11-rc5-mm1/drivers/i2c/i2c-core.c
===================================================================
@@ -680,7 +680,8 @@
 		return;
 
 	if (sequence_match) {
-		/* Poll will go here. */
+		/* This is the one we expected, call the poll routine. */
+		adap->algo->poll(adap, entry, t->next_call_time);
 
 		if (!entry_completed(entry))
 			i2c_start_timer(adap, entry);
@@ -697,13 +698,102 @@
  * ----------------------------------------------------
  */
 
+/* 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) && entry->use_timer)
+		i2c_start_timer(adap, entry);
+}
+
+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;
+	atomic_set(&entry->completed, 1);
+	entry->result = 0;
+	entry->use_timer = 0; /* We poll it directly. */
+	entry->data = NULL;
+	kref_init(&entry->usecount);
+	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;
+			atomic_set(&entry->completed, 0);
+			list_del(&entry->link);
+		}
+		spin_unlock_irqrestore(&adap->q_lock, flags);
+	}
+
+	/* Once the operation is started, we will not
+	   interrupt it. */
+	while (!entry_completed(entry)) {
+		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(entry))
+			break;
+		algo->poll(adap, entry, timeout * USEC_PER_JIFFIE);
+	}
+}
+
 static void i2c_transfer_entry(struct i2c_adapter * adap,
 			       struct i2c_op_q_entry * entry)
 {
 	entry->xfer_type = I2C_OP_I2C;
 	entry->complete = NULL;
 	kref_init(&entry->usecount);
-	if (adap->algo->master_xfer) {
+	if (adap->algo->master_start) {
+		i2c_perform_op_wait(adap, entry);
+	} else if (adap->algo->master_xfer) {
  	 	dev_dbg(&adap->dev, "master_xfer: with %d msgs.\n",
 			entry->i2c.num);
 
@@ -1412,7 +1502,9 @@
 
 	i2c_smbus_format_entry(adap, entry);
 
-	if (algo->smbus_xfer) {
+	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,
@@ -1436,6 +1528,63 @@
 	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;
+	atomic_set(&entry->completed, 1);
+	entry->result = 0;
+	entry->use_timer = 1; /* Let the timer code poll it. */
+	entry->data = NULL;
+	kref_init(&entry->usecount);
+
+	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. */ 
 u32 i2c_get_functionality (struct i2c_adapter *adap)
@@ -1513,7 +1662,7 @@
 		entry->handler(entry);
 
 		if (new_entry) {
-			/* start entry will go here. */
+			i2c_start_entry(adap, new_entry);
 			if (new_entry->start)
 				complete(new_entry->start);
 			/* Do tail recursion ourself. */
@@ -1584,6 +1733,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 +42 -0

54 lines not on this page · raw message