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