Re: [PATCH 2/3] soc: qcom: icc-bwmon: Handle global registers correctly

From: Krzysztof Kozlowski
Date: Sun Mar 05 2023 - 10:07:02 EST


On 04/03/2023 16:39, Konrad Dybcio wrote:
> The BWMON hardware has two sets of registers: one for the monitor itself
> and one called "global". It has what seems to be some kind of a head
> switch and an interrupt control register. It's usually 0x200 in size.
>
> On fairly recent SoCs (with the starting point seemingly being moving
> the OSM programming to the firmware) these two register sets are
> contiguous and overlapping, like this (on sm8450):
>
> /* notice how base.start == global_base.start+0x100 */
> reg = <0x90b6400 0x300>, <0x90b6300 0x200>;
> reg-names = "base", "global_base";
>
> Which led to some confusion and the assumption that since the
> "interesting" global registers begin right after global_base+0x100,
> there's no need to map two separate regions and one can simply subtract
> 0x100 from the offsets.
>
> This is however not the case for anything older than SDM845, as the
> global region can appear in seemingly random spots on the register map.
>
> Add support for it to let bwmon function on older SoCs like MSM8998 and
> allow operation with just one set of registers for newer platforms.
>
> Fixes: b9c2ae6cac40 ("soc: qcom: icc-bwmon: Add bandwidth monitoring driver")

You did not describe any bug to be fixed. Adding support for different
devices with different memory layour is a feature, not bugfix. If this
is a bugfix, please share what exactly is broken on sdm845?

> Signed-off-by: Konrad Dybcio <konrad.dybcio@xxxxxxxxxx>
> ---
> drivers/soc/qcom/icc-bwmon.c | 136 +++++++++++++++++++++++++++++++++++++------
> 1 file changed, 118 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/soc/qcom/icc-bwmon.c b/drivers/soc/qcom/icc-bwmon.c
> index d07be3700db6..9ef632d80ee3 100644
> --- a/drivers/soc/qcom/icc-bwmon.c
> +++ b/drivers/soc/qcom/icc-bwmon.c
> @@ -34,14 +34,27 @@
> /* Internal sampling clock frequency */
> #define HW_TIMER_HZ 19200000
>
> -#define BWMON_V4_GLOBAL_IRQ_CLEAR 0x008
> -#define BWMON_V4_GLOBAL_IRQ_ENABLE 0x00c
> +#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

MSM8998? It's a bit confusing to keep calling it 845 while it is for
MSM8998 variant... or it's not anymore for MSM8998?


> +#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
>
> @@ -118,8 +131,10 @@
> #define BWMON_NEEDS_FORCE_CLEAR BIT(1)
>
> enum bwmon_fields {
> - F_GLOBAL_IRQ_CLEAR,
> - F_GLOBAL_IRQ_ENABLE,
> + /* Fields used only on >=SDM845 with BWMON_HAS_GLOBAL_IRQ */
> + F_GLB_IRQ_CLEAR,
> + F_GLB_IRQ_ENABLE,

I am not sure what's the benefit of this rename.

> +
> F_IRQ_STATUS,
> F_IRQ_CLEAR,
> F_IRQ_ENABLE,
> @@ -145,6 +160,13 @@ enum bwmon_fields {
> F_NUM_FIELDS
> };
>
> +enum bwmon_global_fields {
> + F_GLOBAL_IRQ_CLEAR,
> + F_GLOBAL_IRQ_ENABLE,
> +
> + F_NUM_GLOBAL_FIELDS
> +};
> +
> struct icc_bwmon_data {
> unsigned int sample_ms;
> unsigned int count_unit_kb; /* kbytes */
> @@ -157,6 +179,9 @@ struct icc_bwmon_data {
>
> 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 {
> @@ -166,6 +191,7 @@ struct icc_bwmon {
>
> struct regmap *regmap;
> struct regmap_field *regs[F_NUM_FIELDS];
> + struct regmap_field *global_regs[F_NUM_FIELDS];
>
> unsigned int max_bw_kbps;
> unsigned int min_bw_kbps;
> @@ -175,8 +201,8 @@ struct icc_bwmon {
>
> /* 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_GLB_IRQ_CLEAR] = REG_FIELD(BWMON_V4_GLOBAL_IRQ_CLEAR_845, 0, 0),
> + [F_GLB_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),
> @@ -202,7 +228,7 @@ static const struct reg_field msm8998_bwmon_reg_fields[] = {
> };
>
> 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_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),
> };
> @@ -222,16 +248,34 @@ static const struct regmap_access_table msm8998_bwmon_reg_volatile_table = {
> .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_GLOBAL_IRQ_CLEAR, 0x0 },
> + { BWMON_V4_GLOBAL_IRQ_CLEAR_845, 0x0 },
> { 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,
> @@ -252,10 +296,27 @@ static const struct regmap_config msm8998_bwmon_regmap_cfg = {
> .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,
> +};
> +
> /* 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),
> @@ -369,16 +430,21 @@ static void bwmon_clear_irq(struct icc_bwmon *bwmon)
> 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],
> + if (bwmon->global_regs[0])
> + regmap_field_force_write(bwmon->global_regs[F_GLOBAL_IRQ_CLEAR],
> + BWMON_V4_GLOBAL_IRQ_ENABLE_ENABLE);
> + else
> + regmap_field_force_write(bwmon->regs[F_GLB_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);
> + if (bwmon->global_regs[0])
> + regmap_field_write(bwmon->global_regs[F_GLOBAL_IRQ_ENABLE], 0x0);
> + else
> + regmap_field_write(bwmon->regs[F_GLB_IRQ_ENABLE], 0x0);
> regmap_field_write(bwmon->regs[F_IRQ_ENABLE], 0x0);
>
> /*
> @@ -391,9 +457,13 @@ static void bwmon_disable(struct icc_bwmon *bwmon)
> 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);
> + if (bwmon->global_regs[0])
> + regmap_field_write(bwmon->global_regs[F_GLOBAL_IRQ_ENABLE],
> + BWMON_V4_GLOBAL_IRQ_ENABLE_ENABLE);
> + else
> + regmap_field_write(bwmon->regs[F_GLB_IRQ_ENABLE],
> + BWMON_V4_GLOBAL_IRQ_ENABLE_ENABLE);

Probably this would be more readable if regmap_field_write() is called
only once and you parametrize the first argument (field) from the
'struct bwmon'.

> +
> regmap_field_write(bwmon->regs[F_IRQ_ENABLE], irq_enable);
>
> /* Enable bwmon */
> @@ -556,7 +626,9 @@ static int bwmon_init_regmap(struct platform_device *pdev,
> 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),
> @@ -567,12 +639,38 @@ static int bwmon_init_regmap(struct platform_device *pdev,
> 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_llcc_bwmon_reg_fields) != F_NUM_FIELDS);
>
> - return devm_regmap_field_bulk_alloc(dev, map, bwmon->regs,
> + ret = devm_regmap_field_bulk_alloc(dev, map, bwmon->regs,
> bwmon->data->regmap_fields,
> F_NUM_FIELDS);

What exactly happens now on msm8998 (or updated sdm845 from your binding
example) for the "global" fields in this region? The regmap references
non-existing fields for the "monitor" region, doesn't it?

> + if (ret)
> + return ret;
> +
> + if (bwmon->data->quirks & BWMON_HAS_GLOBAL_IRQ) {
> + /* Map the global base, if separate */
> + base = devm_platform_ioremap_resource(pdev, 1);

Wouldn't this now print errors for sdm845, thus introduce dmesg regression?

> +
> + /* If it's not, bail out early and assume the 845 register scheme */
> + if (IS_ERR(base) && PTR_ERR(base) == -EINVAL)
> + goto exit;
> + else 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,
> +
>

Best regards,
Krzysztof