|
@@ -79,19 +79,38 @@ static const struct of_device_id arm_cci_matches[] = {
|
|
|
|
|
|
#define CCI_PMU_MAX_HW_EVENTS 5 /* CCI PMU has 4 counters + 1 cycle counter */
|
|
#define CCI_PMU_MAX_HW_EVENTS 5 /* CCI PMU has 4 counters + 1 cycle counter */
|
|
|
|
|
|
|
|
+/* Types of interfaces that can generate events */
|
|
|
|
+enum {
|
|
|
|
+ CCI_IF_SLAVE,
|
|
|
|
+ CCI_IF_MASTER,
|
|
|
|
+ CCI_IF_MAX,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct event_range {
|
|
|
|
+ u32 min;
|
|
|
|
+ u32 max;
|
|
|
|
+};
|
|
|
|
+
|
|
struct cci_pmu_hw_events {
|
|
struct cci_pmu_hw_events {
|
|
struct perf_event *events[CCI_PMU_MAX_HW_EVENTS];
|
|
struct perf_event *events[CCI_PMU_MAX_HW_EVENTS];
|
|
unsigned long used_mask[BITS_TO_LONGS(CCI_PMU_MAX_HW_EVENTS)];
|
|
unsigned long used_mask[BITS_TO_LONGS(CCI_PMU_MAX_HW_EVENTS)];
|
|
raw_spinlock_t pmu_lock;
|
|
raw_spinlock_t pmu_lock;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+struct cci_pmu_model {
|
|
|
|
+ char *name;
|
|
|
|
+ struct event_range event_ranges[CCI_IF_MAX];
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static struct cci_pmu_model cci_pmu_models[];
|
|
|
|
+
|
|
struct cci_pmu {
|
|
struct cci_pmu {
|
|
void __iomem *base;
|
|
void __iomem *base;
|
|
struct pmu pmu;
|
|
struct pmu pmu;
|
|
int nr_irqs;
|
|
int nr_irqs;
|
|
int irqs[CCI_PMU_MAX_HW_EVENTS];
|
|
int irqs[CCI_PMU_MAX_HW_EVENTS];
|
|
unsigned long active_irqs;
|
|
unsigned long active_irqs;
|
|
- struct pmu_port_event_ranges *port_ranges;
|
|
|
|
|
|
+ const struct cci_pmu_model *model;
|
|
struct cci_pmu_hw_events hw_events;
|
|
struct cci_pmu_hw_events hw_events;
|
|
struct platform_device *plat_device;
|
|
struct platform_device *plat_device;
|
|
int num_events;
|
|
int num_events;
|
|
@@ -152,53 +171,11 @@ enum cci400_perf_events {
|
|
#define CCI_REV_R1_MASTER_PORT_MIN_EV 0x00
|
|
#define CCI_REV_R1_MASTER_PORT_MIN_EV 0x00
|
|
#define CCI_REV_R1_MASTER_PORT_MAX_EV 0x11
|
|
#define CCI_REV_R1_MASTER_PORT_MAX_EV 0x11
|
|
|
|
|
|
-struct pmu_port_event_ranges {
|
|
|
|
- u8 slave_min;
|
|
|
|
- u8 slave_max;
|
|
|
|
- u8 master_min;
|
|
|
|
- u8 master_max;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-static struct pmu_port_event_ranges port_event_range[] = {
|
|
|
|
- [CCI_REV_R0] = {
|
|
|
|
- .slave_min = CCI_REV_R0_SLAVE_PORT_MIN_EV,
|
|
|
|
- .slave_max = CCI_REV_R0_SLAVE_PORT_MAX_EV,
|
|
|
|
- .master_min = CCI_REV_R0_MASTER_PORT_MIN_EV,
|
|
|
|
- .master_max = CCI_REV_R0_MASTER_PORT_MAX_EV,
|
|
|
|
- },
|
|
|
|
- [CCI_REV_R1] = {
|
|
|
|
- .slave_min = CCI_REV_R1_SLAVE_PORT_MIN_EV,
|
|
|
|
- .slave_max = CCI_REV_R1_SLAVE_PORT_MAX_EV,
|
|
|
|
- .master_min = CCI_REV_R1_MASTER_PORT_MIN_EV,
|
|
|
|
- .master_max = CCI_REV_R1_MASTER_PORT_MAX_EV,
|
|
|
|
- },
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Export different PMU names for the different revisions so userspace knows
|
|
|
|
- * because the event ids are different
|
|
|
|
- */
|
|
|
|
-static char *const pmu_names[] = {
|
|
|
|
- [CCI_REV_R0] = "CCI_400",
|
|
|
|
- [CCI_REV_R1] = "CCI_400_r1",
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-static int pmu_is_valid_slave_event(u8 ev_code)
|
|
|
|
-{
|
|
|
|
- return pmu->port_ranges->slave_min <= ev_code &&
|
|
|
|
- ev_code <= pmu->port_ranges->slave_max;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static int pmu_is_valid_master_event(u8 ev_code)
|
|
|
|
-{
|
|
|
|
- return pmu->port_ranges->master_min <= ev_code &&
|
|
|
|
- ev_code <= pmu->port_ranges->master_max;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
static int pmu_validate_hw_event(u8 hw_event)
|
|
static int pmu_validate_hw_event(u8 hw_event)
|
|
{
|
|
{
|
|
u8 ev_source = CCI_PMU_EVENT_SOURCE(hw_event);
|
|
u8 ev_source = CCI_PMU_EVENT_SOURCE(hw_event);
|
|
u8 ev_code = CCI_PMU_EVENT_CODE(hw_event);
|
|
u8 ev_code = CCI_PMU_EVENT_CODE(hw_event);
|
|
|
|
+ int if_type;
|
|
|
|
|
|
switch (ev_source) {
|
|
switch (ev_source) {
|
|
case CCI_PORT_S0:
|
|
case CCI_PORT_S0:
|
|
@@ -207,18 +184,22 @@ static int pmu_validate_hw_event(u8 hw_event)
|
|
case CCI_PORT_S3:
|
|
case CCI_PORT_S3:
|
|
case CCI_PORT_S4:
|
|
case CCI_PORT_S4:
|
|
/* Slave Interface */
|
|
/* Slave Interface */
|
|
- if (pmu_is_valid_slave_event(ev_code))
|
|
|
|
- return hw_event;
|
|
|
|
|
|
+ if_type = CCI_IF_SLAVE;
|
|
break;
|
|
break;
|
|
case CCI_PORT_M0:
|
|
case CCI_PORT_M0:
|
|
case CCI_PORT_M1:
|
|
case CCI_PORT_M1:
|
|
case CCI_PORT_M2:
|
|
case CCI_PORT_M2:
|
|
/* Master Interface */
|
|
/* Master Interface */
|
|
- if (pmu_is_valid_master_event(ev_code))
|
|
|
|
- return hw_event;
|
|
|
|
|
|
+ if_type = CCI_IF_MASTER;
|
|
break;
|
|
break;
|
|
|
|
+ default:
|
|
|
|
+ return -ENOENT;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (ev_code >= pmu->model->event_ranges[if_type].min &&
|
|
|
|
+ ev_code <= pmu->model->event_ranges[if_type].max)
|
|
|
|
+ return hw_event;
|
|
|
|
+
|
|
return -ENOENT;
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -234,11 +215,9 @@ static int probe_cci_revision(void)
|
|
return CCI_REV_R1;
|
|
return CCI_REV_R1;
|
|
}
|
|
}
|
|
|
|
|
|
-static struct pmu_port_event_ranges *port_range_by_rev(void)
|
|
|
|
|
|
+static const struct cci_pmu_model *probe_cci_model(struct platform_device *pdev)
|
|
{
|
|
{
|
|
- int rev = probe_cci_revision();
|
|
|
|
-
|
|
|
|
- return &port_event_range[rev];
|
|
|
|
|
|
+ return &cci_pmu_models[probe_cci_revision()];
|
|
}
|
|
}
|
|
|
|
|
|
static int pmu_is_valid_counter(struct cci_pmu *cci_pmu, int idx)
|
|
static int pmu_is_valid_counter(struct cci_pmu *cci_pmu, int idx)
|
|
@@ -816,9 +795,9 @@ static const struct attribute_group *pmu_attr_groups[] = {
|
|
|
|
|
|
static int cci_pmu_init(struct cci_pmu *cci_pmu, struct platform_device *pdev)
|
|
static int cci_pmu_init(struct cci_pmu *cci_pmu, struct platform_device *pdev)
|
|
{
|
|
{
|
|
- char *name = pmu_names[probe_cci_revision()];
|
|
|
|
|
|
+ char *name = cci_pmu->model->name;
|
|
cci_pmu->pmu = (struct pmu) {
|
|
cci_pmu->pmu = (struct pmu) {
|
|
- .name = pmu_names[probe_cci_revision()],
|
|
|
|
|
|
+ .name = cci_pmu->model->name,
|
|
.task_ctx_nr = perf_invalid_context,
|
|
.task_ctx_nr = perf_invalid_context,
|
|
.pmu_enable = cci_pmu_enable,
|
|
.pmu_enable = cci_pmu_enable,
|
|
.pmu_disable = cci_pmu_disable,
|
|
.pmu_disable = cci_pmu_disable,
|
|
@@ -871,6 +850,35 @@ static struct notifier_block cci_pmu_cpu_nb = {
|
|
.priority = CPU_PRI_PERF + 1,
|
|
.priority = CPU_PRI_PERF + 1,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static struct cci_pmu_model cci_pmu_models[] = {
|
|
|
|
+ [CCI_REV_R0] = {
|
|
|
|
+ .name = "CCI_400",
|
|
|
|
+ .event_ranges = {
|
|
|
|
+ [CCI_IF_SLAVE] = {
|
|
|
|
+ CCI_REV_R0_SLAVE_PORT_MIN_EV,
|
|
|
|
+ CCI_REV_R0_SLAVE_PORT_MAX_EV,
|
|
|
|
+ },
|
|
|
|
+ [CCI_IF_MASTER] = {
|
|
|
|
+ CCI_REV_R0_MASTER_PORT_MIN_EV,
|
|
|
|
+ CCI_REV_R0_MASTER_PORT_MAX_EV,
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ [CCI_REV_R1] = {
|
|
|
|
+ .name = "CCI_400_r1",
|
|
|
|
+ .event_ranges = {
|
|
|
|
+ [CCI_IF_SLAVE] = {
|
|
|
|
+ CCI_REV_R1_SLAVE_PORT_MIN_EV,
|
|
|
|
+ CCI_REV_R1_SLAVE_PORT_MAX_EV,
|
|
|
|
+ },
|
|
|
|
+ [CCI_IF_MASTER] = {
|
|
|
|
+ CCI_REV_R1_MASTER_PORT_MIN_EV,
|
|
|
|
+ CCI_REV_R1_MASTER_PORT_MAX_EV,
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+
|
|
static const struct of_device_id arm_cci_pmu_matches[] = {
|
|
static const struct of_device_id arm_cci_pmu_matches[] = {
|
|
{
|
|
{
|
|
.compatible = "arm,cci-400-pmu",
|
|
.compatible = "arm,cci-400-pmu",
|
|
@@ -878,6 +886,16 @@ static const struct of_device_id arm_cci_pmu_matches[] = {
|
|
{},
|
|
{},
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static inline const struct cci_pmu_model *get_cci_model(struct platform_device *pdev)
|
|
|
|
+{
|
|
|
|
+ const struct of_device_id *match = of_match_node(arm_cci_pmu_matches,
|
|
|
|
+ pdev->dev.of_node);
|
|
|
|
+ if (!match)
|
|
|
|
+ return NULL;
|
|
|
|
+
|
|
|
|
+ return probe_cci_model(pdev);
|
|
|
|
+}
|
|
|
|
+
|
|
static bool is_duplicate_irq(int irq, int *irqs, int nr_irqs)
|
|
static bool is_duplicate_irq(int irq, int *irqs, int nr_irqs)
|
|
{
|
|
{
|
|
int i;
|
|
int i;
|
|
@@ -893,11 +911,19 @@ static int cci_pmu_probe(struct platform_device *pdev)
|
|
{
|
|
{
|
|
struct resource *res;
|
|
struct resource *res;
|
|
int i, ret, irq;
|
|
int i, ret, irq;
|
|
|
|
+ const struct cci_pmu_model *model;
|
|
|
|
+
|
|
|
|
+ model = get_cci_model(pdev);
|
|
|
|
+ if (!model) {
|
|
|
|
+ dev_warn(&pdev->dev, "CCI PMU version not supported\n");
|
|
|
|
+ return -ENODEV;
|
|
|
|
+ }
|
|
|
|
|
|
pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL);
|
|
pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL);
|
|
if (!pmu)
|
|
if (!pmu)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
+ pmu->model = model;
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
pmu->base = devm_ioremap_resource(&pdev->dev, res);
|
|
pmu->base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(pmu->base))
|
|
if (IS_ERR(pmu->base))
|
|
@@ -929,12 +955,6 @@ static int cci_pmu_probe(struct platform_device *pdev)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
- pmu->port_ranges = port_range_by_rev();
|
|
|
|
- if (!pmu->port_ranges) {
|
|
|
|
- dev_warn(&pdev->dev, "CCI PMU version not supported\n");
|
|
|
|
- return -EINVAL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
raw_spin_lock_init(&pmu->hw_events.pmu_lock);
|
|
raw_spin_lock_init(&pmu->hw_events.pmu_lock);
|
|
mutex_init(&pmu->reserve_mutex);
|
|
mutex_init(&pmu->reserve_mutex);
|
|
atomic_set(&pmu->active_events, 0);
|
|
atomic_set(&pmu->active_events, 0);
|
|
@@ -948,6 +968,7 @@ static int cci_pmu_probe(struct platform_device *pdev)
|
|
if (ret)
|
|
if (ret)
|
|
return ret;
|
|
return ret;
|
|
|
|
|
|
|
|
+ pr_info("ARM %s PMU driver probed", pmu->model->name);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|