1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for the Intel Broxton PMC |
4 | * |
5 | * (C) Copyright 2014 - 2020 Intel Corporation |
6 | * |
7 | * This driver is based on Intel SCU IPC driver (intel_scu_ipc.c) by |
8 | * Sreedhara DS <sreedhara.ds@intel.com> |
9 | * |
10 | * The PMC (Power Management Controller) running on the ARC processor |
11 | * communicates with another entity running in the IA (Intel Architecture) |
12 | * core through an IPC (Intel Processor Communications) mechanism which in |
13 | * turn sends messages between the IA and the PMC. |
14 | */ |
15 | |
16 | #include <linux/acpi.h> |
17 | #include <linux/delay.h> |
18 | #include <linux/errno.h> |
19 | #include <linux/interrupt.h> |
20 | #include <linux/io-64-nonatomic-lo-hi.h> |
21 | #include <linux/mfd/core.h> |
22 | #include <linux/mfd/intel_pmc_bxt.h> |
23 | #include <linux/module.h> |
24 | #include <linux/platform_device.h> |
25 | #include <linux/platform_data/itco_wdt.h> |
26 | |
27 | #include <asm/intel_scu_ipc.h> |
28 | |
29 | /* Residency with clock rate at 19.2MHz to usecs */ |
30 | #define S0IX_RESIDENCY_IN_USECS(d, s) \ |
31 | ({ \ |
32 | u64 result = 10ull * ((d) + (s)); \ |
33 | do_div(result, 192); \ |
34 | result; \ |
35 | }) |
36 | |
37 | /* Resources exported from IFWI */ |
38 | #define PLAT_RESOURCE_IPC_INDEX 0 |
39 | #define PLAT_RESOURCE_IPC_SIZE 0x1000 |
40 | #define PLAT_RESOURCE_GCR_OFFSET 0x1000 |
41 | #define PLAT_RESOURCE_GCR_SIZE 0x1000 |
42 | #define PLAT_RESOURCE_BIOS_DATA_INDEX 1 |
43 | #define PLAT_RESOURCE_BIOS_IFACE_INDEX 2 |
44 | #define PLAT_RESOURCE_TELEM_SSRAM_INDEX 3 |
45 | #define PLAT_RESOURCE_ISP_DATA_INDEX 4 |
46 | #define PLAT_RESOURCE_ISP_IFACE_INDEX 5 |
47 | #define PLAT_RESOURCE_GTD_DATA_INDEX 6 |
48 | #define PLAT_RESOURCE_GTD_IFACE_INDEX 7 |
49 | #define PLAT_RESOURCE_ACPI_IO_INDEX 0 |
50 | |
51 | /* |
52 | * BIOS does not create an ACPI device for each PMC function, but |
53 | * exports multiple resources from one ACPI device (IPC) for multiple |
54 | * functions. This driver is responsible for creating a child device and |
55 | * to export resources for those functions. |
56 | */ |
57 | #define SMI_EN_OFFSET 0x0040 |
58 | #define SMI_EN_SIZE 4 |
59 | #define TCO_BASE_OFFSET 0x0060 |
60 | #define TCO_REGS_SIZE 16 |
61 | #define TELEM_SSRAM_SIZE 240 |
62 | #define TELEM_PMC_SSRAM_OFFSET 0x1b00 |
63 | #define TELEM_PUNIT_SSRAM_OFFSET 0x1a00 |
64 | |
65 | /* Commands */ |
66 | #define PMC_NORTHPEAK_CTRL 0xed |
67 | |
68 | static inline bool is_gcr_valid(u32 offset) |
69 | { |
70 | return offset < PLAT_RESOURCE_GCR_SIZE - 8; |
71 | } |
72 | |
73 | /** |
74 | * intel_pmc_gcr_read64() - Read a 64-bit PMC GCR register |
75 | * @pmc: PMC device pointer |
76 | * @offset: offset of GCR register from GCR address base |
77 | * @data: data pointer for storing the register output |
78 | * |
79 | * Reads the 64-bit PMC GCR register at given offset. |
80 | * |
81 | * Return: Negative value on error or 0 on success. |
82 | */ |
83 | int intel_pmc_gcr_read64(struct intel_pmc_dev *pmc, u32 offset, u64 *data) |
84 | { |
85 | if (!is_gcr_valid(offset)) |
86 | return -EINVAL; |
87 | |
88 | spin_lock(lock: &pmc->gcr_lock); |
89 | *data = readq(addr: pmc->gcr_mem_base + offset); |
90 | spin_unlock(lock: &pmc->gcr_lock); |
91 | |
92 | return 0; |
93 | } |
94 | EXPORT_SYMBOL_GPL(intel_pmc_gcr_read64); |
95 | |
96 | /** |
97 | * intel_pmc_gcr_update() - Update PMC GCR register bits |
98 | * @pmc: PMC device pointer |
99 | * @offset: offset of GCR register from GCR address base |
100 | * @mask: bit mask for update operation |
101 | * @val: update value |
102 | * |
103 | * Updates the bits of given GCR register as specified by |
104 | * @mask and @val. |
105 | * |
106 | * Return: Negative value on error or 0 on success. |
107 | */ |
108 | int intel_pmc_gcr_update(struct intel_pmc_dev *pmc, u32 offset, u32 mask, u32 val) |
109 | { |
110 | u32 new_val; |
111 | |
112 | if (!is_gcr_valid(offset)) |
113 | return -EINVAL; |
114 | |
115 | spin_lock(lock: &pmc->gcr_lock); |
116 | new_val = readl(addr: pmc->gcr_mem_base + offset); |
117 | |
118 | new_val = (new_val & ~mask) | (val & mask); |
119 | writel(val: new_val, addr: pmc->gcr_mem_base + offset); |
120 | |
121 | new_val = readl(addr: pmc->gcr_mem_base + offset); |
122 | spin_unlock(lock: &pmc->gcr_lock); |
123 | |
124 | /* Check whether the bit update is successful */ |
125 | return (new_val & mask) != (val & mask) ? -EIO : 0; |
126 | } |
127 | EXPORT_SYMBOL_GPL(intel_pmc_gcr_update); |
128 | |
129 | /** |
130 | * intel_pmc_s0ix_counter_read() - Read S0ix residency |
131 | * @pmc: PMC device pointer |
132 | * @data: Out param that contains current S0ix residency count. |
133 | * |
134 | * Writes to @data how many usecs the system has been in low-power S0ix |
135 | * state. |
136 | * |
137 | * Return: An error code or 0 on success. |
138 | */ |
139 | int intel_pmc_s0ix_counter_read(struct intel_pmc_dev *pmc, u64 *data) |
140 | { |
141 | u64 deep, shlw; |
142 | |
143 | spin_lock(lock: &pmc->gcr_lock); |
144 | deep = readq(addr: pmc->gcr_mem_base + PMC_GCR_TELEM_DEEP_S0IX_REG); |
145 | shlw = readq(addr: pmc->gcr_mem_base + PMC_GCR_TELEM_SHLW_S0IX_REG); |
146 | spin_unlock(lock: &pmc->gcr_lock); |
147 | |
148 | *data = S0IX_RESIDENCY_IN_USECS(deep, shlw); |
149 | return 0; |
150 | } |
151 | EXPORT_SYMBOL_GPL(intel_pmc_s0ix_counter_read); |
152 | |
153 | /** |
154 | * simplecmd_store() - Send a simple IPC command |
155 | * @dev: Device under the attribute is |
156 | * @attr: Attribute in question |
157 | * @buf: Buffer holding data to be stored to the attribute |
158 | * @count: Number of bytes in @buf |
159 | * |
160 | * Expects a string with two integers separated with space. These two |
161 | * values hold command and subcommand that is send to PMC. |
162 | * |
163 | * Return: Number number of bytes written (@count) or negative errno in |
164 | * case of error. |
165 | */ |
166 | static ssize_t simplecmd_store(struct device *dev, struct device_attribute *attr, |
167 | const char *buf, size_t count) |
168 | { |
169 | struct intel_pmc_dev *pmc = dev_get_drvdata(dev); |
170 | struct intel_scu_ipc_dev *scu = pmc->scu; |
171 | int subcmd; |
172 | int cmd; |
173 | int ret; |
174 | |
175 | ret = sscanf(buf, "%d %d" , &cmd, &subcmd); |
176 | if (ret != 2) { |
177 | dev_err(dev, "Invalid values, expected: cmd subcmd\n" ); |
178 | return -EINVAL; |
179 | } |
180 | |
181 | ret = intel_scu_ipc_dev_simple_command(scu, cmd, sub: subcmd); |
182 | if (ret) |
183 | return ret; |
184 | |
185 | return count; |
186 | } |
187 | static DEVICE_ATTR_WO(simplecmd); |
188 | |
189 | /** |
190 | * northpeak_store() - Enable or disable Northpeak |
191 | * @dev: Device under the attribute is |
192 | * @attr: Attribute in question |
193 | * @buf: Buffer holding data to be stored to the attribute |
194 | * @count: Number of bytes in @buf |
195 | * |
196 | * Expects an unsigned integer. Non-zero enables Northpeak and zero |
197 | * disables it. |
198 | * |
199 | * Return: Number number of bytes written (@count) or negative errno in |
200 | * case of error. |
201 | */ |
202 | static ssize_t northpeak_store(struct device *dev, struct device_attribute *attr, |
203 | const char *buf, size_t count) |
204 | { |
205 | struct intel_pmc_dev *pmc = dev_get_drvdata(dev); |
206 | struct intel_scu_ipc_dev *scu = pmc->scu; |
207 | unsigned long val; |
208 | int subcmd; |
209 | int ret; |
210 | |
211 | ret = kstrtoul(s: buf, base: 0, res: &val); |
212 | if (ret) |
213 | return ret; |
214 | |
215 | /* Northpeak is enabled if subcmd == 1 and disabled if it is 0 */ |
216 | if (val) |
217 | subcmd = 1; |
218 | else |
219 | subcmd = 0; |
220 | |
221 | ret = intel_scu_ipc_dev_simple_command(scu, PMC_NORTHPEAK_CTRL, sub: subcmd); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | return count; |
226 | } |
227 | static DEVICE_ATTR_WO(northpeak); |
228 | |
229 | static struct attribute *intel_pmc_attrs[] = { |
230 | &dev_attr_northpeak.attr, |
231 | &dev_attr_simplecmd.attr, |
232 | NULL |
233 | }; |
234 | |
235 | static const struct attribute_group intel_pmc_group = { |
236 | .attrs = intel_pmc_attrs, |
237 | }; |
238 | |
239 | static const struct attribute_group *intel_pmc_groups[] = { |
240 | &intel_pmc_group, |
241 | NULL |
242 | }; |
243 | |
244 | static struct resource punit_res[6]; |
245 | |
246 | static struct mfd_cell punit = { |
247 | .name = "intel_punit_ipc" , |
248 | .resources = punit_res, |
249 | }; |
250 | |
251 | static struct itco_wdt_platform_data tco_pdata = { |
252 | .name = "Apollo Lake SoC" , |
253 | .version = 5, |
254 | .no_reboot_use_pmc = true, |
255 | }; |
256 | |
257 | static struct resource tco_res[2]; |
258 | |
259 | static const struct mfd_cell tco = { |
260 | .name = "iTCO_wdt" , |
261 | .ignore_resource_conflicts = true, |
262 | .resources = tco_res, |
263 | .num_resources = ARRAY_SIZE(tco_res), |
264 | .platform_data = &tco_pdata, |
265 | .pdata_size = sizeof(tco_pdata), |
266 | }; |
267 | |
268 | static const struct resource telem_res[] = { |
269 | DEFINE_RES_MEM(TELEM_PUNIT_SSRAM_OFFSET, TELEM_SSRAM_SIZE), |
270 | DEFINE_RES_MEM(TELEM_PMC_SSRAM_OFFSET, TELEM_SSRAM_SIZE), |
271 | }; |
272 | |
273 | static const struct mfd_cell telem = { |
274 | .name = "intel_telemetry" , |
275 | .resources = telem_res, |
276 | .num_resources = ARRAY_SIZE(telem_res), |
277 | }; |
278 | |
279 | static int intel_pmc_get_tco_resources(struct platform_device *pdev) |
280 | { |
281 | struct resource *res; |
282 | |
283 | if (acpi_has_watchdog()) |
284 | return 0; |
285 | |
286 | res = platform_get_resource(pdev, IORESOURCE_IO, |
287 | PLAT_RESOURCE_ACPI_IO_INDEX); |
288 | if (!res) { |
289 | dev_err(&pdev->dev, "Failed to get IO resource\n" ); |
290 | return -EINVAL; |
291 | } |
292 | |
293 | tco_res[0].flags = IORESOURCE_IO; |
294 | tco_res[0].start = res->start + TCO_BASE_OFFSET; |
295 | tco_res[0].end = tco_res[0].start + TCO_REGS_SIZE - 1; |
296 | tco_res[1].flags = IORESOURCE_IO; |
297 | tco_res[1].start = res->start + SMI_EN_OFFSET; |
298 | tco_res[1].end = tco_res[1].start + SMI_EN_SIZE - 1; |
299 | |
300 | return 0; |
301 | } |
302 | |
303 | static int intel_pmc_get_resources(struct platform_device *pdev, |
304 | struct intel_pmc_dev *pmc, |
305 | struct intel_scu_ipc_data *scu_data) |
306 | { |
307 | struct resource gcr_res; |
308 | size_t npunit_res = 0; |
309 | struct resource *res; |
310 | int ret; |
311 | |
312 | scu_data->irq = platform_get_irq_optional(pdev, 0); |
313 | |
314 | res = platform_get_resource(pdev, IORESOURCE_MEM, |
315 | PLAT_RESOURCE_IPC_INDEX); |
316 | if (!res) { |
317 | dev_err(&pdev->dev, "Failed to get IPC resource\n" ); |
318 | return -EINVAL; |
319 | } |
320 | |
321 | /* IPC registers */ |
322 | scu_data->mem.flags = res->flags; |
323 | scu_data->mem.start = res->start; |
324 | scu_data->mem.end = res->start + PLAT_RESOURCE_IPC_SIZE - 1; |
325 | |
326 | /* GCR registers */ |
327 | gcr_res.flags = res->flags; |
328 | gcr_res.start = res->start + PLAT_RESOURCE_GCR_OFFSET; |
329 | gcr_res.end = gcr_res.start + PLAT_RESOURCE_GCR_SIZE - 1; |
330 | |
331 | pmc->gcr_mem_base = devm_ioremap_resource(dev: &pdev->dev, res: &gcr_res); |
332 | if (IS_ERR(ptr: pmc->gcr_mem_base)) |
333 | return PTR_ERR(ptr: pmc->gcr_mem_base); |
334 | |
335 | /* Only register iTCO watchdog if there is no WDAT ACPI table */ |
336 | ret = intel_pmc_get_tco_resources(pdev); |
337 | if (ret) |
338 | return ret; |
339 | |
340 | /* BIOS data register */ |
341 | res = platform_get_resource(pdev, IORESOURCE_MEM, |
342 | PLAT_RESOURCE_BIOS_DATA_INDEX); |
343 | if (!res) { |
344 | dev_err(&pdev->dev, "Failed to get resource of P-unit BIOS data\n" ); |
345 | return -EINVAL; |
346 | } |
347 | punit_res[npunit_res++] = *res; |
348 | |
349 | /* BIOS interface register */ |
350 | res = platform_get_resource(pdev, IORESOURCE_MEM, |
351 | PLAT_RESOURCE_BIOS_IFACE_INDEX); |
352 | if (!res) { |
353 | dev_err(&pdev->dev, "Failed to get resource of P-unit BIOS interface\n" ); |
354 | return -EINVAL; |
355 | } |
356 | punit_res[npunit_res++] = *res; |
357 | |
358 | /* ISP data register, optional */ |
359 | res = platform_get_resource(pdev, IORESOURCE_MEM, |
360 | PLAT_RESOURCE_ISP_DATA_INDEX); |
361 | if (res) |
362 | punit_res[npunit_res++] = *res; |
363 | |
364 | /* ISP interface register, optional */ |
365 | res = platform_get_resource(pdev, IORESOURCE_MEM, |
366 | PLAT_RESOURCE_ISP_IFACE_INDEX); |
367 | if (res) |
368 | punit_res[npunit_res++] = *res; |
369 | |
370 | /* GTD data register, optional */ |
371 | res = platform_get_resource(pdev, IORESOURCE_MEM, |
372 | PLAT_RESOURCE_GTD_DATA_INDEX); |
373 | if (res) |
374 | punit_res[npunit_res++] = *res; |
375 | |
376 | /* GTD interface register, optional */ |
377 | res = platform_get_resource(pdev, IORESOURCE_MEM, |
378 | PLAT_RESOURCE_GTD_IFACE_INDEX); |
379 | if (res) |
380 | punit_res[npunit_res++] = *res; |
381 | |
382 | punit.num_resources = npunit_res; |
383 | |
384 | /* Telemetry SSRAM is optional */ |
385 | res = platform_get_resource(pdev, IORESOURCE_MEM, |
386 | PLAT_RESOURCE_TELEM_SSRAM_INDEX); |
387 | if (res) |
388 | pmc->telem_base = res; |
389 | |
390 | return 0; |
391 | } |
392 | |
393 | static int intel_pmc_create_devices(struct intel_pmc_dev *pmc) |
394 | { |
395 | int ret; |
396 | |
397 | if (!acpi_has_watchdog()) { |
398 | ret = devm_mfd_add_devices(dev: pmc->dev, PLATFORM_DEVID_AUTO, cells: &tco, |
399 | n_devs: 1, NULL, irq_base: 0, NULL); |
400 | if (ret) |
401 | return ret; |
402 | } |
403 | |
404 | ret = devm_mfd_add_devices(dev: pmc->dev, PLATFORM_DEVID_AUTO, cells: &punit, n_devs: 1, |
405 | NULL, irq_base: 0, NULL); |
406 | if (ret) |
407 | return ret; |
408 | |
409 | if (pmc->telem_base) { |
410 | ret = devm_mfd_add_devices(dev: pmc->dev, PLATFORM_DEVID_AUTO, |
411 | cells: &telem, n_devs: 1, mem_base: pmc->telem_base, irq_base: 0, NULL); |
412 | } |
413 | |
414 | return ret; |
415 | } |
416 | |
417 | static const struct acpi_device_id intel_pmc_acpi_ids[] = { |
418 | { "INT34D2" }, |
419 | { } |
420 | }; |
421 | MODULE_DEVICE_TABLE(acpi, intel_pmc_acpi_ids); |
422 | |
423 | static int intel_pmc_probe(struct platform_device *pdev) |
424 | { |
425 | struct intel_scu_ipc_data scu_data = {}; |
426 | struct intel_pmc_dev *pmc; |
427 | int ret; |
428 | |
429 | pmc = devm_kzalloc(dev: &pdev->dev, size: sizeof(*pmc), GFP_KERNEL); |
430 | if (!pmc) |
431 | return -ENOMEM; |
432 | |
433 | pmc->dev = &pdev->dev; |
434 | spin_lock_init(&pmc->gcr_lock); |
435 | |
436 | ret = intel_pmc_get_resources(pdev, pmc, scu_data: &scu_data); |
437 | if (ret) { |
438 | dev_err(&pdev->dev, "Failed to request resources\n" ); |
439 | return ret; |
440 | } |
441 | |
442 | pmc->scu = devm_intel_scu_ipc_register(&pdev->dev, &scu_data); |
443 | if (IS_ERR(ptr: pmc->scu)) |
444 | return PTR_ERR(ptr: pmc->scu); |
445 | |
446 | platform_set_drvdata(pdev, data: pmc); |
447 | |
448 | ret = intel_pmc_create_devices(pmc); |
449 | if (ret) |
450 | dev_err(&pdev->dev, "Failed to create PMC devices\n" ); |
451 | |
452 | return ret; |
453 | } |
454 | |
455 | static struct platform_driver intel_pmc_driver = { |
456 | .probe = intel_pmc_probe, |
457 | .driver = { |
458 | .name = "intel_pmc_bxt" , |
459 | .acpi_match_table = intel_pmc_acpi_ids, |
460 | .dev_groups = intel_pmc_groups, |
461 | }, |
462 | }; |
463 | module_platform_driver(intel_pmc_driver); |
464 | |
465 | MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>" ); |
466 | MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>" ); |
467 | MODULE_DESCRIPTION("Intel Broxton PMC driver" ); |
468 | MODULE_LICENSE("GPL v2" ); |
469 | |