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