Commit | Line | Data |
---|---|---|
4f567b9f SS |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* | |
3 | * AMD MP2 PCIe communication driver | |
f75203cd | 4 | * Copyright 2020-2021 Advanced Micro Devices, Inc. |
4f567b9f SS |
5 | * |
6 | * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> | |
7 | * Sandeep Singh <Sandeep.singh@amd.com> | |
f75203cd | 8 | * Basavaraj Natikar <Basavaraj.Natikar@amd.com> |
4f567b9f SS |
9 | */ |
10 | ||
11 | #include <linux/bitops.h> | |
12 | #include <linux/delay.h> | |
2105e8e0 | 13 | #include <linux/devm-helpers.h> |
4f567b9f | 14 | #include <linux/dma-mapping.h> |
25615e45 | 15 | #include <linux/dmi.h> |
4f567b9f SS |
16 | #include <linux/interrupt.h> |
17 | #include <linux/io-64-nonatomic-lo-hi.h> | |
173709f5 | 18 | #include <linux/iopoll.h> |
4f567b9f SS |
19 | #include <linux/module.h> |
20 | #include <linux/slab.h> | |
21 | ||
22 | #include "amd_sfh_pcie.h" | |
93ce5e02 | 23 | #include "sfh1_1/amd_sfh_init.h" |
4f567b9f SS |
24 | |
25 | #define DRIVER_NAME "pcie_mp2_amd" | |
26 | #define DRIVER_DESC "AMD(R) PCIe MP2 Communication Driver" | |
27 | ||
4b393f0f SS |
28 | #define ACEL_EN BIT(0) |
29 | #define GYRO_EN BIT(1) | |
952f7d10 | 30 | #define MAGNO_EN BIT(2) |
24a31ea9 | 31 | #define HPD_EN BIT(16) |
4f567b9f | 32 | #define ALS_EN BIT(19) |
4bd76356 | 33 | #define ACS_EN BIT(22) |
4f567b9f | 34 | |
952f7d10 HG |
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 | ||
c1db0073 BN |
39 | static bool intr_disable = true; |
40 | ||
173709f5 BN |
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 | ||
333861f4 | 45 | /* Get response with status within a max of 10 seconds timeout */ |
173709f5 BN |
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 || | |
333861f4 | 49 | cmd_resp.response_v2.sensor_id == sid)), 500, 10000000)) |
173709f5 BN |
50 | return cmd_resp.response_v2.response; |
51 | ||
52 | return SENSOR_DISABLED; | |
53 | } | |
54 | ||
f264481a BN |
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; | |
c1db0073 | 61 | cmd_base.cmd_v2.intr_disable = intr_disable; |
f264481a BN |
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(info.dma_address, privdata->mmio + AMD_C2P_MSG1); | |
70 | writel(cmd_base.ul, 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; | |
c1db0073 | 79 | cmd_base.cmd_v2.intr_disable = intr_disable; |
f264481a BN |
80 | cmd_base.cmd_v2.period = 0; |
81 | cmd_base.cmd_v2.sensor_id = sensor_idx; | |
82 | cmd_base.cmd_v2.length = 16; | |
83 | ||
8aa63486 | 84 | writeq(0x0, privdata->mmio + AMD_C2P_MSG1); |
f264481a BN |
85 | writel(cmd_base.ul, 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; | |
c1db0073 | 93 | cmd_base.cmd_v2.intr_disable = intr_disable; |
f264481a BN |
94 | cmd_base.cmd_v2.period = 0; |
95 | cmd_base.cmd_v2.sensor_id = 0; | |
96 | ||
97 | writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0); | |
98 | } | |
99 | ||
014730c4 | 100 | void amd_sfh_clear_intr_v2(struct amd_mp2_dev *privdata) |
fb75a379 | 101 | { |
6296562f BN |
102 | if (readl(privdata->mmio + amd_get_p2c_val(privdata, 4))) { |
103 | writel(0, privdata->mmio + amd_get_p2c_val(privdata, 4)); | |
104 | writel(0xf, privdata->mmio + amd_get_p2c_val(privdata, 5)); | |
fb75a379 BN |
105 | } |
106 | } | |
107 | ||
014730c4 | 108 | void amd_sfh_clear_intr(struct amd_mp2_dev *privdata) |
fb75a379 BN |
109 | { |
110 | if (privdata->mp2_ops->clear_intr) | |
111 | privdata->mp2_ops->clear_intr(privdata); | |
112 | } | |
113 | ||
7f016b35 BN |
114 | static irqreturn_t amd_sfh_irq_handler(int irq, void *data) |
115 | { | |
116 | amd_sfh_clear_intr(data); | |
117 | ||
118 | return IRQ_HANDLED; | |
119 | } | |
120 | ||
014730c4 | 121 | int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata) |
7f016b35 BN |
122 | { |
123 | int rc; | |
124 | ||
125 | pci_intx(privdata->pdev, true); | |
126 | ||
127 | rc = devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq, | |
128 | amd_sfh_irq_handler, 0, DRIVER_NAME, 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 | ||
b5d7f43e BN |
138 | static int amd_sfh_dis_sts_v2(struct amd_mp2_dev *privdata) |
139 | { | |
140 | return (readl(privdata->mmio + AMD_P2C_MSG(1)) & | |
141 | SENSOR_DISCOVERY_STATUS_MASK) >> SENSOR_DISCOVERY_STATUS_SHIFT; | |
142 | } | |
143 | ||
87cb7952 | 144 | static void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info) |
4f567b9f SS |
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 | ||
de30491e | 160 | writeq(info.dma_address, privdata->mmio + AMD_C2P_MSG2); |
4f567b9f SS |
161 | writel(cmd_param.ul, privdata->mmio + AMD_C2P_MSG1); |
162 | writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0); | |
163 | } | |
164 | ||
87cb7952 | 165 | static void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx) |
4f567b9f SS |
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(0x0, privdata->mmio + AMD_C2P_MSG2); | |
176 | writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0); | |
177 | } | |
178 | ||
87cb7952 | 179 | static void amd_stop_all_sensors(struct amd_mp2_dev *privdata) |
4f567b9f SS |
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(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0); | |
190 | } | |
191 | ||
25615e45 HG |
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 | ||
4f567b9f SS |
208 | int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id) |
209 | { | |
210 | int activestatus, num_of_sensors = 0; | |
25615e45 | 211 | const struct dmi_system_id *dmi_id; |
a9e54f4b | 212 | |
25615e45 HG |
213 | if (sensor_mask_override == -1) { |
214 | dmi_id = dmi_first_match(dmi_sensor_mask_overrides); | |
215 | if (dmi_id) | |
216 | sensor_mask_override = (long)dmi_id->driver_data; | |
217 | } | |
218 | ||
952f7d10 HG |
219 | if (sensor_mask_override >= 0) { |
220 | activestatus = sensor_mask_override; | |
221 | } else { | |
f264481a | 222 | activestatus = privdata->mp2_acs >> 4; |
952f7d10 | 223 | } |
4f567b9f | 224 | |
4f567b9f SS |
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 | ||
24a31ea9 BN |
237 | if (HPD_EN & activestatus) |
238 | sensor_id[num_of_sensors++] = HPD_IDX; | |
239 | ||
4bd76356 BN |
240 | if (ACS_EN & activestatus) |
241 | sensor_id[num_of_sensors++] = ACS_IDX; | |
242 | ||
4f567b9f SS |
243 | return num_of_sensors; |
244 | } | |
245 | ||
246 | static void amd_mp2_pci_remove(void *privdata) | |
247 | { | |
f264481a | 248 | struct amd_mp2_dev *mp2 = privdata; |
4f567b9f | 249 | amd_sfh_hid_client_deinit(privdata); |
f264481a | 250 | mp2->mp2_ops->stop_all(mp2); |
7f016b35 | 251 | pci_intx(mp2->pdev, false); |
fb75a379 | 252 | amd_sfh_clear_intr(mp2); |
f264481a BN |
253 | } |
254 | ||
786aa1b9 | 255 | static struct amd_mp2_ops amd_sfh_ops_v2 = { |
f264481a BN |
256 | .start = amd_start_sensor_v2, |
257 | .stop = amd_stop_sensor_v2, | |
258 | .stop_all = amd_stop_all_sensor_v2, | |
173709f5 | 259 | .response = amd_sfh_wait_response_v2, |
fb75a379 | 260 | .clear_intr = amd_sfh_clear_intr_v2, |
7f016b35 | 261 | .init_intr = amd_sfh_irq_init_v2, |
b5d7f43e | 262 | .discovery_status = amd_sfh_dis_sts_v2, |
722658f8 | 263 | .remove = amd_mp2_pci_remove, |
f264481a BN |
264 | }; |
265 | ||
786aa1b9 | 266 | static struct amd_mp2_ops amd_sfh_ops = { |
f264481a BN |
267 | .start = amd_start_sensor, |
268 | .stop = amd_stop_sensor, | |
269 | .stop_all = amd_stop_all_sensors, | |
722658f8 | 270 | .remove = amd_mp2_pci_remove, |
f264481a BN |
271 | }; |
272 | ||
273 | static void mp2_select_ops(struct amd_mp2_dev *privdata) | |
274 | { | |
275 | u8 acs; | |
276 | ||
277 | privdata->mp2_acs = readl(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 | } | |
4f567b9f SS |
288 | } |
289 | ||
014730c4 | 290 | int amd_sfh_irq_init(struct amd_mp2_dev *privdata) |
7f016b35 BN |
291 | { |
292 | if (privdata->mp2_ops->init_intr) | |
293 | return privdata->mp2_ops->init_intr(privdata); | |
294 | ||
295 | return 0; | |
296 | } | |
297 | ||
c1db0073 BN |
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 | ||
adada3f4 AO |
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 | ||
2105e8e0 BN |
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); | |
2105e8e0 BN |
336 | int rc; |
337 | ||
338 | rc = mp2->sfh1_1_ops->init(mp2); | |
6856f079 | 339 | if (rc) |
2105e8e0 | 340 | return; |
2105e8e0 BN |
341 | |
342 | amd_sfh_clear_intr(mp2); | |
343 | mp2->init_done = 1; | |
344 | } | |
345 | ||
346 | static void sfh_init_work(struct work_struct *work) | |
347 | { | |
348 | struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work); | |
349 | struct pci_dev *pdev = mp2->pdev; | |
350 | int rc; | |
351 | ||
352 | rc = amd_sfh_hid_client_init(mp2); | |
353 | if (rc) { | |
354 | amd_sfh_clear_intr(mp2); | |
355 | dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc); | |
356 | return; | |
357 | } | |
358 | ||
359 | amd_sfh_clear_intr(mp2); | |
360 | mp2->init_done = 1; | |
361 | } | |
362 | ||
363 | static void amd_sfh_remove(struct pci_dev *pdev) | |
364 | { | |
365 | struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev); | |
366 | ||
367 | flush_work(&mp2->work); | |
368 | if (mp2->init_done) | |
369 | mp2->mp2_ops->remove(mp2); | |
370 | } | |
371 | ||
4f567b9f SS |
372 | static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) |
373 | { | |
374 | struct amd_mp2_dev *privdata; | |
375 | int rc; | |
376 | ||
adada3f4 AO |
377 | if (dmi_first_match(dmi_nodevs)) |
378 | return -ENODEV; | |
379 | ||
c1db0073 BN |
380 | dmi_check_system(dmi_sfh_table); |
381 | ||
4f567b9f SS |
382 | privdata = devm_kzalloc(&pdev->dev, sizeof(*privdata), GFP_KERNEL); |
383 | if (!privdata) | |
384 | return -ENOMEM; | |
385 | ||
386 | privdata->pdev = pdev; | |
ba70a4ff | 387 | dev_set_drvdata(&pdev->dev, privdata); |
4f567b9f SS |
388 | rc = pcim_enable_device(pdev); |
389 | if (rc) | |
390 | return rc; | |
391 | ||
392 | rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); | |
393 | if (rc) | |
394 | return rc; | |
395 | ||
396 | privdata->mmio = pcim_iomap_table(pdev)[2]; | |
397 | pci_set_master(pdev); | |
c45d2b54 | 398 | rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); |
4f567b9f | 399 | if (rc) { |
0f203948 CJ |
400 | dev_err(&pdev->dev, "failed to set DMA mask\n"); |
401 | return rc; | |
4f567b9f | 402 | } |
0aad9c95 BN |
403 | |
404 | privdata->cl_data = devm_kzalloc(&pdev->dev, sizeof(struct amdtp_cl_data), GFP_KERNEL); | |
405 | if (!privdata->cl_data) | |
406 | return -ENOMEM; | |
407 | ||
93ce5e02 BN |
408 | privdata->sfh1_1_ops = (const struct amd_sfh1_1_ops *)id->driver_data; |
409 | if (privdata->sfh1_1_ops) { | |
6296562f BN |
410 | if (boot_cpu_data.x86 >= 0x1A) |
411 | privdata->rver = 1; | |
412 | ||
2105e8e0 | 413 | rc = devm_work_autocancel(&pdev->dev, &privdata->work, sfh1_1_init_work); |
93ce5e02 BN |
414 | if (rc) |
415 | return rc; | |
2105e8e0 BN |
416 | |
417 | schedule_work(&privdata->work); | |
418 | return 0; | |
93ce5e02 BN |
419 | } |
420 | ||
88a04049 BN |
421 | mp2_select_ops(privdata); |
422 | ||
7f016b35 BN |
423 | rc = amd_sfh_irq_init(privdata); |
424 | if (rc) { | |
425 | dev_err(&pdev->dev, "amd_sfh_irq_init failed\n"); | |
426 | return rc; | |
427 | } | |
428 | ||
2105e8e0 | 429 | rc = devm_work_autocancel(&pdev->dev, &privdata->work, sfh_init_work); |
fb75a379 BN |
430 | if (rc) { |
431 | amd_sfh_clear_intr(privdata); | |
4f567b9f | 432 | return rc; |
fb75a379 BN |
433 | } |
434 | ||
2105e8e0 BN |
435 | schedule_work(&privdata->work); |
436 | return 0; | |
4f567b9f SS |
437 | } |
438 | ||
1353ecaf BN |
439 | static void amd_sfh_shutdown(struct pci_dev *pdev) |
440 | { | |
441 | struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev); | |
442 | ||
2105e8e0 BN |
443 | if (mp2) { |
444 | flush_work(&mp2->work); | |
445 | if (mp2->init_done) | |
446 | mp2->mp2_ops->stop_all(mp2); | |
447 | } | |
1353ecaf BN |
448 | } |
449 | ||
0873d1af BN |
450 | static int __maybe_unused amd_mp2_pci_resume(struct device *dev) |
451 | { | |
ba70a4ff | 452 | struct amd_mp2_dev *mp2 = dev_get_drvdata(dev); |
0873d1af | 453 | |
2105e8e0 BN |
454 | flush_work(&mp2->work); |
455 | if (mp2->init_done) | |
456 | mp2->mp2_ops->resume(mp2); | |
0cf74235 | 457 | |
0873d1af BN |
458 | return 0; |
459 | } | |
460 | ||
461 | static int __maybe_unused amd_mp2_pci_suspend(struct device *dev) | |
462 | { | |
ba70a4ff | 463 | struct amd_mp2_dev *mp2 = dev_get_drvdata(dev); |
0873d1af | 464 | |
2105e8e0 BN |
465 | flush_work(&mp2->work); |
466 | if (mp2->init_done) | |
467 | mp2->mp2_ops->suspend(mp2); | |
0cf74235 | 468 | |
0873d1af BN |
469 | return 0; |
470 | } | |
471 | ||
472 | static SIMPLE_DEV_PM_OPS(amd_mp2_pm_ops, amd_mp2_pci_suspend, | |
473 | amd_mp2_pci_resume); | |
474 | ||
4f567b9f SS |
475 | static const struct pci_device_id amd_mp2_pci_tbl[] = { |
476 | { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) }, | |
93ce5e02 BN |
477 | { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2_1_1), |
478 | .driver_data = (kernel_ulong_t)&sfh1_1_ops }, | |
4f567b9f SS |
479 | { } |
480 | }; | |
481 | MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl); | |
482 | ||
483 | static struct pci_driver amd_mp2_pci_driver = { | |
484 | .name = DRIVER_NAME, | |
485 | .id_table = amd_mp2_pci_tbl, | |
486 | .probe = amd_mp2_pci_probe, | |
0873d1af | 487 | .driver.pm = &amd_mp2_pm_ops, |
1353ecaf | 488 | .shutdown = amd_sfh_shutdown, |
2105e8e0 | 489 | .remove = amd_sfh_remove, |
4f567b9f SS |
490 | }; |
491 | module_pci_driver(amd_mp2_pci_driver); | |
492 | ||
493 | MODULE_DESCRIPTION(DRIVER_DESC); | |
494 | MODULE_LICENSE("Dual BSD/GPL"); | |
495 | MODULE_AUTHOR("Shyam Sundar S K <Shyam-sundar.S-k@amd.com>"); | |
496 | MODULE_AUTHOR("Sandeep Singh <Sandeep.singh@amd.com>"); | |
f75203cd | 497 | MODULE_AUTHOR("Basavaraj Natikar <Basavaraj.Natikar@amd.com>"); |