1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright(c) 2019-2022, Intel Corporation. All rights reserved. |
4 | * |
5 | * Intel Management Engine Interface (Intel MEI) Linux driver |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/mei_aux.h> |
10 | #include <linux/device.h> |
11 | #include <linux/irqreturn.h> |
12 | #include <linux/jiffies.h> |
13 | #include <linux/ktime.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/kthread.h> |
17 | |
18 | #include "mei_dev.h" |
19 | #include "hw-me.h" |
20 | #include "hw-me-regs.h" |
21 | |
22 | #include "mei-trace.h" |
23 | |
24 | #define MEI_GSC_RPM_TIMEOUT 500 |
25 | |
26 | static int mei_gsc_read_hfs(const struct mei_device *dev, int where, u32 *val) |
27 | { |
28 | struct mei_me_hw *hw = to_me_hw(dev); |
29 | |
30 | *val = ioread32(hw->mem_addr + where + 0xC00); |
31 | |
32 | return 0; |
33 | } |
34 | |
35 | static void mei_gsc_set_ext_op_mem(const struct mei_me_hw *hw, struct resource *mem) |
36 | { |
37 | u32 low = lower_32_bits(mem->start); |
38 | u32 hi = upper_32_bits(mem->start); |
39 | u32 limit = (resource_size(res: mem) / SZ_4K) | GSC_EXT_OP_MEM_VALID; |
40 | |
41 | iowrite32(low, hw->mem_addr + H_GSC_EXT_OP_MEM_BASE_ADDR_LO_REG); |
42 | iowrite32(hi, hw->mem_addr + H_GSC_EXT_OP_MEM_BASE_ADDR_HI_REG); |
43 | iowrite32(limit, hw->mem_addr + H_GSC_EXT_OP_MEM_LIMIT_REG); |
44 | } |
45 | |
46 | static int mei_gsc_probe(struct auxiliary_device *aux_dev, |
47 | const struct auxiliary_device_id *aux_dev_id) |
48 | { |
49 | struct mei_aux_device *adev = auxiliary_dev_to_mei_aux_dev(aux_dev); |
50 | struct mei_device *dev; |
51 | struct mei_me_hw *hw; |
52 | struct device *device; |
53 | const struct mei_cfg *cfg; |
54 | int ret; |
55 | |
56 | cfg = mei_me_get_cfg(idx: aux_dev_id->driver_data); |
57 | if (!cfg) |
58 | return -ENODEV; |
59 | |
60 | device = &aux_dev->dev; |
61 | |
62 | dev = mei_me_dev_init(parent: device, cfg, slow_fw: adev->slow_firmware); |
63 | if (!dev) { |
64 | ret = -ENOMEM; |
65 | goto err; |
66 | } |
67 | |
68 | hw = to_me_hw(dev); |
69 | hw->mem_addr = devm_ioremap_resource(dev: device, res: &adev->bar); |
70 | if (IS_ERR(ptr: hw->mem_addr)) { |
71 | ret = PTR_ERR(ptr: hw->mem_addr); |
72 | goto err; |
73 | } |
74 | |
75 | hw->irq = adev->irq; |
76 | hw->read_fws = mei_gsc_read_hfs; |
77 | |
78 | dev_set_drvdata(dev: device, data: dev); |
79 | |
80 | if (adev->ext_op_mem.start) { |
81 | mei_gsc_set_ext_op_mem(hw, mem: &adev->ext_op_mem); |
82 | dev->pxp_mode = MEI_DEV_PXP_INIT; |
83 | } |
84 | |
85 | /* use polling */ |
86 | if (mei_me_hw_use_polling(hw)) { |
87 | mei_disable_interrupts(dev); |
88 | mei_clear_interrupts(dev); |
89 | init_waitqueue_head(&hw->wait_active); |
90 | hw->is_active = true; /* start in active mode for initialization */ |
91 | hw->polling_thread = kthread_run(mei_me_polling_thread, dev, |
92 | "kmegscirqd/%s" , dev_name(device)); |
93 | if (IS_ERR(ptr: hw->polling_thread)) { |
94 | ret = PTR_ERR(ptr: hw->polling_thread); |
95 | dev_err(device, "unable to create kernel thread: %d\n" , ret); |
96 | goto err; |
97 | } |
98 | } else { |
99 | ret = devm_request_threaded_irq(dev: device, irq: hw->irq, |
100 | handler: mei_me_irq_quick_handler, |
101 | thread_fn: mei_me_irq_thread_handler, |
102 | IRQF_ONESHOT, KBUILD_MODNAME, dev_id: dev); |
103 | if (ret) { |
104 | dev_err(device, "irq register failed %d\n" , ret); |
105 | goto err; |
106 | } |
107 | } |
108 | |
109 | pm_runtime_get_noresume(dev: device); |
110 | pm_runtime_set_active(dev: device); |
111 | pm_runtime_enable(dev: device); |
112 | |
113 | /* Continue to char device setup in spite of firmware handshake failure. |
114 | * In order to provide access to the firmware status registers to the user |
115 | * space via sysfs. |
116 | */ |
117 | if (mei_start(dev)) |
118 | dev_warn(device, "init hw failure.\n" ); |
119 | |
120 | pm_runtime_set_autosuspend_delay(dev: device, MEI_GSC_RPM_TIMEOUT); |
121 | pm_runtime_use_autosuspend(dev: device); |
122 | |
123 | ret = mei_register(dev, parent: device); |
124 | if (ret) |
125 | goto register_err; |
126 | |
127 | pm_runtime_put_noidle(dev: device); |
128 | return 0; |
129 | |
130 | register_err: |
131 | mei_stop(dev); |
132 | if (!mei_me_hw_use_polling(hw)) |
133 | devm_free_irq(dev: device, irq: hw->irq, dev_id: dev); |
134 | |
135 | err: |
136 | dev_err(device, "probe failed: %d\n" , ret); |
137 | dev_set_drvdata(dev: device, NULL); |
138 | return ret; |
139 | } |
140 | |
141 | static void mei_gsc_remove(struct auxiliary_device *aux_dev) |
142 | { |
143 | struct mei_device *dev; |
144 | struct mei_me_hw *hw; |
145 | |
146 | dev = dev_get_drvdata(dev: &aux_dev->dev); |
147 | hw = to_me_hw(dev); |
148 | |
149 | mei_stop(dev); |
150 | |
151 | hw = to_me_hw(dev); |
152 | if (mei_me_hw_use_polling(hw)) |
153 | kthread_stop(k: hw->polling_thread); |
154 | |
155 | mei_deregister(dev); |
156 | |
157 | pm_runtime_disable(dev: &aux_dev->dev); |
158 | |
159 | mei_disable_interrupts(dev); |
160 | if (!mei_me_hw_use_polling(hw)) |
161 | devm_free_irq(dev: &aux_dev->dev, irq: hw->irq, dev_id: dev); |
162 | } |
163 | |
164 | static int __maybe_unused mei_gsc_pm_suspend(struct device *device) |
165 | { |
166 | struct mei_device *dev = dev_get_drvdata(dev: device); |
167 | |
168 | mei_stop(dev); |
169 | |
170 | mei_disable_interrupts(dev); |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static int __maybe_unused mei_gsc_pm_resume(struct device *device) |
176 | { |
177 | struct mei_device *dev = dev_get_drvdata(dev: device); |
178 | struct auxiliary_device *aux_dev; |
179 | struct mei_aux_device *adev; |
180 | int err; |
181 | struct mei_me_hw *hw; |
182 | |
183 | hw = to_me_hw(dev); |
184 | aux_dev = to_auxiliary_dev(dev: device); |
185 | adev = auxiliary_dev_to_mei_aux_dev(aux_dev); |
186 | if (adev->ext_op_mem.start) { |
187 | mei_gsc_set_ext_op_mem(hw, mem: &adev->ext_op_mem); |
188 | dev->pxp_mode = MEI_DEV_PXP_INIT; |
189 | } |
190 | |
191 | err = mei_restart(dev); |
192 | if (err) |
193 | return err; |
194 | |
195 | /* Start timer if stopped in suspend */ |
196 | schedule_delayed_work(dwork: &dev->timer_work, HZ); |
197 | |
198 | return 0; |
199 | } |
200 | |
201 | static int __maybe_unused mei_gsc_pm_runtime_idle(struct device *device) |
202 | { |
203 | struct mei_device *dev = dev_get_drvdata(dev: device); |
204 | |
205 | if (mei_write_is_idle(dev)) |
206 | pm_runtime_autosuspend(dev: device); |
207 | |
208 | return -EBUSY; |
209 | } |
210 | |
211 | static int __maybe_unused mei_gsc_pm_runtime_suspend(struct device *device) |
212 | { |
213 | struct mei_device *dev = dev_get_drvdata(dev: device); |
214 | struct mei_me_hw *hw; |
215 | int ret; |
216 | |
217 | mutex_lock(&dev->device_lock); |
218 | |
219 | if (mei_write_is_idle(dev)) { |
220 | hw = to_me_hw(dev); |
221 | hw->pg_state = MEI_PG_ON; |
222 | |
223 | if (mei_me_hw_use_polling(hw)) |
224 | hw->is_active = false; |
225 | ret = 0; |
226 | } else { |
227 | ret = -EAGAIN; |
228 | } |
229 | |
230 | mutex_unlock(lock: &dev->device_lock); |
231 | |
232 | return ret; |
233 | } |
234 | |
235 | static int __maybe_unused mei_gsc_pm_runtime_resume(struct device *device) |
236 | { |
237 | struct mei_device *dev = dev_get_drvdata(dev: device); |
238 | struct mei_me_hw *hw; |
239 | irqreturn_t irq_ret; |
240 | |
241 | mutex_lock(&dev->device_lock); |
242 | |
243 | hw = to_me_hw(dev); |
244 | hw->pg_state = MEI_PG_OFF; |
245 | |
246 | if (mei_me_hw_use_polling(hw)) { |
247 | hw->is_active = true; |
248 | wake_up(&hw->wait_active); |
249 | } |
250 | |
251 | mutex_unlock(lock: &dev->device_lock); |
252 | |
253 | irq_ret = mei_me_irq_thread_handler(irq: 1, dev_id: dev); |
254 | if (irq_ret != IRQ_HANDLED) |
255 | dev_err(dev->dev, "thread handler fail %d\n" , irq_ret); |
256 | |
257 | return 0; |
258 | } |
259 | |
260 | static const struct dev_pm_ops mei_gsc_pm_ops = { |
261 | SET_SYSTEM_SLEEP_PM_OPS(mei_gsc_pm_suspend, |
262 | mei_gsc_pm_resume) |
263 | SET_RUNTIME_PM_OPS(mei_gsc_pm_runtime_suspend, |
264 | mei_gsc_pm_runtime_resume, |
265 | mei_gsc_pm_runtime_idle) |
266 | }; |
267 | |
268 | static const struct auxiliary_device_id mei_gsc_id_table[] = { |
269 | { |
270 | .name = "i915.mei-gsc" , |
271 | .driver_data = MEI_ME_GSC_CFG, |
272 | |
273 | }, |
274 | { |
275 | .name = "i915.mei-gscfi" , |
276 | .driver_data = MEI_ME_GSCFI_CFG, |
277 | }, |
278 | { |
279 | .name = "xe.mei-gscfi" , |
280 | .driver_data = MEI_ME_GSCFI_CFG, |
281 | }, |
282 | { |
283 | /* sentinel */ |
284 | } |
285 | }; |
286 | MODULE_DEVICE_TABLE(auxiliary, mei_gsc_id_table); |
287 | |
288 | static struct auxiliary_driver mei_gsc_driver = { |
289 | .probe = mei_gsc_probe, |
290 | .remove = mei_gsc_remove, |
291 | .driver = { |
292 | /* auxiliary_driver_register() sets .name to be the modname */ |
293 | .pm = &mei_gsc_pm_ops, |
294 | }, |
295 | .id_table = mei_gsc_id_table |
296 | }; |
297 | module_auxiliary_driver(mei_gsc_driver); |
298 | |
299 | MODULE_AUTHOR("Intel Corporation" ); |
300 | MODULE_ALIAS("auxiliary:i915.mei-gsc" ); |
301 | MODULE_ALIAS("auxiliary:i915.mei-gscfi" ); |
302 | MODULE_ALIAS("auxiliary:xe.mei-gscfi" ); |
303 | MODULE_DESCRIPTION("Intel(R) Graphics System Controller" ); |
304 | MODULE_LICENSE("GPL" ); |
305 | |