i2c: Add a sysfs interface to instantiate devices
Add a sysfs interface to instantiate and delete I2C devices. This is primarily a replacement of the force_* module parameters implemented by some i2c drivers. These module parameters were implemented internally by the I2C_CLIENT_INSMOD* macros, which don't scale well. This can also be used when developing a driver on a self-soldered board which doesn't yet have proper I2C device declaration at the platform level, and presumably for various debugging situations. Signed-off-by: Jean Delvare <khali@linux-fr.org> Cc: David Brownell <dbrownell@users.sourceforge.net>
This commit is contained in:
parent
35fc37f818
commit
99cd8e2587
3 changed files with 168 additions and 2 deletions
|
@ -165,3 +165,47 @@ was done there. Two significant differences are:
|
|||
Once again, method 3 should be avoided wherever possible. Explicit device
|
||||
instantiation (methods 1 and 2) is much preferred for it is safer and
|
||||
faster.
|
||||
|
||||
|
||||
Method 4: Instantiate from user-space
|
||||
-------------------------------------
|
||||
|
||||
In general, the kernel should know which I2C devices are connected and
|
||||
what addresses they live at. However, in certain cases, it does not, so a
|
||||
sysfs interface was added to let the user provide the information. This
|
||||
interface is made of 2 attribute files which are created in every I2C bus
|
||||
directory: new_device and delete_device. Both files are write only and you
|
||||
must write the right parameters to them in order to properly instantiate,
|
||||
respectively delete, an I2C device.
|
||||
|
||||
File new_device takes 2 parameters: the name of the I2C device (a string)
|
||||
and the address of the I2C device (a number, typically expressed in
|
||||
hexadecimal starting with 0x, but can also be expressed in decimal.)
|
||||
|
||||
File delete_device takes a single parameter: the address of the I2C
|
||||
device. As no two devices can live at the same address on a given I2C
|
||||
segment, the address is sufficient to uniquely identify the device to be
|
||||
deleted.
|
||||
|
||||
Example:
|
||||
# echo eeprom 0x50 > /sys/class/i2c-adapter/i2c-3/new_device
|
||||
|
||||
While this interface should only be used when in-kernel device declaration
|
||||
can't be done, there is a variety of cases where it can be helpful:
|
||||
* The I2C driver usually detects devices (method 3 above) but the bus
|
||||
segment your device lives on doesn't have the proper class bit set and
|
||||
thus detection doesn't trigger.
|
||||
* The I2C driver usually detects devices, but your device lives at an
|
||||
unexpected address.
|
||||
* The I2C driver usually detects devices, but your device is not detected,
|
||||
either because the detection routine is too strict, or because your
|
||||
device is not officially supported yet but you know it is compatible.
|
||||
* You are developing a driver on a test board, where you soldered the I2C
|
||||
device yourself.
|
||||
|
||||
This interface is a replacement for the force_* module parameters some I2C
|
||||
drivers implement. Being implemented in i2c-core rather than in each
|
||||
device driver individually, it is much more efficient, and also has the
|
||||
advantage that you do not have to reload the driver to change a setting.
|
||||
You can also instantiate the device before the driver is loaded or even
|
||||
available, and you don't need to know what driver the device needs.
|
||||
|
|
|
@ -38,11 +38,12 @@
|
|||
#include "i2c-core.h"
|
||||
|
||||
|
||||
/* core_lock protects i2c_adapter_idr, and guarantees
|
||||
/* core_lock protects i2c_adapter_idr, userspace_devices, and guarantees
|
||||
that device detection, deletion of detected devices, and attach_adapter
|
||||
and detach_adapter calls are serialized */
|
||||
static DEFINE_MUTEX(core_lock);
|
||||
static DEFINE_IDR(i2c_adapter_idr);
|
||||
static LIST_HEAD(userspace_devices);
|
||||
|
||||
static int i2c_check_addr(struct i2c_adapter *adapter, int addr);
|
||||
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver);
|
||||
|
@ -373,8 +374,128 @@ show_adapter_name(struct device *dev, struct device_attribute *attr, char *buf)
|
|||
return sprintf(buf, "%s\n", adap->name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Let users instantiate I2C devices through sysfs. This can be used when
|
||||
* platform initialization code doesn't contain the proper data for
|
||||
* whatever reason. Also useful for drivers that do device detection and
|
||||
* detection fails, either because the device uses an unexpected address,
|
||||
* or this is a compatible device with different ID register values.
|
||||
*
|
||||
* Parameter checking may look overzealous, but we really don't want
|
||||
* the user to provide incorrect parameters.
|
||||
*/
|
||||
static ssize_t
|
||||
i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_adapter *adap = to_i2c_adapter(dev);
|
||||
struct i2c_board_info info;
|
||||
struct i2c_client *client;
|
||||
char *blank, end;
|
||||
int res;
|
||||
|
||||
dev_warn(dev, "The new_device interface is still experimental "
|
||||
"and may change in a near future\n");
|
||||
memset(&info, 0, sizeof(struct i2c_board_info));
|
||||
|
||||
blank = strchr(buf, ' ');
|
||||
if (!blank) {
|
||||
dev_err(dev, "%s: Missing parameters\n", "new_device");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (blank - buf > I2C_NAME_SIZE - 1) {
|
||||
dev_err(dev, "%s: Invalid device name\n", "new_device");
|
||||
return -EINVAL;
|
||||
}
|
||||
memcpy(info.type, buf, blank - buf);
|
||||
|
||||
/* Parse remaining parameters, reject extra parameters */
|
||||
res = sscanf(++blank, "%hi%c", &info.addr, &end);
|
||||
if (res < 1) {
|
||||
dev_err(dev, "%s: Can't parse I2C address\n", "new_device");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (res > 1 && end != '\n') {
|
||||
dev_err(dev, "%s: Extra parameters\n", "new_device");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (info.addr < 0x03 || info.addr > 0x77) {
|
||||
dev_err(dev, "%s: Invalid I2C address 0x%hx\n", "new_device",
|
||||
info.addr);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
client = i2c_new_device(adap, &info);
|
||||
if (!client)
|
||||
return -EEXIST;
|
||||
|
||||
/* Keep track of the added device */
|
||||
mutex_lock(&core_lock);
|
||||
list_add_tail(&client->detected, &userspace_devices);
|
||||
mutex_unlock(&core_lock);
|
||||
dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
|
||||
info.type, info.addr);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* And of course let the users delete the devices they instantiated, if
|
||||
* they got it wrong. This interface can only be used to delete devices
|
||||
* instantiated by i2c_sysfs_new_device above. This guarantees that we
|
||||
* don't delete devices to which some kernel code still has references.
|
||||
*
|
||||
* Parameter checking may look overzealous, but we really don't want
|
||||
* the user to delete the wrong device.
|
||||
*/
|
||||
static ssize_t
|
||||
i2c_sysfs_delete_device(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_adapter *adap = to_i2c_adapter(dev);
|
||||
struct i2c_client *client, *next;
|
||||
unsigned short addr;
|
||||
char end;
|
||||
int res;
|
||||
|
||||
/* Parse parameters, reject extra parameters */
|
||||
res = sscanf(buf, "%hi%c", &addr, &end);
|
||||
if (res < 1) {
|
||||
dev_err(dev, "%s: Can't parse I2C address\n", "delete_device");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (res > 1 && end != '\n') {
|
||||
dev_err(dev, "%s: Extra parameters\n", "delete_device");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Make sure the device was added through sysfs */
|
||||
res = -ENOENT;
|
||||
mutex_lock(&core_lock);
|
||||
list_for_each_entry_safe(client, next, &userspace_devices, detected) {
|
||||
if (client->addr == addr && client->adapter == adap) {
|
||||
dev_info(dev, "%s: Deleting device %s at 0x%02hx\n",
|
||||
"delete_device", client->name, client->addr);
|
||||
|
||||
list_del(&client->detected);
|
||||
i2c_unregister_device(client);
|
||||
res = count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&core_lock);
|
||||
|
||||
if (res < 0)
|
||||
dev_err(dev, "%s: Can't find device in list\n",
|
||||
"delete_device");
|
||||
return res;
|
||||
}
|
||||
|
||||
static struct device_attribute i2c_adapter_attrs[] = {
|
||||
__ATTR(name, S_IRUGO, show_adapter_name, NULL),
|
||||
__ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device),
|
||||
__ATTR(delete_device, S_IWUSR, NULL, i2c_sysfs_delete_device),
|
||||
{ },
|
||||
};
|
||||
|
||||
|
|
|
@ -178,7 +178,8 @@ struct i2c_driver {
|
|||
* @driver: device's driver, hence pointer to access routines
|
||||
* @dev: Driver model device node for the slave.
|
||||
* @irq: indicates the IRQ generated by this device (if any)
|
||||
* @detected: member of an i2c_driver.clients list
|
||||
* @detected: member of an i2c_driver.clients list or i2c-core's
|
||||
* userspace_devices list
|
||||
*
|
||||
* An i2c_client identifies a single device (i.e. chip) connected to an
|
||||
* i2c bus. The behaviour exposed to Linux is defined by the driver
|
||||
|
|
Loading…
Reference in a new issue