summaryrefslogtreecommitdiff
path: root/drivers/i2c/busses/i2c-designware-amdpsp.c
blob: 63454b06e5da17b648c6db3e28d4a6b76b2977f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// SPDX-License-Identifier: GPL-2.0

#include <linux/i2c.h>
#include <linux/pci.h>
#include <linux/psp-platform-access.h>
#include <linux/psp.h>
#include <linux/workqueue.h>

#include "i2c-designware-core.h"

#define PSP_I2C_RESERVATION_TIME_MS 100

#define PSP_I2C_REQ_RETRY_CNT		400
#define PSP_I2C_REQ_RETRY_DELAY_US	(25 * USEC_PER_MSEC)
#define PSP_I2C_REQ_STS_OK		0x0
#define PSP_I2C_REQ_STS_BUS_BUSY	0x1
#define PSP_I2C_REQ_STS_INV_PARAM	0x3

enum psp_i2c_req_type {
	PSP_I2C_REQ_ACQUIRE,
	PSP_I2C_REQ_RELEASE,
	PSP_I2C_REQ_MAX
};

struct psp_i2c_req {
	struct psp_req_buffer_hdr hdr;
	enum psp_i2c_req_type type;
};

static DEFINE_MUTEX(psp_i2c_access_mutex);
static unsigned long psp_i2c_sem_acquired;
static u32 psp_i2c_access_count;
static bool psp_i2c_mbox_fail;
static struct device *psp_i2c_dev;

static int (*_psp_send_i2c_req)(struct psp_i2c_req *req);

/* Helper to verify status returned by PSP */
static int check_i2c_req_sts(struct psp_i2c_req *req)
{
	u32 status;

	/* Status field in command-response buffer is updated by PSP */
	status = READ_ONCE(req->hdr.status);

	switch (status) {
	case PSP_I2C_REQ_STS_OK:
		return 0;
	case PSP_I2C_REQ_STS_BUS_BUSY:
		return -EBUSY;
	case PSP_I2C_REQ_STS_INV_PARAM:
	default:
		return -EIO;
	}
}

/*
 * Errors in x86-PSP i2c-arbitration protocol may occur at two levels:
 * 1. mailbox communication - PSP is not operational or some IO errors with
 *    basic communication had happened.
 * 2. i2c-requests - PSP refuses to grant i2c arbitration to x86 for too long.
 *
 * In order to distinguish between these in error handling code all mailbox
 * communication errors on the first level (from CCP symbols) will be passed
 * up and if -EIO is returned the second level will be checked.
 */
static int psp_send_i2c_req_cezanne(struct psp_i2c_req *req)
{
	int ret;

	ret = psp_send_platform_access_msg(PSP_I2C_REQ_BUS_CMD, (struct psp_request *)req);
	if (ret == -EIO)
		return check_i2c_req_sts(req);

	return ret;
}

static int psp_send_i2c_req_doorbell(struct psp_i2c_req *req)
{
	int ret;

	ret = psp_ring_platform_doorbell(req->type, &req->hdr.status);
	if (ret == -EIO)
		return check_i2c_req_sts(req);

	return ret;
}

static int psp_send_i2c_req(enum psp_i2c_req_type i2c_req_type)
{
	struct psp_i2c_req *req;
	unsigned long start;
	int status, ret;

	/* Allocate command-response buffer */
	req = kzalloc(sizeof(*req), GFP_KERNEL);
	if (!req)
		return -ENOMEM;

	req->hdr.payload_size = sizeof(*req);
	req->type = i2c_req_type;

	start = jiffies;
	ret = read_poll_timeout(_psp_send_i2c_req, status,
				(status != -EBUSY),
				PSP_I2C_REQ_RETRY_DELAY_US,
				PSP_I2C_REQ_RETRY_CNT * PSP_I2C_REQ_RETRY_DELAY_US,
				0, req);
	if (ret) {
		dev_err(psp_i2c_dev, "Timed out waiting for PSP to %s I2C bus\n",
			(i2c_req_type == PSP_I2C_REQ_ACQUIRE) ?
			"release" : "acquire");
		goto cleanup;
	}

	ret = status;
	if (ret) {
		dev_err(psp_i2c_dev, "PSP communication error\n");
		goto cleanup;
	}

	dev_dbg(psp_i2c_dev, "Request accepted by PSP after %ums\n",
		jiffies_to_msecs(jiffies - start));

cleanup:
	if (ret) {
		dev_err(psp_i2c_dev, "Assume i2c bus is for exclusive host usage\n");
		psp_i2c_mbox_fail = true;
	}

	kfree(req);
	return ret;
}

static void release_bus(void)
{
	int status;

	if (!psp_i2c_sem_acquired)
		return;

	status = psp_send_i2c_req(PSP_I2C_REQ_RELEASE);
	if (status)
		return;

	dev_dbg(psp_i2c_dev, "PSP semaphore held for %ums\n",
		jiffies_to_msecs(jiffies - psp_i2c_sem_acquired));

	psp_i2c_sem_acquired = 0;
}

static void psp_release_i2c_bus_deferred(struct work_struct *work)
{
	mutex_lock(&psp_i2c_access_mutex);

	/*
	 * If there is any pending transaction, cannot release the bus here.
	 * psp_release_i2c_bus will take care of this later.
	 */
	if (psp_i2c_access_count)
		goto cleanup;

	release_bus();

cleanup:
	mutex_unlock(&psp_i2c_access_mutex);
}
static DECLARE_DELAYED_WORK(release_queue, psp_release_i2c_bus_deferred);

static int psp_acquire_i2c_bus(void)
{
	int status;

	mutex_lock(&psp_i2c_access_mutex);

	/* Return early if mailbox malfunctioned */
	if (psp_i2c_mbox_fail)
		goto cleanup;

	psp_i2c_access_count++;

	/*
	 * No need to request bus arbitration once we are inside semaphore
	 * reservation period.
	 */
	if (psp_i2c_sem_acquired)
		goto cleanup;

	status = psp_send_i2c_req(PSP_I2C_REQ_ACQUIRE);
	if (status)
		goto cleanup;

	psp_i2c_sem_acquired = jiffies;

	schedule_delayed_work(&release_queue,
			      msecs_to_jiffies(PSP_I2C_RESERVATION_TIME_MS));

	/*
	 * In case of errors with PSP arbitrator psp_i2c_mbox_fail variable is
	 * set above. As a consequence consecutive calls to acquire will bypass
	 * communication with PSP. At any case i2c bus is granted to the caller,
	 * thus always return success.
	 */
cleanup:
	mutex_unlock(&psp_i2c_access_mutex);
	return 0;
}

static void psp_release_i2c_bus(void)
{
	mutex_lock(&psp_i2c_access_mutex);

	/* Return early if mailbox was malfunctional */
	if (psp_i2c_mbox_fail)
		goto cleanup;

	/*
	 * If we are last owner of PSP semaphore, need to release aribtration
	 * via mailbox.
	 */
	psp_i2c_access_count--;
	if (psp_i2c_access_count)
		goto cleanup;

	/*
	 * Send a release command to PSP if the semaphore reservation timeout
	 * elapsed but x86 still owns the controller.
	 */
	if (!delayed_work_pending(&release_queue))
		release_bus();

cleanup:
	mutex_unlock(&psp_i2c_access_mutex);
}

/*
 * Locking methods are based on the default implementation from
 * drivers/i2c/i2c-core-base.c, but with psp acquire and release operations
 * added. With this in place we can ensure that i2c clients on the bus shared
 * with psp are able to lock HW access to the bus for arbitrary number of
 * operations - that is e.g. write-wait-read.
 */
static void i2c_adapter_dw_psp_lock_bus(struct i2c_adapter *adapter,
					unsigned int flags)
{
	psp_acquire_i2c_bus();
	rt_mutex_lock_nested(&adapter->bus_lock, i2c_adapter_depth(adapter));
}

static int i2c_adapter_dw_psp_trylock_bus(struct i2c_adapter *adapter,
					  unsigned int flags)
{
	int ret;

	ret = rt_mutex_trylock(&adapter->bus_lock);
	if (ret)
		return ret;

	psp_acquire_i2c_bus();

	return ret;
}

static void i2c_adapter_dw_psp_unlock_bus(struct i2c_adapter *adapter,
					  unsigned int flags)
{
	psp_release_i2c_bus();
	rt_mutex_unlock(&adapter->bus_lock);
}

static const struct i2c_lock_operations i2c_dw_psp_lock_ops = {
	.lock_bus = i2c_adapter_dw_psp_lock_bus,
	.trylock_bus = i2c_adapter_dw_psp_trylock_bus,
	.unlock_bus = i2c_adapter_dw_psp_unlock_bus,
};

int i2c_dw_amdpsp_probe_lock_support(struct dw_i2c_dev *dev)
{
	struct pci_dev *rdev;

	if (!IS_REACHABLE(CONFIG_CRYPTO_DEV_CCP_DD))
		return -ENODEV;

	if (!dev)
		return -ENODEV;

	if (!(dev->flags & ARBITRATION_SEMAPHORE))
		return -ENODEV;

	/* Allow to bind only one instance of a driver */
	if (psp_i2c_dev)
		return -EEXIST;

	/* Cezanne uses platform mailbox, Mendocino and later use doorbell */
	rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
	if (rdev->device == 0x1630)
		_psp_send_i2c_req = psp_send_i2c_req_cezanne;
	else
		_psp_send_i2c_req = psp_send_i2c_req_doorbell;
	pci_dev_put(rdev);

	if (psp_check_platform_access_status())
		return -EPROBE_DEFER;

	psp_i2c_dev = dev->dev;

	dev_info(psp_i2c_dev, "I2C bus managed by AMD PSP\n");

	/*
	 * Install global locking callbacks for adapter as well as internal i2c
	 * controller locks.
	 */
	dev->adapter.lock_ops = &i2c_dw_psp_lock_ops;
	dev->acquire_lock = psp_acquire_i2c_bus;
	dev->release_lock = psp_release_i2c_bus;

	return 0;
}