// 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_device.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		0x008
#define BWMON_V4_GLOBAL_IRQ_ENABLE		0x00c
/*
 * All values here and further are matching regmap fields, so without absolute
 * register offsets.
 */
#define BWMON_V4_GLOBAL_IRQ_ENABLE_ENABLE	BIT(0)

#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 {
	F_GLOBAL_IRQ_CLEAR,
	F_GLOBAL_IRQ_ENABLE,
	F_IRQ_STATUS,
	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 */
	unsigned int default_highbw_kbps;
	unsigned int default_medbw_kbps;
	unsigned int default_lowbw_kbps;
	u8 zone1_thres_count;
	u8 zone3_thres_count;
	unsigned int quirks;

	const struct regmap_config *regmap_cfg;
	const struct reg_field *regmap_fields;
};

struct icc_bwmon {
	struct device *dev;
	const struct icc_bwmon_data *data;
	int irq;

	struct regmap *regmap;
	struct regmap_field *regs[F_NUM_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]	= REG_FIELD(BWMON_V4_GLOBAL_IRQ_CLEAR, 0, 0),
	[F_GLOBAL_IRQ_ENABLE]	= REG_FIELD(BWMON_V4_GLOBAL_IRQ_ENABLE, 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 msm8998_bwmon_reg_noread_ranges[] = {
	regmap_reg_range(BWMON_V4_GLOBAL_IRQ_CLEAR, BWMON_V4_GLOBAL_IRQ_CLEAR),
	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),
};

/*
 * 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_GLOBAL_IRQ_CLEAR, 0x0 },
	{ BWMON_V4_IRQ_CLEAR, 0x0 },
	{ BWMON_V4_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,
};

/* 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)
{
	/*
	 * 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(bwmon->regs[F_GLOBAL_IRQ_CLEAR],
					 BWMON_V4_GLOBAL_IRQ_ENABLE_ENABLE);
}

static void bwmon_disable(struct icc_bwmon *bwmon)
{
	/* Disable interrupts. Strict ordering, see bwmon_clear_irq(). */
	if (bwmon->data->quirks & BWMON_HAS_GLOBAL_IRQ)
		regmap_field_write(bwmon->regs[F_GLOBAL_IRQ_ENABLE], 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)
{
	/* Enable interrupts */
	if (bwmon->data->quirks & BWMON_HAS_GLOBAL_IRQ)
		regmap_field_write(bwmon->regs[F_GLOBAL_IRQ_ENABLE],
				   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;
	int window;

	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],
			    data->default_highbw_kbps);
	bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_MED],
			    data->default_medbw_kbps);
	bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_LOW],
			    data->default_lowbw_kbps);

	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;

	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_reg_fields) != F_NUM_FIELDS);
	BUILD_BUG_ON(ARRAY_SIZE(sdm845_llcc_bwmon_reg_fields) != F_NUM_FIELDS);

	return devm_regmap_field_bulk_alloc(dev, map, bwmon->regs,
					   bwmon->data->regmap_fields,
					   F_NUM_FIELDS);
}

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 = 64,
	.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,
	.quirks = BWMON_HAS_GLOBAL_IRQ,
	.regmap_fields = msm8998_bwmon_reg_fields,
	.regmap_cfg = &msm8998_bwmon_regmap_cfg,
};

static const struct icc_bwmon_data sdm845_llcc_bwmon_data = {
	.sample_ms = 4,
	.count_unit_kb = 1024,
	.default_highbw_kbps = 800 * 1024, /* 800 MBps */
	.default_medbw_kbps = 256 * 1024, /* 256 MBps */
	.default_lowbw_kbps = 0,
	.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,
	.default_highbw_kbps = 800 * 1024, /* 800 MBps */
	.default_medbw_kbps = 256 * 1024, /* 256 MBps */
	.default_lowbw_kbps = 0,
	.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[] = {
	{
		.compatible = "qcom,msm8998-bwmon",
		.data = &msm8998_bwmon_data
	}, {
		.compatible = "qcom,sdm845-llcc-bwmon",
		.data = &sdm845_llcc_bwmon_data
	}, {
		.compatible = "qcom,sc7280-llcc-bwmon",
		.data = &sc7280_llcc_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");
