|
@@ -20,7 +20,9 @@
|
|
|
/* With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi>.
|
|
|
All SMBus-related things are written by Frodo Looijaard <frodol@dds.nl>
|
|
|
SMBus 2.0 support by Mark Studebaker <mdsxyz123@yahoo.com> and
|
|
|
- Jean Delvare <khali@linux-fr.org> */
|
|
|
+ Jean Delvare <khali@linux-fr.org>
|
|
|
+ Mux support by Rodolfo Giometti <giometti@enneenne.com> and
|
|
|
+ Michael Lawnick <michael.lawnick.ext@nsn.com> */
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/kernel.h>
|
|
@@ -423,11 +425,87 @@ static int __i2c_check_addr_busy(struct device *dev, void *addrp)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/* walk up mux tree */
|
|
|
+static int i2c_check_mux_parents(struct i2c_adapter *adapter, int addr)
|
|
|
+{
|
|
|
+ int result;
|
|
|
+
|
|
|
+ result = device_for_each_child(&adapter->dev, &addr,
|
|
|
+ __i2c_check_addr_busy);
|
|
|
+
|
|
|
+ if (!result && i2c_parent_is_i2c_adapter(adapter))
|
|
|
+ result = i2c_check_mux_parents(
|
|
|
+ to_i2c_adapter(adapter->dev.parent), addr);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+/* recurse down mux tree */
|
|
|
+static int i2c_check_mux_children(struct device *dev, void *addrp)
|
|
|
+{
|
|
|
+ int result;
|
|
|
+
|
|
|
+ if (dev->type == &i2c_adapter_type)
|
|
|
+ result = device_for_each_child(dev, addrp,
|
|
|
+ i2c_check_mux_children);
|
|
|
+ else
|
|
|
+ result = __i2c_check_addr_busy(dev, addrp);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
|
|
|
{
|
|
|
- return device_for_each_child(&adapter->dev, &addr,
|
|
|
- __i2c_check_addr_busy);
|
|
|
+ int result = 0;
|
|
|
+
|
|
|
+ if (i2c_parent_is_i2c_adapter(adapter))
|
|
|
+ result = i2c_check_mux_parents(
|
|
|
+ to_i2c_adapter(adapter->dev.parent), addr);
|
|
|
+
|
|
|
+ if (!result)
|
|
|
+ result = device_for_each_child(&adapter->dev, &addr,
|
|
|
+ i2c_check_mux_children);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * i2c_lock_adapter - Get exclusive access to an I2C bus segment
|
|
|
+ * @adapter: Target I2C bus segment
|
|
|
+ */
|
|
|
+void i2c_lock_adapter(struct i2c_adapter *adapter)
|
|
|
+{
|
|
|
+ if (i2c_parent_is_i2c_adapter(adapter))
|
|
|
+ i2c_lock_adapter(to_i2c_adapter(adapter->dev.parent));
|
|
|
+ else
|
|
|
+ rt_mutex_lock(&adapter->bus_lock);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(i2c_lock_adapter);
|
|
|
+
|
|
|
+/**
|
|
|
+ * i2c_trylock_adapter - Try to get exclusive access to an I2C bus segment
|
|
|
+ * @adapter: Target I2C bus segment
|
|
|
+ */
|
|
|
+static int i2c_trylock_adapter(struct i2c_adapter *adapter)
|
|
|
+{
|
|
|
+ if (i2c_parent_is_i2c_adapter(adapter))
|
|
|
+ return i2c_trylock_adapter(to_i2c_adapter(adapter->dev.parent));
|
|
|
+ else
|
|
|
+ return rt_mutex_trylock(&adapter->bus_lock);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * i2c_unlock_adapter - Release exclusive access to an I2C bus segment
|
|
|
+ * @adapter: Target I2C bus segment
|
|
|
+ */
|
|
|
+void i2c_unlock_adapter(struct i2c_adapter *adapter)
|
|
|
+{
|
|
|
+ if (i2c_parent_is_i2c_adapter(adapter))
|
|
|
+ i2c_unlock_adapter(to_i2c_adapter(adapter->dev.parent));
|
|
|
+ else
|
|
|
+ rt_mutex_unlock(&adapter->bus_lock);
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(i2c_unlock_adapter);
|
|
|
|
|
|
/**
|
|
|
* i2c_new_device - instantiate an i2c device
|
|
@@ -633,9 +711,9 @@ i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,
|
|
|
return -EINVAL;
|
|
|
|
|
|
/* Keep track of the added device */
|
|
|
- i2c_lock_adapter(adap);
|
|
|
+ mutex_lock(&adap->userspace_clients_lock);
|
|
|
list_add_tail(&client->detected, &adap->userspace_clients);
|
|
|
- i2c_unlock_adapter(adap);
|
|
|
+ mutex_unlock(&adap->userspace_clients_lock);
|
|
|
dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
|
|
|
info.type, info.addr);
|
|
|
|
|
@@ -674,7 +752,7 @@ i2c_sysfs_delete_device(struct device *dev, struct device_attribute *attr,
|
|
|
|
|
|
/* Make sure the device was added through sysfs */
|
|
|
res = -ENOENT;
|
|
|
- i2c_lock_adapter(adap);
|
|
|
+ mutex_lock(&adap->userspace_clients_lock);
|
|
|
list_for_each_entry_safe(client, next, &adap->userspace_clients,
|
|
|
detected) {
|
|
|
if (client->addr == addr) {
|
|
@@ -687,7 +765,7 @@ i2c_sysfs_delete_device(struct device *dev, struct device_attribute *attr,
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
- i2c_unlock_adapter(adap);
|
|
|
+ mutex_unlock(&adap->userspace_clients_lock);
|
|
|
|
|
|
if (res < 0)
|
|
|
dev_err(dev, "%s: Can't find device in list\n",
|
|
@@ -714,10 +792,11 @@ static const struct attribute_group *i2c_adapter_attr_groups[] = {
|
|
|
NULL
|
|
|
};
|
|
|
|
|
|
-static struct device_type i2c_adapter_type = {
|
|
|
+struct device_type i2c_adapter_type = {
|
|
|
.groups = i2c_adapter_attr_groups,
|
|
|
.release = i2c_adapter_dev_release,
|
|
|
};
|
|
|
+EXPORT_SYMBOL_GPL(i2c_adapter_type);
|
|
|
|
|
|
#ifdef CONFIG_I2C_COMPAT
|
|
|
static struct class_compat *i2c_adapter_compat_class;
|
|
@@ -760,7 +839,7 @@ static int __process_new_adapter(struct device_driver *d, void *data)
|
|
|
|
|
|
static int i2c_register_adapter(struct i2c_adapter *adap)
|
|
|
{
|
|
|
- int res = 0, dummy;
|
|
|
+ int res = 0;
|
|
|
|
|
|
/* Can't register until after driver model init */
|
|
|
if (unlikely(WARN_ON(!i2c_bus_type.p))) {
|
|
@@ -769,6 +848,7 @@ static int i2c_register_adapter(struct i2c_adapter *adap)
|
|
|
}
|
|
|
|
|
|
rt_mutex_init(&adap->bus_lock);
|
|
|
+ mutex_init(&adap->userspace_clients_lock);
|
|
|
INIT_LIST_HEAD(&adap->userspace_clients);
|
|
|
|
|
|
/* Set default timeout to 1 second if not already set */
|
|
@@ -801,8 +881,7 @@ static int i2c_register_adapter(struct i2c_adapter *adap)
|
|
|
|
|
|
/* Notify drivers */
|
|
|
mutex_lock(&core_lock);
|
|
|
- dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
|
|
|
- __process_new_adapter);
|
|
|
+ bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
|
|
|
mutex_unlock(&core_lock);
|
|
|
|
|
|
return 0;
|
|
@@ -975,7 +1054,7 @@ int i2c_del_adapter(struct i2c_adapter *adap)
|
|
|
return res;
|
|
|
|
|
|
/* Remove devices instantiated from sysfs */
|
|
|
- i2c_lock_adapter(adap);
|
|
|
+ mutex_lock(&adap->userspace_clients_lock);
|
|
|
list_for_each_entry_safe(client, next, &adap->userspace_clients,
|
|
|
detected) {
|
|
|
dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,
|
|
@@ -983,7 +1062,7 @@ int i2c_del_adapter(struct i2c_adapter *adap)
|
|
|
list_del(&client->detected);
|
|
|
i2c_unregister_device(client);
|
|
|
}
|
|
|
- i2c_unlock_adapter(adap);
|
|
|
+ mutex_unlock(&adap->userspace_clients_lock);
|
|
|
|
|
|
/* Detach any active clients. This can't fail, thus we do not
|
|
|
checking the returned value. */
|
|
@@ -1238,12 +1317,12 @@ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
|
|
|
#endif
|
|
|
|
|
|
if (in_atomic() || irqs_disabled()) {
|
|
|
- ret = rt_mutex_trylock(&adap->bus_lock);
|
|
|
+ ret = i2c_trylock_adapter(adap);
|
|
|
if (!ret)
|
|
|
/* I2C activity is ongoing. */
|
|
|
return -EAGAIN;
|
|
|
} else {
|
|
|
- rt_mutex_lock(&adap->bus_lock);
|
|
|
+ i2c_lock_adapter(adap);
|
|
|
}
|
|
|
|
|
|
/* Retry automatically on arbitration loss */
|
|
@@ -1255,7 +1334,7 @@ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
|
|
|
if (time_after(jiffies, orig_jiffies + adap->timeout))
|
|
|
break;
|
|
|
}
|
|
|
- rt_mutex_unlock(&adap->bus_lock);
|
|
|
+ i2c_unlock_adapter(adap);
|
|
|
|
|
|
return ret;
|
|
|
} else {
|
|
@@ -1350,13 +1429,17 @@ static int i2c_default_probe(struct i2c_adapter *adap, unsigned short addr)
|
|
|
I2C_SMBUS_BYTE_DATA, &dummy);
|
|
|
else
|
|
|
#endif
|
|
|
- if ((addr & ~0x07) == 0x30 || (addr & ~0x0f) == 0x50
|
|
|
- || !i2c_check_functionality(adap, I2C_FUNC_SMBUS_QUICK))
|
|
|
- err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
|
|
|
- I2C_SMBUS_BYTE, &dummy);
|
|
|
- else
|
|
|
+ if (!((addr & ~0x07) == 0x30 || (addr & ~0x0f) == 0x50)
|
|
|
+ && i2c_check_functionality(adap, I2C_FUNC_SMBUS_QUICK))
|
|
|
err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_WRITE, 0,
|
|
|
I2C_SMBUS_QUICK, NULL);
|
|
|
+ else if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE))
|
|
|
+ err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
|
|
|
+ I2C_SMBUS_BYTE, &dummy);
|
|
|
+ else {
|
|
|
+ dev_warn(&adap->dev, "No suitable probing method supported\n");
|
|
|
+ err = -EOPNOTSUPP;
|
|
|
+ }
|
|
|
|
|
|
return err >= 0;
|
|
|
}
|
|
@@ -1437,16 +1520,6 @@ static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
|
|
|
if (!(adapter->class & driver->class))
|
|
|
goto exit_free;
|
|
|
|
|
|
- /* Stop here if the bus doesn't support probing */
|
|
|
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE)) {
|
|
|
- if (address_list[0] == I2C_CLIENT_END)
|
|
|
- goto exit_free;
|
|
|
-
|
|
|
- dev_warn(&adapter->dev, "Probing not supported\n");
|
|
|
- err = -EOPNOTSUPP;
|
|
|
- goto exit_free;
|
|
|
- }
|
|
|
-
|
|
|
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
|
|
|
dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
|
|
|
"addr 0x%02x\n", adap_id, address_list[i]);
|
|
@@ -1461,18 +1534,23 @@ static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+int i2c_probe_func_quick_read(struct i2c_adapter *adap, unsigned short addr)
|
|
|
+{
|
|
|
+ return i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
|
|
|
+ I2C_SMBUS_QUICK, NULL) >= 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(i2c_probe_func_quick_read);
|
|
|
+
|
|
|
struct i2c_client *
|
|
|
i2c_new_probed_device(struct i2c_adapter *adap,
|
|
|
struct i2c_board_info *info,
|
|
|
- unsigned short const *addr_list)
|
|
|
+ unsigned short const *addr_list,
|
|
|
+ int (*probe)(struct i2c_adapter *, unsigned short addr))
|
|
|
{
|
|
|
int i;
|
|
|
|
|
|
- /* Stop here if the bus doesn't support probing */
|
|
|
- if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE)) {
|
|
|
- dev_err(&adap->dev, "Probing not supported\n");
|
|
|
- return NULL;
|
|
|
- }
|
|
|
+ if (!probe)
|
|
|
+ probe = i2c_default_probe;
|
|
|
|
|
|
for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
|
|
|
/* Check address validity */
|
|
@@ -1490,7 +1568,7 @@ i2c_new_probed_device(struct i2c_adapter *adap,
|
|
|
}
|
|
|
|
|
|
/* Test address responsiveness */
|
|
|
- if (i2c_default_probe(adap, addr_list[i]))
|
|
|
+ if (probe(adap, addr_list[i]))
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -2002,7 +2080,7 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
|
|
|
flags &= I2C_M_TEN | I2C_CLIENT_PEC;
|
|
|
|
|
|
if (adapter->algo->smbus_xfer) {
|
|
|
- rt_mutex_lock(&adapter->bus_lock);
|
|
|
+ i2c_lock_adapter(adapter);
|
|
|
|
|
|
/* Retry automatically on arbitration loss */
|
|
|
orig_jiffies = jiffies;
|
|
@@ -2016,7 +2094,7 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
|
|
|
orig_jiffies + adapter->timeout))
|
|
|
break;
|
|
|
}
|
|
|
- rt_mutex_unlock(&adapter->bus_lock);
|
|
|
+ i2c_unlock_adapter(adapter);
|
|
|
} else
|
|
|
res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
|
|
|
command, protocol, data);
|