[i2c] [patch 2.6.23-rc6] SMBus alert support
David Brownell
david-b at pacbell.net
Fri Sep 14 19:42:51 CEST 2007
Infrastructure supporting optional SMBALERT# interrupts and the related
SMBus protocols.
- The i2c_adapter now includes a work_struct doing the work of talking
to the Alert Response Address until nobody responds any more (and
hence the IRQ is no longer asserted). Handles seven-bit addresses,
and ignores PEC; ten-bit addresess currently seem pointless.
- Some of the ways that work_struct could be driven:
* Adapter driver provides an IRQ, which is bound to a handler
which schedules that work_struct (using keventd for now).
This IRQ driven code is always enabled, if it's available.
* Adapter driver asks the core to set it up by setting a flag,
i2c_adapter.do_setup_alert, and scheduling that work itself.
SMBALERT# could be a subcase of the adapter's normal interrupt
handler. (Or, when not all of the IRQ signals are properly
wired up, some board need timer based polling.)
- The "i2c-gpio" driver now handles an optional named resource for
that SMBus alert signal. "Named" since, when this is substituted
for a misbehaving "native" driver, positional ids should be left
alone. (It might be better to put this logic into i2c core, to
apply whenever the i2c_adapter.dev.parent is a platform device.)
- There's a new driver method used to report that a given device has
issued an alert. Its parameter includes the one bit of information
provided by the device in its alert response message.
Tested with a single alerting device, hence no concurrent alerts.
Signed-off-by: David Brownell <dbrownell at users.sourceforge.net>
---
NOTE: for some reason i2c-gpio issues *SEVEN* retries when no client
responds at the Alert Response Address. Even one would seem to be
excessive; but it defaults to three retries. However, I don't see
where the extra four retries come from ...
drivers/i2c/busses/i2c-gpio.c | 6 +
drivers/i2c/i2c-core.c | 136 +++++++++++++++++++++++++++++++++++++++++-
include/linux/i2c.h | 13 ++++
3 files changed, 154 insertions(+), 1 deletion(-)
--- a/include/linux/i2c.h 2007-09-14 09:26:51.000000000 -0700
+++ b/include/linux/i2c.h 2007-09-14 09:33:34.000000000 -0700
@@ -34,6 +34,7 @@
#include <linux/device.h> /* for struct device */
#include <linux/sched.h> /* for completion */
#include <linux/mutex.h>
+#include <linux/workqueue.h>
extern struct bus_type i2c_bus_type;
@@ -136,6 +137,11 @@ struct i2c_driver {
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
+ /* SMBus alert protocol support; the low bit of the code sometimes
+ * passes event data (e.g. exceeding upper vs lower limit).
+ */
+ void (*alert)(struct i2c_client *, bool flag);
+
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
@@ -328,6 +334,13 @@ struct i2c_adapter {
struct list_head list;
char name[48];
struct completion dev_released;
+
+ /* SMBALERT# support */
+ unsigned do_setup_alert:1;
+ unsigned is_alert_irq:1;
+ int irq;
+ struct i2c_client *ara;
+ struct work_struct alert;
};
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
--- a/drivers/i2c/i2c-core.c 2007-09-14 09:26:51.000000000 -0700
+++ b/drivers/i2c/i2c-core.c 2007-09-14 10:31:28.000000000 -0700
@@ -33,6 +33,8 @@
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/completion.h>
+#include <linux/interrupt.h>
+
#include <asm/uaccess.h>
#include "i2c-core.h"
@@ -323,6 +325,93 @@ static void i2c_scan_static_board_info(s
mutex_unlock(&__i2c_board_lock);
}
+/*
+ * The IRQ handler needs to hand work off to a task which can issue SMBus
+ * calls, because those sleeping calls can't be made in IRQ context.
+ */
+static void smbus_alert(struct work_struct *work)
+{
+ struct i2c_adapter *bus;
+
+ bus = container_of(work, struct i2c_adapter, alert);
+ for (;;) {
+ s32 status;
+ unsigned short addr;
+ struct i2c_client *client;
+ bool flag;
+
+ /* Devices with pending alerts reply in address order, low
+ * to high, because of arbitration. After responding, an
+ * SMBus device stops asserting SMBALERT# ... so we can
+ * re-enable the IRQ as soon as read_byte() gets no reply.
+ *
+ * NOTE that SMBus 2.0 reserves 10-bit addresess for future
+ * use. We neither handle them, nor try to use PEC here.
+ */
+ status = i2c_smbus_read_byte(bus->ara);
+ if (status < 0)
+ break;
+ flag = status & 1;
+ addr = status >> 1;
+
+ dev_dbg(&bus->dev, "SMBALERT# %d from dev %d\n", flag, addr);
+
+ /* Notify any driver for the device which issued the alert.
+ * The locking ensures it won't disappear while we do that.
+ */
+ mutex_lock(&core_lists);
+ list_for_each_entry(client, &bus->clients, list) {
+ if (client->addr != addr)
+ continue;
+ if (client->flags & I2C_CLIENT_TEN)
+ continue;
+ if (!client->driver)
+ break;
+
+ /* Drivers should either disable alerts or provide
+ * at least a minimal handler.
+ *
+ * REVISIT: drop lock while we call alert().
+ */
+ if (client->driver->alert)
+ client->driver->alert(client, flag);
+ else
+ dev_warn(&client->dev, "no driver alert()!\n");
+ break;
+ }
+ mutex_unlock(&core_lists);
+ }
+
+ enable_irq(bus->irq);
+}
+
+static irqreturn_t smbus_irq(int irq, void *adap)
+{
+ struct i2c_adapter *bus = adap;
+
+ disable_irq_nosync(irq);
+ schedule_work(&bus->alert);
+ return IRQ_HANDLED;
+}
+
+static int smbalert_nop(struct i2c_client *c)
+{
+ return 0;
+}
+
+static struct i2c_driver smbalert_driver = {
+ .driver = {
+ .name = "smbus_alert",
+ .owner = THIS_MODULE,
+ },
+ .probe = smbalert_nop,
+ .remove = smbalert_nop,
+};
+
+static const struct i2c_board_info ara_board_info = {
+ I2C_BOARD_INFO("smbus_alert", 0x0c),
+};
+
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0;
@@ -333,6 +422,9 @@ static int i2c_register_adapter(struct i
mutex_init(&adap->clist_lock);
INIT_LIST_HEAD(&adap->clients);
+ /* Setup SMBALERT# infrastructure. */
+ INIT_WORK(&adap->alert, smbus_alert);
+
mutex_lock(&core_lists);
list_add_tail(&adap->list, &adapters);
@@ -352,6 +444,26 @@ static int i2c_register_adapter(struct i
if (res)
goto out_list;
+ /* If we'll be handling SMBus alerts, register the alert responder
+ * address so that nobody else can accidentally claim it.
+ * Handling can be done either through our IRQ handler, or by the
+ * adapter (from its handler, periodic polling, or whatever).
+ */
+ if (adap->irq > 0 || adap->do_setup_alert)
+ adap->ara = i2c_new_device(adap, &ara_board_info);
+
+ if (adap->irq > 0 && adap->ara) {
+ /* Platform setup probably made this IRQF_TRIGGER_LOW */
+ res = devm_request_irq(&adap->dev, adap->irq, smbus_irq,
+ IRQF_SHARED, "smbus_alert", adap);
+ if (res == 0) {
+ dev_dbg(&adap->dev, "supports SMBALERT#\n");
+ adap->is_alert_irq = 1;
+ } else
+ res = 0;
+
+ }
+
dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
/* create pre-declared device nodes for new-style drivers */
@@ -529,6 +641,12 @@ int i2c_del_adapter(struct i2c_adapter *
}
}
+ if (adap->is_alert_irq) {
+ devm_free_irq(&adap->dev, adap->irq, adap);
+ adap->is_alert_irq = 0;
+ }
+ cancel_work_sync(&adap->alert);
+
/* clean up the sysfs representation */
init_completion(&adap->dev_released);
device_unregister(&adap->dev);
@@ -852,12 +970,28 @@ static int __init i2c_init(void)
retval = bus_register(&i2c_bus_type);
if (retval)
return retval;
- return class_register(&i2c_adapter_class);
+
+ retval = i2c_add_driver(&smbalert_driver);
+ if (retval)
+ goto alert_err;
+
+ retval = class_register(&i2c_adapter_class);
+ if (retval)
+ goto class_err;
+
+ return 0;
+
+class_err:
+ i2c_del_driver(&smbalert_driver);
+alert_err:
+ bus_unregister(&i2c_bus_type);
+ return retval;
}
static void __exit i2c_exit(void)
{
class_unregister(&i2c_adapter_class);
+ i2c_del_driver(&smbalert_driver);
bus_unregister(&i2c_bus_type);
}
--- a/drivers/i2c/busses/i2c-gpio.c 2007-09-14 09:02:56.000000000 -0700
+++ b/drivers/i2c/busses/i2c-gpio.c 2007-09-14 09:31:25.000000000 -0700
@@ -82,6 +82,7 @@ static int __init i2c_gpio_probe(struct
struct i2c_gpio_platform_data *pdata;
struct i2c_algo_bit_data *bit_data;
struct i2c_adapter *adap;
+ struct resource *smbalert;
int ret;
pdata = pdev->dev.platform_data;
@@ -142,6 +143,11 @@ static int __init i2c_gpio_probe(struct
adap->algo_data = bit_data;
adap->dev.parent = &pdev->dev;
+ smbalert = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "smbalert#");
+ if (smbalert)
+ adap->irq = smbalert->start;
+
/*
* If "dev->id" is negative we consider it as zero.
* The reason to do so is to avoid sysfs names that only make
More information about the i2c
mailing list