* 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/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pm_opp.h>
+#include <linux/regmap.h>
 #include <linux/sizes.h>
 
 /*
 /* Internal sampling clock frequency */
 #define HW_TIMER_HZ                            19200000
 
-#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_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_V4_ENABLE                                0x2a0
 #define BWMON_ENABLE_ENABLE                    BIT(0)
 
-#define BWMON_CLEAR                            0x2a4
+#define BWMON_V4_CLEAR                         0x2a4
 #define BWMON_CLEAR_CLEAR                      BIT(0)
 #define BWMON_CLEAR_CLEAR_ALL                  BIT(1)
 
-#define BWMON_SAMPLE_WINDOW                    0x2a8
-#define BWMON_THRESHOLD_HIGH                   0x2ac
-#define BWMON_THRESHOLD_MED                    0x2b0
-#define BWMON_THRESHOLD_LOW                    0x2b4
+#define BWMON_V4_SAMPLE_WINDOW                 0x2a8
+#define BWMON_V4_THRESHOLD_HIGH                        0x2ac
+#define BWMON_V4_THRESHOLD_MED                 0x2b0
+#define BWMON_V4_THRESHOLD_LOW                 0x2b4
 
-#define BWMON_ZONE_ACTIONS                     0x2b8
+#define BWMON_V4_ZONE_ACTIONS                  0x2b8
 /*
  * Actions to perform on some zone 'z' when current zone hits the threshold:
  * Increment counter of zone 'z'
                                                 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
+ * 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_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_V4_THRESHOLD_COUNT               0x2bc
 #define BWMON_THRESHOLD_COUNT_ZONE0_DEFAULT    0xff
 #define BWMON_THRESHOLD_COUNT_ZONE2_DEFAULT    0xff
 
-#define BWMON_ZONE_MAX(zone)                   (0x2e0 + 4 * (zone))
+#define BWMON_V4_ZONE_MAX(zone)                        (0x2e0 + 4 * (zone))
+
+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 default_lowbw_kbps;
        u8 zone1_thres_count;
        u8 zone3_thres_count;
+
+       const struct regmap_config *regmap_cfg;
+       const struct reg_field *regmap_fields;
 };
 
 struct icc_bwmon {
        struct device *dev;
        const struct icc_bwmon_data *data;
-       void __iomem *base;
        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,
+};
+
 static void bwmon_clear_counters(struct icc_bwmon *bwmon, bool clear_all)
 {
        unsigned int val = BWMON_CLEAR_CLEAR;
         * 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(val, bwmon->base + BWMON_CLEAR);
+       regmap_field_force_write(bwmon->regs[F_CLEAR], val);
 }
 
 static void bwmon_clear_irq(struct icc_bwmon *bwmon)
         * 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(BWMON_GLOBAL_IRQ_ENABLE_ENABLE,
-              bwmon->base + BWMON_GLOBAL_IRQ_CLEAR);
+       regmap_field_force_write(bwmon->regs[F_IRQ_CLEAR], BWMON_IRQ_ENABLE_MASK);
+       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(). */
-       writel(0x0, bwmon->base + BWMON_GLOBAL_IRQ_ENABLE);
-       writel(0x0, bwmon->base + BWMON_IRQ_ENABLE);
+       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.
         */
-       writel(0x0, bwmon->base + BWMON_ENABLE);
+       regmap_field_write(bwmon->regs[F_ENABLE], 0x0);
 }
 
 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);
+       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 */
-       writel(BWMON_ENABLE_ENABLE, bwmon->base + BWMON_ENABLE);
+       regmap_field_write(bwmon->regs[F_ENABLE], BWMON_ENABLE_ENABLE);
 }
 
 static unsigned int bwmon_kbps_to_count(struct icc_bwmon *bwmon,
        return kbps / bwmon->data->count_unit_kb;
 }
 
-static void bwmon_set_threshold(struct icc_bwmon *bwmon, unsigned int reg,
-                               unsigned int kbps)
+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);
-       writel_relaxed(thres, bwmon->base + reg);
+       regmap_field_write(reg, thres);
 }
 
 static void bwmon_start(struct icc_bwmon *bwmon)
 {
        const struct icc_bwmon_data *data = bwmon->data;
-       unsigned int thres_count;
        int window;
 
        bwmon_clear_counters(bwmon, true);
 
        window = mult_frac(bwmon->data->sample_ms, HW_TIMER_HZ, MSEC_PER_SEC);
        /* Maximum sampling window: 0xfffff */
-       writel_relaxed(window, bwmon->base + BWMON_SAMPLE_WINDOW);
+       regmap_field_write(bwmon->regs[F_SAMPLE_WINDOW], window);
 
-       bwmon_set_threshold(bwmon, BWMON_THRESHOLD_HIGH,
+       bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_HIGH],
                            data->default_highbw_kbps);
-       bwmon_set_threshold(bwmon, BWMON_THRESHOLD_MED,
+       bwmon_set_threshold(bwmon, bwmon->regs[F_THRESHOLD_MED],
                            data->default_medbw_kbps);
-       bwmon_set_threshold(bwmon, BWMON_THRESHOLD_LOW,
+       bwmon_set_threshold(bwmon, bwmon->regs[F_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() */
+       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);
        unsigned int status, max;
        int zone;
 
-       status = readl(bwmon->base + BWMON_IRQ_STATUS);
+       if (regmap_field_read(bwmon->regs[F_IRQ_STATUS], &status))
+               return IRQ_NONE;
+
        status &= BWMON_IRQ_ENABLE_MASK;
        if (!status) {
                /*
 
        bwmon_disable(bwmon);
 
-       zone = get_bitmask_order(status >> BWMON_IRQ_STATUS_ZONE_SHIFT) - 1;
+       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.
         */
-       max = readl(bwmon->base + BWMON_ZONE_MAX(zone)) + 1;
+       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);
 
        up_kbps = bwmon->target_kbps + 1;
 
        if (bwmon->target_kbps >= bwmon->max_bw_kbps)
-               irq_enable = BIT(BWMON_IRQ_ENABLE_ZONE1_SHIFT);
+               irq_enable = BIT(1);
        else if (bwmon->target_kbps <= bwmon->min_bw_kbps)
-               irq_enable = BIT(BWMON_IRQ_ENABLE_ZONE3_SHIFT);
+               irq_enable = BIT(3);
        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_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);
        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;
+
+       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);
+       ret = devm_regmap_field_bulk_alloc(dev, map, bwmon->regs,
+                                          bwmon->data->regmap_fields,
+                                          F_NUM_FIELDS);
+
+       return ret;
+}
+
 static int bwmon_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
 
        bwmon->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);
-       }
+       ret = bwmon_init_regmap(pdev, bwmon);
+       if (ret)
+               return ret;
 
        bwmon->irq = platform_get_irq(pdev, 0);
        if (bwmon->irq < 0)
        return 0;
 }
 
-/* BWMON v4 */
 static const struct icc_bwmon_data msm8998_bwmon_data = {
        .sample_ms = 4,
        .count_unit_kb = 64,
        .default_lowbw_kbps = 0,
        .zone1_thres_count = 16,
        .zone3_thres_count = 1,
+       .regmap_fields = msm8998_bwmon_reg_fields,
+       .regmap_cfg = &msm8998_bwmon_regmap_cfg,
 };
 
 static const struct of_device_id bwmon_of_match[] = {