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 
23 #define DRIVER_NAME	"pcie_mp2_amd"
24 #define DRIVER_DESC	"AMD(R) PCIe MP2 Communication Driver"
25 
26 #define ACEL_EN		BIT(0)
27 #define GYRO_EN		BIT(1)
28 #define MAGNO_EN	BIT(2)
29 #define HPD_EN		BIT(16)
30 #define ALS_EN		BIT(19)
31 
32 static int sensor_mask_override = -1;
33 module_param_named(sensor_mask, sensor_mask_override, int, 0444);
34 MODULE_PARM_DESC(sensor_mask, "override the detected sensors mask");
35 
36 static int amd_sfh_wait_response_v2(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts)
37 {
38 	union cmd_response cmd_resp;
39 
40 	/* Get response with status within a max of 800 ms timeout */
41 	if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG(0), cmd_resp.resp,
42 				(cmd_resp.response_v2.response == sensor_sts &&
43 				cmd_resp.response_v2.status == 0 && (sid == 0xff ||
44 				cmd_resp.response_v2.sensor_id == sid)), 500, 800000))
45 		return cmd_resp.response_v2.response;
46 
47 	return SENSOR_DISABLED;
48 }
49 
50 static void amd_start_sensor_v2(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info)
51 {
52 	union sfh_cmd_base cmd_base;
53 
54 	cmd_base.ul = 0;
55 	cmd_base.cmd_v2.cmd_id = ENABLE_SENSOR;
56 	cmd_base.cmd_v2.period = info.period;
57 	cmd_base.cmd_v2.sensor_id = info.sensor_idx;
58 	cmd_base.cmd_v2.length = 16;
59 
60 	if (info.sensor_idx == als_idx)
61 		cmd_base.cmd_v2.mem_type = USE_C2P_REG;
62 
63 	writeq(info.dma_address, privdata->mmio + AMD_C2P_MSG1);
64 	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
65 }
66 
67 static void amd_stop_sensor_v2(struct amd_mp2_dev *privdata, u16 sensor_idx)
68 {
69 	union sfh_cmd_base cmd_base;
70 
71 	cmd_base.ul = 0;
72 	cmd_base.cmd_v2.cmd_id = DISABLE_SENSOR;
73 	cmd_base.cmd_v2.period = 0;
74 	cmd_base.cmd_v2.sensor_id = sensor_idx;
75 	cmd_base.cmd_v2.length  = 16;
76 
77 	writeq(0x0, privdata->mmio + AMD_C2P_MSG1);
78 	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
79 }
80 
81 static void amd_stop_all_sensor_v2(struct amd_mp2_dev *privdata)
82 {
83 	union sfh_cmd_base cmd_base;
84 
85 	cmd_base.cmd_v2.cmd_id = STOP_ALL_SENSORS;
86 	cmd_base.cmd_v2.period = 0;
87 	cmd_base.cmd_v2.sensor_id = 0;
88 
89 	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
90 }
91 
92 void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info)
93 {
94 	union sfh_cmd_param cmd_param;
95 	union sfh_cmd_base cmd_base;
96 
97 	/* fill up command register */
98 	memset(&cmd_base, 0, sizeof(cmd_base));
99 	cmd_base.s.cmd_id = ENABLE_SENSOR;
100 	cmd_base.s.period = info.period;
101 	cmd_base.s.sensor_id = info.sensor_idx;
102 
103 	/* fill up command param register */
104 	memset(&cmd_param, 0, sizeof(cmd_param));
105 	cmd_param.s.buf_layout = 1;
106 	cmd_param.s.buf_length = 16;
107 
108 	writeq(info.dma_address, privdata->mmio + AMD_C2P_MSG2);
109 	writel(cmd_param.ul, privdata->mmio + AMD_C2P_MSG1);
110 	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
111 }
112 
113 void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
114 {
115 	union sfh_cmd_base cmd_base;
116 
117 	/* fill up command register */
118 	memset(&cmd_base, 0, sizeof(cmd_base));
119 	cmd_base.s.cmd_id = DISABLE_SENSOR;
120 	cmd_base.s.period = 0;
121 	cmd_base.s.sensor_id = sensor_idx;
122 
123 	writeq(0x0, privdata->mmio + AMD_C2P_MSG2);
124 	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
125 }
126 
127 void amd_stop_all_sensors(struct amd_mp2_dev *privdata)
128 {
129 	union sfh_cmd_base cmd_base;
130 
131 	/* fill up command register */
132 	memset(&cmd_base, 0, sizeof(cmd_base));
133 	cmd_base.s.cmd_id = STOP_ALL_SENSORS;
134 	cmd_base.s.period = 0;
135 	cmd_base.s.sensor_id = 0;
136 
137 	writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
138 }
139 
140 static const struct dmi_system_id dmi_sensor_mask_overrides[] = {
141 	{
142 		.matches = {
143 			DMI_MATCH(DMI_PRODUCT_NAME, "HP ENVY x360 Convertible 13-ag0xxx"),
144 		},
145 		.driver_data = (void *)(ACEL_EN | MAGNO_EN),
146 	},
147 	{
148 		.matches = {
149 			DMI_MATCH(DMI_PRODUCT_NAME, "HP ENVY x360 Convertible 15-cp0xxx"),
150 		},
151 		.driver_data = (void *)(ACEL_EN | MAGNO_EN),
152 	},
153 	{ }
154 };
155 
156 int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
157 {
158 	int activestatus, num_of_sensors = 0;
159 	const struct dmi_system_id *dmi_id;
160 
161 	if (sensor_mask_override == -1) {
162 		dmi_id = dmi_first_match(dmi_sensor_mask_overrides);
163 		if (dmi_id)
164 			sensor_mask_override = (long)dmi_id->driver_data;
165 	}
166 
167 	if (sensor_mask_override >= 0) {
168 		activestatus = sensor_mask_override;
169 	} else {
170 		activestatus = privdata->mp2_acs >> 4;
171 	}
172 
173 	if (ACEL_EN  & activestatus)
174 		sensor_id[num_of_sensors++] = accel_idx;
175 
176 	if (GYRO_EN & activestatus)
177 		sensor_id[num_of_sensors++] = gyro_idx;
178 
179 	if (MAGNO_EN & activestatus)
180 		sensor_id[num_of_sensors++] = mag_idx;
181 
182 	if (ALS_EN & activestatus)
183 		sensor_id[num_of_sensors++] = als_idx;
184 
185 	if (HPD_EN & activestatus)
186 		sensor_id[num_of_sensors++] = HPD_IDX;
187 
188 	return num_of_sensors;
189 }
190 
191 static void amd_mp2_pci_remove(void *privdata)
192 {
193 	struct amd_mp2_dev *mp2 = privdata;
194 	amd_sfh_hid_client_deinit(privdata);
195 	mp2->mp2_ops->stop_all(mp2);
196 }
197 
198 static const struct amd_mp2_ops amd_sfh_ops_v2 = {
199 	.start = amd_start_sensor_v2,
200 	.stop = amd_stop_sensor_v2,
201 	.stop_all = amd_stop_all_sensor_v2,
202 	.response = amd_sfh_wait_response_v2,
203 };
204 
205 static const struct amd_mp2_ops amd_sfh_ops = {
206 	.start = amd_start_sensor,
207 	.stop = amd_stop_sensor,
208 	.stop_all = amd_stop_all_sensors,
209 };
210 
211 static void mp2_select_ops(struct amd_mp2_dev *privdata)
212 {
213 	u8 acs;
214 
215 	privdata->mp2_acs = readl(privdata->mmio + AMD_P2C_MSG3);
216 	acs = privdata->mp2_acs & GENMASK(3, 0);
217 
218 	switch (acs) {
219 	case V2_STATUS:
220 		privdata->mp2_ops = &amd_sfh_ops_v2;
221 		break;
222 	default:
223 		privdata->mp2_ops = &amd_sfh_ops;
224 		break;
225 	}
226 }
227 
228 static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
229 {
230 	struct amd_mp2_dev *privdata;
231 	int rc;
232 
233 	privdata = devm_kzalloc(&pdev->dev, sizeof(*privdata), GFP_KERNEL);
234 	if (!privdata)
235 		return -ENOMEM;
236 
237 	privdata->pdev = pdev;
238 	dev_set_drvdata(&pdev->dev, privdata);
239 	rc = pcim_enable_device(pdev);
240 	if (rc)
241 		return rc;
242 
243 	rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME);
244 	if (rc)
245 		return rc;
246 
247 	privdata->mmio = pcim_iomap_table(pdev)[2];
248 	pci_set_master(pdev);
249 	rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
250 	if (rc) {
251 		rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
252 		if (rc) {
253 			dev_err(&pdev->dev, "failed to set DMA mask\n");
254 			return rc;
255 		}
256 	}
257 
258 	privdata->cl_data = devm_kzalloc(&pdev->dev, sizeof(struct amdtp_cl_data), GFP_KERNEL);
259 	if (!privdata->cl_data)
260 		return -ENOMEM;
261 
262 	mp2_select_ops(privdata);
263 
264 	rc = amd_sfh_hid_client_init(privdata);
265 	if (rc)
266 		return rc;
267 
268 	return devm_add_action_or_reset(&pdev->dev, amd_mp2_pci_remove, privdata);
269 }
270 
271 static int __maybe_unused amd_mp2_pci_resume(struct device *dev)
272 {
273 	struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
274 	struct amdtp_cl_data *cl_data = mp2->cl_data;
275 	struct amd_mp2_sensor_info info;
276 	int i, status;
277 
278 	for (i = 0; i < cl_data->num_hid_devices; i++) {
279 		if (cl_data->sensor_sts[i] == SENSOR_DISABLED) {
280 			info.period = AMD_SFH_IDLE_LOOP;
281 			info.sensor_idx = cl_data->sensor_idx[i];
282 			info.dma_address = cl_data->sensor_dma_addr[i];
283 			mp2->mp2_ops->start(mp2, info);
284 			status = amd_sfh_wait_for_response
285 					(mp2, cl_data->sensor_idx[i], SENSOR_ENABLED);
286 			if (status == SENSOR_ENABLED)
287 				cl_data->sensor_sts[i] = SENSOR_ENABLED;
288 			dev_dbg(dev, "resume sid 0x%x status 0x%x\n",
289 				cl_data->sensor_idx[i], cl_data->sensor_sts[i]);
290 		}
291 	}
292 
293 	return 0;
294 }
295 
296 static int __maybe_unused amd_mp2_pci_suspend(struct device *dev)
297 {
298 	struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
299 	struct amdtp_cl_data *cl_data = mp2->cl_data;
300 	int i, status;
301 
302 	for (i = 0; i < cl_data->num_hid_devices; i++) {
303 		if (cl_data->sensor_idx[i] != HPD_IDX &&
304 		    cl_data->sensor_sts[i] == SENSOR_ENABLED) {
305 			mp2->mp2_ops->stop(mp2, cl_data->sensor_idx[i]);
306 			status = amd_sfh_wait_for_response
307 					(mp2, cl_data->sensor_idx[i], SENSOR_DISABLED);
308 			if (status != SENSOR_ENABLED)
309 				cl_data->sensor_sts[i] = SENSOR_DISABLED;
310 			dev_dbg(dev, "suspend sid 0x%x status 0x%x\n",
311 				cl_data->sensor_idx[i], cl_data->sensor_sts[i]);
312 		}
313 	}
314 
315 	return 0;
316 }
317 
318 static SIMPLE_DEV_PM_OPS(amd_mp2_pm_ops, amd_mp2_pci_suspend,
319 		amd_mp2_pci_resume);
320 
321 static const struct pci_device_id amd_mp2_pci_tbl[] = {
322 	{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) },
323 	{ }
324 };
325 MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl);
326 
327 static struct pci_driver amd_mp2_pci_driver = {
328 	.name		= DRIVER_NAME,
329 	.id_table	= amd_mp2_pci_tbl,
330 	.probe		= amd_mp2_pci_probe,
331 	.driver.pm	= &amd_mp2_pm_ops,
332 };
333 module_pci_driver(amd_mp2_pci_driver);
334 
335 MODULE_DESCRIPTION(DRIVER_DESC);
336 MODULE_LICENSE("Dual BSD/GPL");
337 MODULE_AUTHOR("Shyam Sundar S K <Shyam-sundar.S-k@amd.com>");
338 MODULE_AUTHOR("Sandeep Singh <Sandeep.singh@amd.com>");
339 MODULE_AUTHOR("Basavaraj Natikar <Basavaraj.Natikar@amd.com>");
340