xref: /linux/drivers/misc/amd-sbi/tsi.c (revision ba27ea7abd35d260e3b489f985ec2b4113c0fd85)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * tsi.c - AMD SBTSI I2C core driver. Probes the SBTSI device over I2C
4  *         and publishes an auxiliary device on the auxiliary bus.
5  *
6  * Copyright (C) 2026 Advanced Micro Devices, Inc.
7  */
8 
9 #include <linux/auxiliary_bus.h>
10 #include <linux/bitfield.h>
11 #include <linux/module.h>
12 #include <linux/of.h>
13 #include <linux/misc/tsi.h>
14 #include <linux/slab.h>
15 
16 #define SBTSI_REG_CONFIG		0x03 /* RO */
17 
18 /*
19  * Bit for reporting value with temperature measurement range.
20  * bit == 0: Use default temperature range (0C to 255.875C).
21  * bit == 1: Use extended temperature range (-49C to +206.875C).
22  */
23 #define SBTSI_CONFIG_EXT_RANGE_SHIFT	2
24 
25 /*
26  * ReadOrder bit specifies the reading order of integer and decimal part of
27  * CPU temperature for atomic reads. If bit == 0, reading integer part triggers
28  * latching of the decimal part, so integer part should be read first.
29  */
30 #define SBTSI_CONFIG_READ_ORDER_SHIFT	5
31 
32 static void sbtsi_adev_release(struct device *dev)
33 {
34 	kfree(to_auxiliary_dev(dev));
35 }
36 
37 static void sbtsi_unregister_hwmon_adev(void *_adev)
38 {
39 	struct auxiliary_device *adev = _adev;
40 
41 	auxiliary_device_delete(adev);
42 	auxiliary_device_uninit(adev);
43 }
44 
45 /*
46  * Create and publish an auxiliary device. The hwmon driver in
47  * drivers/hwmon/sbtsi_temp.c binds to this device.
48  *
49  * @dev:      I2C device (parent of the auxiliary device)
50  * @dev_addr: I2C address — used as the auxiliary device instance ID so that
51  *            each socket gets a unique name.
52  */
53 static int sbtsi_create_hwmon_adev(struct device *dev, u8 dev_addr)
54 {
55 	struct auxiliary_device *adev;
56 	int ret;
57 
58 	adev = kzalloc_obj(*adev);
59 	if (!adev)
60 		return -ENOMEM;
61 
62 	adev->name = AMD_SBTSI_AUX_HWMON;
63 	adev->id = dev_addr;
64 	adev->dev.parent = dev;
65 	adev->dev.release = sbtsi_adev_release;
66 
67 	ret = auxiliary_device_init(adev);
68 	if (ret) {
69 		kfree(adev);
70 		return ret;
71 	}
72 
73 	ret = __auxiliary_device_add(adev, AMD_SBTSI_ADEV);
74 	if (ret) {
75 		auxiliary_device_uninit(adev);
76 		return ret;
77 	}
78 
79 	return devm_add_action_or_reset(dev, sbtsi_unregister_hwmon_adev, adev);
80 }
81 
82 static int sbtsi_probe_common(struct device *dev, struct sbtsi_data *data)
83 {
84 	u8 val;
85 	int err;
86 
87 	err = sbtsi_xfer(data, SBTSI_REG_CONFIG, &val, true);
88 	if (err)
89 		return err;
90 
91 	data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), val);
92 	data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val);
93 
94 	dev_set_drvdata(dev, data);
95 	return sbtsi_create_hwmon_adev(dev, data->dev_addr);
96 }
97 
98 static int sbtsi_i2c_probe(struct i2c_client *client)
99 {
100 	struct device *dev = &client->dev;
101 	struct sbtsi_data *data;
102 
103 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
104 	if (!data)
105 		return -ENOMEM;
106 
107 	data->client = client;
108 
109 	/* In a multi-socket system, devices that are otherwise identical do not
110 	 * share the same static address; each instance resides at a unique I2C
111 	 * client address on the same or different bus. Use the I2C client
112 	 * address as the auxiliary device instance ID to ensure each socket
113 	 * receives a distinct auxiliary device name.
114 	 */
115 	data->dev_addr = client->addr;
116 	return sbtsi_probe_common(dev, data);
117 }
118 
119 static const struct i2c_device_id sbtsi_id[] = {
120 	{ .name = "sbtsi" },
121 	{ }
122 };
123 MODULE_DEVICE_TABLE(i2c, sbtsi_id);
124 
125 static const struct of_device_id __maybe_unused sbtsi_of_match[] = {
126 	{
127 		.compatible = "amd,sbtsi",
128 	},
129 	{ },
130 };
131 MODULE_DEVICE_TABLE(of, sbtsi_of_match);
132 
133 static struct i2c_driver sbtsi_driver = {
134 	.driver = {
135 		.name = "sbtsi-i2c",
136 		.of_match_table = of_match_ptr(sbtsi_of_match),
137 	},
138 	.probe = sbtsi_i2c_probe,
139 	.id_table = sbtsi_id,
140 };
141 
142 module_i2c_driver(sbtsi_driver);
143 
144 MODULE_DESCRIPTION("AMD SB-TSI I2C core driver");
145 MODULE_LICENSE("GPL");
146