mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-02-14 11:15:54 +00:00
420 lines
12 KiB
C
420 lines
12 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/interconnect.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_opp.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_GLOBAL_IRQ_STATUS 0x0
|
|
#define BWMON_GLOBAL_IRQ_CLEAR 0x8
|
|
#define BWMON_GLOBAL_IRQ_ENABLE 0xc
|
|
#define BWMON_GLOBAL_IRQ_ENABLE_ENABLE BIT(0)
|
|
|
|
#define BWMON_IRQ_STATUS 0x100
|
|
#define BWMON_IRQ_STATUS_ZONE_SHIFT 4
|
|
#define BWMON_IRQ_CLEAR 0x108
|
|
#define BWMON_IRQ_ENABLE 0x10c
|
|
#define BWMON_IRQ_ENABLE_ZONE1_SHIFT 5
|
|
#define BWMON_IRQ_ENABLE_ZONE2_SHIFT 6
|
|
#define BWMON_IRQ_ENABLE_ZONE3_SHIFT 7
|
|
#define BWMON_IRQ_ENABLE_MASK (BIT(BWMON_IRQ_ENABLE_ZONE1_SHIFT) | \
|
|
BIT(BWMON_IRQ_ENABLE_ZONE3_SHIFT))
|
|
|
|
#define BWMON_ENABLE 0x2a0
|
|
#define BWMON_ENABLE_ENABLE BIT(0)
|
|
|
|
#define BWMON_CLEAR 0x2a4
|
|
#define BWMON_CLEAR_CLEAR BIT(0)
|
|
|
|
#define BWMON_SAMPLE_WINDOW 0x2a8
|
|
#define BWMON_THRESHOLD_HIGH 0x2ac
|
|
#define BWMON_THRESHOLD_MED 0x2b0
|
|
#define BWMON_THRESHOLD_LOW 0x2b4
|
|
|
|
#define BWMON_ZONE_ACTIONS 0x2b8
|
|
/*
|
|
* 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))
|
|
/* Value for BWMON_ZONE_ACTIONS */
|
|
#define BWMON_ZONE_ACTIONS_DEFAULT (BWMON_ZONE_ACTIONS_ZONE0 | \
|
|
BWMON_ZONE_ACTIONS_ZONE1 << 8 | \
|
|
BWMON_ZONE_ACTIONS_ZONE2 << 16 | \
|
|
BWMON_ZONE_ACTIONS_ZONE3 << 24)
|
|
|
|
/*
|
|
* There is no clear documentation/explanation of BWMON_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_THRESHOLD_COUNT 0x2bc
|
|
#define BWMON_THRESHOLD_COUNT_ZONE1_SHIFT 8
|
|
#define BWMON_THRESHOLD_COUNT_ZONE2_SHIFT 16
|
|
#define BWMON_THRESHOLD_COUNT_ZONE3_SHIFT 24
|
|
#define BWMON_THRESHOLD_COUNT_ZONE0_DEFAULT 0xff
|
|
#define BWMON_THRESHOLD_COUNT_ZONE2_DEFAULT 0xff
|
|
|
|
/* BWMONv4 count registers use count unit of 64 kB */
|
|
#define BWMON_COUNT_UNIT_KB 64
|
|
#define BWMON_ZONE_COUNT 0x2d8
|
|
#define BWMON_ZONE_MAX(zone) (0x2e0 + 4 * (zone))
|
|
|
|
struct icc_bwmon_data {
|
|
unsigned int sample_ms;
|
|
unsigned int default_highbw_kbps;
|
|
unsigned int default_medbw_kbps;
|
|
unsigned int default_lowbw_kbps;
|
|
u8 zone1_thres_count;
|
|
u8 zone3_thres_count;
|
|
};
|
|
|
|
struct icc_bwmon {
|
|
struct device *dev;
|
|
void __iomem *base;
|
|
int irq;
|
|
|
|
unsigned int default_lowbw_kbps;
|
|
unsigned int sample_ms;
|
|
unsigned int max_bw_kbps;
|
|
unsigned int min_bw_kbps;
|
|
unsigned int target_kbps;
|
|
unsigned int current_kbps;
|
|
};
|
|
|
|
static void bwmon_clear_counters(struct icc_bwmon *bwmon)
|
|
{
|
|
/*
|
|
* 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.
|
|
*/
|
|
writel(BWMON_CLEAR_CLEAR, bwmon->base + BWMON_CLEAR);
|
|
}
|
|
|
|
static void bwmon_clear_irq(struct icc_bwmon *bwmon)
|
|
{
|
|
/*
|
|
* 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.
|
|
*/
|
|
writel(BWMON_IRQ_ENABLE_MASK, bwmon->base + BWMON_IRQ_CLEAR);
|
|
writel(BIT(0), bwmon->base + BWMON_GLOBAL_IRQ_CLEAR);
|
|
}
|
|
|
|
static void bwmon_disable(struct icc_bwmon *bwmon)
|
|
{
|
|
/* Disable interrupts. Strict ordering, see bwmon_clear_irq(). */
|
|
writel(0x0, bwmon->base + BWMON_GLOBAL_IRQ_ENABLE);
|
|
writel(0x0, bwmon->base + BWMON_IRQ_ENABLE);
|
|
|
|
/*
|
|
* Disable bwmon. Must happen before bwmon_clear_irq() to avoid spurious
|
|
* IRQ.
|
|
*/
|
|
writel(0x0, bwmon->base + BWMON_ENABLE);
|
|
}
|
|
|
|
static void bwmon_enable(struct icc_bwmon *bwmon, unsigned int irq_enable)
|
|
{
|
|
/* Enable interrupts */
|
|
writel(BWMON_GLOBAL_IRQ_ENABLE_ENABLE,
|
|
bwmon->base + BWMON_GLOBAL_IRQ_ENABLE);
|
|
writel(irq_enable, bwmon->base + BWMON_IRQ_ENABLE);
|
|
|
|
/* Enable bwmon */
|
|
writel(BWMON_ENABLE_ENABLE, bwmon->base + BWMON_ENABLE);
|
|
}
|
|
|
|
static unsigned int bwmon_kbps_to_count(unsigned int kbps)
|
|
{
|
|
return kbps / BWMON_COUNT_UNIT_KB;
|
|
}
|
|
|
|
static void bwmon_set_threshold(struct icc_bwmon *bwmon, unsigned int reg,
|
|
unsigned int kbps)
|
|
{
|
|
unsigned int thres;
|
|
|
|
thres = mult_frac(bwmon_kbps_to_count(kbps), bwmon->sample_ms,
|
|
MSEC_PER_SEC);
|
|
writel_relaxed(thres, bwmon->base + reg);
|
|
}
|
|
|
|
static void bwmon_start(struct icc_bwmon *bwmon,
|
|
const struct icc_bwmon_data *data)
|
|
{
|
|
unsigned int thres_count;
|
|
int window;
|
|
|
|
bwmon_clear_counters(bwmon);
|
|
|
|
window = mult_frac(bwmon->sample_ms, HW_TIMER_HZ, MSEC_PER_SEC);
|
|
/* Maximum sampling window: 0xfffff */
|
|
writel_relaxed(window, bwmon->base + BWMON_SAMPLE_WINDOW);
|
|
|
|
bwmon_set_threshold(bwmon, BWMON_THRESHOLD_HIGH,
|
|
data->default_highbw_kbps);
|
|
bwmon_set_threshold(bwmon, BWMON_THRESHOLD_MED,
|
|
data->default_medbw_kbps);
|
|
bwmon_set_threshold(bwmon, BWMON_THRESHOLD_LOW,
|
|
data->default_lowbw_kbps);
|
|
|
|
thres_count = data->zone3_thres_count << BWMON_THRESHOLD_COUNT_ZONE3_SHIFT |
|
|
BWMON_THRESHOLD_COUNT_ZONE2_DEFAULT << BWMON_THRESHOLD_COUNT_ZONE2_SHIFT |
|
|
data->zone1_thres_count << BWMON_THRESHOLD_COUNT_ZONE1_SHIFT |
|
|
BWMON_THRESHOLD_COUNT_ZONE0_DEFAULT;
|
|
writel_relaxed(thres_count, bwmon->base + BWMON_THRESHOLD_COUNT);
|
|
writel_relaxed(BWMON_ZONE_ACTIONS_DEFAULT,
|
|
bwmon->base + BWMON_ZONE_ACTIONS);
|
|
/* Write barriers in bwmon_clear_irq() */
|
|
|
|
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;
|
|
|
|
status = readl(bwmon->base + BWMON_IRQ_STATUS);
|
|
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 >> BWMON_IRQ_STATUS_ZONE_SHIFT) - 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.
|
|
*/
|
|
max = readl(bwmon->base + BWMON_ZONE_MAX(zone)) + 1;
|
|
max *= BWMON_COUNT_UNIT_KB;
|
|
bwmon->target_kbps = mult_frac(max, MSEC_PER_SEC, bwmon->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(BWMON_IRQ_ENABLE_ZONE1_SHIFT);
|
|
else if (bwmon->target_kbps <= bwmon->min_bw_kbps)
|
|
irq_enable = BIT(BWMON_IRQ_ENABLE_ZONE3_SHIFT);
|
|
else
|
|
irq_enable = BWMON_IRQ_ENABLE_MASK;
|
|
|
|
bwmon_set_threshold(bwmon, BWMON_THRESHOLD_HIGH, up_kbps);
|
|
bwmon_set_threshold(bwmon, BWMON_THRESHOLD_MED, down_kbps);
|
|
/* Write barriers in bwmon_clear_counters() */
|
|
bwmon_clear_counters(bwmon);
|
|
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_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct dev_pm_opp *opp;
|
|
struct icc_bwmon *bwmon;
|
|
const struct icc_bwmon_data *data;
|
|
int ret;
|
|
|
|
bwmon = devm_kzalloc(dev, sizeof(*bwmon), GFP_KERNEL);
|
|
if (!bwmon)
|
|
return -ENOMEM;
|
|
|
|
data = of_device_get_match_data(dev);
|
|
|
|
bwmon->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(bwmon->base)) {
|
|
dev_err(dev, "failed to map bwmon registers\n");
|
|
return PTR_ERR(bwmon->base);
|
|
}
|
|
|
|
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, ret, "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, ret, "failed to find min peak bandwidth\n");
|
|
|
|
bwmon->sample_ms = data->sample_ms;
|
|
bwmon->default_lowbw_kbps = data->default_lowbw_kbps;
|
|
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, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bwmon_remove(struct platform_device *pdev)
|
|
{
|
|
struct icc_bwmon *bwmon = platform_get_drvdata(pdev);
|
|
|
|
bwmon_disable(bwmon);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* BWMON v4 */
|
|
static const struct icc_bwmon_data msm8998_bwmon_data = {
|
|
.sample_ms = 4,
|
|
.default_highbw_kbps = 4800 * 1024, /* 4.8 GBps */
|
|
.default_medbw_kbps = 512 * 1024, /* 512 MBps */
|
|
.default_lowbw_kbps = 0,
|
|
.zone1_thres_count = 16,
|
|
.zone3_thres_count = 1,
|
|
};
|
|
|
|
static const struct of_device_id bwmon_of_match[] = {
|
|
{ .compatible = "qcom,msm8998-bwmon", .data = &msm8998_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");
|