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