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