summaryrefslogtreecommitdiffstats
path: root/drivers/i2c/busses/i2c-nvidia-gpu.c
blob: 62e18b4db0eda63db4c1401b9a734e3f8288b08f (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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// SPDX-License-Identifier: GPL-2.0
/*
 * Nvidia GPU I2C controller Driver
 *
 * Copyright (C) 2018 NVIDIA Corporation. All rights reserved.
 * Author: Ajay Gupta <ajayg@nvidia.com>
 */
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>

#include <asm/unaligned.h>

/* I2C definitions */
#define I2C_MST_CNTL				0x00
#define I2C_MST_CNTL_GEN_START			BIT(0)
#define I2C_MST_CNTL_GEN_STOP			BIT(1)
#define I2C_MST_CNTL_CMD_READ			(1 << 2)
#define I2C_MST_CNTL_CMD_WRITE			(2 << 2)
#define I2C_MST_CNTL_BURST_SIZE_SHIFT		6
#define I2C_MST_CNTL_GEN_NACK			BIT(28)
#define I2C_MST_CNTL_STATUS			GENMASK(30, 29)
#define I2C_MST_CNTL_STATUS_OKAY		(0 << 29)
#define I2C_MST_CNTL_STATUS_NO_ACK		(1 << 29)
#define I2C_MST_CNTL_STATUS_TIMEOUT		(2 << 29)
#define I2C_MST_CNTL_STATUS_BUS_BUSY		(3 << 29)
#define I2C_MST_CNTL_CYCLE_TRIGGER		BIT(31)

#define I2C_MST_ADDR				0x04

#define I2C_MST_I2C0_TIMING				0x08
#define I2C_MST_I2C0_TIMING_SCL_PERIOD_100KHZ		0x10e
#define I2C_MST_I2C0_TIMING_TIMEOUT_CLK_CNT		16
#define I2C_MST_I2C0_TIMING_TIMEOUT_CLK_CNT_MAX		255
#define I2C_MST_I2C0_TIMING_TIMEOUT_CHECK		BIT(24)

#define I2C_MST_DATA					0x0c

#define I2C_MST_HYBRID_PADCTL				0x20
#define I2C_MST_HYBRID_PADCTL_MODE_I2C			BIT(0)
#define I2C_MST_HYBRID_PADCTL_I2C_SCL_INPUT_RCV		BIT(14)
#define I2C_MST_HYBRID_PADCTL_I2C_SDA_INPUT_RCV		BIT(15)

struct gpu_i2c_dev {
	struct device *dev;
	void __iomem *regs;
	struct i2c_adapter adapter;
	struct i2c_board_info *gpu_ccgx_ucsi;
	struct i2c_client *ccgx_client;
};

static void gpu_enable_i2c_bus(struct gpu_i2c_dev *i2cd)
{
	u32 val;

	/* enable I2C */
	val = readl(i2cd->regs + I2C_MST_HYBRID_PADCTL);
	val |= I2C_MST_HYBRID_PADCTL_MODE_I2C |
		I2C_MST_HYBRID_PADCTL_I2C_SCL_INPUT_RCV |
		I2C_MST_HYBRID_PADCTL_I2C_SDA_INPUT_RCV;
	writel(val, i2cd->regs + I2C_MST_HYBRID_PADCTL);

	/* enable 100KHZ mode */
	val = I2C_MST_I2C0_TIMING_SCL_PERIOD_100KHZ;
	val |= (I2C_MST_I2C0_TIMING_TIMEOUT_CLK_CNT_MAX
	    << I2C_MST_I2C0_TIMING_TIMEOUT_CLK_CNT);
	val |= I2C_MST_I2C0_TIMING_TIMEOUT_CHECK;
	writel(val, i2cd->regs + I2C_MST_I2C0_TIMING);
}

static int gpu_i2c_check_status(struct gpu_i2c_dev *i2cd)
{
	unsigned long target = jiffies + msecs_to_jiffies(1000);
	u32 val;

	do {
		val = readl(i2cd->regs + I2C_MST_CNTL);
		if (!(val & I2C_MST_CNTL_CYCLE_TRIGGER))
			break;
		if ((val & I2C_MST_CNTL_STATUS) !=
				I2C_MST_CNTL_STATUS_BUS_BUSY)
			break;
		usleep_range(500, 600);
	} while (time_is_after_jiffies(target));

	if (time_is_before_jiffies(target)) {
		dev_err(i2cd->dev, "i2c timeout error %x\n", val);
		return -ETIMEDOUT;
	}

	val = readl(i2cd->regs + I2C_MST_CNTL);
	switch (val & I2C_MST_CNTL_STATUS) {
	case I2C_MST_CNTL_STATUS_OKAY:
		return 0;
	case I2C_MST_CNTL_STATUS_NO_ACK:
		return -ENXIO;
	case I2C_MST_CNTL_STATUS_TIMEOUT:
		return -ETIMEDOUT;
	default:
		return 0;
	}
}

static int gpu_i2c_read(struct gpu_i2c_dev *i2cd, u8 *data, u16 len)
{
	int status;
	u32 val;

	val = I2C_MST_CNTL_GEN_START | I2C_MST_CNTL_CMD_READ |
		(len << I2C_MST_CNTL_BURST_SIZE_SHIFT) |
		I2C_MST_CNTL_CYCLE_TRIGGER | I2C_MST_CNTL_GEN_NACK;
	writel(val, i2cd->regs + I2C_MST_CNTL);

	status = gpu_i2c_check_status(i2cd);
	if (status < 0)
		return status;

	val = readl(i2cd->regs + I2C_MST_DATA);
	switch (len) {
	case 1:
		data[0] = val;
		break;
	case 2:
		put_unaligned_be16(val, data);
		break;
	case 3:
		put_unaligned_be16(val >> 8, data);
		data[2] = val;
		break;
	case 4:
		put_unaligned_be32(val, data);
		break;
	default:
		break;
	}
	return status;
}

static int gpu_i2c_start(struct gpu_i2c_dev *i2cd)
{
	writel(I2C_MST_CNTL_GEN_START, i2cd->regs + I2C_MST_CNTL);
	return gpu_i2c_check_status(i2cd);
}

static int gpu_i2c_stop(struct gpu_i2c_dev *i2cd)
{
	writel(I2C_MST_CNTL_GEN_STOP, i2cd->regs + I2C_MST_CNTL);
	return gpu_i2c_check_status(i2cd);
}

static int gpu_i2c_write(struct gpu_i2c_dev *i2cd, u8 data)
{
	u32 val;

	writel(data, i2cd->regs + I2C_MST_DATA);

	val = I2C_MST_CNTL_CMD_WRITE | (1 << I2C_MST_CNTL_BURST_SIZE_SHIFT);
	writel(val, i2cd->regs + I2C_MST_CNTL);

	return gpu_i2c_check_status(i2cd);
}

static int gpu_i2c_master_xfer(struct i2c_adapter *adap,
			       struct i2c_msg *msgs, int num)
{
	struct gpu_i2c_dev *i2cd = i2c_get_adapdata(adap);
	int status, status2;
	bool send_stop = true;
	int i, j;

	/*
	 * The controller supports maximum 4 byte read due to known
	 * limitation of sending STOP after every read.
	 */
	pm_runtime_get_sync(i2cd->dev);
	for (i = 0; i < num; i++) {
		if (msgs[i].flags & I2C_M_RD) {
			/* program client address before starting read */
			writel(msgs[i].addr, i2cd->regs + I2C_MST_ADDR);
			/* gpu_i2c_read has implicit start */
			status = gpu_i2c_read(i2cd, msgs[i].buf, msgs[i].len);
			if (status < 0)
				goto exit;
		} else {
			u8 addr = i2c_8bit_addr_from_msg(msgs + i);

			status = gpu_i2c_start(i2cd);
			if (status < 0) {
				if (i == 0)
					send_stop = false;
				goto exit;
			}

			status = gpu_i2c_write(i2cd, addr);
			if (status < 0)
				goto exit;

			for (j = 0; j < msgs[i].len; j++) {
				status = gpu_i2c_write(i2cd, msgs[i].buf[j]);
				if (status < 0)
					goto exit;
			}
		}
	}
	send_stop = false;
	status = gpu_i2c_stop(i2cd);
	if (status < 0)
		goto exit;

	status = i;
exit:
	if (send_stop) {
		status2 = gpu_i2c_stop(i2cd);
		if (status2 < 0)
			dev_err(i2cd->dev, "i2c stop failed %d\n", status2);
	}
	pm_runtime_mark_last_busy(i2cd->dev);
	pm_runtime_put_autosuspend(i2cd->dev);
	return status;
}

static const struct i2c_adapter_quirks gpu_i2c_quirks = {
	.max_read_len = 4,
	.max_comb_2nd_msg_len = 4,
	.flags = I2C_AQ_COMB_WRITE_THEN_READ,
};

static u32 gpu_i2c_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

static const struct i2c_algorithm gpu_i2c_algorithm = {
	.master_xfer	= gpu_i2c_master_xfer,
	.functionality	= gpu_i2c_functionality,
};

/*
 * This driver is for Nvidia GPU cards with USB Type-C interface.
 * We want to identify the cards using vendor ID and class code only
 * to avoid dependency of adding product id for any new card which
 * requires this driver.
 * Currently there is no class code defined for UCSI device over PCI
 * so using UNKNOWN class for now and it will be updated when UCSI
 * over PCI gets a class code.
 * There is no other NVIDIA cards with UNKNOWN class code. Even if the
 * driver gets loaded for an undesired card then eventually i2c_read()
 * (initiated from UCSI i2c_client) will timeout or UCSI commands will
 * timeout.
 */
#define PCI_CLASS_SERIAL_UNKNOWN	0x0c80
static const struct pci_device_id gpu_i2c_ids[] = {
	{ PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
		PCI_CLASS_SERIAL_UNKNOWN << 8, 0xffffff00},
	{ }
};
MODULE_DEVICE_TABLE(pci, gpu_i2c_ids);

static const struct property_entry ccgx_props[] = {
	/* Use FW built for NVIDIA (nv) only */
	PROPERTY_ENTRY_U16("ccgx,firmware-build", ('n' << 8) | 'v'),
	{ }
};

static int gpu_populate_client(struct gpu_i2c_dev *i2cd, int irq)
{
	i2cd->gpu_ccgx_ucsi = devm_kzalloc(i2cd->dev,
					   sizeof(*i2cd->gpu_ccgx_ucsi),
					   GFP_KERNEL);
	if (!i2cd->gpu_ccgx_ucsi)
		return -ENOMEM;

	strlcpy(i2cd->gpu_ccgx_ucsi->type, "ccgx-ucsi",
		sizeof(i2cd->gpu_ccgx_ucsi->type));
	i2cd->gpu_ccgx_ucsi->addr = 0x8;
	i2cd->gpu_ccgx_ucsi->irq = irq;
	i2cd->gpu_ccgx_ucsi->properties = ccgx_props;
	i2cd->ccgx_client = i2c_new_client_device(&i2cd->adapter, i2cd->gpu_ccgx_ucsi);
	if (IS_ERR(i2cd->ccgx_client))
		return PTR_ERR(i2cd->ccgx_client);

	return 0;
}

static int gpu_i2c_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	struct gpu_i2c_dev *i2cd;
	int status;

	i2cd = devm_kzalloc(&pdev->dev, sizeof(*i2cd), GFP_KERNEL);
	if (!i2cd)
		return -ENOMEM;

	i2cd->dev = &pdev->dev;
	dev_set_drvdata(&pdev->dev, i2cd);

	status = pcim_enable_device(pdev);
	if (status < 0) {
		dev_err(&pdev->dev, "pcim_enable_device failed %d\n", status);
		return status;
	}

	pci_set_master(pdev);

	i2cd->regs = pcim_iomap(pdev, 0, 0);
	if (!i2cd->regs) {
		dev_err(&pdev->dev, "pcim_iomap failed\n");
		return -ENOMEM;
	}

	status = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
	if (status < 0) {
		dev_err(&pdev->dev, "pci_alloc_irq_vectors err %d\n", status);
		return status;
	}

	gpu_enable_i2c_bus(i2cd);

	i2c_set_adapdata(&i2cd->adapter, i2cd);
	i2cd->adapter.owner = THIS_MODULE;
	strlcpy(i2cd->adapter.name, "NVIDIA GPU I2C adapter",
		sizeof(i2cd->adapter.name));
	i2cd->adapter.algo = &gpu_i2c_algorithm;
	i2cd->adapter.quirks = &gpu_i2c_quirks;
	i2cd->adapter.dev.parent = &pdev->dev;
	status = i2c_add_adapter(&i2cd->adapter);
	if (status < 0)
		goto free_irq_vectors;

	status = gpu_populate_client(i2cd, pdev->irq);
	if (status < 0) {
		dev_err(&pdev->dev, "gpu_populate_client failed %d\n", status);
		goto del_adapter;
	}

	pm_runtime_set_autosuspend_delay(&pdev->dev, 3000);
	pm_runtime_use_autosuspend(&pdev->dev);
	pm_runtime_put_autosuspend(&pdev->dev);
	pm_runtime_allow(&pdev->dev);

	return 0;

del_adapter:
	i2c_del_adapter(&i2cd->adapter);
free_irq_vectors:
	pci_free_irq_vectors(pdev);
	return status;
}

static void gpu_i2c_remove(struct pci_dev *pdev)
{
	struct gpu_i2c_dev *i2cd = dev_get_drvdata(&pdev->dev);

	pm_runtime_get_noresume(i2cd->dev);
	i2c_del_adapter(&i2cd->adapter);
	pci_free_irq_vectors(pdev);
}

/*
 * We need gpu_i2c_suspend() even if it is stub, for runtime pm to work
 * correctly. Without it, lspci shows runtime pm status as "D0" for the card.
 * Documentation/power/pci.rst also insists for driver to provide this.
 */
static __maybe_unused int gpu_i2c_suspend(struct device *dev)
{
	return 0;
}

static __maybe_unused int gpu_i2c_resume(struct device *dev)
{
	struct gpu_i2c_dev *i2cd = dev_get_drvdata(dev);

	gpu_enable_i2c_bus(i2cd);
	/*
	 * Runtime resume ccgx client so that it can see for any
	 * connector change event. Old ccg firmware has known
	 * issue of not triggering interrupt when a device is
	 * connected to runtime resume the controller.
	 */
	pm_request_resume(&i2cd->ccgx_client->dev);
	return 0;
}

static UNIVERSAL_DEV_PM_OPS(gpu_i2c_driver_pm, gpu_i2c_suspend, gpu_i2c_resume,
			    NULL);

static struct pci_driver gpu_i2c_driver = {
	.name		= "nvidia-gpu",
	.id_table	= gpu_i2c_ids,
	.probe		= gpu_i2c_probe,
	.remove		= gpu_i2c_remove,
	.driver		= {
		.pm	= &gpu_i2c_driver_pm,
	},
};

module_pci_driver(gpu_i2c_driver);

MODULE_AUTHOR("Ajay Gupta <ajayg@nvidia.com>");
MODULE_DESCRIPTION("Nvidia GPU I2C controller Driver");
MODULE_LICENSE("GPL v2");
OpenPOWER on IntegriCloud