[PATCH 09/13] i2c: add AD24xx I2C controller driver

Alvin Šipraga
2024-05-17T13:15:14Z

From: Alvin Šipraga <alsi@bang-olufsen.dk>

Signed-off-by: Alvin Šipraga <alsi@bang-olufsen.dk>
---
drivers/a2b/Kconfig | 1 +
drivers/clk/Kconfig | 2 +-
drivers/i2c/busses/Kconfig | 7 +++
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-ad24xx.c | 121 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 131 insertions(+), 1 deletion(-)

diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
index 08acf5728023..e3c38520a90a 100644
--- a/drivers/a2b/Kconfig
+++ b/drivers/a2b/Kconfig
@@ -36,6 +36,7 @@ config A2B_AD24XX_NODE
imply GPIO_AD24XX
imply SND_SOC_AD24XX
imply COMMON_CLK_AD24XX
+ imply I2C_AD24XX
help
Say Y here to enable support for AD24xx A2B transceiver nodes. This
applies to both main nodes and subordinate nodes. Supported models
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a3d54b077e68..460762f44434 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -258,7 +258,7 @@ config COMMON_CLK_LAN966X
within the SoC.

config COMMON_CLK_AD24XX
- bool "Clock driver for Analog Devices Inc. AD24xx"
+ tristate "Clock driver for Analog Devices Inc. AD24xx"
depends on A2B_AD24XX_NODE
help
This driver supports the clock output functionality of AD24xx series
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index fe6e8a1bb607..d1f303bd7c90 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1387,6 +1387,13 @@ config I2C_ACORN

If you don't know, say Y.

+config I2C_AD24XX
+ tristate "Analog Devices Inc. AD24xx I2C controller support"
+ depends on A2B_AD24XX_NODE
+ help
+ Say yes if you want to support the I2C controller function of AD24xx
+ A2B transceiver chips.
+
config I2C_ELEKTOR
tristate "Elektor ISA card"
depends on ISA && HAS_IOPORT_MAP && BROKEN_ON_SMP
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3d65934f5eb4..892a32b02267 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -145,6 +145,7 @@ obj-$(CONFIG_I2C_VIPERBOARD) += i2c-viperboard.o

# Other I2C/SMBus bus drivers
obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o
+obj-$(CONFIG_I2C_AD24XX) += i2c-ad24xx.o
obj-$(CONFIG_I2C_BCM_KONA) += i2c-bcm-kona.o
obj-$(CONFIG_I2C_BRCMSTB) += i2c-brcmstb.o
obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += i2c-cros-ec-tunnel.o
diff --git a/drivers/i2c/busses/i2c-ad24xx.c b/drivers/i2c/busses/i2c-ad24xx.c
new file mode 100644
index 000000000000..ad9657df25fb
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ad24xx.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD24xx I2C controller (master) driver
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <alsi@bang-olufsen.dk>
+ */
+
+#include <linux/a2b/a2b.h>
+#include <linux/a2b/ad24xx.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+
+struct ad24xx_i2c_adapter {
+ struct device *dev;
+ struct a2b_func *func;
+ struct a2b_node *node;
+ struct i2c_adapter adap;
+};
+
+static int ad24xx_i2c_adapter_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct ad24xx_i2c_adapter *ada = i2c_get_adapdata(adap);
+ struct a2b_node *node = ada->node;
+
+ return a2b_node_i2c_xfer(node, msgs, num);
+}
+
+static u32 ad24xx_i2c_adapter_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_adapter_quirks ad24xx_i2c_adapter_quirks = {
+ .flags = I2C_AQ_COMB | I2C_AQ_COMB_SAME_ADDR,
+};
+
+static const struct i2c_algorithm ad24xx_i2c_adapter_algo = {
+ .master_xfer = ad24xx_i2c_adapter_xfer,
+ .functionality = ad24xx_i2c_adapter_functionality,
+};
+
+static int ad24xx_i2c_adapter_probe(struct device *dev)
+{
+ struct a2b_func *func = to_a2b_func(dev);
+ struct device_node *np = dev->of_node;
+ struct ad24xx_i2c_adapter *ada;
+ unsigned int val = 0;
+ u32 bus_speed;
+ int ret;
+
+ ada = devm_kzalloc(dev, sizeof(*ada), GFP_KERNEL);
+ if (!ada)
+ return -ENOMEM;
+
+ ada->dev = dev;
+ ada->func = func;
+ ada->node = func->node;
+
+ ada->adap.owner = THIS_MODULE;
+ ada->adap.algo = &ad24xx_i2c_adapter_algo;
+ ada->adap.dev.parent = dev;
+ ada->adap.dev.of_node = dev->of_node;
+ ada->adap.quirks = &ad24xx_i2c_adapter_quirks;
+ strscpy(ada->adap.name, dev_name(dev), sizeof(ada->adap.name));
+ i2c_set_adapdata(&ada->adap, ada);
+
+ ret = of_property_read_u32(np, "clock-frequency", &bus_speed);
+ if (ret)
+ bus_speed = I2C_MAX_STANDARD_MODE_FREQ;
+
+ if (bus_speed != I2C_MAX_STANDARD_MODE_FREQ &&
+ bus_speed != I2C_MAX_FAST_MODE_FREQ)
+ return -EINVAL;
+
+ val |= FIELD_PREP(A2B_I2CCFG_DATARATE_MASK,
+ bus_speed == I2C_MAX_FAST_MODE_FREQ ? 1 : 0);
+ val |= FIELD_PREP(A2B_I2CCFG_FRAMERATE_MASK,
+ func->node->bus->sff == A2B_SFF_44100 ? 1 : 0);
+
+ ret = a2b_node_write(func->node, A2B_I2CCFG, val);
+ if (ret)
+ return ret;
+
+ ret = devm_i2c_add_adapter(dev, &ada->adap);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id ad24xx_i2c_adapter_of_match_table[] = {
+ { .compatible = "adi,ad2401-i2c" },
+ { .compatible = "adi,ad2402-i2c" },
+ { .compatible = "adi,ad2403-i2c" },
+ { .compatible = "adi,ad2410-i2c" },
+ { .compatible = "adi,ad2420-i2c" },
+ { .compatible = "adi,ad2421-i2c" },
+ { .compatible = "adi,ad2422-i2c" },
+ { .compatible = "adi,ad2425-i2c" },
+ { .compatible = "adi,ad2426-i2c" },
+ { .compatible = "adi,ad2427-i2c" },
+ { .compatible = "adi,ad2428-i2c" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ad24xx_i2c_adapter_of_match_table);
+
+static struct a2b_driver ad24xx_i2c_adapter_driver = {
+ .driver = {
+ .name = "ad24xx-i2c-adapter",
+ .of_match_table = ad24xx_i2c_adapter_of_match_table,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = ad24xx_i2c_adapter_probe,
+};
+module_a2b_driver(ad24xx_i2c_adapter_driver);
+
+MODULE_AUTHOR("Alvin Šipraga <alsi@bang-olufsen.dk>");
+MODULE_DESCRIPTION("AD24xx I2C controller driver");
+MODULE_LICENSE("GPL");

--
2.44.0