diff options
-rw-r--r-- | Documentation/i2c/smbus-protocol | 16 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 2 | ||||
-rw-r--r-- | drivers/i2c/i2c-smbus.c | 263 | ||||
-rw-r--r-- | include/linux/i2c-smbus.h | 50 | ||||
-rw-r--r-- | include/linux/i2c.h | 7 |
5 files changed, 337 insertions, 1 deletions
diff --git a/Documentation/i2c/smbus-protocol b/Documentation/i2c/smbus-protocol index 9df4744..7c19d1a 100644 --- a/Documentation/i2c/smbus-protocol +++ b/Documentation/i2c/smbus-protocol @@ -185,6 +185,22 @@ the protocol. All ARP communications use slave address 0x61 and require PEC checksums. +SMBus Alert +=========== + +SMBus Alert was introduced in Revision 1.0 of the specification. + +The SMBus alert protocol allows several SMBus slave devices to share a +single interrupt pin on the SMBus master, while still allowing the master +to know which slave triggered the interrupt. + +This is implemented the following way in the Linux kernel: +* I2C bus drivers which support SMBus alert should call + i2c_setup_smbus_alert() to setup SMBus alert support. +* I2C drivers for devices which can trigger SMBus alerts should implement + the optional alert() callback. + + I2C Block Transactions ====================== diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index ba26e6c..7111c93 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -3,7 +3,7 @@ # obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o -obj-$(CONFIG_I2C) += i2c-core.o +obj-$(CONFIG_I2C) += i2c-core.o i2c-smbus.o obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o obj-y += busses/ chips/ algos/ diff --git a/drivers/i2c/i2c-smbus.c b/drivers/i2c/i2c-smbus.c new file mode 100644 index 0000000..4212782 --- /dev/null +++ b/drivers/i2c/i2c-smbus.c @@ -0,0 +1,263 @@ +/* + * i2c-smbus.c - SMBus extensions to the I2C protocol + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2010 Jean Delvare <khali@linux-fr.org> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/semaphore.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/i2c.h> +#include <linux/i2c-smbus.h> + +struct i2c_smbus_alert { + unsigned int alert_edge_triggered:1; + int irq; + struct work_struct alert; + struct i2c_client *ara; /* Alert response address */ +}; + +struct alert_data { + unsigned short addr; + u8 flag:1; +}; + +/* If this is the alerting device, notify its driver */ +static int smbus_do_alert(struct device *dev, void *addrp) +{ + struct i2c_client *client = i2c_verify_client(dev); + struct alert_data *data = addrp; + + if (!client || client->addr != data->addr) + return 0; + if (client->flags & I2C_CLIENT_TEN) + return 0; + + /* + * Drivers should either disable alerts, or provide at least + * a minimal handler. Lock so client->driver won't change. + */ + down(&dev->sem); + if (client->driver) { + if (client->driver->alert) + client->driver->alert(client, data->flag); + else + dev_warn(&client->dev, "no driver alert()!\n"); + } else + dev_dbg(&client->dev, "alert with no driver\n"); + up(&dev->sem); + + /* Stop iterating after we find the device */ + return -EBUSY; +} + +/* + * The alert 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_smbus_alert *alert; + struct i2c_client *ara; + unsigned short prev_addr = 0; /* Not a valid address */ + + alert = container_of(work, struct i2c_smbus_alert, alert); + ara = alert->ara; + + for (;;) { + s32 status; + struct alert_data data; + + /* + * Devices with pending alerts reply in address order, low + * to high, because of slave transmit arbitration. After + * responding, an SMBus device stops asserting SMBALERT#. + * + * 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(ara); + if (status < 0) + break; + + data.flag = status & 1; + data.addr = status >> 1; + + if (data.addr == prev_addr) { + dev_warn(&ara->dev, "Duplicate SMBALERT# from dev " + "0x%02x, skipping\n", data.addr); + break; + } + dev_dbg(&ara->dev, "SMBALERT# from dev 0x%02x, flag %d\n", + data.addr, data.flag); + + /* Notify driver for the device which issued the alert */ + device_for_each_child(&ara->adapter->dev, &data, + smbus_do_alert); + prev_addr = data.addr; + } + + /* We handled all alerts; re-enable level-triggered IRQs */ + if (!alert->alert_edge_triggered) + enable_irq(alert->irq); +} + +static irqreturn_t smbalert_irq(int irq, void *d) +{ + struct i2c_smbus_alert *alert = d; + + /* Disable level-triggered IRQs until we handle them */ + if (!alert->alert_edge_triggered) + disable_irq_nosync(irq); + + schedule_work(&alert->alert); + return IRQ_HANDLED; +} + +/* Setup SMBALERT# infrastructure */ +static int smbalert_probe(struct i2c_client *ara, + const struct i2c_device_id *id) +{ + struct i2c_smbus_alert_setup *setup = ara->dev.platform_data; + struct i2c_smbus_alert *alert; + struct i2c_adapter *adapter = ara->adapter; + int res; + + alert = kzalloc(sizeof(struct i2c_smbus_alert), GFP_KERNEL); + if (!alert) + return -ENOMEM; + + alert->alert_edge_triggered = setup->alert_edge_triggered; + alert->irq = setup->irq; + INIT_WORK(&alert->alert, smbus_alert); + alert->ara = ara; + + if (setup->irq > 0) { + res = devm_request_irq(&ara->dev, setup->irq, smbalert_irq, + 0, "smbus_alert", alert); + if (res) { + kfree(alert); + return res; + } + } + + i2c_set_clientdata(ara, alert); + dev_info(&adapter->dev, "supports SMBALERT#, %s trigger\n", + setup->alert_edge_triggered ? "edge" : "level"); + + return 0; +} + +/* IRQ resource is managed so it is freed automatically */ +static int smbalert_remove(struct i2c_client *ara) +{ + struct i2c_smbus_alert *alert = i2c_get_clientdata(ara); + + cancel_work_sync(&alert->alert); + + i2c_set_clientdata(ara, NULL); + kfree(alert); + return 0; +} + +static const struct i2c_device_id smbalert_ids[] = { + { "smbus_alert", 0 }, + { /* LIST END */ } +}; +MODULE_DEVICE_TABLE(i2c, smbalert_ids); + +static struct i2c_driver smbalert_driver = { + .driver = { + .name = "smbus_alert", + }, + .probe = smbalert_probe, + .remove = smbalert_remove, + .id_table = smbalert_ids, +}; + +/** + * i2c_setup_smbus_alert - Setup SMBus alert support + * @adapter: the target adapter + * @setup: setup data for the SMBus alert handler + * Context: can sleep + * + * Setup handling of the SMBus alert protocol on a given I2C bus segment. + * + * Handling can be done either through our IRQ handler, or by the + * adapter (from its handler, periodic polling, or whatever). + * + * NOTE that if we manage the IRQ, we *MUST* know if it's level or + * edge triggered in order to hand it to the workqueue correctly. + * If triggering the alert seems to wedge the system, you probably + * should have said it's level triggered. + * + * This returns the ara client, which should be saved for later use with + * i2c_handle_smbus_alert() and ultimately i2c_unregister_device(); or NULL + * to indicate an error. + */ +struct i2c_client *i2c_setup_smbus_alert(struct i2c_adapter *adapter, + struct i2c_smbus_alert_setup *setup) +{ + struct i2c_board_info ara_board_info = { + I2C_BOARD_INFO("smbus_alert", 0x0c), + .platform_data = setup, + }; + + return i2c_new_device(adapter, &ara_board_info); +} +EXPORT_SYMBOL_GPL(i2c_setup_smbus_alert); + +/** + * i2c_handle_smbus_alert - Handle an SMBus alert + * @ara: the ARA client on the relevant adapter + * Context: can't sleep + * + * Helper function to be called from an I2C bus driver's interrupt + * handler. It will schedule the alert work, in turn calling the + * corresponding I2C device driver's alert function. + * + * It is assumed that ara is a valid i2c client previously returned by + * i2c_setup_smbus_alert(). + */ +int i2c_handle_smbus_alert(struct i2c_client *ara) +{ + struct i2c_smbus_alert *alert = i2c_get_clientdata(ara); + + return schedule_work(&alert->alert); +} +EXPORT_SYMBOL_GPL(i2c_handle_smbus_alert); + +static int __init i2c_smbus_init(void) +{ + return i2c_add_driver(&smbalert_driver); +} + +static void __exit i2c_smbus_exit(void) +{ + i2c_del_driver(&smbalert_driver); +} + +module_init(i2c_smbus_init); +module_exit(i2c_smbus_exit); + +MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("SMBus protocol extensions support"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/i2c-smbus.h b/include/linux/i2c-smbus.h new file mode 100644 index 0000000..63f57a8 --- /dev/null +++ b/include/linux/i2c-smbus.h @@ -0,0 +1,50 @@ +/* + * i2c-smbus.h - SMBus extensions to the I2C protocol + * + * Copyright (C) 2010 Jean Delvare <khali@linux-fr.org> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _LINUX_I2C_SMBUS_H +#define _LINUX_I2C_SMBUS_H + +#include <linux/i2c.h> + + +/** + * i2c_smbus_alert_setup - platform data for the smbus_alert i2c client + * @alert_edge_triggered: whether the alert interrupt is edge (1) or level (0) + * triggered + * @irq: IRQ number, if the smbus_alert driver should take care of interrupt + * handling + * + * If irq is not specified, the smbus_alert driver doesn't take care of + * interrupt handling. In that case it is up to the I2C bus driver to either + * handle the interrupts or to poll for alerts. + * + * If irq is specified then it it crucial that alert_edge_triggered is + * properly set. + */ +struct i2c_smbus_alert_setup { + unsigned int alert_edge_triggered:1; + int irq; +}; + +struct i2c_client *i2c_setup_smbus_alert(struct i2c_adapter *adapter, + struct i2c_smbus_alert_setup *setup); +int i2c_handle_smbus_alert(struct i2c_client *ara); + +#endif /* _LINUX_I2C_SMBUS_H */ diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 02fc617..476abd0 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -152,6 +152,13 @@ struct i2c_driver { int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); + /* Alert callback, for example for the SMBus alert protocol. + * The format and meaning of the data value depends on the protocol. + * For the SMBus alert protocol, there is a single bit of data passed + * as the alert response's low bit ("event flag"). + */ + void (*alert)(struct i2c_client *, unsigned int data); + /* a ioctl like command that can be used to perform specific functions * with the device. */ |