1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * AMD MP2 PCIe communication driver
4 * Copyright 2020-2021 Advanced Micro Devices, Inc.
5 *
6 * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
7 *	    Sandeep Singh <Sandeep.singh@amd.com>
8 *	    Basavaraj Natikar <Basavaraj.Natikar@amd.com>
9 */
10
11#include <linux/bitops.h>
12#include <linux/delay.h>
13#include <linux/dma-mapping.h>
14#include <linux/dmi.h>
15#include <linux/interrupt.h>
16#include <linux/io-64-nonatomic-lo-hi.h>
17#include <linux/iopoll.h>
18#include <linux/module.h>
19#include <linux/slab.h>
20
21#include "amd_sfh_pcie.h"
22#include "sfh1_1/amd_sfh_init.h"
23
24#define DRIVER_NAME	"pcie_mp2_amd"
25#define DRIVER_DESC	"AMD(R) PCIe MP2 Communication Driver"
26
27#define ACEL_EN		BIT(0)
28#define GYRO_EN		BIT(1)
29#define MAGNO_EN	BIT(2)
30#define HPD_EN		BIT(16)
31#define ALS_EN		BIT(19)
32#define ACS_EN		BIT(22)
33
34static int sensor_mask_override = -1;
35module_param_named(sensor_mask, sensor_mask_override, int, 0444);
36MODULE_PARM_DESC(sensor_mask, "override the detected sensors mask");
37
38static bool intr_disable = true;
39
40static int amd_sfh_wait_response_v2(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts)
41{
42	union cmd_response cmd_resp;
43
44	/* Get response with status within a max of 1600 ms timeout */
45	if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG(0), cmd_resp.resp,
46				(cmd_resp.response_v2.response == sensor_sts &&
47				cmd_resp.response_v2.status == 0 && (sid == 0xff ||
48				cmd_resp.response_v2.sensor_id == sid)), 500, 1600000))
49		return cmd_resp.response_v2.response;
50
51	return SENSOR_DISABLED;
52}
53
54static void amd_start_sensor_v2(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info)
55{
56	union sfh_cmd_base cmd_base;
57
58	cmd_base.ul = 0;
59	cmd_base.cmd_v2.cmd_id = ENABLE_SENSOR;
60	cmd_base.cmd_v2.intr_disable = intr_disable;
61	cmd_base.cmd_v2.period = info.period;
62	cmd_base.cmd_v2.sensor_id = info.sensor_idx;
63	cmd_base.cmd_v2.length = 16;
64
65	if (info.sensor_idx == als_idx)
66		cmd_base.cmd_v2.mem_type = USE_C2P_REG;
67
68	writeq(info.dma_address, privdata->mmio + AMD_C2P_MSG1);
69	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
70}
71
72static void amd_stop_sensor_v2(struct amd_mp2_dev *privdata, u16 sensor_idx)
73{
74	union sfh_cmd_base cmd_base;
75
76	cmd_base.ul = 0;
77	cmd_base.cmd_v2.cmd_id = DISABLE_SENSOR;
78	cmd_base.cmd_v2.intr_disable = intr_disable;
79	cmd_base.cmd_v2.period = 0;
80	cmd_base.cmd_v2.sensor_id = sensor_idx;
81	cmd_base.cmd_v2.length  = 16;
82
83	writeq(0x0, privdata->mmio + AMD_C2P_MSG1);
84	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
85}
86
87static void amd_stop_all_sensor_v2(struct amd_mp2_dev *privdata)
88{
89	union sfh_cmd_base cmd_base;
90
91	cmd_base.cmd_v2.cmd_id = STOP_ALL_SENSORS;
92	cmd_base.cmd_v2.intr_disable = intr_disable;
93	cmd_base.cmd_v2.period = 0;
94	cmd_base.cmd_v2.sensor_id = 0;
95
96	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
97}
98
99void amd_sfh_clear_intr_v2(struct amd_mp2_dev *privdata)
100{
101	if (readl(privdata->mmio + AMD_P2C_MSG(4))) {
102		writel(0, privdata->mmio + AMD_P2C_MSG(4));
103		writel(0xf, privdata->mmio + AMD_P2C_MSG(5));
104	}
105}
106
107void amd_sfh_clear_intr(struct amd_mp2_dev *privdata)
108{
109	if (privdata->mp2_ops->clear_intr)
110		privdata->mp2_ops->clear_intr(privdata);
111}
112
113static irqreturn_t amd_sfh_irq_handler(int irq, void *data)
114{
115	amd_sfh_clear_intr(data);
116
117	return IRQ_HANDLED;
118}
119
120int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata)
121{
122	int rc;
123
124	pci_intx(privdata->pdev, true);
125
126	rc = devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq,
127			      amd_sfh_irq_handler, 0, DRIVER_NAME, privdata);
128	if (rc) {
129		dev_err(&privdata->pdev->dev, "failed to request irq %d err=%d\n",
130			privdata->pdev->irq, rc);
131		return rc;
132	}
133
134	return 0;
135}
136
137static int amd_sfh_dis_sts_v2(struct amd_mp2_dev *privdata)
138{
139	return (readl(privdata->mmio + AMD_P2C_MSG(1)) &
140		      SENSOR_DISCOVERY_STATUS_MASK) >> SENSOR_DISCOVERY_STATUS_SHIFT;
141}
142
143static void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info)
144{
145	union sfh_cmd_param cmd_param;
146	union sfh_cmd_base cmd_base;
147
148	/* fill up command register */
149	memset(&cmd_base, 0, sizeof(cmd_base));
150	cmd_base.s.cmd_id = ENABLE_SENSOR;
151	cmd_base.s.period = info.period;
152	cmd_base.s.sensor_id = info.sensor_idx;
153
154	/* fill up command param register */
155	memset(&cmd_param, 0, sizeof(cmd_param));
156	cmd_param.s.buf_layout = 1;
157	cmd_param.s.buf_length = 16;
158
159	writeq(info.dma_address, privdata->mmio + AMD_C2P_MSG2);
160	writel(cmd_param.ul, privdata->mmio + AMD_C2P_MSG1);
161	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
162}
163
164static void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
165{
166	union sfh_cmd_base cmd_base;
167
168	/* fill up command register */
169	memset(&cmd_base, 0, sizeof(cmd_base));
170	cmd_base.s.cmd_id = DISABLE_SENSOR;
171	cmd_base.s.period = 0;
172	cmd_base.s.sensor_id = sensor_idx;
173
174	writeq(0x0, privdata->mmio + AMD_C2P_MSG2);
175	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
176}
177
178static void amd_stop_all_sensors(struct amd_mp2_dev *privdata)
179{
180	union sfh_cmd_base cmd_base;
181
182	/* fill up command register */
183	memset(&cmd_base, 0, sizeof(cmd_base));
184	cmd_base.s.cmd_id = STOP_ALL_SENSORS;
185	cmd_base.s.period = 0;
186	cmd_base.s.sensor_id = 0;
187
188	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
189}
190
191static const struct dmi_system_id dmi_sensor_mask_overrides[] = {
192	{
193		.matches = {
194			DMI_MATCH(DMI_PRODUCT_NAME, "HP ENVY x360 Convertible 13-ag0xxx"),
195		},
196		.driver_data = (void *)(ACEL_EN | MAGNO_EN),
197	},
198	{
199		.matches = {
200			DMI_MATCH(DMI_PRODUCT_NAME, "HP ENVY x360 Convertible 15-cp0xxx"),
201		},
202		.driver_data = (void *)(ACEL_EN | MAGNO_EN),
203	},
204	{ }
205};
206
207int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
208{
209	int activestatus, num_of_sensors = 0;
210	const struct dmi_system_id *dmi_id;
211
212	if (sensor_mask_override == -1) {
213		dmi_id = dmi_first_match(dmi_sensor_mask_overrides);
214		if (dmi_id)
215			sensor_mask_override = (long)dmi_id->driver_data;
216	}
217
218	if (sensor_mask_override >= 0) {
219		activestatus = sensor_mask_override;
220	} else {
221		activestatus = privdata->mp2_acs >> 4;
222	}
223
224	if (ACEL_EN  & activestatus)
225		sensor_id[num_of_sensors++] = accel_idx;
226
227	if (GYRO_EN & activestatus)
228		sensor_id[num_of_sensors++] = gyro_idx;
229
230	if (MAGNO_EN & activestatus)
231		sensor_id[num_of_sensors++] = mag_idx;
232
233	if (ALS_EN & activestatus)
234		sensor_id[num_of_sensors++] = als_idx;
235
236	if (HPD_EN & activestatus)
237		sensor_id[num_of_sensors++] = HPD_IDX;
238
239	if (ACS_EN & activestatus)
240		sensor_id[num_of_sensors++] = ACS_IDX;
241
242	return num_of_sensors;
243}
244
245static void amd_mp2_pci_remove(void *privdata)
246{
247	struct amd_mp2_dev *mp2 = privdata;
248	amd_sfh_hid_client_deinit(privdata);
249	mp2->mp2_ops->stop_all(mp2);
250	pci_intx(mp2->pdev, false);
251	amd_sfh_clear_intr(mp2);
252}
253
254static struct amd_mp2_ops amd_sfh_ops_v2 = {
255	.start = amd_start_sensor_v2,
256	.stop = amd_stop_sensor_v2,
257	.stop_all = amd_stop_all_sensor_v2,
258	.response = amd_sfh_wait_response_v2,
259	.clear_intr = amd_sfh_clear_intr_v2,
260	.init_intr = amd_sfh_irq_init_v2,
261	.discovery_status = amd_sfh_dis_sts_v2,
262	.remove = amd_mp2_pci_remove,
263};
264
265static struct amd_mp2_ops amd_sfh_ops = {
266	.start = amd_start_sensor,
267	.stop = amd_stop_sensor,
268	.stop_all = amd_stop_all_sensors,
269	.remove = amd_mp2_pci_remove,
270};
271
272static void mp2_select_ops(struct amd_mp2_dev *privdata)
273{
274	u8 acs;
275
276	privdata->mp2_acs = readl(privdata->mmio + AMD_P2C_MSG3);
277	acs = privdata->mp2_acs & GENMASK(3, 0);
278
279	switch (acs) {
280	case V2_STATUS:
281		privdata->mp2_ops = &amd_sfh_ops_v2;
282		break;
283	default:
284		privdata->mp2_ops = &amd_sfh_ops;
285		break;
286	}
287}
288
289int amd_sfh_irq_init(struct amd_mp2_dev *privdata)
290{
291	if (privdata->mp2_ops->init_intr)
292		return privdata->mp2_ops->init_intr(privdata);
293
294	return 0;
295}
296
297static int mp2_disable_intr(const struct dmi_system_id *id)
298{
299	intr_disable = false;
300	return 0;
301}
302
303static const struct dmi_system_id dmi_sfh_table[] = {
304	{
305		/*
306		 * https://bugzilla.kernel.org/show_bug.cgi?id=218104
307		 */
308		.callback = mp2_disable_intr,
309		.matches = {
310			DMI_MATCH(DMI_SYS_VENDOR, "HP"),
311			DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook x360 435 G7"),
312		},
313	},
314	{}
315};
316
317static const struct dmi_system_id dmi_nodevs[] = {
318	{
319		/*
320		 * Google Chromebooks use Chrome OS Embedded Controller Sensor
321		 * Hub instead of Sensor Hub Fusion and leaves MP2
322		 * uninitialized, which disables all functionalities, even
323		 * including the registers necessary for feature detections.
324		 */
325		.matches = {
326			DMI_MATCH(DMI_SYS_VENDOR, "Google"),
327		},
328	},
329	{ }
330};
331
332static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
333{
334	struct amd_mp2_dev *privdata;
335	int rc;
336
337	if (dmi_first_match(dmi_nodevs))
338		return -ENODEV;
339
340	dmi_check_system(dmi_sfh_table);
341
342	privdata = devm_kzalloc(&pdev->dev, sizeof(*privdata), GFP_KERNEL);
343	if (!privdata)
344		return -ENOMEM;
345
346	privdata->pdev = pdev;
347	dev_set_drvdata(&pdev->dev, privdata);
348	rc = pcim_enable_device(pdev);
349	if (rc)
350		return rc;
351
352	rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME);
353	if (rc)
354		return rc;
355
356	privdata->mmio = pcim_iomap_table(pdev)[2];
357	pci_set_master(pdev);
358	rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
359	if (rc) {
360		dev_err(&pdev->dev, "failed to set DMA mask\n");
361		return rc;
362	}
363
364	privdata->cl_data = devm_kzalloc(&pdev->dev, sizeof(struct amdtp_cl_data), GFP_KERNEL);
365	if (!privdata->cl_data)
366		return -ENOMEM;
367
368	privdata->sfh1_1_ops = (const struct amd_sfh1_1_ops *)id->driver_data;
369	if (privdata->sfh1_1_ops) {
370		rc = privdata->sfh1_1_ops->init(privdata);
371		if (rc)
372			return rc;
373		goto init_done;
374	}
375
376	mp2_select_ops(privdata);
377
378	rc = amd_sfh_irq_init(privdata);
379	if (rc) {
380		dev_err(&pdev->dev, "amd_sfh_irq_init failed\n");
381		return rc;
382	}
383
384	rc = amd_sfh_hid_client_init(privdata);
385	if (rc) {
386		amd_sfh_clear_intr(privdata);
387		if (rc != -EOPNOTSUPP)
388			dev_err(&pdev->dev, "amd_sfh_hid_client_init failed\n");
389		return rc;
390	}
391
392init_done:
393	amd_sfh_clear_intr(privdata);
394
395	return devm_add_action_or_reset(&pdev->dev, privdata->mp2_ops->remove, privdata);
396}
397
398static void amd_sfh_shutdown(struct pci_dev *pdev)
399{
400	struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev);
401
402	if (mp2 && mp2->mp2_ops)
403		mp2->mp2_ops->stop_all(mp2);
404}
405
406static int __maybe_unused amd_mp2_pci_resume(struct device *dev)
407{
408	struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
409
410	mp2->mp2_ops->resume(mp2);
411
412	return 0;
413}
414
415static int __maybe_unused amd_mp2_pci_suspend(struct device *dev)
416{
417	struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
418
419	mp2->mp2_ops->suspend(mp2);
420
421	return 0;
422}
423
424static SIMPLE_DEV_PM_OPS(amd_mp2_pm_ops, amd_mp2_pci_suspend,
425		amd_mp2_pci_resume);
426
427static const struct pci_device_id amd_mp2_pci_tbl[] = {
428	{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) },
429	{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2_1_1),
430	  .driver_data = (kernel_ulong_t)&sfh1_1_ops },
431	{ }
432};
433MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl);
434
435static struct pci_driver amd_mp2_pci_driver = {
436	.name		= DRIVER_NAME,
437	.id_table	= amd_mp2_pci_tbl,
438	.probe		= amd_mp2_pci_probe,
439	.driver.pm	= &amd_mp2_pm_ops,
440	.shutdown	= amd_sfh_shutdown,
441};
442module_pci_driver(amd_mp2_pci_driver);
443
444MODULE_DESCRIPTION(DRIVER_DESC);
445MODULE_LICENSE("Dual BSD/GPL");
446MODULE_AUTHOR("Shyam Sundar S K <Shyam-sundar.S-k@amd.com>");
447MODULE_AUTHOR("Sandeep Singh <Sandeep.singh@amd.com>");
448MODULE_AUTHOR("Basavaraj Natikar <Basavaraj.Natikar@amd.com>");
449