.. | .. |
---|
| 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]); |
---|