Messages in this thread Patch in this message |  | | From | Salih Erim <> | | Subject | [PATCH v2 3/5] iio: adc: versal-sysmon: add I2C driver | | Date | Sat, 2 May 2026 12:19:49 +0100 |
| |
Add I2C bus driver for Versal SysMon to enable voltage and temperature monitoring when the Versal chip has SysMon configured with an I2C interface.
The I2C protocol uses a custom 8-byte write format: - Bytes 0-3: Data (little-endian, 32-bit) - Bytes 4-5: Register offset (split into low/high parts) - Byte 6: Instruction (read=0x4, write=0x8) - Byte 7: Reserved
For reads, the driver sends the 8-byte command then receives 4 bytes of data. For writes, it sends the 8-byte command with embedded data.
The driver uses the regmap API with custom read/write callbacks to share the bus-agnostic core driver (versal-sysmon-core).
Event support is not available on I2C since the SysMon interrupt lines are not routed over the I2C bus.
Co-developed-by: Conall O'Griofa <conall.ogriofa@amd.com> Signed-off-by: Conall O'Griofa <conall.ogriofa@amd.com> Signed-off-by: Salih Erim <salih.erim@amd.com> --- Changes in v2: - New patch (I2C was deferred to Series B in v1) - Uses regmap API with custom I2C read/write callbacks - Shares core module with MMIO driver via sysmon_core_probe() - No event support (I2C has no interrupt line) - Separate VERSAL_SYSMON_I2C Kconfig symbol - Reverse Christmas Tree variable ordering in read/write functions drivers/iio/adc/Kconfig | 13 +++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/versal-sysmon-i2c.c | 166 ++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 drivers/iio/adc/versal-sysmon-i2c.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index c7f19057484..8f9fc9de74a 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1963,6 +1963,19 @@ config VERSAL_SYSMON To compile this driver as a module, choose M here: the module will be called versal-sysmon. +config VERSAL_SYSMON_I2C + tristate "AMD Versal SysMon I2C driver" + depends on I2C + select VERSAL_SYSMON_CORE + help + Say yes here to have support for the AMD/Xilinx Versal System + Monitor (SysMon) via I2C interface. This driver enables voltage + and temperature monitoring when the Versal chip has SysMon + configured with I2C access. + + To compile this driver as a module, choose M here: the module + will be called versal-sysmon-i2c. + config VF610_ADC tristate "Freescale vf610 ADC driver" depends on HAS_IOMEM diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d7696b1b157..5abb611fe46 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -169,6 +169,7 @@ obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o obj-$(CONFIG_VERSAL_SYSMON_CORE) += versal-sysmon-core.o obj-$(CONFIG_VERSAL_SYSMON) += versal-sysmon.o +obj-$(CONFIG_VERSAL_SYSMON_I2C) += versal-sysmon-i2c.o obj-$(CONFIG_VF610_ADC) += vf610_adc.o obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o diff --git a/drivers/iio/adc/versal-sysmon-i2c.c b/drivers/iio/adc/versal-sysmon-i2c.c new file mode 100644 index 00000000000..0464b356bbf --- /dev/null +++ b/drivers/iio/adc/versal-sysmon-i2c.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Versal SysMon I2C driver + * + * Copyright (C) 2023 - 2026, Advanced Micro Devices, Inc. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "versal-sysmon.h" + +#define SYSMON_I2C_READ_SIZE 4 +#define SYSMON_I2C_WRITE_SIZE 8 + +#define SYSMON_I2C_INSTR_READ BIT(2) +#define SYSMON_I2C_INSTR_WRITE BIT(3) + +#define SYSMON_I2C_DATA0_MASK GENMASK(7, 0) +#define SYSMON_I2C_DATA1_MASK GENMASK(15, 8) +#define SYSMON_I2C_DATA2_MASK GENMASK(23, 16) +#define SYSMON_I2C_DATA3_MASK GENMASK(31, 24) + +#define SYSMON_I2C_OFS_LOW_MASK GENMASK(9, 2) +#define SYSMON_I2C_OFS_HIGH_MASK GENMASK(15, 10) + +enum sysmon_i2c_payload_idx { + SYSMON_I2C_DATA0_IDX = 0, + SYSMON_I2C_DATA1_IDX, + SYSMON_I2C_DATA2_IDX, + SYSMON_I2C_DATA3_IDX, + SYSMON_I2C_OFS_LOW_IDX, + SYSMON_I2C_OFS_HIGH_IDX, + SYSMON_I2C_INSTR_IDX, +}; + +struct sysmon_i2c { + struct i2c_client *client; +}; + +static int sysmon_i2c_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + u8 write_buf[SYSMON_I2C_WRITE_SIZE] = { 0 }; + u8 read_buf[SYSMON_I2C_READ_SIZE]; + struct sysmon_i2c *priv = context; + int ret; + + write_buf[SYSMON_I2C_OFS_LOW_IDX] = + FIELD_GET(SYSMON_I2C_OFS_LOW_MASK, reg); + write_buf[SYSMON_I2C_OFS_HIGH_IDX] = + FIELD_GET(SYSMON_I2C_OFS_HIGH_MASK, reg); + write_buf[SYSMON_I2C_INSTR_IDX] = SYSMON_I2C_INSTR_READ; + + ret = i2c_master_send(priv->client, write_buf, SYSMON_I2C_WRITE_SIZE); + if (ret < 0) + return ret; + if (ret != SYSMON_I2C_WRITE_SIZE) + return -EIO; + + ret = i2c_master_recv(priv->client, read_buf, SYSMON_I2C_READ_SIZE); + if (ret < 0) + return ret; + if (ret != SYSMON_I2C_READ_SIZE) + return -EIO; + + *val = FIELD_PREP(SYSMON_I2C_DATA0_MASK, + read_buf[SYSMON_I2C_DATA0_IDX]) | + FIELD_PREP(SYSMON_I2C_DATA1_MASK, + read_buf[SYSMON_I2C_DATA1_IDX]) | + FIELD_PREP(SYSMON_I2C_DATA2_MASK, + read_buf[SYSMON_I2C_DATA2_IDX]) | + FIELD_PREP(SYSMON_I2C_DATA3_MASK, + read_buf[SYSMON_I2C_DATA3_IDX]); + + return 0; +} + +static int sysmon_i2c_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + u8 write_buf[SYSMON_I2C_WRITE_SIZE] = { 0 }; + struct sysmon_i2c *priv = context; + int ret; + + write_buf[SYSMON_I2C_DATA0_IDX] = + FIELD_GET(SYSMON_I2C_DATA0_MASK, val); + write_buf[SYSMON_I2C_DATA1_IDX] = + FIELD_GET(SYSMON_I2C_DATA1_MASK, val); + write_buf[SYSMON_I2C_DATA2_IDX] = + FIELD_GET(SYSMON_I2C_DATA2_MASK, val); + write_buf[SYSMON_I2C_DATA3_IDX] = + FIELD_GET(SYSMON_I2C_DATA3_MASK, val); + write_buf[SYSMON_I2C_OFS_LOW_IDX] = + FIELD_GET(SYSMON_I2C_OFS_LOW_MASK, reg); + write_buf[SYSMON_I2C_OFS_HIGH_IDX] = + FIELD_GET(SYSMON_I2C_OFS_HIGH_MASK, reg); + write_buf[SYSMON_I2C_INSTR_IDX] = SYSMON_I2C_INSTR_WRITE; + + ret = i2c_master_send(priv->client, write_buf, SYSMON_I2C_WRITE_SIZE); + if (ret < 0) + return ret; + if (ret != SYSMON_I2C_WRITE_SIZE) + return -EIO; + + return 0; +} + +static const struct regmap_config sysmon_i2c_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = SYSMON_REG_STRIDE, + .max_register = SYSMON_MAX_REG, + .reg_read = sysmon_i2c_reg_read, + .reg_write = sysmon_i2c_reg_write, +}; + +static int sysmon_i2c_probe(struct i2c_client *client) +{ + struct sysmon_i2c *priv; + struct regmap *regmap; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + regmap = devm_regmap_init(&client->dev, NULL, priv, + &sysmon_i2c_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* I2C has no IRQ connection; events are not supported */ + return sysmon_core_probe(&client->dev, regmap, 0); +} + +static const struct of_device_id sysmon_i2c_of_match_table[] = { + { .compatible = "xlnx,versal-sysmon-i2c" }, + { } +}; +MODULE_DEVICE_TABLE(of, sysmon_i2c_of_match_table); + +static const struct i2c_device_id sysmon_i2c_id_table[] = { + { "versal-sysmon" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sysmon_i2c_id_table); + +static struct i2c_driver sysmon_i2c_driver = { + .probe = sysmon_i2c_probe, + .driver = { + .name = "versal-sysmon-i2c", + .of_match_table = sysmon_i2c_of_match_table, + }, + .id_table = sysmon_i2c_id_table, +}; +module_i2c_driver(sysmon_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("AMD Versal SysMon I2C Driver"); +MODULE_AUTHOR("Conall O'Griofa <conall.ogriofa@amd.com>"); +MODULE_AUTHOR("Salih Erim <salih.erim@amd.com>"); -- 2.48.1
|  |