]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
of: unitest: Add I2C overlay unit tests.
authorPantelis Antoniou <pantelis.antoniou@konsulko.com>
Mon, 12 Jan 2015 17:02:49 +0000 (19:02 +0200)
committerRob Herring <robh@kernel.org>
Wed, 4 Feb 2015 16:43:14 +0000 (10:43 -0600)
Introduce I2C device tree overlay tests.
Tests insertion and removal of i2c adapters, i2c devices, and muxes.

Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
Signed-off-by: Rob Herring <robh@kernel.org>
Documentation/devicetree/bindings/unittest.txt
drivers/of/unittest-data/tests-overlay.dtsi
drivers/of/unittest.c

index 0f92a22fddfad1d90cb5f44728696cbe35aed747..8933211f32f945c70b568d35372342ab3baca3d3 100644 (file)
@@ -1,4 +1,4 @@
-* OF selftest platform device
+1) OF selftest platform device
 
 ** selftest
 
@@ -12,3 +12,60 @@ Example:
                compatible = "selftest";
                status = "okay";
        };
+
+2) OF selftest i2c adapter platform device
+
+** platform device unittest adapter
+
+Required properties:
+- compatible: must be selftest-i2c-bus
+
+Children nodes contain selftest i2c devices.
+
+Example:
+       selftest-i2c-bus {
+               compatible = "selftest-i2c-bus";
+               status = "okay";
+       };
+
+3) OF selftest i2c device
+
+** I2C selftest device
+
+Required properties:
+- compatible: must be selftest-i2c-dev
+
+All other properties are optional
+
+Example:
+       selftest-i2c-dev {
+               compatible = "selftest-i2c-dev";
+               status = "okay";
+       };
+
+4) OF selftest i2c mux device
+
+** I2C selftest mux
+
+Required properties:
+- compatible: must be selftest-i2c-mux
+
+Children nodes contain selftest i2c bus nodes per channel.
+
+Example:
+       selftest-i2c-mux {
+               compatible = "selftest-i2c-mux";
+               status = "okay";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               channel-0 {
+                       reg = <0>;
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       i2c-dev {
+                               reg = <8>;
+                               compatible = "selftest-i2c-dev";
+                               status = "okay";
+                       };
+               };
+       };
index a2b687d5f324700a0adff54e991905053445a167..244226cbb5a3b7fb8929fd75b77171cb172c1aa9 100644 (file)
                                        status = "disabled";
                                        reg = <8>;
                                };
+
+                               i2c-test-bus {
+                                       compatible = "selftest-i2c-bus";
+                                       status = "okay";
+                                       reg = <50>;
+
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       test-selftest12 {
+                                               reg = <8>;
+                                               compatible = "selftest-i2c-dev";
+                                               status = "disabled";
+                                       };
+
+                                       test-selftest13 {
+                                               reg = <9>;
+                                               compatible = "selftest-i2c-dev";
+                                               status = "okay";
+                                       };
+
+                                       test-selftest14 {
+                                               reg = <10>;
+                                               compatible = "selftest-i2c-mux";
+                                               status = "okay";
+
+                                               #address-cells = <1>;
+                                               #size-cells = <0>;
+
+                                               i2c@0 {
+                                                       #address-cells = <1>;
+                                                       #size-cells = <0>;
+                                                       reg = <0>;
+
+                                                       test-mux-dev {
+                                                               reg = <32>;
+                                                               compatible = "selftest-i2c-dev";
+                                                               status = "okay";
+                                                       };
+                                               };
+                                       };
+                               };
                        };
                };
 
                                };
                        };
                };
+
+               /* test enable using absolute target path (i2c) */
+               overlay12 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-selftest12";
+                               __overlay__ {
+                                       status = "okay";
+                               };
+                       };
+               };
+
+               /* test disable using absolute target path (i2c) */
+               overlay13 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-selftest13";
+                               __overlay__ {
+                                       status = "disabled";
+                               };
+                       };
+               };
+
+               /* test mux overlay */
+               overlay15 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus";
+                               __overlay__ {
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+                                       test-selftest15 {
+                                               reg = <11>;
+                                               compatible = "selftest-i2c-mux";
+                                               status = "okay";
+
+                                               #address-cells = <1>;
+                                               #size-cells = <0>;
+
+                                               i2c@0 {
+                                                       #address-cells = <1>;
+                                                       #size-cells = <0>;
+                                                       reg = <0>;
+
+                                                       test-mux-dev {
+                                                               reg = <32>;
+                                                               compatible = "selftest-i2c-dev";
+                                                               status = "okay";
+                                                       };
+                                               };
+                                       };
+                               };
+                       };
+               };
+
        };
 };
index 12cdbc1e30420e3c5cd0590814ce9a9cee469558..e86213b0e7e55ab24b1e3dd32d967c40065b73b7 100644 (file)
@@ -20,6 +20,9 @@
 #include <linux/platform_device.h>
 #include <linux/of_platform.h>
 
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+
 #include "of_private.h"
 
 static struct selftest_results {
@@ -991,17 +994,94 @@ static int of_path_platform_device_exists(const char *path)
        return pdev != NULL;
 }
 
-static const char *selftest_path(int nr)
+#if IS_ENABLED(CONFIG_I2C)
+
+/* get the i2c client device instantiated at the path */
+static struct i2c_client *of_path_to_i2c_client(const char *path)
+{
+       struct device_node *np;
+       struct i2c_client *client;
+
+       np = of_find_node_by_path(path);
+       if (np == NULL)
+               return NULL;
+
+       client = of_find_i2c_device_by_node(np);
+       of_node_put(np);
+
+       return client;
+}
+
+/* find out if a i2c client device exists at that path */
+static int of_path_i2c_client_exists(const char *path)
+{
+       struct i2c_client *client;
+
+       client = of_path_to_i2c_client(path);
+       if (client)
+               put_device(&client->dev);
+       return client != NULL;
+}
+#else
+static int of_path_i2c_client_exists(const char *path)
+{
+       return 0;
+}
+#endif
+
+enum overlay_type {
+       PDEV_OVERLAY,
+       I2C_OVERLAY
+};
+
+static int of_path_device_type_exists(const char *path,
+               enum overlay_type ovtype)
 {
+       switch (ovtype) {
+       case PDEV_OVERLAY:
+               return of_path_platform_device_exists(path);
+       case I2C_OVERLAY:
+               return of_path_i2c_client_exists(path);
+       }
+       return 0;
+}
+
+static const char *selftest_path(int nr, enum overlay_type ovtype)
+{
+       const char *base;
        static char buf[256];
 
-       snprintf(buf, sizeof(buf) - 1,
-               "/testcase-data/overlay-node/test-bus/test-selftest%d", nr);
+       switch (ovtype) {
+       case PDEV_OVERLAY:
+               base = "/testcase-data/overlay-node/test-bus";
+               break;
+       case I2C_OVERLAY:
+               base = "/testcase-data/overlay-node/test-bus/i2c-test-bus";
+               break;
+       default:
+               buf[0] = '\0';
+               return buf;
+       }
+       snprintf(buf, sizeof(buf) - 1, "%s/test-selftest%d", base, nr);
        buf[sizeof(buf) - 1] = '\0';
-
        return buf;
 }
 
+static int of_selftest_device_exists(int selftest_nr, enum overlay_type ovtype)
+{
+       const char *path;
+
+       path = selftest_path(selftest_nr, ovtype);
+
+       switch (ovtype) {
+       case PDEV_OVERLAY:
+               return of_path_platform_device_exists(path);
+       case I2C_OVERLAY:
+               return of_path_i2c_client_exists(path);
+       }
+       return 0;
+}
+
 static const char *overlay_path(int nr)
 {
        static char buf[256];
@@ -1050,16 +1130,15 @@ out:
 
 /* apply an overlay while checking before and after states */
 static int of_selftest_apply_overlay_check(int overlay_nr, int selftest_nr,
-               int before, int after)
+               int before, int after, enum overlay_type ovtype)
 {
        int ret;
 
        /* selftest device must not be in before state */
-       if (of_path_platform_device_exists(selftest_path(selftest_nr))
-                       != before) {
+       if (of_selftest_device_exists(selftest_nr, ovtype) != before) {
                selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
                                overlay_path(overlay_nr),
-                               selftest_path(selftest_nr),
+                               selftest_path(selftest_nr, ovtype),
                                !before ? "enabled" : "disabled");
                return -EINVAL;
        }
@@ -1071,11 +1150,10 @@ static int of_selftest_apply_overlay_check(int overlay_nr, int selftest_nr,
        }
 
        /* selftest device must be to set to after state */
-       if (of_path_platform_device_exists(selftest_path(selftest_nr))
-                       != after) {
+       if (of_selftest_device_exists(selftest_nr, ovtype) != after) {
                selftest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
                                overlay_path(overlay_nr),
-                               selftest_path(selftest_nr),
+                               selftest_path(selftest_nr, ovtype),
                                !after ? "enabled" : "disabled");
                return -EINVAL;
        }
@@ -1085,16 +1163,16 @@ static int of_selftest_apply_overlay_check(int overlay_nr, int selftest_nr,
 
 /* apply an overlay and then revert it while checking before, after states */
 static int of_selftest_apply_revert_overlay_check(int overlay_nr,
-               int selftest_nr, int before, int after)
+               int selftest_nr, int before, int after,
+               enum overlay_type ovtype)
 {
        int ret, ov_id;
 
        /* selftest device must be in before state */
-       if (of_path_platform_device_exists(selftest_path(selftest_nr))
-                       != before) {
+       if (of_selftest_device_exists(selftest_nr, ovtype) != before) {
                selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
                                overlay_path(overlay_nr),
-                               selftest_path(selftest_nr),
+                               selftest_path(selftest_nr, ovtype),
                                !before ? "enabled" : "disabled");
                return -EINVAL;
        }
@@ -1107,11 +1185,10 @@ static int of_selftest_apply_revert_overlay_check(int overlay_nr,
        }
 
        /* selftest device must be in after state */
-       if (of_path_platform_device_exists(selftest_path(selftest_nr))
-                       != after) {
+       if (of_selftest_device_exists(selftest_nr, ovtype) != after) {
                selftest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
                                overlay_path(overlay_nr),
-                               selftest_path(selftest_nr),
+                               selftest_path(selftest_nr, ovtype),
                                !after ? "enabled" : "disabled");
                return -EINVAL;
        }
@@ -1120,16 +1197,15 @@ static int of_selftest_apply_revert_overlay_check(int overlay_nr,
        if (ret != 0) {
                selftest(0, "overlay @\"%s\" failed to be destroyed @\"%s\"\n",
                                overlay_path(overlay_nr),
-                               selftest_path(selftest_nr));
+                               selftest_path(selftest_nr, ovtype));
                return ret;
        }
 
        /* selftest device must be again in before state */
-       if (of_path_platform_device_exists(selftest_path(selftest_nr))
-                       != before) {
+       if (of_selftest_device_exists(selftest_nr, PDEV_OVERLAY) != before) {
                selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
                                overlay_path(overlay_nr),
-                               selftest_path(selftest_nr),
+                               selftest_path(selftest_nr, ovtype),
                                !before ? "enabled" : "disabled");
                return -EINVAL;
        }
@@ -1143,7 +1219,7 @@ static void of_selftest_overlay_0(void)
        int ret;
 
        /* device should enable */
-       ret = of_selftest_apply_overlay_check(0, 0, 0, 1);
+       ret = of_selftest_apply_overlay_check(0, 0, 0, 1, PDEV_OVERLAY);
        if (ret != 0)
                return;
 
@@ -1156,7 +1232,7 @@ static void of_selftest_overlay_1(void)
        int ret;
 
        /* device should disable */
-       ret = of_selftest_apply_overlay_check(1, 1, 1, 0);
+       ret = of_selftest_apply_overlay_check(1, 1, 1, 0, PDEV_OVERLAY);
        if (ret != 0)
                return;
 
@@ -1169,7 +1245,7 @@ static void of_selftest_overlay_2(void)
        int ret;
 
        /* device should enable */
-       ret = of_selftest_apply_overlay_check(2, 2, 0, 1);
+       ret = of_selftest_apply_overlay_check(2, 2, 0, 1, PDEV_OVERLAY);
        if (ret != 0)
                return;
 
@@ -1182,7 +1258,7 @@ static void of_selftest_overlay_3(void)
        int ret;
 
        /* device should disable */
-       ret = of_selftest_apply_overlay_check(3, 3, 1, 0);
+       ret = of_selftest_apply_overlay_check(3, 3, 1, 0, PDEV_OVERLAY);
        if (ret != 0)
                return;
 
@@ -1195,7 +1271,7 @@ static void of_selftest_overlay_4(void)
        int ret;
 
        /* device should disable */
-       ret = of_selftest_apply_overlay_check(4, 4, 0, 1);
+       ret = of_selftest_apply_overlay_check(4, 4, 0, 1, PDEV_OVERLAY);
        if (ret != 0)
                return;
 
@@ -1208,7 +1284,7 @@ static void of_selftest_overlay_5(void)
        int ret;
 
        /* device should disable */
-       ret = of_selftest_apply_revert_overlay_check(5, 5, 0, 1);
+       ret = of_selftest_apply_revert_overlay_check(5, 5, 0, 1, PDEV_OVERLAY);
        if (ret != 0)
                return;
 
@@ -1225,12 +1301,12 @@ static void of_selftest_overlay_6(void)
 
        /* selftest device must be in before state */
        for (i = 0; i < 2; i++) {
-               if (of_path_platform_device_exists(
-                                       selftest_path(selftest_nr + i))
+               if (of_selftest_device_exists(selftest_nr + i, PDEV_OVERLAY)
                                != before) {
                        selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
                                        overlay_path(overlay_nr + i),
-                                       selftest_path(selftest_nr + i),
+                                       selftest_path(selftest_nr + i,
+                                               PDEV_OVERLAY),
                                        !before ? "enabled" : "disabled");
                        return;
                }
@@ -1257,12 +1333,12 @@ static void of_selftest_overlay_6(void)
 
        for (i = 0; i < 2; i++) {
                /* selftest device must be in after state */
-               if (of_path_platform_device_exists(
-                                       selftest_path(selftest_nr + i))
+               if (of_selftest_device_exists(selftest_nr + i, PDEV_OVERLAY)
                                != after) {
                        selftest(0, "overlay @\"%s\" failed @\"%s\" %s\n",
                                        overlay_path(overlay_nr + i),
-                                       selftest_path(selftest_nr + i),
+                                       selftest_path(selftest_nr + i,
+                                               PDEV_OVERLAY),
                                        !after ? "enabled" : "disabled");
                        return;
                }
@@ -1273,19 +1349,20 @@ static void of_selftest_overlay_6(void)
                if (ret != 0) {
                        selftest(0, "overlay @\"%s\" failed destroy @\"%s\"\n",
                                        overlay_path(overlay_nr + i),
-                                       selftest_path(selftest_nr + i));
+                                       selftest_path(selftest_nr + i,
+                                               PDEV_OVERLAY));
                        return;
                }
        }
 
        for (i = 0; i < 2; i++) {
                /* selftest device must be again in before state */
-               if (of_path_platform_device_exists(
-                                       selftest_path(selftest_nr + i))
+               if (of_selftest_device_exists(selftest_nr + i, PDEV_OVERLAY)
                                != before) {
                        selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
                                        overlay_path(overlay_nr + i),
-                                       selftest_path(selftest_nr + i),
+                                       selftest_path(selftest_nr + i,
+                                               PDEV_OVERLAY),
                                        !before ? "enabled" : "disabled");
                        return;
                }
@@ -1327,7 +1404,8 @@ static void of_selftest_overlay_8(void)
        if (ret == 0) {
                selftest(0, "overlay @\"%s\" was destroyed @\"%s\"\n",
                                overlay_path(overlay_nr + 0),
-                               selftest_path(selftest_nr));
+                               selftest_path(selftest_nr,
+                                       PDEV_OVERLAY));
                return;
        }
 
@@ -1337,7 +1415,8 @@ static void of_selftest_overlay_8(void)
                if (ret != 0) {
                        selftest(0, "overlay @\"%s\" not destroyed @\"%s\"\n",
                                        overlay_path(overlay_nr + i),
-                                       selftest_path(selftest_nr));
+                                       selftest_path(selftest_nr,
+                                               PDEV_OVERLAY));
                        return;
                }
        }
@@ -1352,16 +1431,17 @@ static void of_selftest_overlay_10(void)
        char *child_path;
 
        /* device should disable */
-       ret = of_selftest_apply_overlay_check(10, 10, 0, 1);
-       if (selftest(ret == 0, "overlay test %d failed; overlay application\n", 10))
+       ret = of_selftest_apply_overlay_check(10, 10, 0, 1, PDEV_OVERLAY);
+       if (selftest(ret == 0,
+                       "overlay test %d failed; overlay application\n", 10))
                return;
 
        child_path = kasprintf(GFP_KERNEL, "%s/test-selftest101",
-                       selftest_path(10));
+                       selftest_path(10, PDEV_OVERLAY));
        if (selftest(child_path, "overlay test %d failed; kasprintf\n", 10))
                return;
 
-       ret = of_path_platform_device_exists(child_path);
+       ret = of_path_device_type_exists(child_path, PDEV_OVERLAY);
        kfree(child_path);
        if (selftest(ret, "overlay test %d failed; no child device\n", 10))
                return;
@@ -1373,11 +1453,331 @@ static void of_selftest_overlay_11(void)
        int ret;
 
        /* device should disable */
-       ret = of_selftest_apply_revert_overlay_check(11, 11, 0, 1);
-       if (selftest(ret == 0, "overlay test %d failed; overlay application\n", 11))
+       ret = of_selftest_apply_revert_overlay_check(11, 11, 0, 1,
+                       PDEV_OVERLAY);
+       if (selftest(ret == 0,
+                       "overlay test %d failed; overlay application\n", 11))
+               return;
+}
+
+#if IS_ENABLED(CONFIG_I2C) && IS_ENABLED(CONFIG_OF_OVERLAY)
+
+struct selftest_i2c_bus_data {
+       struct platform_device  *pdev;
+       struct i2c_adapter      adap;
+};
+
+static int selftest_i2c_master_xfer(struct i2c_adapter *adap,
+               struct i2c_msg *msgs, int num)
+{
+       struct selftest_i2c_bus_data *std = i2c_get_adapdata(adap);
+
+       (void)std;
+
+       return num;
+}
+
+static u32 selftest_i2c_functionality(struct i2c_adapter *adap)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm selftest_i2c_algo = {
+       .master_xfer    = selftest_i2c_master_xfer,
+       .functionality  = selftest_i2c_functionality,
+};
+
+static int selftest_i2c_bus_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+       struct selftest_i2c_bus_data *std;
+       struct i2c_adapter *adap;
+       int ret;
+
+       if (np == NULL) {
+               dev_err(dev, "No OF data for device\n");
+               return -EINVAL;
+
+       }
+
+       dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+
+       std = devm_kzalloc(dev, sizeof(*std), GFP_KERNEL);
+       if (!std) {
+               dev_err(dev, "Failed to allocate selftest i2c data\n");
+               return -ENOMEM;
+       }
+
+       /* link them together */
+       std->pdev = pdev;
+       platform_set_drvdata(pdev, std);
+
+       adap = &std->adap;
+       i2c_set_adapdata(adap, std);
+       adap->nr = -1;
+       strlcpy(adap->name, pdev->name, sizeof(adap->name));
+       adap->class = I2C_CLASS_DEPRECATED;
+       adap->algo = &selftest_i2c_algo;
+       adap->dev.parent = dev;
+       adap->dev.of_node = dev->of_node;
+       adap->timeout = 5 * HZ;
+       adap->retries = 3;
+
+       ret = i2c_add_numbered_adapter(adap);
+       if (ret != 0) {
+               dev_err(dev, "Failed to add I2C adapter\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int selftest_i2c_bus_remove(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+       struct selftest_i2c_bus_data *std = platform_get_drvdata(pdev);
+
+       dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+       i2c_del_adapter(&std->adap);
+
+       return 0;
+}
+
+static struct of_device_id selftest_i2c_bus_match[] = {
+       { .compatible = "selftest-i2c-bus", },
+       {},
+};
+
+static struct platform_driver selftest_i2c_bus_driver = {
+       .probe                  = selftest_i2c_bus_probe,
+       .remove                 = selftest_i2c_bus_remove,
+       .driver = {
+               .name           = "selftest-i2c-bus",
+               .of_match_table = of_match_ptr(selftest_i2c_bus_match),
+       },
+};
+
+static int selftest_i2c_dev_probe(struct i2c_client *client,
+               const struct i2c_device_id *id)
+{
+       struct device *dev = &client->dev;
+       struct device_node *np = client->dev.of_node;
+
+       if (!np) {
+               dev_err(dev, "No OF node\n");
+               return -EINVAL;
+       }
+
+       dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+
+       return 0;
+};
+
+static int selftest_i2c_dev_remove(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct device_node *np = client->dev.of_node;
+
+       dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+       return 0;
+}
+
+static const struct i2c_device_id selftest_i2c_dev_id[] = {
+       { .name = "selftest-i2c-dev" },
+       { }
+};
+
+static struct i2c_driver selftest_i2c_dev_driver = {
+       .driver = {
+               .name = "selftest-i2c-dev",
+               .owner = THIS_MODULE,
+       },
+       .probe = selftest_i2c_dev_probe,
+       .remove = selftest_i2c_dev_remove,
+       .id_table = selftest_i2c_dev_id,
+};
+
+#if IS_ENABLED(CONFIG_I2C_MUX)
+
+struct selftest_i2c_mux_data {
+       int nchans;
+       struct i2c_adapter *adap[];
+};
+
+static int selftest_i2c_mux_select_chan(struct i2c_adapter *adap,
+                              void *client, u32 chan)
+{
+       return 0;
+}
+
+static int selftest_i2c_mux_probe(struct i2c_client *client,
+               const struct i2c_device_id *id)
+{
+       int ret, i, nchans, size;
+       struct device *dev = &client->dev;
+       struct i2c_adapter *adap = to_i2c_adapter(dev->parent);
+       struct device_node *np = client->dev.of_node, *child;
+       struct selftest_i2c_mux_data *stm;
+       u32 reg, max_reg;
+
+       dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+
+       if (!np) {
+               dev_err(dev, "No OF node\n");
+               return -EINVAL;
+       }
+
+       max_reg = (u32)-1;
+       for_each_child_of_node(np, child) {
+               ret = of_property_read_u32(child, "reg", &reg);
+               if (ret)
+                       continue;
+               if (max_reg == (u32)-1 || reg > max_reg)
+                       max_reg = reg;
+       }
+       nchans = max_reg == (u32)-1 ? 0 : max_reg + 1;
+       if (nchans == 0) {
+               dev_err(dev, "No channels\n");
+               return -EINVAL;
+       }
+
+       size = offsetof(struct selftest_i2c_mux_data, adap[nchans]);
+       stm = devm_kzalloc(dev, size, GFP_KERNEL);
+       if (!stm) {
+               dev_err(dev, "Out of memory\n");
+               return -ENOMEM;
+       }
+       stm->nchans = nchans;
+       for (i = 0; i < nchans; i++) {
+               stm->adap[i] = i2c_add_mux_adapter(adap, dev, client,
+                               0, i, 0, selftest_i2c_mux_select_chan, NULL);
+               if (!stm->adap[i]) {
+                       dev_err(dev, "Failed to register mux #%d\n", i);
+                       for (i--; i >= 0; i--)
+                               i2c_del_mux_adapter(stm->adap[i]);
+                       return -ENODEV;
+               }
+       }
+
+       i2c_set_clientdata(client, stm);
+
+       return 0;
+};
+
+static int selftest_i2c_mux_remove(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct device_node *np = client->dev.of_node;
+       struct selftest_i2c_mux_data *stm = i2c_get_clientdata(client);
+       int i;
+
+       dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+       for (i = stm->nchans - 1; i >= 0; i--)
+               i2c_del_mux_adapter(stm->adap[i]);
+       return 0;
+}
+
+static const struct i2c_device_id selftest_i2c_mux_id[] = {
+       { .name = "selftest-i2c-mux" },
+       { }
+};
+
+static struct i2c_driver selftest_i2c_mux_driver = {
+       .driver = {
+               .name = "selftest-i2c-mux",
+               .owner = THIS_MODULE,
+       },
+       .probe = selftest_i2c_mux_probe,
+       .remove = selftest_i2c_mux_remove,
+       .id_table = selftest_i2c_mux_id,
+};
+
+#endif
+
+static int of_selftest_overlay_i2c_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&selftest_i2c_dev_driver);
+       if (selftest(ret == 0,
+                       "could not register selftest i2c device driver\n"))
+               return ret;
+
+       ret = platform_driver_register(&selftest_i2c_bus_driver);
+       if (selftest(ret == 0,
+                       "could not register selftest i2c bus driver\n"))
+               return ret;
+
+#if IS_ENABLED(CONFIG_I2C_MUX)
+       ret = i2c_add_driver(&selftest_i2c_mux_driver);
+       if (selftest(ret == 0,
+                       "could not register selftest i2c mux driver\n"))
+               return ret;
+#endif
+
+       return 0;
+}
+
+static void of_selftest_overlay_i2c_cleanup(void)
+{
+#if IS_ENABLED(CONFIG_I2C_MUX)
+       i2c_del_driver(&selftest_i2c_mux_driver);
+#endif
+       platform_driver_unregister(&selftest_i2c_bus_driver);
+       i2c_del_driver(&selftest_i2c_dev_driver);
+}
+
+static void of_selftest_overlay_i2c_12(void)
+{
+       int ret;
+
+       /* device should enable */
+       ret = of_selftest_apply_overlay_check(12, 12, 0, 1, I2C_OVERLAY);
+       if (ret != 0)
+               return;
+
+       selftest(1, "overlay test %d passed\n", 12);
+}
+
+/* test deactivation of device */
+static void of_selftest_overlay_i2c_13(void)
+{
+       int ret;
+
+       /* device should disable */
+       ret = of_selftest_apply_overlay_check(13, 13, 1, 0, I2C_OVERLAY);
+       if (ret != 0)
                return;
+
+       selftest(1, "overlay test %d passed\n", 13);
+}
+
+/* just check for i2c mux existence */
+static void of_selftest_overlay_i2c_14(void)
+{
 }
 
+static void of_selftest_overlay_i2c_15(void)
+{
+       int ret;
+
+       /* device should enable */
+       ret = of_selftest_apply_overlay_check(16, 15, 0, 1, I2C_OVERLAY);
+       if (ret != 0)
+               return;
+
+       selftest(1, "overlay test %d passed\n", 15);
+}
+
+#else
+
+static inline void of_selftest_overlay_i2c_14(void) { }
+static inline void of_selftest_overlay_i2c_15(void) { }
+
+#endif
+
 static void __init of_selftest_overlay(void)
 {
        struct device_node *bus_np = NULL;
@@ -1402,15 +1802,15 @@ static void __init of_selftest_overlay(void)
                goto out;
        }
 
-       if (!of_path_platform_device_exists(selftest_path(100))) {
+       if (!of_selftest_device_exists(100, PDEV_OVERLAY)) {
                selftest(0, "could not find selftest0 @ \"%s\"\n",
-                               selftest_path(100));
+                               selftest_path(100, PDEV_OVERLAY));
                goto out;
        }
 
-       if (of_path_platform_device_exists(selftest_path(101))) {
+       if (of_selftest_device_exists(101, PDEV_OVERLAY)) {
                selftest(0, "selftest1 @ \"%s\" should not exist\n",
-                               selftest_path(101));
+                               selftest_path(101, PDEV_OVERLAY));
                goto out;
        }
 
@@ -1429,6 +1829,18 @@ static void __init of_selftest_overlay(void)
        of_selftest_overlay_10();
        of_selftest_overlay_11();
 
+#if IS_ENABLED(CONFIG_I2C)
+       if (selftest(of_selftest_overlay_i2c_init() == 0, "i2c init failed\n"))
+               goto out;
+
+       of_selftest_overlay_i2c_12();
+       of_selftest_overlay_i2c_13();
+       of_selftest_overlay_i2c_14();
+       of_selftest_overlay_i2c_15();
+
+       of_selftest_overlay_i2c_cleanup();
+#endif
+
 out:
        of_node_put(bus_np);
 }