The AC200 companion IC exposes a paged, big-endian 16-bit I2C register
interface shared by its functions. Its Ethernet PHY is described
independently on MDIO and accesses the control registers through a
phandle to this provider.

Enable the required input clock, retain the vendor startup delay, and
deassert the chip reset without resetting unrelated functions. Provide
typed register accessors and report clock and I2C failures to consumers.
Select the page on every access so direct I2C commands cannot invalidate
a software page cache.

Signed-off-by: James Hilliard <[email protected]>
---
 MAINTAINERS                  |   6 ++
 drivers/misc/Kconfig         |   9 +++
 drivers/misc/Makefile        |   1 +
 drivers/misc/xpowers-ac200.c | 148 +++++++++++++++++++++++++++++++++++++++++++
 include/xpowers-ac200.h      |  34 ++++++++++
 5 files changed, 198 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index e724357419b..a3a7a121066 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1918,6 +1918,12 @@ F:       include/xen.h
 F:     lib/sscanf.c
 F:     test/lib/sscanf.c
 
+X-POWERS AC200
+M:     James Hilliard <[email protected]>
+S:     Maintained
+F:     drivers/misc/xpowers-ac200.c
+F:     include/xpowers-ac200.h
+
 XTENSA
 M:     Max Filippov <[email protected]>
 S:     Maintained
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6f6456120d0..d3b2cc12edd 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -746,6 +746,15 @@ config SL28CPLD
          the base driver which provides common access methods for the
          sub-drivers.
 
+config XPOWERS_AC200
+       bool "X-Powers AC200 register provider"
+       depends on DM_I2C && CLK
+       depends on MISC
+       help
+         Enable the I2C register provider for the X-Powers AC200 mixed-signal
+         companion IC. Function drivers use the exported accessors to reach
+         the AC200 paged register map.
+
 config SPL_SOCFPGA_DT_REG
        bool "Enable register setting from device tree in SPL"
        depends on SPL
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 3ec2bc2b201..128159c12ee 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -87,6 +87,7 @@ obj-$(CONFIG_$(PHASE_)TURRIS_OMNIA_MCU) += turris_omnia_mcu.o
 obj-$(CONFIG_TWL4030_LED) += twl4030_led.o
 obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress_config.o
 obj-$(CONFIG_WINBOND_W83627) += winbond_w83627.o
+obj-$(CONFIG_XPOWERS_AC200) += xpowers-ac200.o
 obj-$(CONFIG_JZ4780_EFUSE) += jz4780_efuse.o
 obj-$(CONFIG_MICROCHIP_FLEXCOM) += microchip_flexcom.o
 obj-$(CONFIG_MPFS_SYSCONTROLLER) += mpfs_syscontroller.o
diff --git a/drivers/misc/xpowers-ac200.c b/drivers/misc/xpowers-ac200.c
new file mode 100644
index 00000000000..cfcc5ba4958
--- /dev/null
+++ b/drivers/misc/xpowers-ac200.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * X-Powers AC200 I2C register provider
+ *
+ * Copyright (C) 2019 Jernej Skrabec <[email protected]>
+ * Copyright (C) 2026 James Hilliard <[email protected]>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <i2c.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <asm/unaligned.h>
+#include <xpowers-ac200.h>
+
+#define AC200_SYS_CONTROL_REG                  0x0002
+#define AC200_SYS_CONTROL_CHIP_RESET_DEASSERT  BIT(0)
+
+#define AC200_TWI_REG_ADDR_H                   0xfe
+#define AC200_MAX_REG                          0xa1f2
+
+struct ac200_priv {
+       struct clk clk;
+};
+
+static int ac200_select_page(struct udevice *dev, unsigned int page)
+{
+       u8 buf[2];
+
+       /* Select explicitly: an I2C command can also change the hardware page. 
*/
+       put_unaligned_be16(page, buf);
+       return dm_i2c_write(dev, AC200_TWI_REG_ADDR_H, buf, sizeof(buf));
+}
+
+int ac200_read(struct udevice *dev, unsigned int reg, u16 *value)
+{
+       u8 buf[2];
+       int ret;
+
+       if (!value || reg > AC200_MAX_REG || reg & 1)
+               return -EINVAL;
+
+       ret = ac200_select_page(dev, reg >> 8);
+       if (ret)
+               return ret;
+
+       ret = dm_i2c_read(dev, reg & 0xff, buf, sizeof(buf));
+       if (ret)
+               return ret;
+
+       *value = get_unaligned_be16(buf);
+
+       return 0;
+}
+
+int ac200_write(struct udevice *dev, unsigned int reg, u16 value)
+{
+       u8 buf[2];
+       int ret;
+
+       if (reg > AC200_MAX_REG || reg & 1)
+               return -EINVAL;
+
+       ret = ac200_select_page(dev, reg >> 8);
+       if (ret)
+               return ret;
+
+       put_unaligned_be16(value, buf);
+
+       return dm_i2c_write(dev, reg & 0xff, buf, sizeof(buf));
+}
+
+int ac200_update_bits(struct udevice *dev, unsigned int reg, u16 mask,
+                     u16 value)
+{
+       u16 old;
+       int ret;
+
+       ret = ac200_read(dev, reg, &old);
+       if (ret)
+               return ret;
+
+       value = (old & ~mask) | (value & mask);
+       return old == value ? 0 : ac200_write(dev, reg, value);
+}
+
+ulong ac200_get_clock_rate(struct udevice *dev)
+{
+       struct ac200_priv *priv = dev_get_priv(dev);
+
+       return clk_get_rate(&priv->clk);
+}
+
+static int ac200_probe(struct udevice *dev)
+{
+       struct ac200_priv *priv = dev_get_priv(dev);
+       int ret;
+
+       ret = clk_get_by_index(dev, 0, &priv->clk);
+       if (ret)
+               return ret;
+       ret = clk_enable(&priv->clk);
+       if (ret)
+               return ret;
+
+       /*
+        * No minimum delay is documented. Match the vendor driver's delay
+        * before its first register access after the input clock is enabled.
+        */
+       mdelay(40);
+
+       /* Release reset without resetting other functions of the companion IC. 
*/
+       ret = ac200_update_bits(dev, AC200_SYS_CONTROL_REG,
+                               AC200_SYS_CONTROL_CHIP_RESET_DEASSERT,
+                               AC200_SYS_CONTROL_CHIP_RESET_DEASSERT);
+       if (ret)
+               goto err_disable_clk;
+
+       return 0;
+
+err_disable_clk:
+       clk_disable(&priv->clk);
+
+       return ret;
+}
+
+static int ac200_remove(struct udevice *dev)
+{
+       struct ac200_priv *priv = dev_get_priv(dev);
+
+       return clk_disable(&priv->clk);
+}
+
+static const struct udevice_id ac200_ids[] = {
+       { .compatible = "x-powers,ac200" },
+       { }
+};
+
+U_BOOT_DRIVER(xpowers_ac200) = {
+       .name = "xpowers_ac200",
+       .id = UCLASS_MISC,
+       .of_match = ac200_ids,
+       .probe = ac200_probe,
+       .remove = ac200_remove,
+       .priv_auto = sizeof(struct ac200_priv),
+};
diff --git a/include/xpowers-ac200.h b/include/xpowers-ac200.h
new file mode 100644
index 00000000000..6aa4bde6f59
--- /dev/null
+++ b/include/xpowers-ac200.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef __XPOWERS_AC200_H
+#define __XPOWERS_AC200_H
+
+#include <linux/errno.h>
+#include <linux/kconfig.h>
+#include <linux/types.h>
+
+struct udevice;
+
+#if IS_ENABLED(CONFIG_XPOWERS_AC200)
+int ac200_read(struct udevice *dev, unsigned int reg, u16 *value);
+int ac200_write(struct udevice *dev, unsigned int reg, u16 value);
+int ac200_update_bits(struct udevice *dev, unsigned int reg, u16 mask,
+                     u16 value);
+ulong ac200_get_clock_rate(struct udevice *dev);
+#else
+static inline int ac200_read(struct udevice *dev, unsigned int reg, u16 *value)
+{
+       return -ENOSYS;
+}
+
+static inline int ac200_write(struct udevice *dev, unsigned int reg, u16 value)
+{
+       return -ENOSYS;
+}
+
+static inline ulong ac200_get_clock_rate(struct udevice *dev)
+{
+       return -ENOSYS;
+}
+#endif
+
+#endif

-- 
2.53.0

Reply via email to