1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * PCI glue code for MIPI I3C HCI driver
4 *
5 * Copyright (C) 2024 Intel Corporation
6 *
7 * Author: Jarkko Nikula <[email protected]>
8 */
9 #include <linux/acpi.h>
10 #include <linux/idr.h>
11 #include <linux/kernel.h>
12 #include <linux/module.h>
13 #include <linux/pci.h>
14 #include <linux/platform_device.h>
15
16 struct mipi_i3c_hci_pci_info {
17 int (*init)(struct pci_dev *pci);
18 };
19
20 #define INTEL_PRIV_OFFSET 0x2b0
21 #define INTEL_PRIV_SIZE 0x28
22 #define INTEL_PRIV_RESETS 0x04
23 #define INTEL_PRIV_RESETS_RESET BIT(0)
24 #define INTEL_PRIV_RESETS_RESET_DONE BIT(1)
25
26 static DEFINE_IDA(mipi_i3c_hci_pci_ida);
27
mipi_i3c_hci_pci_intel_init(struct pci_dev * pci)28 static int mipi_i3c_hci_pci_intel_init(struct pci_dev *pci)
29 {
30 unsigned long timeout;
31 void __iomem *priv;
32
33 priv = devm_ioremap(&pci->dev,
34 pci_resource_start(pci, 0) + INTEL_PRIV_OFFSET,
35 INTEL_PRIV_SIZE);
36 if (!priv)
37 return -ENOMEM;
38
39 /* Assert reset, wait for completion and release reset */
40 writel(0, priv + INTEL_PRIV_RESETS);
41 timeout = jiffies + msecs_to_jiffies(10);
42 while (!(readl(priv + INTEL_PRIV_RESETS) &
43 INTEL_PRIV_RESETS_RESET_DONE)) {
44 if (time_after(jiffies, timeout))
45 break;
46 cpu_relax();
47 }
48 writel(INTEL_PRIV_RESETS_RESET, priv + INTEL_PRIV_RESETS);
49
50 return 0;
51 }
52
53 static struct mipi_i3c_hci_pci_info intel_info = {
54 .init = mipi_i3c_hci_pci_intel_init,
55 };
56
mipi_i3c_hci_pci_probe(struct pci_dev * pci,const struct pci_device_id * id)57 static int mipi_i3c_hci_pci_probe(struct pci_dev *pci,
58 const struct pci_device_id *id)
59 {
60 struct mipi_i3c_hci_pci_info *info;
61 struct platform_device *pdev;
62 struct resource res[2];
63 int dev_id, ret;
64
65 ret = pcim_enable_device(pci);
66 if (ret)
67 return ret;
68
69 pci_set_master(pci);
70
71 memset(&res, 0, sizeof(res));
72
73 res[0].flags = IORESOURCE_MEM;
74 res[0].start = pci_resource_start(pci, 0);
75 res[0].end = pci_resource_end(pci, 0);
76
77 res[1].flags = IORESOURCE_IRQ;
78 res[1].start = pci->irq;
79 res[1].end = pci->irq;
80
81 dev_id = ida_alloc(&mipi_i3c_hci_pci_ida, GFP_KERNEL);
82 if (dev_id < 0)
83 return dev_id;
84
85 pdev = platform_device_alloc("mipi-i3c-hci", dev_id);
86 if (!pdev)
87 return -ENOMEM;
88
89 pdev->dev.parent = &pci->dev;
90 device_set_node(&pdev->dev, dev_fwnode(&pci->dev));
91
92 ret = platform_device_add_resources(pdev, res, ARRAY_SIZE(res));
93 if (ret)
94 goto err;
95
96 info = (struct mipi_i3c_hci_pci_info *)id->driver_data;
97 if (info && info->init) {
98 ret = info->init(pci);
99 if (ret)
100 goto err;
101 }
102
103 ret = platform_device_add(pdev);
104 if (ret)
105 goto err;
106
107 pci_set_drvdata(pci, pdev);
108
109 return 0;
110
111 err:
112 platform_device_put(pdev);
113 ida_free(&mipi_i3c_hci_pci_ida, dev_id);
114 return ret;
115 }
116
mipi_i3c_hci_pci_remove(struct pci_dev * pci)117 static void mipi_i3c_hci_pci_remove(struct pci_dev *pci)
118 {
119 struct platform_device *pdev = pci_get_drvdata(pci);
120 int dev_id = pdev->id;
121
122 platform_device_unregister(pdev);
123 ida_free(&mipi_i3c_hci_pci_ida, dev_id);
124 }
125
126 static const struct pci_device_id mipi_i3c_hci_pci_devices[] = {
127 /* Panther Lake-H */
128 { PCI_VDEVICE(INTEL, 0xe37c), (kernel_ulong_t)&intel_info},
129 { PCI_VDEVICE(INTEL, 0xe36f), (kernel_ulong_t)&intel_info},
130 /* Panther Lake-P */
131 { PCI_VDEVICE(INTEL, 0xe47c), (kernel_ulong_t)&intel_info},
132 { PCI_VDEVICE(INTEL, 0xe46f), (kernel_ulong_t)&intel_info},
133 { },
134 };
135 MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices);
136
137 static struct pci_driver mipi_i3c_hci_pci_driver = {
138 .name = "mipi_i3c_hci_pci",
139 .id_table = mipi_i3c_hci_pci_devices,
140 .probe = mipi_i3c_hci_pci_probe,
141 .remove = mipi_i3c_hci_pci_remove,
142 };
143
144 module_pci_driver(mipi_i3c_hci_pci_driver);
145
146 MODULE_AUTHOR("Jarkko Nikula <[email protected]>");
147 MODULE_LICENSE("GPL");
148 MODULE_DESCRIPTION("MIPI I3C HCI driver on PCI bus");
149