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