| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | | - * exynos_ppmu.c - EXYNOS PPMU (Platform Performance Monitoring Unit) support |
|---|
| 3 | + * exynos_ppmu.c - Exynos PPMU (Platform Performance Monitoring Unit) support |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd. |
|---|
| 5 | 6 | * Author : Chanwoo Choi <cw00.choi@samsung.com> |
|---|
| 6 | | - * |
|---|
| 7 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 8 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 9 | | - * published by the Free Software Foundation. |
|---|
| 10 | 7 | * |
|---|
| 11 | 8 | * This driver is based on drivers/devfreq/exynos/exynos_ppmu.c |
|---|
| 12 | 9 | */ |
|---|
| .. | .. |
|---|
| 16 | 13 | #include <linux/kernel.h> |
|---|
| 17 | 14 | #include <linux/module.h> |
|---|
| 18 | 15 | #include <linux/of_address.h> |
|---|
| 16 | +#include <linux/of_device.h> |
|---|
| 19 | 17 | #include <linux/platform_device.h> |
|---|
| 20 | 18 | #include <linux/regmap.h> |
|---|
| 21 | 19 | #include <linux/suspend.h> |
|---|
| 22 | 20 | #include <linux/devfreq-event.h> |
|---|
| 23 | 21 | |
|---|
| 24 | 22 | #include "exynos-ppmu.h" |
|---|
| 23 | + |
|---|
| 24 | +enum exynos_ppmu_type { |
|---|
| 25 | + EXYNOS_TYPE_PPMU, |
|---|
| 26 | + EXYNOS_TYPE_PPMU_V2, |
|---|
| 27 | +}; |
|---|
| 25 | 28 | |
|---|
| 26 | 29 | struct exynos_ppmu_data { |
|---|
| 27 | 30 | struct clk *clk; |
|---|
| .. | .. |
|---|
| 36 | 39 | struct regmap *regmap; |
|---|
| 37 | 40 | |
|---|
| 38 | 41 | struct exynos_ppmu_data ppmu; |
|---|
| 42 | + enum exynos_ppmu_type ppmu_type; |
|---|
| 39 | 43 | }; |
|---|
| 40 | 44 | |
|---|
| 41 | 45 | #define PPMU_EVENT(name) \ |
|---|
| .. | .. |
|---|
| 89 | 93 | PPMU_EVENT(d1-cpu), |
|---|
| 90 | 94 | PPMU_EVENT(d1-general), |
|---|
| 91 | 95 | PPMU_EVENT(d1-rt), |
|---|
| 96 | + |
|---|
| 97 | + /* For Exynos5422 SoC */ |
|---|
| 98 | + PPMU_EVENT(dmc0_0), |
|---|
| 99 | + PPMU_EVENT(dmc0_1), |
|---|
| 100 | + PPMU_EVENT(dmc1_0), |
|---|
| 101 | + PPMU_EVENT(dmc1_1), |
|---|
| 92 | 102 | }; |
|---|
| 93 | 103 | |
|---|
| 94 | | -static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev) |
|---|
| 104 | +static int __exynos_ppmu_find_ppmu_id(const char *edev_name) |
|---|
| 95 | 105 | { |
|---|
| 96 | 106 | int i; |
|---|
| 97 | 107 | |
|---|
| 98 | 108 | for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) |
|---|
| 99 | | - if (!strcmp(edev->desc->name, ppmu_events[i].name)) |
|---|
| 109 | + if (!strcmp(edev_name, ppmu_events[i].name)) |
|---|
| 100 | 110 | return ppmu_events[i].id; |
|---|
| 101 | 111 | |
|---|
| 102 | 112 | return -EINVAL; |
|---|
| 113 | +} |
|---|
| 114 | + |
|---|
| 115 | +static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev) |
|---|
| 116 | +{ |
|---|
| 117 | + return __exynos_ppmu_find_ppmu_id(edev->desc->name); |
|---|
| 103 | 118 | } |
|---|
| 104 | 119 | |
|---|
| 105 | 120 | /* |
|---|
| .. | .. |
|---|
| 154 | 169 | if (ret < 0) |
|---|
| 155 | 170 | return ret; |
|---|
| 156 | 171 | |
|---|
| 157 | | - /* Set the event of Read/Write data count */ |
|---|
| 172 | + /* Set the event of proper data type monitoring */ |
|---|
| 158 | 173 | ret = regmap_write(info->regmap, PPMU_BEVTxSEL(id), |
|---|
| 159 | | - PPMU_RO_DATA_CNT | PPMU_WO_DATA_CNT); |
|---|
| 174 | + edev->desc->event_type); |
|---|
| 160 | 175 | if (ret < 0) |
|---|
| 161 | 176 | return ret; |
|---|
| 162 | 177 | |
|---|
| .. | .. |
|---|
| 368 | 383 | if (ret < 0) |
|---|
| 369 | 384 | return ret; |
|---|
| 370 | 385 | |
|---|
| 371 | | - /* Set the event of Read/Write data count */ |
|---|
| 372 | | - switch (id) { |
|---|
| 373 | | - case PPMU_PMNCNT0: |
|---|
| 374 | | - case PPMU_PMNCNT1: |
|---|
| 375 | | - case PPMU_PMNCNT2: |
|---|
| 376 | | - ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id), |
|---|
| 377 | | - PPMU_V2_RO_DATA_CNT | PPMU_V2_WO_DATA_CNT); |
|---|
| 378 | | - if (ret < 0) |
|---|
| 379 | | - return ret; |
|---|
| 380 | | - break; |
|---|
| 381 | | - case PPMU_PMNCNT3: |
|---|
| 382 | | - ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id), |
|---|
| 383 | | - PPMU_V2_EVT3_RW_DATA_CNT); |
|---|
| 384 | | - if (ret < 0) |
|---|
| 385 | | - return ret; |
|---|
| 386 | | - break; |
|---|
| 387 | | - } |
|---|
| 386 | + /* Set the event of proper data type monitoring */ |
|---|
| 387 | + ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id), |
|---|
| 388 | + edev->desc->event_type); |
|---|
| 389 | + if (ret < 0) |
|---|
| 390 | + return ret; |
|---|
| 388 | 391 | |
|---|
| 389 | 392 | /* Reset cycle counter/performance counter and enable PPMU */ |
|---|
| 390 | 393 | ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc); |
|---|
| .. | .. |
|---|
| 483 | 486 | static const struct of_device_id exynos_ppmu_id_match[] = { |
|---|
| 484 | 487 | { |
|---|
| 485 | 488 | .compatible = "samsung,exynos-ppmu", |
|---|
| 486 | | - .data = (void *)&exynos_ppmu_ops, |
|---|
| 489 | + .data = (void *)EXYNOS_TYPE_PPMU, |
|---|
| 487 | 490 | }, { |
|---|
| 488 | 491 | .compatible = "samsung,exynos-ppmu-v2", |
|---|
| 489 | | - .data = (void *)&exynos_ppmu_v2_ops, |
|---|
| 492 | + .data = (void *)EXYNOS_TYPE_PPMU_V2, |
|---|
| 490 | 493 | }, |
|---|
| 491 | 494 | { /* sentinel */ }, |
|---|
| 492 | 495 | }; |
|---|
| 493 | 496 | MODULE_DEVICE_TABLE(of, exynos_ppmu_id_match); |
|---|
| 494 | 497 | |
|---|
| 495 | | -static struct devfreq_event_ops *exynos_bus_get_ops(struct device_node *np) |
|---|
| 496 | | -{ |
|---|
| 497 | | - const struct of_device_id *match; |
|---|
| 498 | | - |
|---|
| 499 | | - match = of_match_node(exynos_ppmu_id_match, np); |
|---|
| 500 | | - return (struct devfreq_event_ops *)match->data; |
|---|
| 501 | | -} |
|---|
| 502 | | - |
|---|
| 503 | 498 | static int of_get_devfreq_events(struct device_node *np, |
|---|
| 504 | 499 | struct exynos_ppmu *info) |
|---|
| 505 | 500 | { |
|---|
| 506 | 501 | struct devfreq_event_desc *desc; |
|---|
| 507 | | - struct devfreq_event_ops *event_ops; |
|---|
| 508 | 502 | struct device *dev = info->dev; |
|---|
| 509 | 503 | struct device_node *events_np, *node; |
|---|
| 510 | 504 | int i, j, count; |
|---|
| 505 | + const struct of_device_id *of_id; |
|---|
| 506 | + int ret; |
|---|
| 511 | 507 | |
|---|
| 512 | 508 | events_np = of_get_child_by_name(np, "events"); |
|---|
| 513 | 509 | if (!events_np) { |
|---|
| .. | .. |
|---|
| 515 | 511 | "failed to get child node of devfreq-event devices\n"); |
|---|
| 516 | 512 | return -EINVAL; |
|---|
| 517 | 513 | } |
|---|
| 518 | | - event_ops = exynos_bus_get_ops(np); |
|---|
| 519 | 514 | |
|---|
| 520 | 515 | count = of_get_child_count(events_np); |
|---|
| 521 | 516 | desc = devm_kcalloc(dev, count, sizeof(*desc), GFP_KERNEL); |
|---|
| 522 | | - if (!desc) |
|---|
| 517 | + if (!desc) { |
|---|
| 518 | + of_node_put(events_np); |
|---|
| 523 | 519 | return -ENOMEM; |
|---|
| 520 | + } |
|---|
| 524 | 521 | info->num_events = count; |
|---|
| 522 | + |
|---|
| 523 | + of_id = of_match_device(exynos_ppmu_id_match, dev); |
|---|
| 524 | + if (of_id) |
|---|
| 525 | + info->ppmu_type = (enum exynos_ppmu_type)of_id->data; |
|---|
| 526 | + else { |
|---|
| 527 | + of_node_put(events_np); |
|---|
| 528 | + return -EINVAL; |
|---|
| 529 | + } |
|---|
| 525 | 530 | |
|---|
| 526 | 531 | j = 0; |
|---|
| 527 | 532 | for_each_child_of_node(events_np, node) { |
|---|
| .. | .. |
|---|
| 529 | 534 | if (!ppmu_events[i].name) |
|---|
| 530 | 535 | continue; |
|---|
| 531 | 536 | |
|---|
| 532 | | - if (!of_node_cmp(node->name, ppmu_events[i].name)) |
|---|
| 537 | + if (of_node_name_eq(node, ppmu_events[i].name)) |
|---|
| 533 | 538 | break; |
|---|
| 534 | 539 | } |
|---|
| 535 | 540 | |
|---|
| 536 | 541 | if (i == ARRAY_SIZE(ppmu_events)) { |
|---|
| 537 | 542 | dev_warn(dev, |
|---|
| 538 | | - "don't know how to configure events : %s\n", |
|---|
| 539 | | - node->name); |
|---|
| 543 | + "don't know how to configure events : %pOFn\n", |
|---|
| 544 | + node); |
|---|
| 540 | 545 | continue; |
|---|
| 541 | 546 | } |
|---|
| 542 | 547 | |
|---|
| 543 | | - desc[j].ops = event_ops; |
|---|
| 548 | + switch (info->ppmu_type) { |
|---|
| 549 | + case EXYNOS_TYPE_PPMU: |
|---|
| 550 | + desc[j].ops = &exynos_ppmu_ops; |
|---|
| 551 | + break; |
|---|
| 552 | + case EXYNOS_TYPE_PPMU_V2: |
|---|
| 553 | + desc[j].ops = &exynos_ppmu_v2_ops; |
|---|
| 554 | + break; |
|---|
| 555 | + } |
|---|
| 556 | + |
|---|
| 544 | 557 | desc[j].driver_data = info; |
|---|
| 545 | 558 | |
|---|
| 546 | 559 | of_property_read_string(node, "event-name", &desc[j].name); |
|---|
| 560 | + ret = of_property_read_u32(node, "event-data-type", |
|---|
| 561 | + &desc[j].event_type); |
|---|
| 562 | + if (ret) { |
|---|
| 563 | + /* Set the event of proper data type counting. |
|---|
| 564 | + * Check if the data type has been defined in DT, |
|---|
| 565 | + * use default if not. |
|---|
| 566 | + */ |
|---|
| 567 | + if (info->ppmu_type == EXYNOS_TYPE_PPMU_V2) { |
|---|
| 568 | + int id; |
|---|
| 569 | + /* Not all registers take the same value for |
|---|
| 570 | + * read+write data count. |
|---|
| 571 | + */ |
|---|
| 572 | + id = __exynos_ppmu_find_ppmu_id(desc[j].name); |
|---|
| 573 | + |
|---|
| 574 | + switch (id) { |
|---|
| 575 | + case PPMU_PMNCNT0: |
|---|
| 576 | + case PPMU_PMNCNT1: |
|---|
| 577 | + case PPMU_PMNCNT2: |
|---|
| 578 | + desc[j].event_type = PPMU_V2_RO_DATA_CNT |
|---|
| 579 | + | PPMU_V2_WO_DATA_CNT; |
|---|
| 580 | + break; |
|---|
| 581 | + case PPMU_PMNCNT3: |
|---|
| 582 | + desc[j].event_type = |
|---|
| 583 | + PPMU_V2_EVT3_RW_DATA_CNT; |
|---|
| 584 | + break; |
|---|
| 585 | + } |
|---|
| 586 | + } else { |
|---|
| 587 | + desc[j].event_type = PPMU_RO_DATA_CNT | |
|---|
| 588 | + PPMU_WO_DATA_CNT; |
|---|
| 589 | + } |
|---|
| 590 | + } |
|---|
| 547 | 591 | |
|---|
| 548 | 592 | j++; |
|---|
| 549 | 593 | } |
|---|
| .. | .. |
|---|
| 636 | 680 | for (i = 0; i < info->num_events; i++) { |
|---|
| 637 | 681 | edev[i] = devm_devfreq_event_add_edev(&pdev->dev, &desc[i]); |
|---|
| 638 | 682 | if (IS_ERR(edev[i])) { |
|---|
| 639 | | - ret = PTR_ERR(edev[i]); |
|---|
| 640 | 683 | dev_err(&pdev->dev, |
|---|
| 641 | 684 | "failed to add devfreq-event device\n"); |
|---|
| 642 | 685 | return PTR_ERR(edev[i]); |
|---|