6484be9dd1
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it as merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Signed-off-by: Rob Herring <robh@kernel.org> Link: https://lore.kernel.org/r/20230714175142.4067795-1-robh@kernel.org Signed-off-by: Bjorn Andersson <andersson@kernel.org>
876 lines
29 KiB
C
876 lines
29 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
|
|
* Copyright (C) 2021-2022 Linaro Ltd
|
|
* Author: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>, based on
|
|
* previous work of Thara Gopinath and msm-4.9 downstream sources.
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/interconnect.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_opp.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/sizes.h>
|
|
|
|
/*
|
|
* The BWMON samples data throughput within 'sample_ms' time. With three
|
|
* configurable thresholds (Low, Medium and High) gives four windows (called
|
|
* zones) of current bandwidth:
|
|
*
|
|
* Zone 0: byte count < THRES_LO
|
|
* Zone 1: THRES_LO < byte count < THRES_MED
|
|
* Zone 2: THRES_MED < byte count < THRES_HIGH
|
|
* Zone 3: THRES_HIGH < byte count
|
|
*
|
|
* Zones 0 and 2 are not used by this driver.
|
|
*/
|
|
|
|
/* Internal sampling clock frequency */
|
|
#define HW_TIMER_HZ 19200000
|
|
|
|
#define BWMON_V4_GLOBAL_IRQ_CLEAR 0x108
|
|
#define BWMON_V4_GLOBAL_IRQ_ENABLE 0x10c
|
|
/*
|
|
* All values here and further are matching regmap fields, so without absolute
|
|
* register offsets.
|
|
*/
|
|
#define BWMON_V4_GLOBAL_IRQ_ENABLE_ENABLE BIT(0)
|
|
|
|
/*
|
|
* Starting with SDM845, the BWMON4 register space has changed a bit:
|
|
* the global registers were jammed into the beginning of the monitor region.
|
|
* To keep the proper offsets, one would have to map <GLOBAL_BASE 0x200> and
|
|
* <GLOBAL_BASE+0x100 0x300>, which is straight up wrong.
|
|
* To facilitate for that, while allowing the older, arguably more proper
|
|
* implementations to work, offset the global registers by -0x100 to avoid
|
|
* having to map half of the global registers twice.
|
|
*/
|
|
#define BWMON_V4_845_OFFSET 0x100
|
|
#define BWMON_V4_GLOBAL_IRQ_CLEAR_845 (BWMON_V4_GLOBAL_IRQ_CLEAR - BWMON_V4_845_OFFSET)
|
|
#define BWMON_V4_GLOBAL_IRQ_ENABLE_845 (BWMON_V4_GLOBAL_IRQ_ENABLE - BWMON_V4_845_OFFSET)
|
|
|
|
#define BWMON_V4_IRQ_STATUS 0x100
|
|
#define BWMON_V4_IRQ_CLEAR 0x108
|
|
|
|
#define BWMON_V4_IRQ_ENABLE 0x10c
|
|
#define BWMON_IRQ_ENABLE_MASK (BIT(1) | BIT(3))
|
|
#define BWMON_V5_IRQ_STATUS 0x000
|
|
#define BWMON_V5_IRQ_CLEAR 0x008
|
|
#define BWMON_V5_IRQ_ENABLE 0x00c
|
|
|
|
#define BWMON_V4_ENABLE 0x2a0
|
|
#define BWMON_V5_ENABLE 0x010
|
|
#define BWMON_ENABLE_ENABLE BIT(0)
|
|
|
|
#define BWMON_V4_CLEAR 0x2a4
|
|
#define BWMON_V5_CLEAR 0x014
|
|
#define BWMON_CLEAR_CLEAR BIT(0)
|
|
#define BWMON_CLEAR_CLEAR_ALL BIT(1)
|
|
|
|
#define BWMON_V4_SAMPLE_WINDOW 0x2a8
|
|
#define BWMON_V5_SAMPLE_WINDOW 0x020
|
|
|
|
#define BWMON_V4_THRESHOLD_HIGH 0x2ac
|
|
#define BWMON_V4_THRESHOLD_MED 0x2b0
|
|
#define BWMON_V4_THRESHOLD_LOW 0x2b4
|
|
#define BWMON_V5_THRESHOLD_HIGH 0x024
|
|
#define BWMON_V5_THRESHOLD_MED 0x028
|
|
#define BWMON_V5_THRESHOLD_LOW 0x02c
|
|
|
|
#define BWMON_V4_ZONE_ACTIONS 0x2b8
|
|
#define BWMON_V5_ZONE_ACTIONS 0x030
|
|
/*
|
|
* Actions to perform on some zone 'z' when current zone hits the threshold:
|
|
* Increment counter of zone 'z'
|
|
*/
|
|
#define BWMON_ZONE_ACTIONS_INCREMENT(z) (0x2 << ((z) * 2))
|
|
/* Clear counter of zone 'z' */
|
|
#define BWMON_ZONE_ACTIONS_CLEAR(z) (0x1 << ((z) * 2))
|
|
|
|
/* Zone 0 threshold hit: Clear zone count */
|
|
#define BWMON_ZONE_ACTIONS_ZONE0 (BWMON_ZONE_ACTIONS_CLEAR(0))
|
|
|
|
/* Zone 1 threshold hit: Increment zone count & clear lower zones */
|
|
#define BWMON_ZONE_ACTIONS_ZONE1 (BWMON_ZONE_ACTIONS_INCREMENT(1) | \
|
|
BWMON_ZONE_ACTIONS_CLEAR(0))
|
|
|
|
/* Zone 2 threshold hit: Increment zone count & clear lower zones */
|
|
#define BWMON_ZONE_ACTIONS_ZONE2 (BWMON_ZONE_ACTIONS_INCREMENT(2) | \
|
|
BWMON_ZONE_ACTIONS_CLEAR(1) | \
|
|
BWMON_ZONE_ACTIONS_CLEAR(0))
|
|
|
|
/* Zone 3 threshold hit: Increment zone count & clear lower zones */
|
|
#define BWMON_ZONE_ACTIONS_ZONE3 (BWMON_ZONE_ACTIONS_INCREMENT(3) | \
|
|
BWMON_ZONE_ACTIONS_CLEAR(2) | \
|
|
BWMON_ZONE_ACTIONS_CLEAR(1) | \
|
|
BWMON_ZONE_ACTIONS_CLEAR(0))
|
|
|
|
/*
|
|
* There is no clear documentation/explanation of BWMON_V4_THRESHOLD_COUNT
|
|
* register. Based on observations, this is number of times one threshold has to
|
|
* be reached, to trigger interrupt in given zone.
|
|
*
|
|
* 0xff are maximum values meant to ignore the zones 0 and 2.
|
|
*/
|
|
#define BWMON_V4_THRESHOLD_COUNT 0x2bc
|
|
#define BWMON_V5_THRESHOLD_COUNT 0x034
|
|
#define BWMON_THRESHOLD_COUNT_ZONE0_DEFAULT 0xff
|
|
#define BWMON_THRESHOLD_COUNT_ZONE2_DEFAULT 0xff
|
|
|
|
#define BWMON_V4_ZONE_MAX(zone) (0x2e0 + 4 * (zone))
|
|
#define BWMON_V5_ZONE_MAX(zone) (0x044 + 4 * (zone))
|
|
|
|
/* Quirks for specific BWMON types */
|
|
#define BWMON_HAS_GLOBAL_IRQ BIT(0)
|
|
#define BWMON_NEEDS_FORCE_CLEAR BIT(1)
|
|
|
|
enum bwmon_fields {
|
|
/* Global region fields, keep them at the top */
|
|
F_GLOBAL_IRQ_CLEAR,
|
|
F_GLOBAL_IRQ_ENABLE,
|
|
F_NUM_GLOBAL_FIELDS,
|
|
|
|
/* Monitor region fields */
|
|
F_IRQ_STATUS = F_NUM_GLOBAL_FIELDS,
|
|
F_IRQ_CLEAR,
|
|
F_IRQ_ENABLE,
|
|
F_ENABLE,
|
|
F_CLEAR,
|
|
F_SAMPLE_WINDOW,
|
|
F_THRESHOLD_HIGH,
|
|
F_THRESHOLD_MED,
|
|
F_THRESHOLD_LOW,
|
|
F_ZONE_ACTIONS_ZONE0,
|
|
F_ZONE_ACTIONS_ZONE1,
|
|
F_ZONE_ACTIONS_ZONE2,
|
|
F_ZONE_ACTIONS_ZONE3,
|
|
F_THRESHOLD_COUNT_ZONE0,
|
|
F_THRESHOLD_COUNT_ZONE1,
|
|
F_THRESHOLD_COUNT_ZONE2,
|
|
F_THRESHOLD_COUNT_ZONE3,
|
|
F_ZONE0_MAX,
|
|
F_ZONE1_MAX,
|
|
F_ZONE2_MAX,
|
|
F_ZONE3_MAX,
|
|
|
|
F_NUM_FIELDS
|
|
};
|
|
|
|
struct icc_bwmon_data {
|
|
unsigned int sample_ms;
|
|
unsigned int count_unit_kb; /* kbytes */
|
|
u8 zone1_thres_count;
|
|
u8 zone3_thres_count;
|
|
unsigned int quirks;
|
|
|
|
const struct regmap_config *regmap_cfg;
|
|
const struct reg_field *regmap_fields;
|
|
|
|
const struct regmap_config *global_regmap_cfg;
|
|
const struct reg_field *global_regmap_fields;
|
|
};
|
|
|
|
struct icc_bwmon {
|
|
struct device *dev;
|
|
const struct icc_bwmon_data *data;
|
|
int irq;
|
|
|
|
struct regmap_field *regs[F_NUM_FIELDS];
|
|
struct regmap_field *global_regs[F_NUM_GLOBAL_FIELDS];
|
|
|
|
unsigned int max_bw_kbps;
|
|
unsigned int min_bw_kbps;
|
|
unsigned int target_kbps;
|
|
unsigned int current_kbps;
|
|
};
|
|
|
|
/* BWMON v4 */
|
|
static const struct reg_field msm8998_bwmon_reg_fields[] = {
|
|
[F_GLOBAL_IRQ_CLEAR] = {},
|
|
[F_GLOBAL_IRQ_ENABLE] = {},
|
|
[F_IRQ_STATUS] = REG_FIELD(BWMON_V4_IRQ_STATUS, 4, 7),
|
|
[F_IRQ_CLEAR] = REG_FIELD(BWMON_V4_IRQ_CLEAR, 4, 7),
|
|
[F_IRQ_ENABLE] = REG_FIELD(BWMON_V4_IRQ_ENABLE, 4, 7),
|
|
/* F_ENABLE covers entire register to disable other features */
|
|
[F_ENABLE] = REG_FIELD(BWMON_V4_ENABLE, 0, 31),
|
|
[F_CLEAR] = REG_FIELD(BWMON_V4_CLEAR, 0, 1),
|
|
[F_SAMPLE_WINDOW] = REG_FIELD(BWMON_V4_SAMPLE_WINDOW, 0, 23),
|
|
[F_THRESHOLD_HIGH] = REG_FIELD(BWMON_V4_THRESHOLD_HIGH, 0, 11),
|
|
[F_THRESHOLD_MED] = REG_FIELD(BWMON_V4_THRESHOLD_MED, 0, 11),
|
|
[F_THRESHOLD_LOW] = REG_FIELD(BWMON_V4_THRESHOLD_LOW, 0, 11),
|
|
[F_ZONE_ACTIONS_ZONE0] = REG_FIELD(BWMON_V4_ZONE_ACTIONS, 0, 7),
|
|
[F_ZONE_ACTIONS_ZONE1] = REG_FIELD(BWMON_V4_ZONE_ACTIONS, 8, 15),
|
|
[F_ZONE_ACTIONS_ZONE2] = REG_FIELD(BWMON_V4_ZONE_ACTIONS, 16, 23),
|
|
[F_ZONE_ACTIONS_ZONE3] = REG_FIELD(BWMON_V4_ZONE_ACTIONS, 24, 31),
|
|
[F_THRESHOLD_COUNT_ZONE0] = REG_FIELD(BWMON_V4_THRESHOLD_COUNT, 0, 7),
|
|
[F_THRESHOLD_COUNT_ZONE1] = REG_FIELD(BWMON_V4_THRESHOLD_COUNT, 8, 15),
|
|
[F_THRESHOLD_COUNT_ZONE2] = REG_FIELD(BWMON_V4_THRESHOLD_COUNT, 16, 23),
|
|
[F_THRESHOLD_COUNT_ZONE3] = REG_FIELD(BWMON_V4_THRESHOLD_COUNT, 24, 31),
|
|
[F_ZONE0_MAX] = REG_FIELD(BWMON_V4_ZONE_MAX(0), 0, 11),
|
|
[F_ZONE1_MAX] = REG_FIELD(BWMON_V4_ZONE_MAX(1), 0, 11),
|
|
[F_ZONE2_MAX] = REG_FIELD(BWMON_V4_ZONE_MAX(2), 0, 11),
|
|
[F_ZONE3_MAX] = REG_FIELD(BWMON_V4_ZONE_MAX(3), 0, 11),
|
|
};
|
|
|
|
static const struct regmap_range msm8998_bwmon_reg_noread_ranges[] = {
|
|
regmap_reg_range(BWMON_V4_IRQ_CLEAR, BWMON_V4_IRQ_CLEAR),
|
|
regmap_reg_range(BWMON_V4_CLEAR, BWMON_V4_CLEAR),
|
|
};
|
|
|
|
static const struct regmap_access_table msm8998_bwmon_reg_read_table = {
|
|
.no_ranges = msm8998_bwmon_reg_noread_ranges,
|
|
.n_no_ranges = ARRAY_SIZE(msm8998_bwmon_reg_noread_ranges),
|
|
};
|
|
|
|
static const struct regmap_range msm8998_bwmon_reg_volatile_ranges[] = {
|
|
regmap_reg_range(BWMON_V4_IRQ_STATUS, BWMON_V4_IRQ_STATUS),
|
|
regmap_reg_range(BWMON_V4_ZONE_MAX(0), BWMON_V4_ZONE_MAX(3)),
|
|
};
|
|
|
|
static const struct regmap_access_table msm8998_bwmon_reg_volatile_table = {
|
|
.yes_ranges = msm8998_bwmon_reg_volatile_ranges,
|
|
.n_yes_ranges = ARRAY_SIZE(msm8998_bwmon_reg_volatile_ranges),
|
|
};
|
|
|
|
static const struct reg_field msm8998_bwmon_global_reg_fields[] = {
|
|
[F_GLOBAL_IRQ_CLEAR] = REG_FIELD(BWMON_V4_GLOBAL_IRQ_CLEAR, 0, 0),
|
|
[F_GLOBAL_IRQ_ENABLE] = REG_FIELD(BWMON_V4_GLOBAL_IRQ_ENABLE, 0, 0),
|
|
};
|
|
|
|
static const struct regmap_range msm8998_bwmon_global_reg_noread_ranges[] = {
|
|
regmap_reg_range(BWMON_V4_GLOBAL_IRQ_CLEAR, BWMON_V4_GLOBAL_IRQ_CLEAR),
|
|
};
|
|
|
|
static const struct regmap_access_table msm8998_bwmon_global_reg_read_table = {
|
|
.no_ranges = msm8998_bwmon_global_reg_noread_ranges,
|
|
.n_no_ranges = ARRAY_SIZE(msm8998_bwmon_global_reg_noread_ranges),
|
|
};
|
|
|
|
/*
|
|
* Fill the cache for non-readable registers only as rest does not really
|
|
* matter and can be read from the device.
|
|
*/
|
|
static const struct reg_default msm8998_bwmon_reg_defaults[] = {
|
|
{ BWMON_V4_IRQ_CLEAR, 0x0 },
|
|
{ BWMON_V4_CLEAR, 0x0 },
|
|
};
|
|
|
|
static const struct reg_default msm8998_bwmon_global_reg_defaults[] = {
|
|
{ BWMON_V4_GLOBAL_IRQ_CLEAR, 0x0 },
|
|
};
|
|
|
|
static const struct regmap_config msm8998_bwmon_regmap_cfg = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
/*
|
|
* No concurrent access expected - driver has one interrupt handler,
|
|
* regmap is not shared, no driver or user-space API.
|
|
*/
|
|
.disable_locking = true,
|
|
.rd_table = &msm8998_bwmon_reg_read_table,
|
|
.volatile_table = &msm8998_bwmon_reg_volatile_table,
|
|
.reg_defaults = msm8998_bwmon_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(msm8998_bwmon_reg_defaults),
|
|
/*
|
|
* Cache is necessary for using regmap fields with non-readable
|
|
* registers.
|
|
*/
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
|
|
static const struct regmap_config msm8998_bwmon_global_regmap_cfg = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
/*
|
|
* No concurrent access expected - driver has one interrupt handler,
|
|
* regmap is not shared, no driver or user-space API.
|
|
*/
|
|
.disable_locking = true,
|
|
.rd_table = &msm8998_bwmon_global_reg_read_table,
|
|
.reg_defaults = msm8998_bwmon_global_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(msm8998_bwmon_global_reg_defaults),
|
|
/*
|
|
* Cache is necessary for using regmap fields with non-readable
|
|
* registers.
|
|
*/
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
|
|
static const struct reg_field sdm845_cpu_bwmon_reg_fields[] = {
|
|
[F_GLOBAL_IRQ_CLEAR] = REG_FIELD(BWMON_V4_GLOBAL_IRQ_CLEAR_845, 0, 0),
|
|
[F_GLOBAL_IRQ_ENABLE] = REG_FIELD(BWMON_V4_GLOBAL_IRQ_ENABLE_845, 0, 0),
|
|
[F_IRQ_STATUS] = REG_FIELD(BWMON_V4_IRQ_STATUS, 4, 7),
|
|
[F_IRQ_CLEAR] = REG_FIELD(BWMON_V4_IRQ_CLEAR, 4, 7),
|
|
[F_IRQ_ENABLE] = REG_FIELD(BWMON_V4_IRQ_ENABLE, 4, 7),
|
|
/* F_ENABLE covers entire register to disable other features */
|
|
[F_ENABLE] = REG_FIELD(BWMON_V4_ENABLE, 0, 31),
|
|
[F_CLEAR] = REG_FIELD(BWMON_V4_CLEAR, 0, 1),
|
|
[F_SAMPLE_WINDOW] = REG_FIELD(BWMON_V4_SAMPLE_WINDOW, 0, 23),
|
|
[F_THRESHOLD_HIGH] = REG_FIELD(BWMON_V4_THRESHOLD_HIGH, 0, 11),
|
|
[F_THRESHOLD_MED] = REG_FIELD(BWMON_V4_THRESHOLD_MED, 0, 11),
|
|
[F_THRESHOLD_LOW] = REG_FIELD(BWMON_V4_THRESHOLD_LOW, 0, 11),
|
|
[F_ZONE_ACTIONS_ZONE0] = REG_FIELD(BWMON_V4_ZONE_ACTIONS, 0, 7),
|
|
[F_ZONE_ACTIONS_ZONE1] = REG_FIELD(BWMON_V4_ZONE_ACTIONS, 8, 15),
|
|
[F_ZONE_ACTIONS_ZONE2] = REG_FIELD(BWMON_V4_ZONE_ACTIONS, 16, 23),
|
|
[F_ZONE_ACTIONS_ZONE3] = REG_FIELD(BWMON_V4_ZONE_ACTIONS, 24, 31),
|
|
[F_THRESHOLD_COUNT_ZONE0] = REG_FIELD(BWMON_V4_THRESHOLD_COUNT, 0, 7),
|
|
[F_THRESHOLD_COUNT_ZONE1] = REG_FIELD(BWMON_V4_THRESHOLD_COUNT, 8, 15),
|
|
[F_THRESHOLD_COUNT_ZONE2] = REG_FIELD(BWMON_V4_THRESHOLD_COUNT, 16, 23),
|
|
[F_THRESHOLD_COUNT_ZONE3] = REG_FIELD(BWMON_V4_THRESHOLD_COUNT, 24, 31),
|
|
[F_ZONE0_MAX] = REG_FIELD(BWMON_V4_ZONE_MAX(0), 0, 11),
|
|
[F_ZONE1_MAX] = REG_FIELD(BWMON_V4_ZONE_MAX(1), 0, 11),
|
|
[F_ZONE2_MAX] = REG_FIELD(BWMON_V4_ZONE_MAX(2), 0, 11),
|
|
[F_ZONE3_MAX] = REG_FIELD(BWMON_V4_ZONE_MAX(3), 0, 11),
|
|
};
|
|
|
|
static const struct regmap_range sdm845_cpu_bwmon_reg_noread_ranges[] = {
|
|
regmap_reg_range(BWMON_V4_GLOBAL_IRQ_CLEAR_845, BWMON_V4_GLOBAL_IRQ_CLEAR_845),
|
|
regmap_reg_range(BWMON_V4_IRQ_CLEAR, BWMON_V4_IRQ_CLEAR),
|
|
regmap_reg_range(BWMON_V4_CLEAR, BWMON_V4_CLEAR),
|
|
};
|
|
|
|
static const struct regmap_access_table sdm845_cpu_bwmon_reg_read_table = {
|
|
.no_ranges = sdm845_cpu_bwmon_reg_noread_ranges,
|
|
.n_no_ranges = ARRAY_SIZE(sdm845_cpu_bwmon_reg_noread_ranges),
|
|
};
|
|
|
|
/*
|
|
* Fill the cache for non-readable registers only as rest does not really
|
|
* matter and can be read from the device.
|
|
*/
|
|
static const struct reg_default sdm845_cpu_bwmon_reg_defaults[] = {
|
|
{ BWMON_V4_GLOBAL_IRQ_CLEAR_845, 0x0 },
|
|
{ BWMON_V4_IRQ_CLEAR, 0x0 },
|
|
{ BWMON_V4_CLEAR, 0x0 },
|
|
};
|
|
|
|
static const struct regmap_config sdm845_cpu_bwmon_regmap_cfg = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
/*
|
|
* No concurrent access expected - driver has one interrupt handler,
|
|
* regmap is not shared, no driver or user-space API.
|
|
*/
|
|
.disable_locking = true,
|
|
.rd_table = &sdm845_cpu_bwmon_reg_read_table,
|
|
.volatile_table = &msm8998_bwmon_reg_volatile_table,
|
|
.reg_defaults = sdm845_cpu_bwmon_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(sdm845_cpu_bwmon_reg_defaults),
|
|
/*
|
|
* Cache is necessary for using regmap fields with non-readable
|
|
* registers.
|
|
*/
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
|
|
/* BWMON v5 */
|
|
static const struct reg_field sdm845_llcc_bwmon_reg_fields[] = {
|
|
[F_GLOBAL_IRQ_CLEAR] = {},
|
|
[F_GLOBAL_IRQ_ENABLE] = {},
|
|
[F_IRQ_STATUS] = REG_FIELD(BWMON_V5_IRQ_STATUS, 0, 3),
|
|
[F_IRQ_CLEAR] = REG_FIELD(BWMON_V5_IRQ_CLEAR, 0, 3),
|
|
[F_IRQ_ENABLE] = REG_FIELD(BWMON_V5_IRQ_ENABLE, 0, 3),
|
|
/* F_ENABLE covers entire register to disable other features */
|
|
[F_ENABLE] = REG_FIELD(BWMON_V5_ENABLE, 0, 31),
|
|
[F_CLEAR] = REG_FIELD(BWMON_V5_CLEAR, 0, 1),
|
|
[F_SAMPLE_WINDOW] = REG_FIELD(BWMON_V5_SAMPLE_WINDOW, 0, 19),
|
|
[F_THRESHOLD_HIGH] = REG_FIELD(BWMON_V5_THRESHOLD_HIGH, 0, 11),
|
|
[F_THRESHOLD_MED] = REG_FIELD(BWMON_V5_THRESHOLD_MED, 0, 11),
|
|
[F_THRESHOLD_LOW] = REG_FIELD(BWMON_V5_THRESHOLD_LOW, 0, 11),
|
|
[F_ZONE_ACTIONS_ZONE0] = REG_FIELD(BWMON_V5_ZONE_ACTIONS, 0, 7),
|
|
[F_ZONE_ACTIONS_ZONE1] = REG_FIELD(BWMON_V5_ZONE_ACTIONS, 8, 15),
|
|
[F_ZONE_ACTIONS_ZONE2] = REG_FIELD(BWMON_V5_ZONE_ACTIONS, 16, 23),
|
|
[F_ZONE_ACTIONS_ZONE3] = REG_FIELD(BWMON_V5_ZONE_ACTIONS, 24, 31),
|
|
[F_THRESHOLD_COUNT_ZONE0] = REG_FIELD(BWMON_V5_THRESHOLD_COUNT, 0, 7),
|
|
[F_THRESHOLD_COUNT_ZONE1] = REG_FIELD(BWMON_V5_THRESHOLD_COUNT, 8, 15),
|
|
[F_THRESHOLD_COUNT_ZONE2] = REG_FIELD(BWMON_V5_THRESHOLD_COUNT, 16, 23),
|
|
[F_THRESHOLD_COUNT_ZONE3] = REG_FIELD(BWMON_V5_THRESHOLD_COUNT, 24, 31),
|
|
[F_ZONE0_MAX] = REG_FIELD(BWMON_V5_ZONE_MAX(0), 0, 11),
|
|
[F_ZONE1_MAX] = REG_FIELD(BWMON_V5_ZONE_MAX(1), 0, 11),
|
|
[F_ZONE2_MAX] = REG_FIELD(BWMON_V5_ZONE_MAX(2), 0, 11),
|
|
[F_ZONE3_MAX] = REG_FIELD(BWMON_V5_ZONE_MAX(3), 0, 11),
|
|
};
|
|
|
|
static const struct regmap_range sdm845_llcc_bwmon_reg_noread_ranges[] = {
|
|
regmap_reg_range(BWMON_V5_IRQ_CLEAR, BWMON_V5_IRQ_CLEAR),
|
|
regmap_reg_range(BWMON_V5_CLEAR, BWMON_V5_CLEAR),
|
|
};
|
|
|
|
static const struct regmap_access_table sdm845_llcc_bwmon_reg_read_table = {
|
|
.no_ranges = sdm845_llcc_bwmon_reg_noread_ranges,
|
|
.n_no_ranges = ARRAY_SIZE(sdm845_llcc_bwmon_reg_noread_ranges),
|
|
};
|
|
|
|
static const struct regmap_range sdm845_llcc_bwmon_reg_volatile_ranges[] = {
|
|
regmap_reg_range(BWMON_V5_IRQ_STATUS, BWMON_V5_IRQ_STATUS),
|
|
regmap_reg_range(BWMON_V5_ZONE_MAX(0), BWMON_V5_ZONE_MAX(3)),
|
|
};
|
|
|
|
static const struct regmap_access_table sdm845_llcc_bwmon_reg_volatile_table = {
|
|
.yes_ranges = sdm845_llcc_bwmon_reg_volatile_ranges,
|
|
.n_yes_ranges = ARRAY_SIZE(sdm845_llcc_bwmon_reg_volatile_ranges),
|
|
};
|
|
|
|
/*
|
|
* Fill the cache for non-readable registers only as rest does not really
|
|
* matter and can be read from the device.
|
|
*/
|
|
static const struct reg_default sdm845_llcc_bwmon_reg_defaults[] = {
|
|
{ BWMON_V5_IRQ_CLEAR, 0x0 },
|
|
{ BWMON_V5_CLEAR, 0x0 },
|
|
};
|
|
|
|
static const struct regmap_config sdm845_llcc_bwmon_regmap_cfg = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
/*
|
|
* No concurrent access expected - driver has one interrupt handler,
|
|
* regmap is not shared, no driver or user-space API.
|
|
*/
|
|
.disable_locking = true,
|
|
.rd_table = &sdm845_llcc_bwmon_reg_read_table,
|
|
.volatile_table = &sdm845_llcc_bwmon_reg_volatile_table,
|
|
.reg_defaults = sdm845_llcc_bwmon_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(sdm845_llcc_bwmon_reg_defaults),
|
|
/*
|
|
* Cache is necessary for using regmap fields with non-readable
|
|
* registers.
|
|
*/
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
|
|
static void bwmon_clear_counters(struct icc_bwmon *bwmon, bool clear_all)
|
|
{
|
|
unsigned int val = BWMON_CLEAR_CLEAR;
|
|
|
|
if (clear_all)
|
|
val |= BWMON_CLEAR_CLEAR_ALL;
|
|
/*
|
|
* Clear counters. The order and barriers are
|
|
* important. Quoting downstream Qualcomm msm-4.9 tree:
|
|
*
|
|
* The counter clear and IRQ clear bits are not in the same 4KB
|
|
* region. So, we need to make sure the counter clear is completed
|
|
* before we try to clear the IRQ or do any other counter operations.
|
|
*/
|
|
regmap_field_force_write(bwmon->regs[F_CLEAR], val);
|
|
if (bwmon->data->quirks & BWMON_NEEDS_FORCE_CLEAR)
|
|
regmap_field_force_write(bwmon->regs[F_CLEAR], 0);
|
|
}
|
|
|
|
static void bwmon_clear_irq(struct icc_bwmon *bwmon)
|
|
{
|
|
struct regmap_field *global_irq_clr;
|
|
|
|
if (bwmon->data->global_regmap_fields)
|
|
global_irq_clr = bwmon->global_regs[F_GLOBAL_IRQ_CLEAR];
|
|
else
|
|
global_irq_clr = bwmon->regs[F_GLOBAL_IRQ_CLEAR];
|
|
|
|
/*
|
|
* Clear zone and global interrupts. The order and barriers are
|
|
* important. Quoting downstream Qualcomm msm-4.9 tree:
|
|
*
|
|
* Synchronize the local interrupt clear in mon_irq_clear()
|
|
* with the global interrupt clear here. Otherwise, the CPU
|
|
* may reorder the two writes and clear the global interrupt
|
|
* before the local interrupt, causing the global interrupt
|
|
* to be retriggered by the local interrupt still being high.
|
|
*
|
|
* Similarly, because the global registers are in a different
|
|
* region than the local registers, we need to ensure any register
|
|
* writes to enable the monitor after this call are ordered with the
|
|
* clearing here so that local writes don't happen before the
|
|
* interrupt is cleared.
|
|
*/
|
|
regmap_field_force_write(bwmon->regs[F_IRQ_CLEAR], BWMON_IRQ_ENABLE_MASK);
|
|
if (bwmon->data->quirks & BWMON_NEEDS_FORCE_CLEAR)
|
|
regmap_field_force_write(bwmon->regs[F_IRQ_CLEAR], 0);
|
|
if (bwmon->data->quirks & BWMON_HAS_GLOBAL_IRQ)
|
|
regmap_field_force_write(global_irq_clr,
|
|
BWMON_V4_GLOBAL_IRQ_ENABLE_ENABLE);
|
|
}
|
|
|
|
static void bwmon_disable(struct icc_bwmon *bwmon)
|
|
{
|
|
struct regmap_field *global_irq_en;
|
|
|
|
if (bwmon->data->global_regmap_fields)
|
|
global_irq_en = bwmon->global_regs[F_GLOBAL_IRQ_ENABLE];
|
|
else
|
|
global_irq_en = bwmon->regs[F_GLOBAL_IRQ_ENABLE];
|
|
|
|
/* Disable interrupts. Strict ordering, see bwmon_clear_irq(). */
|
|
if (bwmon->data->quirks & BWMON_HAS_GLOBAL_IRQ)
|
|
regmap_field_write(global_irq_en, 0x0);
|
|
regmap_field_write(bwmon->regs[F_IRQ_ENABLE], 0x0);
|
|
|
|
/*
|
|
* Disable bwmon. Must happen before bwmon_clear_irq() to avoid spurious
|
|
* IRQ.
|
|
*/
|
|
regmap_field_write(bwmon->regs[F_ENABLE], 0x0);
|
|
}
|
|
|
|
static void bwmon_enable(struct icc_bwmon *bwmon, unsigned int irq_enable)
|
|
{
|
|
struct regmap_field *global_irq_en;
|
|
|
|
if (bwmon->data->global_regmap_fields)
|
|
global_irq_en = bwmon->global_regs[F_GLOBAL_IRQ_ENABLE];
|
|
else
|
|
global_irq_en = bwmon->regs[F_GLOBAL_IRQ_ENABLE];
|
|
|
|
/* Enable interrupts */
|
|
if (bwmon->data->quirks & BWMON_HAS_GLOBAL_IRQ)
|
|
regmap_field_write(global_irq_en,
|
|
BWMON_V4_GLOBAL_IRQ_ENABLE_ENABLE);
|
|
|
|
regmap_field_write(bwmon->regs[F_IRQ_ENABLE], irq_enable);
|
|
|
|
/* Enable bwmon */
|
|
regmap_field_write(bwmon->regs[F_ENABLE], BWMON_ENABLE_ENABLE);
|
|
}
|
|
|
|
static unsigned int bwmon_kbps_to_count(struct icc_bwmon *bwmon,
|
|
unsigned int kbps)
|
|
{
|
|
return kbps / bwmon->data->count_unit_kb;
|
|
}
|
|
|
|
static void bwmon_set_threshold(struct icc_bwmon *bwmon,
|
|
struct regmap_field *reg, unsigned int kbps)
|
|
{
|
|
unsigned int thres;
|
|
|
|
thres = mult_frac(bwmon_kbps_to_count(bwmon, kbps),
|
|
bwmon->data->sample_ms, MSEC_PER_SEC);
|
|
regmap_field_write(reg, thres);
|
|
}
|
|
|
|
static void bwmon_start(struct icc_bwmon *bwmon)
|
|
{
|
|
const struct icc_bwmon_data *data = bwmon->data;
|
|
u32 bw_low = 0;
|
|
int window;
|
|
|
|
/* No need to check for errors, as this must have succeeded before. */
|
|
dev_pm_opp_find_bw_ceil(bwmon->dev, &bw_low, 0);
|
|
|
|
bwmon_clear_counters(bwmon, true);
|
|
|
|
window = mult_frac(bwmon->data->sample_ms, HW_TIMER_HZ, MSEC_PER_SEC);
|
|
/* Maximum sampling window: 0xffffff for v4 and 0xfffff for v5 */
|
|
regmap_field_write(bwmon->regs[F_SAMPLE_WINDOW], window);
|
|
|
|
bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_HIGH], bw_low);
|
|
bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_MED], bw_low);
|
|
bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_LOW], 0);
|
|
|
|
regmap_field_write(bwmon->regs[F_THRESHOLD_COUNT_ZONE0],
|
|
BWMON_THRESHOLD_COUNT_ZONE0_DEFAULT);
|
|
regmap_field_write(bwmon->regs[F_THRESHOLD_COUNT_ZONE1],
|
|
data->zone1_thres_count);
|
|
regmap_field_write(bwmon->regs[F_THRESHOLD_COUNT_ZONE2],
|
|
BWMON_THRESHOLD_COUNT_ZONE2_DEFAULT);
|
|
regmap_field_write(bwmon->regs[F_THRESHOLD_COUNT_ZONE3],
|
|
data->zone3_thres_count);
|
|
|
|
regmap_field_write(bwmon->regs[F_ZONE_ACTIONS_ZONE0],
|
|
BWMON_ZONE_ACTIONS_ZONE0);
|
|
regmap_field_write(bwmon->regs[F_ZONE_ACTIONS_ZONE1],
|
|
BWMON_ZONE_ACTIONS_ZONE1);
|
|
regmap_field_write(bwmon->regs[F_ZONE_ACTIONS_ZONE2],
|
|
BWMON_ZONE_ACTIONS_ZONE2);
|
|
regmap_field_write(bwmon->regs[F_ZONE_ACTIONS_ZONE3],
|
|
BWMON_ZONE_ACTIONS_ZONE3);
|
|
|
|
bwmon_clear_irq(bwmon);
|
|
bwmon_enable(bwmon, BWMON_IRQ_ENABLE_MASK);
|
|
}
|
|
|
|
static irqreturn_t bwmon_intr(int irq, void *dev_id)
|
|
{
|
|
struct icc_bwmon *bwmon = dev_id;
|
|
unsigned int status, max;
|
|
int zone;
|
|
|
|
if (regmap_field_read(bwmon->regs[F_IRQ_STATUS], &status))
|
|
return IRQ_NONE;
|
|
|
|
status &= BWMON_IRQ_ENABLE_MASK;
|
|
if (!status) {
|
|
/*
|
|
* Only zone 1 and zone 3 interrupts are enabled but zone 2
|
|
* threshold could be hit and trigger interrupt even if not
|
|
* enabled.
|
|
* Such spurious interrupt might come with valuable max count or
|
|
* not, so solution would be to always check all
|
|
* BWMON_ZONE_MAX() registers to find the highest value.
|
|
* Such case is currently ignored.
|
|
*/
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
bwmon_disable(bwmon);
|
|
|
|
zone = get_bitmask_order(status) - 1;
|
|
/*
|
|
* Zone max bytes count register returns count units within sampling
|
|
* window. Downstream kernel for BWMONv4 (called BWMON type 2 in
|
|
* downstream) always increments the max bytes count by one.
|
|
*/
|
|
if (regmap_field_read(bwmon->regs[F_ZONE0_MAX + zone], &max))
|
|
return IRQ_NONE;
|
|
|
|
max += 1;
|
|
max *= bwmon->data->count_unit_kb;
|
|
bwmon->target_kbps = mult_frac(max, MSEC_PER_SEC, bwmon->data->sample_ms);
|
|
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
static irqreturn_t bwmon_intr_thread(int irq, void *dev_id)
|
|
{
|
|
struct icc_bwmon *bwmon = dev_id;
|
|
unsigned int irq_enable = 0;
|
|
struct dev_pm_opp *opp, *target_opp;
|
|
unsigned int bw_kbps, up_kbps, down_kbps;
|
|
|
|
bw_kbps = bwmon->target_kbps;
|
|
|
|
target_opp = dev_pm_opp_find_bw_ceil(bwmon->dev, &bw_kbps, 0);
|
|
if (IS_ERR(target_opp) && PTR_ERR(target_opp) == -ERANGE)
|
|
target_opp = dev_pm_opp_find_bw_floor(bwmon->dev, &bw_kbps, 0);
|
|
|
|
bwmon->target_kbps = bw_kbps;
|
|
|
|
bw_kbps--;
|
|
opp = dev_pm_opp_find_bw_floor(bwmon->dev, &bw_kbps, 0);
|
|
if (IS_ERR(opp) && PTR_ERR(opp) == -ERANGE)
|
|
down_kbps = bwmon->target_kbps;
|
|
else
|
|
down_kbps = bw_kbps;
|
|
|
|
up_kbps = bwmon->target_kbps + 1;
|
|
|
|
if (bwmon->target_kbps >= bwmon->max_bw_kbps)
|
|
irq_enable = BIT(1);
|
|
else if (bwmon->target_kbps <= bwmon->min_bw_kbps)
|
|
irq_enable = BIT(3);
|
|
else
|
|
irq_enable = BWMON_IRQ_ENABLE_MASK;
|
|
|
|
bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_HIGH],
|
|
up_kbps);
|
|
bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_MED],
|
|
down_kbps);
|
|
bwmon_clear_counters(bwmon, false);
|
|
bwmon_clear_irq(bwmon);
|
|
bwmon_enable(bwmon, irq_enable);
|
|
|
|
if (bwmon->target_kbps == bwmon->current_kbps)
|
|
goto out;
|
|
|
|
dev_pm_opp_set_opp(bwmon->dev, target_opp);
|
|
bwmon->current_kbps = bwmon->target_kbps;
|
|
|
|
out:
|
|
dev_pm_opp_put(target_opp);
|
|
if (!IS_ERR(opp))
|
|
dev_pm_opp_put(opp);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int bwmon_init_regmap(struct platform_device *pdev,
|
|
struct icc_bwmon *bwmon)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
void __iomem *base;
|
|
struct regmap *map;
|
|
int ret;
|
|
|
|
/* Map the monitor base */
|
|
base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(base))
|
|
return dev_err_probe(dev, PTR_ERR(base),
|
|
"failed to map bwmon registers\n");
|
|
|
|
map = devm_regmap_init_mmio(dev, base, bwmon->data->regmap_cfg);
|
|
if (IS_ERR(map))
|
|
return dev_err_probe(dev, PTR_ERR(map),
|
|
"failed to initialize regmap\n");
|
|
|
|
BUILD_BUG_ON(ARRAY_SIZE(msm8998_bwmon_global_reg_fields) != F_NUM_GLOBAL_FIELDS);
|
|
BUILD_BUG_ON(ARRAY_SIZE(msm8998_bwmon_reg_fields) != F_NUM_FIELDS);
|
|
BUILD_BUG_ON(ARRAY_SIZE(sdm845_cpu_bwmon_reg_fields) != F_NUM_FIELDS);
|
|
BUILD_BUG_ON(ARRAY_SIZE(sdm845_llcc_bwmon_reg_fields) != F_NUM_FIELDS);
|
|
|
|
ret = devm_regmap_field_bulk_alloc(dev, map, bwmon->regs,
|
|
bwmon->data->regmap_fields,
|
|
F_NUM_FIELDS);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (bwmon->data->global_regmap_cfg) {
|
|
/* Map the global base, if separate */
|
|
base = devm_platform_ioremap_resource(pdev, 1);
|
|
if (IS_ERR(base))
|
|
return dev_err_probe(dev, PTR_ERR(base),
|
|
"failed to map bwmon global registers\n");
|
|
|
|
map = devm_regmap_init_mmio(dev, base, bwmon->data->global_regmap_cfg);
|
|
if (IS_ERR(map))
|
|
return dev_err_probe(dev, PTR_ERR(map),
|
|
"failed to initialize global regmap\n");
|
|
|
|
ret = devm_regmap_field_bulk_alloc(dev, map, bwmon->global_regs,
|
|
bwmon->data->global_regmap_fields,
|
|
F_NUM_GLOBAL_FIELDS);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int bwmon_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct dev_pm_opp *opp;
|
|
struct icc_bwmon *bwmon;
|
|
int ret;
|
|
|
|
bwmon = devm_kzalloc(dev, sizeof(*bwmon), GFP_KERNEL);
|
|
if (!bwmon)
|
|
return -ENOMEM;
|
|
|
|
bwmon->data = of_device_get_match_data(dev);
|
|
|
|
ret = bwmon_init_regmap(pdev, bwmon);
|
|
if (ret)
|
|
return ret;
|
|
|
|
bwmon->irq = platform_get_irq(pdev, 0);
|
|
if (bwmon->irq < 0)
|
|
return bwmon->irq;
|
|
|
|
ret = devm_pm_opp_of_add_table(dev);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "failed to add OPP table\n");
|
|
|
|
bwmon->max_bw_kbps = UINT_MAX;
|
|
opp = dev_pm_opp_find_bw_floor(dev, &bwmon->max_bw_kbps, 0);
|
|
if (IS_ERR(opp))
|
|
return dev_err_probe(dev, PTR_ERR(opp), "failed to find max peak bandwidth\n");
|
|
|
|
bwmon->min_bw_kbps = 0;
|
|
opp = dev_pm_opp_find_bw_ceil(dev, &bwmon->min_bw_kbps, 0);
|
|
if (IS_ERR(opp))
|
|
return dev_err_probe(dev, PTR_ERR(opp), "failed to find min peak bandwidth\n");
|
|
|
|
bwmon->dev = dev;
|
|
|
|
bwmon_disable(bwmon);
|
|
ret = devm_request_threaded_irq(dev, bwmon->irq, bwmon_intr,
|
|
bwmon_intr_thread,
|
|
IRQF_ONESHOT, dev_name(dev), bwmon);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "failed to request IRQ\n");
|
|
|
|
platform_set_drvdata(pdev, bwmon);
|
|
bwmon_start(bwmon);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bwmon_remove(struct platform_device *pdev)
|
|
{
|
|
struct icc_bwmon *bwmon = platform_get_drvdata(pdev);
|
|
|
|
bwmon_disable(bwmon);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct icc_bwmon_data msm8998_bwmon_data = {
|
|
.sample_ms = 4,
|
|
.count_unit_kb = 1024,
|
|
.zone1_thres_count = 16,
|
|
.zone3_thres_count = 1,
|
|
.quirks = BWMON_HAS_GLOBAL_IRQ,
|
|
.regmap_fields = msm8998_bwmon_reg_fields,
|
|
.regmap_cfg = &msm8998_bwmon_regmap_cfg,
|
|
.global_regmap_fields = msm8998_bwmon_global_reg_fields,
|
|
.global_regmap_cfg = &msm8998_bwmon_global_regmap_cfg,
|
|
};
|
|
|
|
static const struct icc_bwmon_data sdm845_cpu_bwmon_data = {
|
|
.sample_ms = 4,
|
|
.count_unit_kb = 64,
|
|
.zone1_thres_count = 16,
|
|
.zone3_thres_count = 1,
|
|
.quirks = BWMON_HAS_GLOBAL_IRQ,
|
|
.regmap_fields = sdm845_cpu_bwmon_reg_fields,
|
|
.regmap_cfg = &sdm845_cpu_bwmon_regmap_cfg,
|
|
};
|
|
|
|
static const struct icc_bwmon_data sdm845_llcc_bwmon_data = {
|
|
.sample_ms = 4,
|
|
.count_unit_kb = 1024,
|
|
.zone1_thres_count = 16,
|
|
.zone3_thres_count = 1,
|
|
.regmap_fields = sdm845_llcc_bwmon_reg_fields,
|
|
.regmap_cfg = &sdm845_llcc_bwmon_regmap_cfg,
|
|
};
|
|
|
|
static const struct icc_bwmon_data sc7280_llcc_bwmon_data = {
|
|
.sample_ms = 4,
|
|
.count_unit_kb = 64,
|
|
.zone1_thres_count = 16,
|
|
.zone3_thres_count = 1,
|
|
.quirks = BWMON_NEEDS_FORCE_CLEAR,
|
|
.regmap_fields = sdm845_llcc_bwmon_reg_fields,
|
|
.regmap_cfg = &sdm845_llcc_bwmon_regmap_cfg,
|
|
};
|
|
|
|
static const struct of_device_id bwmon_of_match[] = {
|
|
/* BWMONv4, separate monitor and global register spaces */
|
|
{ .compatible = "qcom,msm8998-bwmon", .data = &msm8998_bwmon_data },
|
|
/* BWMONv4, unified register space */
|
|
{ .compatible = "qcom,sdm845-bwmon", .data = &sdm845_cpu_bwmon_data },
|
|
/* BWMONv5 */
|
|
{ .compatible = "qcom,sdm845-llcc-bwmon", .data = &sdm845_llcc_bwmon_data },
|
|
{ .compatible = "qcom,sc7280-llcc-bwmon", .data = &sc7280_llcc_bwmon_data },
|
|
|
|
/* Compatibles kept for legacy reasons */
|
|
{ .compatible = "qcom,sc7280-cpu-bwmon", .data = &sdm845_cpu_bwmon_data },
|
|
{ .compatible = "qcom,sc8280xp-cpu-bwmon", .data = &sdm845_cpu_bwmon_data },
|
|
{ .compatible = "qcom,sm8550-cpu-bwmon", .data = &sdm845_cpu_bwmon_data },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, bwmon_of_match);
|
|
|
|
static struct platform_driver bwmon_driver = {
|
|
.probe = bwmon_probe,
|
|
.remove = bwmon_remove,
|
|
.driver = {
|
|
.name = "qcom-bwmon",
|
|
.of_match_table = bwmon_of_match,
|
|
},
|
|
};
|
|
module_platform_driver(bwmon_driver);
|
|
|
|
MODULE_AUTHOR("Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>");
|
|
MODULE_DESCRIPTION("QCOM BWMON driver");
|
|
MODULE_LICENSE("GPL");
|