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
