diff mbox series

[v2,02/17] regmap-irq: Add get_irq_reg to support unusual register layouts

Message ID 20220607155324.118102-3-aidanmacdonald.0x0@gmail.com
State Superseded
Headers show
Series Add support for AXP192 PMIC | expand

Commit Message

Aidan MacDonald June 7, 2022, 3:53 p.m. UTC
Add a new callback, get_irq_reg, for regmap IRQ chips, to support devices
with unusual register layouts. This is required in the rare cases where
the offset of an IRQ register is not constant with respect to the base
register. This is probably best illustrated with an example (taken from
the AXP192 PMIC):

            mask    status
    IRQ0    0x40    0x44
    IRQ1    0x41    0x45
    IRQ2    0x42    0x46
    IRQ3    0x43    0x47
    IRQ4    0x4a    0x4d

If we set mask_base = 0x40 and status_base = 0x44, the offsets of each
register relative to the base are:

            mask    status
    IRQ0    0       0
    IRQ1    1       1
    IRQ2    2       2
    IRQ3    3       3
    IRQ4    10      9

The existing mapping mechanisms can't include IRQ4 in the same irqchip
as IRQ0-3 because the offset of IRQ4's register depends on which type
of register we're asking for, ie. which base register is used.

The get_irq_reg callback allows drivers to specify an arbitrary mapping
of (base register, register index) pairs to register addresses, instead
of the default linear mapping "base_register + register_index". This
allows unusual layouts, like the one above, to be handled using a single
regmap IRQ chip.

The drawback is that when get_irq_reg is used, it's impossible to use
bulk reads for status registers even if some of them are contiguous,
because the mapping is opaque to regmap-irq. This should be acceptable
for the case of a few infrequently-polled status registers.

Signed-off-by: Aidan MacDonald <aidanmacdonald.0x0@gmail.com>
---
 drivers/base/regmap/regmap-irq.c | 6 ++++--
 include/linux/regmap.h           | 5 +++++
 2 files changed, 9 insertions(+), 2 deletions(-)

Comments

Mark Brown June 8, 2022, 4:17 p.m. UTC | #1
On Tue, Jun 07, 2022 at 04:53:09PM +0100, Aidan MacDonald wrote:

> -	if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
> +	if (chip->get_irq_reg) {
> +		reg = chip->get_irq_reg(base_reg, i);
> +	} else if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {

It seems like it would be cleaner and clearer to refactor things so that
we always have a get_irq_reg() with standard chips getting given a
default implementation which implements the current behaviour.
Aidan MacDonald June 10, 2022, 3:40 p.m. UTC | #2
Mark Brown <broonie@kernel.org> writes:

> On Tue, Jun 07, 2022 at 04:53:09PM +0100, Aidan MacDonald wrote:
>
>> -	if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
>> +	if (chip->get_irq_reg) {
>> +		reg = chip->get_irq_reg(base_reg, i);
>> +	} else if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
>
> It seems like it would be cleaner and clearer to refactor things so that
> we always have a get_irq_reg() with standard chips getting given a
> default implementation which implements the current behaviour.

I don't think that is a good way to clean things up. I only intended
get_irq_reg() to be a quick hack to solve a problem; in my opinion it
would be a poor abstraction to base the API around.

What I'd suggest is something that will simplify regmap-irq. Instead of
defining the base registers, etc. in the chip, introduce a new struct
to describe a register group:

    struct regmap_irq_reg_group {
        unsigned int status_base;
        unsigned int mask_base;
        ...

        unsigned int irq_reg_stride;

        int num_regs;
    };

The idea is that the registers in a group are linearly mapped using the
formula "base + (i * irq_reg_stride)". Then it's possible to allow for
multiple register groups in regmap_irq_chip:

    struct regmap_irq_chip {
        const struct regmap_irq_reg_group *groups;
        unsigned int num_groups;

        unsigned int main_status_base;
        unsigned int num_main_status_bits;
        int num_main_regs;

        ...
    };

It should be straightforward to fit existing chips into this model.

- "Normal" chips which do not use sub_reg_offsets or not_fixed_stride
  will have a single register group describing their register layout.

- Chips which use not_fixed_stride=1 (eg. qcom-pm8008.c) will define
  multiple register groups instead of using sub_reg_offsets, so they
  will look more like a normal chip.

- Chips that use a main status + sub-block IRQ layout will define
  one register group for each sub-block and continue to describe the
  location of the main status registers inside of regmap_irq_chip.
  A group will only get polled if the corresponding main status bit
  is set -- n'th group is polled if n'th bit is set.

I think this scheme is easier to understand than having three or four
different hacks to deal with minor deviations from the simple cases.
It's also more flexible because groups do not need to be homogenous,
unlike the way sub_reg_offsets works.

For the AXP192, I'd just need to add two register groups, one for IRQ0-3
and another with IRQ4 off by itself. So this would remove the need for
get_irq_reg() entirely.

On the other hand, basing the public API around get_irq_reg() doesn't
appear to simplify any existing use case.

What do you think? If you're happy with the idea I don't mind doing the
refactoring in a separate patch series.

I had hoped to avoid a big refactor just to add one chip, though.

Best regards,
Aidan
Matti Vaittinen June 23, 2022, 8:54 a.m. UTC | #3
Hi dee Ho peeps!

Sorry for the late reply.

pe 10. kesäk. 2022 klo 18.43 Aidan MacDonald
(aidanmacdonald.0x0@gmail.com) kirjoitti:
>
> Mark Brown <broonie@kernel.org> writes:
>
> > On Tue, Jun 07, 2022 at 04:53:09PM +0100, Aidan MacDonald wrote:
> >
> >> -    if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
> >> +    if (chip->get_irq_reg) {
> >> +            reg = chip->get_irq_reg(base_reg, i);
> >> +    } else if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
> >
> > It seems like it would be cleaner and clearer to refactor things so that
> > we always have a get_irq_reg() with standard chips getting given a
> > default implementation which implements the current behaviour.
>
> I don't think that is a good way to clean things up. I only intended
> get_irq_reg() to be a quick hack to solve a problem; in my opinion it
> would be a poor abstraction to base the API around.
>
> What I'd suggest is something that will simplify regmap-irq. Instead of
> defining the base registers, etc. in the chip, introduce a new struct
> to describe a register group:
>
>     struct regmap_irq_reg_group {
>         unsigned int status_base;
>         unsigned int mask_base;
>         ...
>
>         unsigned int irq_reg_stride;
>
>         int num_regs;
>     };
>
> The idea is that the registers in a group are linearly mapped using the
> formula "base + (i * irq_reg_stride)". Then it's possible to allow for
> multiple register groups in regmap_irq_chip:
>
>     struct regmap_irq_chip {
>         const struct regmap_irq_reg_group *groups;
>         unsigned int num_groups;
>
>         unsigned int main_status_base;
>         unsigned int num_main_status_bits;
>         int num_main_regs;
>
>         ...
>     };
>
> It should be straightforward to fit existing chips into this model.
>
> - Chips that use a main status + sub-block IRQ layout will define
>   one register group for each sub-block and continue to describe the
>   location of the main status registers inside of regmap_irq_chip.
>   A group will only get polled if the corresponding main status bit
>   is set -- n'th group is polled if n'th bit is set.

Does this work for devices where a single main status bit can flag
IRQs in more than one sub-registers?

Best Regards
 -- Matti
Aidan MacDonald June 23, 2022, 8:35 p.m. UTC | #4
Matti Vaittinen <mazziesaccount@gmail.com> writes:

> Hi dee Ho peeps!
>
> Sorry for the late reply.
>
> pe 10. kesäk. 2022 klo 18.43 Aidan MacDonald
> (aidanmacdonald.0x0@gmail.com) kirjoitti:
>>
>> Mark Brown <broonie@kernel.org> writes:
>>
>> > On Tue, Jun 07, 2022 at 04:53:09PM +0100, Aidan MacDonald wrote:
>> >
>> >> -    if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
>> >> +    if (chip->get_irq_reg) {
>> >> +            reg = chip->get_irq_reg(base_reg, i);
>> >> +    } else if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
>> >
>> > It seems like it would be cleaner and clearer to refactor things so that
>> > we always have a get_irq_reg() with standard chips getting given a
>> > default implementation which implements the current behaviour.
>>
>> I don't think that is a good way to clean things up. I only intended
>> get_irq_reg() to be a quick hack to solve a problem; in my opinion it
>> would be a poor abstraction to base the API around.
>>
>> What I'd suggest is something that will simplify regmap-irq. Instead of
>> defining the base registers, etc. in the chip, introduce a new struct
>> to describe a register group:
>>
>>     struct regmap_irq_reg_group {
>>         unsigned int status_base;
>>         unsigned int mask_base;
>>         ...
>>
>>         unsigned int irq_reg_stride;
>>
>>         int num_regs;
>>     };
>>
>> The idea is that the registers in a group are linearly mapped using the
>> formula "base + (i * irq_reg_stride)". Then it's possible to allow for
>> multiple register groups in regmap_irq_chip:
>>
>>     struct regmap_irq_chip {
>>         const struct regmap_irq_reg_group *groups;
>>         unsigned int num_groups;
>>
>>         unsigned int main_status_base;
>>         unsigned int num_main_status_bits;
>>         int num_main_regs;
>>
>>         ...
>>     };
>>
>> It should be straightforward to fit existing chips into this model.
>>
>> - Chips that use a main status + sub-block IRQ layout will define
>>   one register group for each sub-block and continue to describe the
>>   location of the main status registers inside of regmap_irq_chip.
>>   A group will only get polled if the corresponding main status bit
>>   is set -- n'th group is polled if n'th bit is set.
>
> Does this work for devices where a single main status bit can flag
> IRQs in more than one sub-registers?
>
> Best Regards
>  -- Matti

No, I realized once I got into the refactor that what I outlined here
wouldn't fit that use case well, which is what rohm-bd71828 needs.

There are some other complications with this approach, like how to
go between IRQs and register groups efficiently, and it's generally
a rather heavyweight solution. It might be useful for handling very
hierarchical chips, but I couldn't justify the added complexity when
most chips don't need it -- after all most chips behind slow busses
will have a small number of interrupts and a fairly flat structure.

In the end I went with Mark's suggestion to factor things out around
->get_irq_reg(). At first I thought there might be too many "gotchas"
that'd limit its usefulness, but in the end it proved to be a better
option and a lot easier to implement.
diff mbox series

Patch

diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c
index 4a2e1f6aa94d..e50437b18284 100644
--- a/drivers/base/regmap/regmap-irq.c
+++ b/drivers/base/regmap/regmap-irq.c
@@ -55,7 +55,9 @@  static int sub_irq_reg(struct regmap_irq_chip_data *data,
 	unsigned int offset;
 	int reg = 0;
 
-	if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
+	if (chip->get_irq_reg) {
+		reg = chip->get_irq_reg(base_reg, i);
+	} else if (!chip->sub_reg_offsets || !chip->not_fixed_stride) {
 		/* Assume linear mapping */
 		reg = base_reg + (i * map->reg_stride * data->irq_reg_stride);
 	} else {
@@ -479,7 +481,7 @@  static irqreturn_t regmap_irq_thread(int irq, void *d)
 
 		}
 	} else if (!map->use_single_read && map->reg_stride == 1 &&
-		   data->irq_reg_stride == 1) {
+		   data->irq_reg_stride == 1 && !chip->get_irq_reg) {
 
 		u8 *buf8 = data->status_reg_buf;
 		u16 *buf16 = data->status_reg_buf;
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index 8952fa3d0d59..4828021ab9e8 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -1495,6 +1495,10 @@  struct regmap_irq_sub_irq_map {
  *		     after handling the interrupts in regmap_irq_handler().
  * @set_type_virt:   Driver specific callback to extend regmap_irq_set_type()
  *		     and configure virt regs.
+ * @get_irq_reg:     Callback to map a register index in range [0, num_regs[
+ *		     to a register, relative to a specific base register. This
+ *		     is mainly useful for devices where the register offsets
+ *		     change depending on the base register.
  * @irq_drv_data:    Driver specific IRQ data which is passed as parameter when
  *		     driver specific pre/post interrupt handler is called.
  *
@@ -1545,6 +1549,7 @@  struct regmap_irq_chip {
 	int (*handle_post_irq)(void *irq_drv_data);
 	int (*set_type_virt)(unsigned int **buf, unsigned int type,
 			     unsigned long hwirq, int reg);
+	int (*get_irq_reg)(unsigned int base_reg, int i);
 	void *irq_drv_data;
 };