1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * tsi-core.c - file defining SB-TSI protocols compliant 4 * AMD SoC device. 5 * 6 * Copyright (C) 2026 Advanced Micro Devices, Inc. 7 */ 8 9 #include <linux/fs.h> 10 #include <linux/ioctl.h> 11 #include <linux/module.h> 12 #include <linux/uaccess.h> 13 #include <uapi/misc/amd-apml.h> 14 #include "tsi-core.h" 15 16 static inline struct sbtsi_i3c_priv *to_sbtsi_i3c_priv(struct sbtsi_data *data) 17 { 18 return container_of(data, struct sbtsi_i3c_priv, data); 19 } 20 21 void sbtsi_data_release(struct kref *kref) 22 { 23 struct sbtsi_data *data = container_of(kref, struct sbtsi_data, kref); 24 25 mutex_destroy(&data->lock); 26 if (data->is_i3c) 27 kfree(to_sbtsi_i3c_priv(data)); 28 else 29 kfree(data); 30 } 31 32 /* I2C transfer function */ 33 static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) 34 { 35 if (is_read) { 36 int ret = i2c_smbus_read_byte_data(data->client, reg); 37 38 if (ret < 0) 39 return ret; 40 *val = ret; 41 return 0; 42 } 43 return i2c_smbus_write_byte_data(data->client, reg, *val); 44 } 45 46 /* I3C read transfer function */ 47 static int sbtsi_i3c_read(struct sbtsi_data *data, u8 reg, u8 *val) 48 { 49 struct sbtsi_i3c_priv *priv = to_sbtsi_i3c_priv(data); 50 struct i3c_xfer xfers[2] = { }; 51 int ret; 52 53 priv->tx[0] = reg; 54 55 /* Write the register address (DMA_TO_DEVICE). */ 56 xfers[0].rnw = false; 57 xfers[0].len = 1; 58 xfers[0].data.out = priv->tx; 59 60 /* Read the data byte into a separate buffer (DMA_FROM_DEVICE). */ 61 xfers[1].rnw = true; 62 xfers[1].len = 1; 63 xfers[1].data.in = &priv->rx; 64 65 ret = i3c_device_do_xfers(data->i3cdev, xfers, 2, I3C_SDR); 66 if (ret) 67 return ret; 68 69 *val = priv->rx; 70 return ret; 71 } 72 73 /* I3C write transfer function */ 74 static int sbtsi_i3c_write(struct sbtsi_data *data, u8 reg, u8 val) 75 { 76 struct sbtsi_i3c_priv *priv = to_sbtsi_i3c_priv(data); 77 struct i3c_xfer xfers = { 78 .rnw = false, 79 .len = 2, 80 .data.out = priv->tx, 81 }; 82 83 priv->tx[0] = reg; 84 priv->tx[1] = val; 85 86 return i3c_device_do_xfers(data->i3cdev, &xfers, 1, I3C_SDR); 87 } 88 89 /* Unified transfer function for I2C and I3C access */ 90 int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) 91 { 92 if (data->is_i3c) 93 return is_read ? sbtsi_i3c_read(data, reg, val) 94 : sbtsi_i3c_write(data, reg, *val); 95 return sbtsi_i2c_xfer(data, reg, val, is_read); 96 } 97 EXPORT_SYMBOL_GPL(sbtsi_xfer); 98 99 /* 100 * The mutex protects against concurrent register transfers to the device 101 * over the shared bus. 102 */ 103 static int sbtsi_xfer_ioctl(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read) 104 { 105 guard(sbtsi)(data); 106 107 if (data->detached) 108 return -ENODEV; 109 110 return sbtsi_xfer(data, reg, val, is_read); 111 } 112 113 static int apml_tsi_reg_xfer(struct sbtsi_data *data, 114 struct apml_tsi_xfer_msg __user *arg) 115 { 116 struct apml_tsi_xfer_msg msg = { 0 }; 117 int ret; 118 119 if (copy_from_user(&msg, arg, sizeof(struct apml_tsi_xfer_msg))) 120 return -EFAULT; 121 122 /* 123 * rflag is a boolean direction flag (0 = write, 1 = read). Reject 124 * any other value so the upper values stay reserved for future 125 * extensions instead of being silently treated as a read. 126 */ 127 if (msg.pad || msg.rflag > 1) 128 return -EINVAL; 129 130 ret = sbtsi_xfer_ioctl(data, msg.reg_addr, &msg.data_in_out, msg.rflag); 131 132 if (msg.rflag && !ret) { 133 if (copy_to_user(arg, &msg, sizeof(struct apml_tsi_xfer_msg))) 134 return -EFAULT; 135 } 136 return ret; 137 } 138 139 static int sbtsi_open(struct inode *inode, struct file *fp) 140 { 141 struct sbtsi_data *data; 142 143 data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev); 144 scoped_guard(sbtsi, data) { 145 if (data->detached) 146 return -ENODEV; 147 } 148 149 kref_get(&data->kref); 150 151 return 0; 152 } 153 154 static int sbtsi_release(struct inode *inode, struct file *fp) 155 { 156 struct sbtsi_data *data; 157 158 data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev); 159 kref_put(&data->kref, sbtsi_data_release); 160 return 0; 161 } 162 163 static long sbtsi_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) 164 { 165 void __user *argp = (void __user *)arg; 166 struct sbtsi_data *data; 167 168 data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev); 169 switch (cmd) { 170 case SBTSI_IOCTL_REG_XFER_CMD: 171 return apml_tsi_reg_xfer(data, argp); 172 default: 173 return -ENOTTY; 174 } 175 } 176 177 static const struct file_operations sbtsi_fops = { 178 .owner = THIS_MODULE, 179 .open = sbtsi_open, 180 .release = sbtsi_release, 181 .unlocked_ioctl = sbtsi_ioctl, 182 .compat_ioctl = compat_ptr_ioctl, 183 }; 184 185 int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev) 186 { 187 int ret; 188 189 data->sbtsi_misc_dev.name = devm_kasprintf(dev, GFP_KERNEL, 190 "sbtsi-%x", data->dev_addr); 191 if (!data->sbtsi_misc_dev.name) 192 return -ENOMEM; 193 data->sbtsi_misc_dev.minor = MISC_DYNAMIC_MINOR; 194 data->sbtsi_misc_dev.fops = &sbtsi_fops; 195 data->sbtsi_misc_dev.parent = dev; 196 data->sbtsi_misc_dev.nodename = devm_kasprintf(dev, GFP_KERNEL, 197 "sbtsi-%x", data->dev_addr); 198 if (!data->sbtsi_misc_dev.nodename) 199 return -ENOMEM; 200 data->sbtsi_misc_dev.mode = 0600; 201 202 ret = misc_register(&data->sbtsi_misc_dev); 203 if (ret) 204 return ret; 205 206 return 0; 207 } 208