xref: /linux/drivers/misc/amd-sbi/tsi-core.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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