// 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>
#define CREATE_TRACE_POINTS
#include "trace_icc-bwmon.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 0 x108
#define BWMON_V4_GLOBAL_IRQ_ENABLE 0 x10c
/*
* 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 0 x200 > and
* < GLOBAL_BASE + 0 x100 0 x300 > , which is straight up wrong .
* To facilitate for that , while allowing the older , arguably more proper
* implementations to work , offset the global registers by - 0 x100 to avoid
* having to map half of the global registers twice .
*/
#define BWMON_V4_845_OFFSET 0 x100
#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 0 x100
#define BWMON_V4_IRQ_CLEAR 0 x108
#define BWMON_V4_IRQ_ENABLE 0 x10c
#define BWMON_IRQ_ENABLE_MASK (BIT(1 ) | BIT(3 ))
#define BWMON_V5_IRQ_STATUS 0 x000
#define BWMON_V5_IRQ_CLEAR 0 x008
#define BWMON_V5_IRQ_ENABLE 0 x00c
#define BWMON_V4_ENABLE 0 x2a0
#define BWMON_V5_ENABLE 0 x010
#define BWMON_ENABLE_ENABLE BIT(0 )
#define BWMON_V4_CLEAR 0 x2a4
#define BWMON_V5_CLEAR 0 x014
#define BWMON_CLEAR_CLEAR BIT(0 )
#define BWMON_CLEAR_CLEAR_ALL BIT(1 )
#define BWMON_V4_SAMPLE_WINDOW 0 x2a8
#define BWMON_V5_SAMPLE_WINDOW 0 x020
#define BWMON_V4_THRESHOLD_HIGH 0 x2ac
#define BWMON_V4_THRESHOLD_MED 0 x2b0
#define BWMON_V4_THRESHOLD_LOW 0 x2b4
#define BWMON_V5_THRESHOLD_HIGH 0 x024
#define BWMON_V5_THRESHOLD_MED 0 x028
#define BWMON_V5_THRESHOLD_LOW 0 x02c
#define BWMON_V4_ZONE_ACTIONS 0 x2b8
#define BWMON_V5_ZONE_ACTIONS 0 x030
/*
* Actions to perform on some zone ' z ' when current zone hits the threshold :
* Increment counter of zone ' z '
*/
#define BWMON_ZONE_ACTIONS_INCREMENT(z) (0 x2 << ((z) * 2 ))
/* Clear counter of zone 'z' */
#define BWMON_ZONE_ACTIONS_CLEAR(z) (0 x1 << ((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 .
*
* 0 xff are maximum values meant to ignore the zones 0 and 2 .
*/
#define BWMON_V4_THRESHOLD_COUNT 0 x2bc
#define BWMON_V5_THRESHOLD_COUNT 0 x034
#define BWMON_THRESHOLD_COUNT_ZONE0_DEFAULT 0 xff
#define BWMON_THRESHOLD_COUNT_ZONE2_DEFAULT 0 xff
#define BWMON_V4_ZONE_MAX(zone) (0 x2e0 + 4 * (zone))
#define BWMON_V5_ZONE_MAX(zone) (0 x044 + 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, 0 x0 },
{ BWMON_V4_CLEAR, 0 x0 },
};
static const struct reg_default msm8998_bwmon_global_reg_defaults[] = {
{ BWMON_V4_GLOBAL_IRQ_CLEAR, 0 x0 },
};
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_MAPLE,
};
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_MAPLE,
};
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, 0 x0 },
{ BWMON_V4_IRQ_CLEAR, 0 x0 },
{ BWMON_V4_CLEAR, 0 x0 },
};
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_MAPLE,
};
/* 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, 0 x0 },
{ BWMON_V5_CLEAR, 0 x0 },
};
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_MAPLE,
};
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 4 KB
* 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, 0 x0);
regmap_field_write(bwmon->regs[F_IRQ_ENABLE], 0 x0);
/*
* Disable bwmon . Must happen before bwmon_clear_irq ( ) to avoid spurious
* IRQ .
*/
regmap_field_write(bwmon->regs[F_ENABLE], 0 x0);
}
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_put(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, meas_kbps;
bw_kbps = bwmon->target_kbps;
meas_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);
trace_qcom_bwmon_update(dev_name(bwmon->dev), meas_kbps, up_kbps, down_kbps);
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" );
dev_pm_opp_put(opp);
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" );
dev_pm_opp_put(opp);
bwmon->dev = dev;
bwmon_disable(bwmon);
/*
* SoCs with multiple cpu - bwmon instances can end up using a shared interrupt
* line . Using the devm_ variant might result in the IRQ handler being executed
* after bwmon_disable in bwmon_remove ( )
*/
ret = request_threaded_irq(bwmon->irq, bwmon_intr, bwmon_intr_thread,
IRQF_ONESHOT | IRQF_SHARED, 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 void bwmon_remove(struct platform_device *pdev)
{
struct icc_bwmon *bwmon = platform_get_drvdata(pdev);
bwmon_disable(bwmon);
free_irq(bwmon->irq, bwmon);
}
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" );
Messung V0.5 in Prozent C=95 H=100 G=97
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-10-01)
¤
*© Formatika GbR, Deutschland