Re: [PATCH 20/49] regmap-irq: Fix inverted handling of unmask registers

From: Guru Das Srinagesh
Date: Thu Jun 23 2022 - 20:21:24 EST


On Mon, Jun 20, 2022 at 09:06:15PM +0100, Aidan MacDonald wrote:
> To me "unmask" suggests that we write 1s to the register when
> an interrupt is enabled. This also makes sense because it's the
> opposite of what the "mask" register does (write 1s to disable
> an interrupt).
>
> But regmap-irq does the opposite: for a disabled interrupt, it
> writes 1s to "unmask" and 0s to "mask". This is surprising and
> deviates from the usual way mask registers are handled.

Thank you for fixing this.

>
> Additionally, mask_invert didn't interact with unmask registers
> properly -- it caused them to be ignored entirely.
>
> Fix this by making mask and unmask registers orthogonal, using
> the following behavior:
>
> * Mask registers are written with 1s for disabled interrupts.
> * Unmask registers are written with 1s for enabled interrupts.

This is more in line with my understanding of the semantics as well.

>
> This behavior supports both normal or inverted mask registers
> and separate set/clear registers via different combinations of
> mask_base/unmask_base. The mask_invert flag is made redundant,
> since an inverted mask register can be described more directly
> as an unmask register.
>
> To cope with existing drivers that rely on the old "backward"
> behavior, check for the broken_mask_unmask flag and swap the
> roles of mask/unmask registers. This is a compatibility measure
> which can be dropped once the drivers are updated to use the
> new, more consistent behavior.
>
> Signed-off-by: Aidan MacDonald <aidanmacdonald.0x0@xxxxxxxxx>
> ---
> drivers/base/regmap/regmap-irq.c | 96 +++++++++++++++++---------------
> include/linux/regmap.h | 7 ++-
> 2 files changed, 55 insertions(+), 48 deletions(-)
>
> diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c
> index 875415fc3133..082a2981120c 100644
> --- a/drivers/base/regmap/regmap-irq.c
> +++ b/drivers/base/regmap/regmap-irq.c
> @@ -30,6 +30,9 @@ struct regmap_irq_chip_data {
> int irq;
> int wake_count;
>
> + unsigned int mask_base;
> + unsigned int unmask_base;
> +
> void *status_reg_buf;
> unsigned int *main_status_buf;
> unsigned int *status_buf;
> @@ -95,7 +98,6 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
> struct regmap *map = d->map;
> int i, j, ret;
> u32 reg;
> - u32 unmask_offset;
> u32 val;
>
> if (d->chip->runtime_pm) {
> @@ -124,35 +126,23 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
> * suppress pointless writes.
> */
> for (i = 0; i < d->chip->num_regs; i++) {
> - if (!d->chip->mask_base)
> - continue;
> -
> - reg = sub_irq_reg(d, d->chip->mask_base, i);
> - if (d->chip->mask_invert) {
> + if (d->mask_base) {
> + reg = sub_irq_reg(d, d->mask_base, i);
> ret = regmap_irq_update_mask_bits(d, reg,
> - d->mask_buf_def[i], ~d->mask_buf[i]);
> - } else if (d->chip->unmask_base) {
> - /* set mask with mask_base register */
> + d->mask_buf_def[i], d->mask_buf[i]);
> + if (ret != 0)
> + dev_err(d->map->dev, "Failed to sync masks in %x\n",
> + reg);
> + }
> +
> + if (d->unmask_base) {
> + reg = sub_irq_reg(d, d->unmask_base, i);
> ret = regmap_irq_update_mask_bits(d, reg,
> d->mask_buf_def[i], ~d->mask_buf[i]);
> - if (ret < 0)
> - dev_err(d->map->dev,
> - "Failed to sync unmasks in %x\n",
> + if (ret != 0)
> + dev_err(d->map->dev, "Failed to sync masks in %x\n",
> reg);
> - unmask_offset = d->chip->unmask_base -
> - d->chip->mask_base;
> - /* clear mask with unmask_base register */
> - ret = regmap_irq_update_mask_bits(d,
> - reg + unmask_offset,
> - d->mask_buf_def[i],
> - d->mask_buf[i]);
> - } else {
> - ret = regmap_irq_update_mask_bits(d, reg,
> - d->mask_buf_def[i], d->mask_buf[i]);
> }
> - if (ret != 0)
> - dev_err(d->map->dev, "Failed to sync masks in %x\n",
> - reg);
>
> reg = sub_irq_reg(d, d->chip->wake_base, i);
> if (d->wake_buf) {
> @@ -634,7 +624,6 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
> int i;
> int ret = -ENOMEM;
> u32 reg;
> - u32 unmask_offset;
>
> if (chip->num_regs <= 0)
> return -EINVAL;
> @@ -732,6 +721,24 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
> d->chip = chip;
> d->irq_base = irq_base;
>
> + /*
> + * Swap role of mask_base and unmask_base if mask bits are inverted.
> + *
> + * Historically, chips that specify both mask_base and unmask_base
> + * got inverted mask behavior; this was arguably a bug in regmap-irq
> + * and there was no way to get the normal, non-inverted behavior.
> + * Those chips will set the broken_mask_unmask flag. They don't set
> + * mask_invert so there is no need to worry about interactions with
> + * that flag.
> + */
> + if (chip->mask_invert || chip->broken_mask_unmask) {

I'm not able to comment on whether mask_invert belongs here.

> + d->mask_base = chip->unmask_base;
> + d->unmask_base = chip->mask_base;
> + } else {
> + d->mask_base = chip->mask_base;
> + d->unmask_base = chip->unmask_base;
> + }
> +
> if (chip->irq_reg_stride)
> d->irq_reg_stride = chip->irq_reg_stride;
> else
> @@ -755,28 +762,27 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
> /* Mask all the interrupts by default */
> for (i = 0; i < chip->num_regs; i++) {
> d->mask_buf[i] = d->mask_buf_def[i];
> - if (!chip->mask_base)
> - continue;
>
> - reg = sub_irq_reg(d, d->chip->mask_base, i);
> -
> - if (chip->mask_invert)
> + if (d->mask_base) {
> + reg = sub_irq_reg(d, d->mask_base, i);
> ret = regmap_irq_update_mask_bits(d, reg,
> - d->mask_buf[i], ~d->mask_buf[i]);
> - else if (d->chip->unmask_base) {
> - unmask_offset = d->chip->unmask_base -
> - d->chip->mask_base;
> - ret = regmap_irq_update_mask_bits(d,
> - reg + unmask_offset,
> - d->mask_buf[i],
> - d->mask_buf[i]);
> - } else
> + d->mask_buf_def[i], d->mask_buf[i]);
> + if (ret != 0) {
> + dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
> + reg, ret);
> + goto err_alloc;
> + }
> + }
> +
> + if (d->unmask_base) {

This makes a lot of sense. unmask_base really needed to be handled separately
and not as an offset from mask_base.

> + reg = sub_irq_reg(d, d->unmask_base, i);
> ret = regmap_irq_update_mask_bits(d, reg,
> - d->mask_buf[i], d->mask_buf[i]);
> - if (ret != 0) {
> - dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
> - reg, ret);
> - goto err_alloc;
> + d->mask_buf_def[i], ~d->mask_buf[i]);
> + if (ret != 0) {
> + dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
> + reg, ret);
> + goto err_alloc;
> + }
> }
>
> if (!chip->init_ack_masked)
> diff --git a/include/linux/regmap.h b/include/linux/regmap.h
> index 21a70fd99493..0cf3c4a66946 100644
> --- a/include/linux/regmap.h
> +++ b/include/linux/regmap.h
> @@ -1451,10 +1451,11 @@ struct regmap_irq_sub_irq_map {
> * main_status set.
> *
> * @status_base: Base status register address.
> - * @mask_base: Base mask register address.
> + * @mask_base: Base mask register address. Mask bits are set to 1 when an
> + * interrupt is masked, 0 when unmasked.
> * @mask_writeonly: Base mask register is write only.
> - * @unmask_base: Base unmask register address. for chips who have
> - * separate mask and unmask registers
> + * @unmask_base: Base unmask register address. Unmask bits are set to 1 when
> + * an interrupt is unmasked and 0 when masked.
> * @ack_base: Base ack address. If zero then the chip is clear on read.
> * Using zero value is possible with @use_ack bit.
> * @wake_base: Base address for wake enables. If zero unsupported.
> --
> 2.35.1
>