root/drivers/i2c/muxes/i2c-mux-mule.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Theobroma Systems Mule I2C device multiplexer
 *
 * Copyright (C) 2024 Theobroma Systems Design und Consulting GmbH
 */

#include <linux/i2c-mux.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>

#define MULE_I2C_MUX_CONFIG_REG  0xff
#define MULE_I2C_MUX_DEFAULT_DEV 0x0

struct mule_i2c_reg_mux {
        struct regmap *regmap;
};

static int mule_i2c_mux_select(struct i2c_mux_core *muxc, u32 dev)
{
        struct mule_i2c_reg_mux *mux = muxc->priv;

        return regmap_write(mux->regmap, MULE_I2C_MUX_CONFIG_REG, dev);
}

static int mule_i2c_mux_deselect(struct i2c_mux_core *muxc, u32 dev)
{
        return mule_i2c_mux_select(muxc, MULE_I2C_MUX_DEFAULT_DEV);
}

static void mule_i2c_mux_remove(void *data)
{
        struct i2c_mux_core *muxc = data;

        i2c_mux_del_adapters(muxc);

        mule_i2c_mux_deselect(muxc, MULE_I2C_MUX_DEFAULT_DEV);
}

static int mule_i2c_mux_probe(struct platform_device *pdev)
{
        struct device *mux_dev = &pdev->dev;
        struct mule_i2c_reg_mux *priv;
        struct i2c_client *client;
        struct i2c_mux_core *muxc;
        unsigned int readback;
        int ndev, ret;
        bool old_fw;

        /* Count devices on the mux */
        ndev = of_get_child_count(mux_dev->of_node);
        dev_dbg(mux_dev, "%d devices on the mux\n", ndev);

        client = to_i2c_client(mux_dev->parent);

        muxc = i2c_mux_alloc(client->adapter, mux_dev, ndev, sizeof(*priv),
                             I2C_MUX_LOCKED, mule_i2c_mux_select, mule_i2c_mux_deselect);
        if (!muxc)
                return -ENOMEM;

        priv = i2c_mux_priv(muxc);

        priv->regmap = dev_get_regmap(mux_dev->parent, NULL);
        if (!priv->regmap)
                return dev_err_probe(mux_dev, -ENODEV,
                                     "No parent i2c register map\n");

        platform_set_drvdata(pdev, muxc);

        /*
         * MULE_I2C_MUX_DEFAULT_DEV is guaranteed to exist on all old and new
         * mule fw. Mule fw without mux support will accept write ops to the
         * config register, but readback returns 0xff (register not updated).
         */
        ret = mule_i2c_mux_select(muxc, MULE_I2C_MUX_DEFAULT_DEV);
        if (ret)
                return dev_err_probe(mux_dev, ret,
                                     "Failed to write config register\n");

        ret = regmap_read(priv->regmap, MULE_I2C_MUX_CONFIG_REG, &readback);
        if (ret)
                return dev_err_probe(mux_dev, ret,
                                     "Failed to read config register\n");

        old_fw = (readback != MULE_I2C_MUX_DEFAULT_DEV);

        ret = devm_add_action_or_reset(mux_dev, mule_i2c_mux_remove, muxc);
        if (ret)
                return dev_err_probe(mux_dev, ret,
                                     "Failed to register mux remove\n");

        /* Create device adapters */
        for_each_child_of_node_scoped(mux_dev->of_node, dev) {
                u32 reg;

                ret = of_property_read_u32(dev, "reg", &reg);
                if (ret)
                        return dev_err_probe(mux_dev, ret,
                                             "No reg property found for %s\n",
                                             of_node_full_name(dev));

                if (old_fw && reg != 0) {
                        dev_warn(mux_dev,
                                 "Mux is not supported, please update Mule FW\n");
                        continue;
                }

                ret = mule_i2c_mux_select(muxc, reg);
                if (ret) {
                        dev_warn(mux_dev,
                                 "Device %d not supported, please update Mule FW\n", reg);
                        continue;
                }

                ret = i2c_mux_add_adapter(muxc, 0, reg);
                if (ret)
                        return ret;
        }

        mule_i2c_mux_deselect(muxc, MULE_I2C_MUX_DEFAULT_DEV);

        return 0;
}

static const struct of_device_id mule_i2c_mux_of_match[] = {
        { .compatible = "tsd,mule-i2c-mux", },
        {},
};
MODULE_DEVICE_TABLE(of, mule_i2c_mux_of_match);

static struct platform_driver mule_i2c_mux_driver = {
        .driver = {
                .name   = "mule-i2c-mux",
                .of_match_table = mule_i2c_mux_of_match,
        },
        .probe          = mule_i2c_mux_probe,
};

module_platform_driver(mule_i2c_mux_driver);

MODULE_AUTHOR("Farouk Bouabid <farouk.bouabid@cherry.de>");
MODULE_DESCRIPTION("I2C mux driver for Theobroma Systems Mule");
MODULE_LICENSE("GPL");