// SPDX-License-Identifier: GPL-2.0 /* * Theobroma Systems Mule I2C device multiplexer * * Copyright (C) 2024 Theobroma Systems Design und Consulting GmbH */ #include #include #include #include #include #include #include #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; struct device_node *dev; 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(mux_dev->of_node, dev) { u32 reg; ret = of_property_read_u32(dev, "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 "); MODULE_DESCRIPTION("I2C mux driver for Theobroma Systems Mule"); MODULE_LICENSE("GPL");