xref: /linux/drivers/iio/adc/versal-sysmon-i2c.c (revision 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * AMD Versal SysMon I2C driver
4  *
5  * Copyright (C) 2023 - 2026, Advanced Micro Devices, Inc.
6  */
7 
8 #include <linux/bits.h>
9 #include <linux/err.h>
10 #include <linux/i2c.h>
11 #include <linux/mod_devicetable.h>
12 #include <linux/module.h>
13 #include <linux/regmap.h>
14 #include <linux/types.h>
15 #include <linux/unaligned.h>
16 
17 #include "versal-sysmon.h"
18 
19 #define SYSMON_I2C_INSTR_READ	BIT(2)
20 #define SYSMON_I2C_INSTR_WRITE	BIT(3)
21 
22 /*
23  * I2C command frame layout (8 bytes):
24  *   [0..3] data payload (little-endian u32)
25  *   [4..5] register offset >> 2 (little-endian u16)
26  *   [6]    instruction (read/write)
27  *   [7]    reserved
28  */
29 #define SYSMON_I2C_DATA_OFS	0
30 #define SYSMON_I2C_REG_OFS	4
31 #define SYSMON_I2C_INSTR_OFS	6
32 
33 static int sysmon_i2c_reg_read(void *context, unsigned int reg,
34 			       unsigned int *val)
35 {
36 	struct i2c_client *client = context;
37 	u8 write_buf[8] = { };
38 	u8 read_buf[4];
39 	int ret;
40 
41 	put_unaligned_le16(reg >> 2, &write_buf[SYSMON_I2C_REG_OFS]);
42 	write_buf[SYSMON_I2C_INSTR_OFS] = SYSMON_I2C_INSTR_READ;
43 
44 	ret = i2c_master_send(client, write_buf, sizeof(write_buf));
45 	if (ret < 0)
46 		return ret;
47 	if (ret != sizeof(write_buf))
48 		return -EIO;
49 
50 	ret = i2c_master_recv(client, read_buf, sizeof(read_buf));
51 	if (ret < 0)
52 		return ret;
53 	if (ret != sizeof(read_buf))
54 		return -EIO;
55 
56 	*val = get_unaligned_le32(read_buf);
57 
58 	return 0;
59 }
60 
61 static int sysmon_i2c_reg_write(void *context, unsigned int reg,
62 				unsigned int val)
63 {
64 	struct i2c_client *client = context;
65 	u8 write_buf[8] = { };
66 	int ret;
67 
68 	put_unaligned_le32(val, &write_buf[SYSMON_I2C_DATA_OFS]);
69 	put_unaligned_le16(reg >> 2, &write_buf[SYSMON_I2C_REG_OFS]);
70 	write_buf[SYSMON_I2C_INSTR_OFS] = SYSMON_I2C_INSTR_WRITE;
71 
72 	ret = i2c_master_send(client, write_buf, sizeof(write_buf));
73 	if (ret < 0)
74 		return ret;
75 	if (ret != sizeof(write_buf))
76 		return -EIO;
77 
78 	return 0;
79 }
80 
81 /*
82  * Almost all registers are volatile (live ADC readings, interrupt
83  * status). The rest are not accessed often enough to benefit from
84  * caching.
85  */
86 static const struct regmap_config sysmon_i2c_regmap_config = {
87 	.reg_bits = 32,
88 	.val_bits = 32,
89 	.reg_stride = SYSMON_REG_STRIDE,
90 	.max_register = SYSMON_MAX_REG,
91 	.reg_read = sysmon_i2c_reg_read,
92 	.reg_write = sysmon_i2c_reg_write,
93 };
94 
95 static int sysmon_i2c_probe(struct i2c_client *client)
96 {
97 	struct device *dev = &client->dev;
98 	struct regmap *regmap;
99 
100 	regmap = devm_regmap_init(dev, NULL, client, &sysmon_i2c_regmap_config);
101 	if (IS_ERR(regmap))
102 		return PTR_ERR(regmap);
103 
104 	/* I2C has no IRQ connection; events are not supported */
105 	return devm_versal_sysmon_core_probe(dev, regmap);
106 }
107 
108 static const struct of_device_id sysmon_i2c_of_match_table[] = {
109 	{ .compatible = "xlnx,versal-sysmon" },
110 	{ }
111 };
112 MODULE_DEVICE_TABLE(of, sysmon_i2c_of_match_table);
113 
114 static const struct i2c_device_id sysmon_i2c_id_table[] = {
115 	{ .name = "versal-sysmon" },
116 	{ }
117 };
118 MODULE_DEVICE_TABLE(i2c, sysmon_i2c_id_table);
119 
120 static struct i2c_driver sysmon_i2c_driver = {
121 	.probe = sysmon_i2c_probe,
122 	.driver = {
123 		.name = "versal-sysmon-i2c",
124 		.of_match_table = sysmon_i2c_of_match_table,
125 	},
126 	.id_table = sysmon_i2c_id_table,
127 };
128 module_i2c_driver(sysmon_i2c_driver);
129 
130 MODULE_LICENSE("GPL");
131 MODULE_DESCRIPTION("AMD Versal SysMon I2C Driver");
132 MODULE_IMPORT_NS("VERSAL_SYSMON");
133 MODULE_AUTHOR("Conall O'Griofa <conall.ogriofa@amd.com>");
134 MODULE_AUTHOR("Salih Erim <salih.erim@amd.com>");
135