diff mbox series

[v3,4/7] gpio: max7360: Add MAX7360 gpio support

Message ID 20250113-mdb-max7360-support-v3-4-9519b4acb0b1@bootlin.com
State New
Headers show
Series Add support for MAX7360 | expand

Commit Message

Mathieu Dubois-Briand Jan. 13, 2025, 12:42 p.m. UTC
Add driver for Maxim Integrated MAX7360 GPIO/GPO controller.

Two sets of GPIOs are provided by the device:
- Up to 8 GPIOs, shared with the PWM and rotary encoder functionalities.
  These GPIOs also provide interrupts on input changes.
- Up to 6 GPOs, on unused keypad columns pins.

Co-developed-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
 drivers/gpio/Kconfig        |  11 ++
 drivers/gpio/Makefile       |   1 +
 drivers/gpio/gpio-max7360.c | 454 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 466 insertions(+)

Comments

Linus Walleij Jan. 14, 2025, 2:33 p.m. UTC | #1
Hi Mathieu,

thanks for your patch!

On Mon, Jan 13, 2025 at 1:43 PM Mathieu Dubois-Briand
<mathieu.dubois-briand@bootlin.com> wrote:

> Add driver for Maxim Integrated MAX7360 GPIO/GPO controller.
>
> Two sets of GPIOs are provided by the device:
> - Up to 8 GPIOs, shared with the PWM and rotary encoder functionalities.
>   These GPIOs also provide interrupts on input changes.
> - Up to 6 GPOs, on unused keypad columns pins.
>
> Co-developed-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
(...)
> +#include <linux/gpio/consumer.h>

Why?

My most generic feedback is if you have looked at using
select GPIO_REGMAP for this driver?

The regmap utility library is very helpful, look how other driver
selecting GPIO_REGMAP gets default implementations
from the library just git grep GPIO_REGMAP drivers/gpio/

> +static void max7360_gpio_set_value(struct gpio_chip *gc,
> +                                  unsigned int pin, int state)
> +{
> +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> +       int ret;
> +
> +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL) {

OK some custom stuff...

> +               int off = MAX7360_MAX_GPIO - (gc->ngpio - pin);
> +
> +               ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_PORTS,
> +                                       BIT(off), state ? BIT(off) : 0);

Fairly standard.

> +       } else {
> +               ret = regmap_write(max7360_gpio->regmap,
> +                                  MAX7360_REG_PWMBASE + pin, state ? 0xFF : 0);
> +       }

Some custom stuff.

> +static int max7360_gpio_get_value(struct gpio_chip *gc, unsigned int pin)
> +{
> +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> +       unsigned int val;
> +       int off;
> +       int ret;
> +
> +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL) {
> +               off = MAX7360_MAX_GPIO - (gc->ngpio - pin);
> +
> +               ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_PORTS, &val);
> +       } else {
> +               off = pin;
> +               ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_GPIOIN, &val);
> +       }
> +
> +       if (ret) {
> +               dev_err(max7360_gpio->dev, "failed to read gpio-%d", pin);
> +               return ret;
> +       }
> +
> +       return !!(val & BIT(off));
> +}

Looks like stock template regmap-gpio.

> +static int max7360_gpio_get_direction(struct gpio_chip *gc, unsigned int pin)
> +{
> +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> +       unsigned int val;
> +       int ret;
> +
> +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
> +               return GPIO_LINE_DIRECTION_OUT;
> +
> +       ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_GPIOCTRL, &val);
> +       if (ret) {
> +               dev_err(max7360_gpio->dev, "failed to read gpio-%d direction",
> +                       pin);
> +               return ret;
> +       }
> +
> +       if (val & BIT(pin))
> +               return GPIO_LINE_DIRECTION_OUT;
> +
> +       return GPIO_LINE_DIRECTION_IN;
> +}

Dito.

> +static int max7360_gpio_direction_input(struct gpio_chip *gc, unsigned int pin)
> +{
> +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> +       int ret;
> +
> +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
> +               return -EIO;
> +
> +       ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_GPIOCTRL,
> +                               BIT(pin), 0);
> +       if (ret) {
> +               dev_err(max7360_gpio->dev, "failed to set gpio-%d direction",
> +                       pin);
> +               return ret;
> +       }
> +
> +       return 0;
> +}

Dito.

> +static int max7360_gpio_direction_output(struct gpio_chip *gc, unsigned int pin,
> +                                        int state)
> +{
> +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> +       int ret;
> +
> +       if (max7360_gpio->gpio_function == MAX7360_GPIO_PORT) {
> +               ret = regmap_write_bits(max7360_gpio->regmap,
> +                                       MAX7360_REG_GPIOCTRL, BIT(pin),
> +                                       BIT(pin));
> +               if (ret) {
> +                       dev_err(max7360_gpio->dev,
> +                               "failed to set gpio-%d direction", pin);
> +                       return ret;
> +               }
> +       }
> +
> +       max7360_gpio_set_value(gc, pin, state);
> +
> +       return 0;
> +}

Dito.

> +static int max7360_gpio_request(struct gpio_chip *gc, unsigned int pin)
> +{
> +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> +
> +       /*
> +        * GPOs on COL pins (keypad columns) can always be requested: this
> +        * driver has full access to them, up to the number set in chip.ngpio.
> +        * GPIOs on PORT pins are shared with the PWM and rotary encoder
> +        * drivers: they have to be requested from the MFD driver.
> +        */
> +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
> +               return 0;
> +
> +       return max7360_port_pin_request(max7360_gpio->dev->parent, pin, true);
> +}
> +
> +static void max7360_gpio_free(struct gpio_chip *gc, unsigned int pin)
> +{
> +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> +
> +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
> +               return;
> +
> +       max7360_port_pin_request(max7360_gpio->dev->parent, pin, false);
> +}

The pin request looks a bit like a custom pin control implementation...

But I think it's fine, pin control can be a bit heavy to implement on simple
devices, but if there is elaborate muxing and config going on, pin control
should be used.

Yours,
Linus Walleij
Mathieu Dubois-Briand Jan. 14, 2025, 5:57 p.m. UTC | #2
On Tue Jan 14, 2025 at 3:33 PM CET, Linus Walleij wrote:
> Hi Mathieu,
>
> thanks for your patch!
>
> On Mon, Jan 13, 2025 at 1:43 PM Mathieu Dubois-Briand
> <mathieu.dubois-briand@bootlin.com> wrote:
>
> > Add driver for Maxim Integrated MAX7360 GPIO/GPO controller.
> >
> > Two sets of GPIOs are provided by the device:
> > - Up to 8 GPIOs, shared with the PWM and rotary encoder functionalities.
> >   These GPIOs also provide interrupts on input changes.
> > - Up to 6 GPOs, on unused keypad columns pins.
> >
> > Co-developed-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> > Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> > Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> (...)
> > +#include <linux/gpio/consumer.h>
>
> Why?
>
> My most generic feedback is if you have looked at using
> select GPIO_REGMAP for this driver?
>
> The regmap utility library is very helpful, look how other driver
> selecting GPIO_REGMAP gets default implementations
> from the library just git grep GPIO_REGMAP drivers/gpio/
>

Thanks, I was not aware of that. I tested it and I should be able to get
rid of a lot of code using GPIO_REGMAP.

My main concern so far is with the request()/free() functions, as I
believe I will not be able to define them as callback anymore.

I also saw the equivalent REGMAP_IRQ, but I'm not sure I will be able to
use it, as I believe I would need to have registers identifying the
exact GPIO source of the IRQ.

> > +static void max7360_gpio_set_value(struct gpio_chip *gc,
> > +                                  unsigned int pin, int state)
> > +{
> > +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> > +       int ret;
> > +
> > +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL) {
>
> OK some custom stuff...
>
> > +               int off = MAX7360_MAX_GPIO - (gc->ngpio - pin);
> > +
> > +               ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_PORTS,
> > +                                       BIT(off), state ? BIT(off) : 0);
>
> Fairly standard.
>
> > +       } else {
> > +               ret = regmap_write(max7360_gpio->regmap,
> > +                                  MAX7360_REG_PWMBASE + pin, state ? 0xFF : 0);
> > +       }
>
> Some custom stuff.
>
> > +static int max7360_gpio_get_value(struct gpio_chip *gc, unsigned int pin)
> > +{
> > +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> > +       unsigned int val;
> > +       int off;
> > +       int ret;
> > +
> > +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL) {
> > +               off = MAX7360_MAX_GPIO - (gc->ngpio - pin);
> > +
> > +               ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_PORTS, &val);
> > +       } else {
> > +               off = pin;
> > +               ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_GPIOIN, &val);
> > +       }
> > +
> > +       if (ret) {
> > +               dev_err(max7360_gpio->dev, "failed to read gpio-%d", pin);
> > +               return ret;
> > +       }
> > +
> > +       return !!(val & BIT(off));
> > +}
>
> Looks like stock template regmap-gpio.
>
> > +static int max7360_gpio_get_direction(struct gpio_chip *gc, unsigned int pin)
> > +{
> > +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> > +       unsigned int val;
> > +       int ret;
> > +
> > +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
> > +               return GPIO_LINE_DIRECTION_OUT;
> > +
> > +       ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_GPIOCTRL, &val);
> > +       if (ret) {
> > +               dev_err(max7360_gpio->dev, "failed to read gpio-%d direction",
> > +                       pin);
> > +               return ret;
> > +       }
> > +
> > +       if (val & BIT(pin))
> > +               return GPIO_LINE_DIRECTION_OUT;
> > +
> > +       return GPIO_LINE_DIRECTION_IN;
> > +}
>
> Dito.
>
> > +static int max7360_gpio_direction_input(struct gpio_chip *gc, unsigned int pin)
> > +{
> > +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> > +       int ret;
> > +
> > +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
> > +               return -EIO;
> > +
> > +       ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_GPIOCTRL,
> > +                               BIT(pin), 0);
> > +       if (ret) {
> > +               dev_err(max7360_gpio->dev, "failed to set gpio-%d direction",
> > +                       pin);
> > +               return ret;
> > +       }
> > +
> > +       return 0;
> > +}
>
> Dito.
>
> > +static int max7360_gpio_direction_output(struct gpio_chip *gc, unsigned int pin,
> > +                                        int state)
> > +{
> > +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> > +       int ret;
> > +
> > +       if (max7360_gpio->gpio_function == MAX7360_GPIO_PORT) {
> > +               ret = regmap_write_bits(max7360_gpio->regmap,
> > +                                       MAX7360_REG_GPIOCTRL, BIT(pin),
> > +                                       BIT(pin));
> > +               if (ret) {
> > +                       dev_err(max7360_gpio->dev,
> > +                               "failed to set gpio-%d direction", pin);
> > +                       return ret;
> > +               }
> > +       }
> > +
> > +       max7360_gpio_set_value(gc, pin, state);
> > +
> > +       return 0;
> > +}
>
> Dito.
>
> > +static int max7360_gpio_request(struct gpio_chip *gc, unsigned int pin)
> > +{
> > +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> > +
> > +       /*
> > +        * GPOs on COL pins (keypad columns) can always be requested: this
> > +        * driver has full access to them, up to the number set in chip.ngpio.
> > +        * GPIOs on PORT pins are shared with the PWM and rotary encoder
> > +        * drivers: they have to be requested from the MFD driver.
> > +        */
> > +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
> > +               return 0;
> > +
> > +       return max7360_port_pin_request(max7360_gpio->dev->parent, pin, true);
> > +}
> > +
> > +static void max7360_gpio_free(struct gpio_chip *gc, unsigned int pin)
> > +{
> > +       struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
> > +
> > +       if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
> > +               return;
> > +
> > +       max7360_port_pin_request(max7360_gpio->dev->parent, pin, false);
> > +}
>
> The pin request looks a bit like a custom pin control implementation...
>
> But I think it's fine, pin control can be a bit heavy to implement on simple
> devices, but if there is elaborate muxing and config going on, pin control
> should be used.

Just so remove any doubt, all this does is request the pin for the
exclusive use of this driver, preventing the PWM or rotary encoder
drivers to use it. There is no hardware configuration done here.

Yet I agree that this does look a bit like some pin muxing.

>
> Yours,
> Linus Walleij

Thanks for your review.
Mathieu Dubois-Briand Jan. 17, 2025, 3:22 p.m. UTC | #3
On Tue Jan 14, 2025 at 3:33 PM CET, Linus Walleij wrote:
> On Mon, Jan 13, 2025 at 1:43 PM Mathieu Dubois-Briand
> My most generic feedback is if you have looked at using
> select GPIO_REGMAP for this driver?
>
> The regmap utility library is very helpful, look how other driver
> selecting GPIO_REGMAP gets default implementations
> from the library just git grep GPIO_REGMAP drivers/gpio/
>

I tried to switch to GPIO_REGMAP and I really like it overall, as it
does simplify a lot the code. However, I identified two features that I
was not able to port so far: the request()/free() callbacks and the
interrupts.

So for the request()/free() callbacks, I cannot add them anymore, as
they are set on the gpio_chip structure, and this structure is hidden
behind the gpio_regmap structure. I could easily modify the
gpio_regmap_config structure and gpio_regmap_register() to allow to
provide these callbacks, but is this acceptable? Or should I switch to a
different way to prevent concurrent use of the same pin? I saw you
mentioned the possibility of defining pin control.

On the IRQ side, before switching to GPIO_REGMAP, I was able to define
the IRQ configuration using the irq member of the gpio_chip structure.
This does create the IRQ domain for me in a quite straightforward way.
Again, I will not be able to do that anymore, as the gpio_chip structure
is hidden. 

I saw I can specify my own irq_domain in gpio_regmap_config, so that
would be a way, but I was wondering if there is any way to have
something as easy as previously.

I had a quick look at existing drivers using GPIO_REGMAP and providing
IRQ support: I believe they are all using REGMAP_IRQ. And I believe I
cannot use REGMAP_IRQ here, as if I understood correctly, I would need
to have a register telling me exactly on which GPIO I have a pending
interrupt and I don't have such a thing: all I know is there was an
interrupt related to the GPIOs, and then I have to compare each GPIO
with the previous known state to know which pin is affected.

Do you have any thought about this?

Best regards,
Mathieu
Linus Walleij Jan. 22, 2025, 1:18 p.m. UTC | #4
Hi Mathieu,

On Fri, Jan 17, 2025 at 4:22 PM Mathieu Dubois-Briand
<mathieu.dubois-briand@bootlin.com> wrote:

> I tried to switch to GPIO_REGMAP and I really like it overall, as it
> does simplify a lot the code. However, I identified two features that I
> was not able to port so far: the request()/free() callbacks and the
> interrupts.

Thanks for looking into this!

> So for the request()/free() callbacks, I cannot add them anymore, as
> they are set on the gpio_chip structure, and this structure is hidden
> behind the gpio_regmap structure. I could easily modify the
> gpio_regmap_config structure and gpio_regmap_register() to allow to
> provide these callbacks, but is this acceptable?

Of course. The template is to be used for setting it up, just
modify it if we need more from it.

> On the IRQ side, before switching to GPIO_REGMAP, I was able to define
> the IRQ configuration using the irq member of the gpio_chip structure.
> This does create the IRQ domain for me in a quite straightforward way.
> Again, I will not be able to do that anymore, as the gpio_chip structure
> is hidden.
(...)
> I had a quick look at existing drivers using GPIO_REGMAP and providing
> IRQ support: I believe they are all using REGMAP_IRQ. And I believe I
> cannot use REGMAP_IRQ here,

Then we need to modify GPIO_REGMAP so it's possible to pass
in our own gpio_irq_chip, maybe another member in
the config, simply?

Yours,
Linus Walleij
Andy Shevchenko Jan. 27, 2025, 12:52 p.m. UTC | #5
On Fri, Jan 17, 2025 at 04:22:33PM +0100, Mathieu Dubois-Briand wrote:
> On Tue Jan 14, 2025 at 3:33 PM CET, Linus Walleij wrote:
> > On Mon, Jan 13, 2025 at 1:43 PM Mathieu Dubois-Briand
> > My most generic feedback is if you have looked at using
> > select GPIO_REGMAP for this driver?
> >
> > The regmap utility library is very helpful, look how other driver
> > selecting GPIO_REGMAP gets default implementations
> > from the library just git grep GPIO_REGMAP drivers/gpio/
> 
> I tried to switch to GPIO_REGMAP and I really like it overall, as it
> does simplify a lot the code. However, I identified two features that I
> was not able to port so far: the request()/free() callbacks and the
> interrupts.

You can easily extend the config to provide both if needed.
So, update gpio-regmap.c itself as a prerequisite.

> So for the request()/free() callbacks, I cannot add them anymore, as
> they are set on the gpio_chip structure, and this structure is hidden
> behind the gpio_regmap structure. I could easily modify the
> gpio_regmap_config structure and gpio_regmap_register() to allow to
> provide these callbacks, but is this acceptable? Or should I switch to a
> different way to prevent concurrent use of the same pin? I saw you
> mentioned the possibility of defining pin control.
> 
> On the IRQ side, before switching to GPIO_REGMAP, I was able to define
> the IRQ configuration using the irq member of the gpio_chip structure.
> This does create the IRQ domain for me in a quite straightforward way.
> Again, I will not be able to do that anymore, as the gpio_chip structure
> is hidden. 

Look how it's done in, e.g., drivers/gpio/gpio-104-idi-48.c.

It's pretty straightforward. You create and register an IRQ chip with
devm_regmap_add_irq_chip(). It creates a domain for you.

> I saw I can specify my own irq_domain in gpio_regmap_config, so that
> would be a way, but I was wondering if there is any way to have
> something as easy as previously.
> 
> I had a quick look at existing drivers using GPIO_REGMAP and providing
> IRQ support: I believe they are all using REGMAP_IRQ. And I believe I
> cannot use REGMAP_IRQ here, as if I understood correctly, I would need
> to have a register telling me exactly on which GPIO I have a pending
> interrupt and I don't have such a thing: all I know is there was an
> interrupt related to the GPIOs, and then I have to compare each GPIO
> with the previous known state to know which pin is affected.
> 
> Do you have any thought about this?

I briefly looked at the code of regmap IRQ and I don't see big impediments
for your case. So, you seems to have mask, unmask, and input registers.
What you most likely want to do is to use input as status (regmap IRQ
supports configuration without status registers).

What seems to be needed is the logic that uses previous state to handle a new
one. This is not only this chip that may need this type of handling, so it
will be beneficial for others that may be converted to use gpio-regmap.c.

So, I don't think we need full bypass of the GPIO IRQ chip through
gpio-regmap.c.
Andy Shevchenko Jan. 27, 2025, 1:07 p.m. UTC | #6
On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:
> Add driver for Maxim Integrated MAX7360 GPIO/GPO controller.
> 
> Two sets of GPIOs are provided by the device:
> - Up to 8 GPIOs, shared with the PWM and rotary encoder functionalities.
>   These GPIOs also provide interrupts on input changes.
> - Up to 6 GPOs, on unused keypad columns pins.

...

> +	depends on OF_GPIO

This is wrong. You don't use it.

...

> +#include <linux/of.h>

Shouldn't be here. Instead it should be linux/property.h.

...

> +	/* MAX7360 generates interrupts but does not report which pins changed:
> +	 * compare the pin value with the value they had in previous interrupt
> +	 * and report interrupt if the change match the set IRQ type.
> +	 */

/*
 * Wrong multi-line comment style. Consider using
 * this one as an example. This applies to all the comments
 * in the entire series.
 */

> +	pending = val ^ max7360_gpio->in_values;
> +	for_each_set_bit(irqn, &pending, max7360_gpio->chip.ngpio) {
> +		type = max7360_gpio->irq_types[irqn];
> +		if (max7360_gpio->masked_interrupts & BIT(irqn))
> +			continue;
> +		if ((val & BIT(irqn)) && type == IRQ_TYPE_EDGE_FALLING)
> +			continue;
> +		if (!(val & BIT(irqn)) && type == IRQ_TYPE_EDGE_RISING)
> +			continue;
> +		gpio_irq = irq_find_mapping(max7360_gpio->chip.irq.domain, irqn);
> +		handle_nested_irq(gpio_irq);
> +		count++;

You can also look how gpio-pca953x.c does this. It uses bitmaps all over the
place. But with the gpio-regmap.c in use this should be much more simpler.

> +	}

> +	max7360_gpio->in_values = val;

> +	if (count == 0)

count is redundant, Checking pending against 0 is enough (or in case of
bitmaps, if it's longer than unsigned long, bitmap_empty() is what is here).

> +		return IRQ_NONE;
> +
> +	return IRQ_HANDLED;

...

> +static int max7360_gpio_probe(struct platform_device *pdev)
> +{
> +	struct max7360_gpio *max7360_gpio;
> +	struct platform_device *parent;
> +	unsigned int ngpios;
> +	unsigned int outconf;
> +	struct gpio_irq_chip *girq;
> +	unsigned long flags;
> +	int irq;
> +	int ret;

> +	if (!pdev->dev.parent) {
> +		dev_err(&pdev->dev, "no parent device\n");
> +		return -ENODEV;
> +	}

Probably redundant as one of the calls below may fail if parent is absent (see
below for more).

> +	parent = to_platform_device(pdev->dev.parent);

Why do you need this? Can't the fwnode be propagated to the children and then
the respective APIs to be used?

> +	max7360_gpio = devm_kzalloc(&pdev->dev, sizeof(*max7360_gpio),
> +				    GFP_KERNEL);

With

	struct device *dev = &pdev->dev;

at the beginning of the function this and other lines will become neater and
shorter, in particular this becomes one line even for the strict 80 characters
limit (however we have it being relaxed to 100 nowadays).

> +	if (!max7360_gpio)
> +		return -ENOMEM;

> +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> +		return -ENODEV;
> +	}

This is not needed, it is already done in GPIOLIB core.

> +	max7360_gpio->regmap = dev_get_regmap(pdev->dev.parent, NULL);
> +	if (!max7360_gpio->regmap) {
> +		dev_err(&pdev->dev, "could not get parent regmap\n");
> +		return -ENODEV;

Here and everywhere in the ->probe() and related use

	return dev_err_probe(...);

And drop unneeded curly braces.

> +	}

> +	max7360_gpio->dev = &pdev->dev;

So, why do you need this dup? You can easily get it via GPIO chip, no?

> +	max7360_gpio->chip = max7360_gpio_chip;
> +	max7360_gpio->chip.ngpio = ngpios;
> +	max7360_gpio->chip.parent = &pdev->dev;
> +	max7360_gpio->chip.base = -1;
> +	max7360_gpio->gpio_function = (uintptr_t)device_get_match_data(&pdev->dev);
> +
> +	dev_dbg(&pdev->dev, "gpio count: %d\n", max7360_gpio->chip.ngpio);
> +
> +	if (max7360_gpio->gpio_function == MAX7360_GPIO_PORT) {
> +		/* Port GPIOs: set output mode configuration (constant-current
> +		 * or not).
> +		 * This property is optional.
> +		 */
> +		outconf = 0;
> +		ret = of_property_read_u32(pdev->dev.of_node,
> +					   "maxim,constant-current-disable",
> +					   &outconf);

device_property_read_u32()

> +		if (ret && (ret != -EINVAL)) {
> +			dev_err(&pdev->dev,
> +				"Failed to read maxim,constant-current-disable OF property\n");
> +			return -ENODEV;
> +		}
> +
> +	    regmap_write(max7360_gpio->regmap, MAX7360_REG_GPIOOUTM, outconf);
> +	}
> +
> +	if (max7360_gpio->gpio_function == MAX7360_GPIO_PORT &&
> +	    of_property_read_bool(pdev->dev.of_node, "interrupt-controller")) {
> +		/* Port GPIOs: declare IRQ chip, if configuration was provided.
> +		 */

> +		irq = platform_get_irq_byname(parent, "inti");

fwnode_irq_get_byname() with the propagated firmware node will give you
the same result.

> +		if (irq < 0)
> +			return dev_err_probe(&pdev->dev, irq,
> +					     "Failed to get IRQ\n");
> +
> +		flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED;
> +		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
> +						max7360_gpio_irq, flags,
> +						"max7360-gpio", max7360_gpio);
> +		if (ret)
> +			return dev_err_probe(&pdev->dev, ret,
> +					     "Failed to register interrupt\n");
> +
> +		girq = &max7360_gpio->chip.irq;
> +		gpio_irq_chip_set_chip(girq, &max7360_gpio_irqchip);
> +		girq->parent_handler = NULL;
> +		girq->num_parents = 0;
> +		girq->parents = NULL;
> +		girq->threaded = true;
> +		girq->default_type = IRQ_TYPE_NONE;
> +		girq->handler = handle_simple_irq;
> +	}
> +
> +	ret = devm_gpiochip_add_data(&pdev->dev, &max7360_gpio->chip, max7360_gpio);
> +	if (ret) {
> +		return dev_err_probe(&pdev->dev, ret,
> +				     "unable to add gpiochip\n");
> +	}
> +
> +	return 0;
> +}

...

> +static struct platform_driver max7360_gpio_driver = {
> +	.driver = {
> +		.name	= MAX7360_DRVNAME_GPIO,

> +		.of_match_table = of_match_ptr(max7360_gpio_of_match),

No of_match_ptr() or ACPI_PTR() in new code, please. It appears to be more
harmful than helpful.

> +	},
> +	.probe		= max7360_gpio_probe,
> +};
> +module_platform_driver(max7360_gpio_driver);

...

> +MODULE_ALIAS("platform:" MAX7360_DRVNAME_GPIO);

Why? It doesn't look like it can be instantiated via board files.

...

Overall seems many of my comments are applicable to your entire series (all
patches), please address and feel free to Cc me on v4 for review.
Mathieu Dubois-Briand Feb. 12, 2025, 12:57 p.m. UTC | #7
Hi Andy,

Thanks for your review. I've been addressing most of your comments in
this mail and the ones related to regmap-irq. I should be able to send a
new version in a few days.

However I have a few questions regarding some of the points.

On Mon Jan 27, 2025 at 2:07 PM CET, Andy Shevchenko wrote:
> On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:
> > +	parent = to_platform_device(pdev->dev.parent);
>
> Why do you need this? Can't the fwnode be propagated to the children and then
> the respective APIs to be used?
>

I'm not sure to understand this correctly, what do you mean by
propagating the fwnode to the children?

Just a quick summary of the situation and what I try to do. The device
tree looks like this, only keeping the interesting properties:

io-expander@38 {
  ...
  interrupts = <23 IRQ_TYPE_LEVEL_LOW>,
               <24 IRQ_TYPE_LEVEL_LOW>;
  interrupt-names = "inti", "intk";

  max7360_gpio: gpio {
    ...
  };

  max7360_gpo: gpo {
    ...
  };
};

Our pdev fwnode points either to the "gpio" or "gpo" nodes, the one from
our parent device points to "io-expander@38". Here we need to get the
"inti" interrupt from the parent node. What would be the correct way to
do it?

> > +	if (!max7360_gpio)
> > +		return -ENOMEM;
>
> > +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> > +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> > +		return -ENODEV;
> > +	}
>
> This is not needed, it is already done in GPIOLIB core.
>

I believe this is still needed:
- For gpos, we need the gpio count to correctly set the partition
  between gpo and keypad columns in max7360_set_gpos_count().
- For gpios, we need the gpio count to setup the IRQs.

Best regards,
Mathieu
Andy Shevchenko Feb. 12, 2025, 3:14 p.m. UTC | #8
On Wed, Feb 12, 2025 at 01:57:34PM +0100, Mathieu Dubois-Briand wrote:
> On Mon Jan 27, 2025 at 2:07 PM CET, Andy Shevchenko wrote:
> > On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:

...

> > > +	parent = to_platform_device(pdev->dev.parent);
> >
> > Why do you need this? Can't the fwnode be propagated to the children and then
> > the respective APIs to be used?
> 
> I'm not sure to understand this correctly, what do you mean by
> propagating the fwnode to the children?
> 
> Just a quick summary of the situation and what I try to do. The device
> tree looks like this, only keeping the interesting properties:
> 
> io-expander@38 {
>   ...
>   interrupts = <23 IRQ_TYPE_LEVEL_LOW>,
>                <24 IRQ_TYPE_LEVEL_LOW>;
>   interrupt-names = "inti", "intk";
> 
>   max7360_gpio: gpio {
>     ...
>   };
> 
>   max7360_gpo: gpo {
>     ...
>   };
> };
> 
> Our pdev fwnode points either to the "gpio" or "gpo" nodes, the one from
> our parent device points to "io-expander@38". Here we need to get the
> "inti" interrupt from the parent node. What would be the correct way to
> do it?

Ah, I see now. This is being used only for IRQs, but don't you want to call
actually fwnode_irq_get_byname()? It will makes the intention clearer.

...

> > > +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> > > +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> > > +		return -ENODEV;
> > > +	}
> >
> > This is not needed, it is already done in GPIOLIB core.
> 
> I believe this is still needed:
> - For gpos, we need the gpio count to correctly set the partition
>   between gpo and keypad columns in max7360_set_gpos_count().

Shouldn't be that done somewhere in the GPIO valid mask initialisation?

> - For gpios, we need the gpio count to setup the IRQs.

Doesn't GPIOLIB parse the property before initializing the IRQ valid mask
and other init callbacks?
Mathieu Dubois-Briand Feb. 12, 2025, 4:08 p.m. UTC | #9
On Wed Feb 12, 2025 at 4:14 PM CET, Andy Shevchenko wrote:
> On Wed, Feb 12, 2025 at 01:57:34PM +0100, Mathieu Dubois-Briand wrote:
> > On Mon Jan 27, 2025 at 2:07 PM CET, Andy Shevchenko wrote:
> > > On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:
>
> ...
>
> > > > +	parent = to_platform_device(pdev->dev.parent);
> > >
> > > Why do you need this? Can't the fwnode be propagated to the children and then
> > > the respective APIs to be used?
> > 
> > I'm not sure to understand this correctly, what do you mean by
> > propagating the fwnode to the children?
> > 
> > Just a quick summary of the situation and what I try to do. The device
> > tree looks like this, only keeping the interesting properties:
> > 
> > io-expander@38 {
> >   ...
> >   interrupts = <23 IRQ_TYPE_LEVEL_LOW>,
> >                <24 IRQ_TYPE_LEVEL_LOW>;
> >   interrupt-names = "inti", "intk";
> > 
> >   max7360_gpio: gpio {
> >     ...
> >   };
> > 
> >   max7360_gpo: gpo {
> >     ...
> >   };
> > };
> > 
> > Our pdev fwnode points either to the "gpio" or "gpo" nodes, the one from
> > our parent device points to "io-expander@38". Here we need to get the
> > "inti" interrupt from the parent node. What would be the correct way to
> > do it?
>
> Ah, I see now. This is being used only for IRQs, but don't you want to call
> actually fwnode_irq_get_byname()? It will makes the intention clearer.
>

Sure! I can definitely call fwnode_irq_get_byname().

>
> > > > +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> > > > +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> > > > +		return -ENODEV;
> > > > +	}
> > >
> > > This is not needed, it is already done in GPIOLIB core.
> > 
> > I believe this is still needed:
> > - For gpos, we need the gpio count to correctly set the partition
> >   between gpo and keypad columns in max7360_set_gpos_count().
>
> Shouldn't be that done somewhere in the GPIO valid mask initialisation?
>
> > - For gpios, we need the gpio count to setup the IRQs.
>
> Doesn't GPIOLIB parse the property before initializing the IRQ valid mask
> and other init callbacks?

No, I believe I have to register the IRQ before registering the GPIO, so
I can get the IRQ domain.

Right now I have something like:

irq_chip->num_irqs = ngpios;
devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), max7360_gpio->regmap, irq, flags, 0, irq_chip, &irq_chip_data);
gpio_config.irq_domain = regmap_irq_get_domain(irq_chip_data);
devm_gpio_regmap_register(dev, &gpio_config);

Also, gpiolib will store ngpios in the gpio_chip structure, but while
using gpio-regmap, this structure is masked behind the opaque
gpio_regmap structure. So I believe there is no easy way to retrieve its
value.

This part of the code changed a lot, maybe it would be easier if I push
a new version of the series and we continue the discussion there?
Andy Shevchenko Feb. 12, 2025, 4:17 p.m. UTC | #10
On Wed, Feb 12, 2025 at 05:08:56PM +0100, Mathieu Dubois-Briand wrote:
> On Wed Feb 12, 2025 at 4:14 PM CET, Andy Shevchenko wrote:
> > On Wed, Feb 12, 2025 at 01:57:34PM +0100, Mathieu Dubois-Briand wrote:
> > > On Mon Jan 27, 2025 at 2:07 PM CET, Andy Shevchenko wrote:
> > > > On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:

...

> > > > > +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> > > > > +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> > > > > +		return -ENODEV;
> > > > > +	}
> > > >
> > > > This is not needed, it is already done in GPIOLIB core.
> > > 
> > > I believe this is still needed:
> > > - For gpos, we need the gpio count to correctly set the partition
> > >   between gpo and keypad columns in max7360_set_gpos_count().
> >
> > Shouldn't be that done somewhere in the GPIO valid mask initialisation?
> >
> > > - For gpios, we need the gpio count to setup the IRQs.
> >
> > Doesn't GPIOLIB parse the property before initializing the IRQ valid mask
> > and other init callbacks?
> 
> No, I believe I have to register the IRQ before registering the GPIO, so
> I can get the IRQ domain.
> 
> Right now I have something like:
> 
> irq_chip->num_irqs = ngpios;
> devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), max7360_gpio->regmap,
> irq, flags, 0, irq_chip, &irq_chip_data);
> gpio_config.irq_domain = regmap_irq_get_domain(irq_chip_data);
> devm_gpio_regmap_register(dev, &gpio_config);
> 
> Also, gpiolib will store ngpios in the gpio_chip structure, but while
> using gpio-regmap, this structure is masked behind the opaque
> gpio_regmap structure. So I believe there is no easy way to retrieve its
> value.
> 
> This part of the code changed a lot, maybe it would be easier if I push
> a new version of the series and we continue the discussion there?

So, what seems need to be added is some flag to GPIO regmap configuration
data structure and a code that is called after gpiochip_add_data() in
gpio_regmap_register() to create a domain. This will solve the above issue
and helps other drivers to get rid of potential duplication of
devm_regmap_add_irq_chip_fwnode() calls.

Have you researched this path?
Mathieu Dubois-Briand Feb. 13, 2025, 10:59 a.m. UTC | #11
On Wed Feb 12, 2025 at 5:17 PM CET, Andy Shevchenko wrote:
> On Wed, Feb 12, 2025 at 05:08:56PM +0100, Mathieu Dubois-Briand wrote:
> > On Wed Feb 12, 2025 at 4:14 PM CET, Andy Shevchenko wrote:
> > > On Wed, Feb 12, 2025 at 01:57:34PM +0100, Mathieu Dubois-Briand wrote:
> > > > On Mon Jan 27, 2025 at 2:07 PM CET, Andy Shevchenko wrote:
> > > > > On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:
>
> ...
>
> > > > > > +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> > > > > > +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> > > > > > +		return -ENODEV;
> > > > > > +	}
> > > > >
> > > > > This is not needed, it is already done in GPIOLIB core.
> > > > 
> > > > I believe this is still needed:
> > > > - For gpos, we need the gpio count to correctly set the partition
> > > >   between gpo and keypad columns in max7360_set_gpos_count().
> > >
> > > Shouldn't be that done somewhere in the GPIO valid mask initialisation?
> > >
> > > > - For gpios, we need the gpio count to setup the IRQs.
> > >
> > > Doesn't GPIOLIB parse the property before initializing the IRQ valid mask
> > > and other init callbacks?
> > 
> > No, I believe I have to register the IRQ before registering the GPIO, so
> > I can get the IRQ domain.
> > 
> > Right now I have something like:
> > 
> > irq_chip->num_irqs = ngpios;
> > devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), max7360_gpio->regmap,
> > irq, flags, 0, irq_chip, &irq_chip_data);
> > gpio_config.irq_domain = regmap_irq_get_domain(irq_chip_data);
> > devm_gpio_regmap_register(dev, &gpio_config);
> > 
> > Also, gpiolib will store ngpios in the gpio_chip structure, but while
> > using gpio-regmap, this structure is masked behind the opaque
> > gpio_regmap structure. So I believe there is no easy way to retrieve its
> > value.
> > 
> > This part of the code changed a lot, maybe it would be easier if I push
> > a new version of the series and we continue the discussion there?
>
> So, what seems need to be added is some flag to GPIO regmap configuration
> data structure and a code that is called after gpiochip_add_data() in
> gpio_regmap_register() to create a domain. This will solve the above issue
> and helps other drivers to get rid of potential duplication of
> devm_regmap_add_irq_chip_fwnode() calls.
>
> Have you researched this path?

OK, so looking at the code, I believe it would need to:
- Add some flag in gpio_regmap_config structure, so
  gpio_regmap_register() creates a new IRQ domain.
- Add a function allowing to retrieve this domain out of the gpio_regmap
  structure.
- Allow to pass a domain in the regmap_irq_chip structure, so
  regmap_add_irq_chip_fwnode() use this domain instead of calling
  regmap_irq_create_domain().
- Make sure this domain is still populated with the IRQ data: number of
  IRQs, IRQ base but also a pointer on the regmap_irq_chip_data
  structure in .host_data. I believe this will be a bit tricky.
- Add a function allowing to retrieve ngpio out of the
  gpio_regmap.gpio_chip structure, so it can be used for IRQ setup and
  other places of the driver.

I'm sorry, but I feel like this is a lot of changes to solve this point.
I've been thinking about it, and I can suggest a different solution.

For gpios, I will remove the ngpios property of the device tree and use
a fixed value:
- For the today version of the chip, this is always 8.
- I a chip variant or a similar chip ever arise later with a different
  number of gpios, the fixed value can be set according to the
  "compatible" value.
- This removes any issue with the IRQ setup.

For gpos, we have to keep ngpios, as it depends of the implementation on
the board. That means ngpios will be used:
- For the gpio chip configuration: we let gpiolib retrieve it from the
  device tree.
- In gpio-regmap reg_mask_xlate callback: I can add a function allowing
  to retrieve it from gpio_regmap.gpio_chip, as suggested above.
- In max7360_set_gpos_count() to validate the coherency between
  requested gpios and keypad columns and set the correct configuration
  on the chip:
  - I can also retrieve the value from gpio_regmap.gpio_chip, but that
    means the check is made after the call to
    devm_gpio_regmap_register().
  - Or I will still need to retrieve it using device_property_read_u32()
    here.

How do you feel about this solution?
Mathieu Dubois-Briand Feb. 13, 2025, 1:45 p.m. UTC | #12
On Thu Feb 13, 2025 at 11:59 AM CET, Mathieu Dubois-Briand wrote:
> On Wed Feb 12, 2025 at 5:17 PM CET, Andy Shevchenko wrote:
> > On Wed, Feb 12, 2025 at 05:08:56PM +0100, Mathieu Dubois-Briand wrote:
> > > On Wed Feb 12, 2025 at 4:14 PM CET, Andy Shevchenko wrote:
> > > > On Wed, Feb 12, 2025 at 01:57:34PM +0100, Mathieu Dubois-Briand wrote:
> > > > > On Mon Jan 27, 2025 at 2:07 PM CET, Andy Shevchenko wrote:
> > > > > > On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:
> >
> > ...
> >
> > > > > > > +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> > > > > > > +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> > > > > > > +		return -ENODEV;
> > > > > > > +	}
> > > > > >
> > > > > > This is not needed, it is already done in GPIOLIB core.
> > > > > 
> > > > > I believe this is still needed:
> > > > > - For gpos, we need the gpio count to correctly set the partition
> > > > >   between gpo and keypad columns in max7360_set_gpos_count().
> > > >
> > > > Shouldn't be that done somewhere in the GPIO valid mask initialisation?
> > > >
> > > > > - For gpios, we need the gpio count to setup the IRQs.
> > > >
> > > > Doesn't GPIOLIB parse the property before initializing the IRQ valid mask
> > > > and other init callbacks?
> > > 
> > > No, I believe I have to register the IRQ before registering the GPIO, so
> > > I can get the IRQ domain.
> > > 
> > > Right now I have something like:
> > > 
> > > irq_chip->num_irqs = ngpios;
> > > devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), max7360_gpio->regmap,
> > > irq, flags, 0, irq_chip, &irq_chip_data);
> > > gpio_config.irq_domain = regmap_irq_get_domain(irq_chip_data);
> > > devm_gpio_regmap_register(dev, &gpio_config);
> > > 
> > > Also, gpiolib will store ngpios in the gpio_chip structure, but while
> > > using gpio-regmap, this structure is masked behind the opaque
> > > gpio_regmap structure. So I believe there is no easy way to retrieve its
> > > value.
> > > 
> > > This part of the code changed a lot, maybe it would be easier if I push
> > > a new version of the series and we continue the discussion there?
> >
> > So, what seems need to be added is some flag to GPIO regmap configuration
> > data structure and a code that is called after gpiochip_add_data() in
> > gpio_regmap_register() to create a domain. This will solve the above issue
> > and helps other drivers to get rid of potential duplication of
> > devm_regmap_add_irq_chip_fwnode() calls.
> >
> > Have you researched this path?
>
> OK, so looking at the code, I believe it would need to:
> - Add some flag in gpio_regmap_config structure, so
>   gpio_regmap_register() creates a new IRQ domain.
> - Add a function allowing to retrieve this domain out of the gpio_regmap
>   structure.
> - Allow to pass a domain in the regmap_irq_chip structure, so
>   regmap_add_irq_chip_fwnode() use this domain instead of calling
>   regmap_irq_create_domain().
> - Make sure this domain is still populated with the IRQ data: number of
>   IRQs, IRQ base but also a pointer on the regmap_irq_chip_data
>   structure in .host_data. I believe this will be a bit tricky.
> - Add a function allowing to retrieve ngpio out of the
>   gpio_regmap.gpio_chip structure, so it can be used for IRQ setup and
>   other places of the driver.
>
> I'm sorry, but I feel like this is a lot of changes to solve this point.
> I've been thinking about it, and I can suggest a different solution.
>
> For gpios, I will remove the ngpios property of the device tree and use
> a fixed value:
> - For the today version of the chip, this is always 8.
> - I a chip variant or a similar chip ever arise later with a different
>   number of gpios, the fixed value can be set according to the
>   "compatible" value.
> - This removes any issue with the IRQ setup.
>
> For gpos, we have to keep ngpios, as it depends of the implementation on
> the board. That means ngpios will be used:
> - For the gpio chip configuration: we let gpiolib retrieve it from the
>   device tree.
> - In gpio-regmap reg_mask_xlate callback: I can add a function allowing
>   to retrieve it from gpio_regmap.gpio_chip, as suggested above.
> - In max7360_set_gpos_count() to validate the coherency between
>   requested gpios and keypad columns and set the correct configuration
>   on the chip:
>   - I can also retrieve the value from gpio_regmap.gpio_chip, but that
>     means the check is made after the call to
>     devm_gpio_regmap_register().
>   - Or I will still need to retrieve it using device_property_read_u32()
>     here.
>
> How do you feel about this solution?

Actually there is an additional issue: today, relying on gpiolib to
parse the "ngpios" property does not work with gpio-regmap.

The gpiochip_get_ngpios() function in gpiolib is called in
gpiochip_add_data_with_key(), but when using gpio_regmap_register(),
we first ensure ngpio is set correctly before calling anything.

Yet I believe this check can safely be removed, allowing the magic in
gpiolib happen as expected.
Andy Shevchenko Feb. 13, 2025, 7:47 p.m. UTC | #13
On Thu, Feb 13, 2025 at 02:45:31PM +0100, Mathieu Dubois-Briand wrote:
> On Thu Feb 13, 2025 at 11:59 AM CET, Mathieu Dubois-Briand wrote:
> > On Wed Feb 12, 2025 at 5:17 PM CET, Andy Shevchenko wrote:
> > > On Wed, Feb 12, 2025 at 05:08:56PM +0100, Mathieu Dubois-Briand wrote:
> > > > On Wed Feb 12, 2025 at 4:14 PM CET, Andy Shevchenko wrote:
> > > > > On Wed, Feb 12, 2025 at 01:57:34PM +0100, Mathieu Dubois-Briand wrote:
> > > > > > On Mon Jan 27, 2025 at 2:07 PM CET, Andy Shevchenko wrote:
> > > > > > > On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:

...

> > > > > > > > +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> > > > > > > > +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> > > > > > > > +		return -ENODEV;
> > > > > > > > +	}
> > > > > > >
> > > > > > > This is not needed, it is already done in GPIOLIB core.
> > > > > > 
> > > > > > I believe this is still needed:
> > > > > > - For gpos, we need the gpio count to correctly set the partition
> > > > > >   between gpo and keypad columns in max7360_set_gpos_count().
> > > > >
> > > > > Shouldn't be that done somewhere in the GPIO valid mask initialisation?
> > > > >
> > > > > > - For gpios, we need the gpio count to setup the IRQs.
> > > > >
> > > > > Doesn't GPIOLIB parse the property before initializing the IRQ valid mask
> > > > > and other init callbacks?
> > > > 
> > > > No, I believe I have to register the IRQ before registering the GPIO, so
> > > > I can get the IRQ domain.
> > > > 
> > > > Right now I have something like:
> > > > 
> > > > irq_chip->num_irqs = ngpios;
> > > > devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), max7360_gpio->regmap,
> > > > irq, flags, 0, irq_chip, &irq_chip_data);
> > > > gpio_config.irq_domain = regmap_irq_get_domain(irq_chip_data);
> > > > devm_gpio_regmap_register(dev, &gpio_config);
> > > > 
> > > > Also, gpiolib will store ngpios in the gpio_chip structure, but while
> > > > using gpio-regmap, this structure is masked behind the opaque
> > > > gpio_regmap structure. So I believe there is no easy way to retrieve its
> > > > value.

Would it be needed in your driver ->probe() after all? (See also below)

> > > > This part of the code changed a lot, maybe it would be easier if I push
> > > > a new version of the series and we continue the discussion there?
> > >
> > > So, what seems need to be added is some flag to GPIO regmap configuration
> > > data structure and a code that is called after gpiochip_add_data() in
> > > gpio_regmap_register() to create a domain. This will solve the above issue
> > > and helps other drivers to get rid of potential duplication of
> > > devm_regmap_add_irq_chip_fwnode() calls.
> > >
> > > Have you researched this path?
> >
> > OK, so looking at the code, I believe it would need to:
> > - Add some flag in gpio_regmap_config structure, so
> >   gpio_regmap_register() creates a new IRQ domain.

Easy.

> > - Add a function allowing to retrieve this domain out of the gpio_regmap
> >   structure.

Easy, as there is an API available for regmaps, so it looks like one-liner.

> > - Allow to pass a domain in the regmap_irq_chip structure, so
> >   regmap_add_irq_chip_fwnode() use this domain instead of calling
> >   regmap_irq_create_domain().

You need this because of...? (Please, remind me what the obstacle is there
that requires this to be done)

> > - Make sure this domain is still populated with the IRQ data: number of
> >   IRQs, IRQ base but also a pointer on the regmap_irq_chip_data
> >   structure in .host_data. I believe this will be a bit tricky.

Hmm... But wouldn't gpio-regmap internals have all this information available?

> > - Add a function allowing to retrieve ngpio out of the
> >   gpio_regmap.gpio_chip structure, so it can be used for IRQ setup and
> >   other places of the driver.

I'm not sure where it can be needed.

> > I'm sorry, but I feel like this is a lot of changes to solve this point.
> > I've been thinking about it, and I can suggest a different solution.
> >
> > For gpios, I will remove the ngpios property of the device tree and use
> > a fixed value:
> > - For the today version of the chip, this is always 8.
> > - I a chip variant or a similar chip ever arise later with a different
> >   number of gpios, the fixed value can be set according to the
> >   "compatible" value.
> > - This removes any issue with the IRQ setup.
> >
> > For gpos, we have to keep ngpios, as it depends of the implementation on
> > the board. That means ngpios will be used:
> > - For the gpio chip configuration: we let gpiolib retrieve it from the
> >   device tree.
> > - In gpio-regmap reg_mask_xlate callback: I can add a function allowing
> >   to retrieve it from gpio_regmap.gpio_chip, as suggested above.
> > - In max7360_set_gpos_count() to validate the coherency between
> >   requested gpios and keypad columns and set the correct configuration
> >   on the chip:
> >   - I can also retrieve the value from gpio_regmap.gpio_chip, but that
> >     means the check is made after the call to
> >     devm_gpio_regmap_register().
> >   - Or I will still need to retrieve it using device_property_read_u32()
> >     here.
> >
> > How do you feel about this solution?
> 
> Actually there is an additional issue: today, relying on gpiolib to
> parse the "ngpios" property does not work with gpio-regmap.
> 
> The gpiochip_get_ngpios() function in gpiolib is called in
> gpiochip_add_data_with_key(), but when using gpio_regmap_register(),
> we first ensure ngpio is set correctly before calling anything.
> 
> Yet I believe this check can safely be removed, allowing the magic in
> gpiolib happen as expected.

Not really. I'm about to send a series to address this issue.
Please, test.

...

P.S.
Maybe it's time to send a new version based on this discussion even
if not finished / working, so we can see the exact issues we still have
and target them.
Mathieu Dubois-Briand Feb. 14, 2025, 8:42 a.m. UTC | #14
On Thu Feb 13, 2025 at 8:47 PM CET, Andy Shevchenko wrote:
> On Thu, Feb 13, 2025 at 02:45:31PM +0100, Mathieu Dubois-Briand wrote:
> > On Thu Feb 13, 2025 at 11:59 AM CET, Mathieu Dubois-Briand wrote:
> > > On Wed Feb 12, 2025 at 5:17 PM CET, Andy Shevchenko wrote:
> > > > On Wed, Feb 12, 2025 at 05:08:56PM +0100, Mathieu Dubois-Briand wrote:
> > > > > On Wed Feb 12, 2025 at 4:14 PM CET, Andy Shevchenko wrote:
> > > > > > On Wed, Feb 12, 2025 at 01:57:34PM +0100, Mathieu Dubois-Briand wrote:
> > > > > > > On Mon Jan 27, 2025 at 2:07 PM CET, Andy Shevchenko wrote:
> > > > > > > > On Mon, Jan 13, 2025 at 01:42:28PM +0100, Mathieu Dubois-Briand wrote:
>
> ...
>
> > > > > > > > > +	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
> > > > > > > > > +		dev_err(&pdev->dev, "Missing ngpios OF property\n");
> > > > > > > > > +		return -ENODEV;
> > > > > > > > > +	}
> > > > > > > >
> > > > > > > > This is not needed, it is already done in GPIOLIB core.
> > > > > > > 
> > > > > > > I believe this is still needed:
> > > > > > > - For gpos, we need the gpio count to correctly set the partition
> > > > > > >   between gpo and keypad columns in max7360_set_gpos_count().
> > > > > >
> > > > > > Shouldn't be that done somewhere in the GPIO valid mask initialisation?
> > > > > >
> > > > > > > - For gpios, we need the gpio count to setup the IRQs.
> > > > > >
> > > > > > Doesn't GPIOLIB parse the property before initializing the IRQ valid mask
> > > > > > and other init callbacks?
> > > > > 
> > > > > No, I believe I have to register the IRQ before registering the GPIO, so
> > > > > I can get the IRQ domain.
> > > > > 
> > > > > Right now I have something like:
> > > > > 
> > > > > irq_chip->num_irqs = ngpios;
> > > > > devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), max7360_gpio->regmap,
> > > > > irq, flags, 0, irq_chip, &irq_chip_data);
> > > > > gpio_config.irq_domain = regmap_irq_get_domain(irq_chip_data);
> > > > > devm_gpio_regmap_register(dev, &gpio_config);
> > > > > 
> > > > > Also, gpiolib will store ngpios in the gpio_chip structure, but while
> > > > > using gpio-regmap, this structure is masked behind the opaque
> > > > > gpio_regmap structure. So I believe there is no easy way to retrieve its
> > > > > value.
>
> Would it be needed in your driver ->probe() after all? (See also below)
>

No necessarily in the probe with the changes previously described, but I
will need it in other functions. So either I get it in the probe and
store it, or I will need to retrieve it by other means.

> > > > > This part of the code changed a lot, maybe it would be easier if I push
> > > > > a new version of the series and we continue the discussion there?
> > > >
> > > > So, what seems need to be added is some flag to GPIO regmap configuration
> > > > data structure and a code that is called after gpiochip_add_data() in
> > > > gpio_regmap_register() to create a domain. This will solve the above issue
> > > > and helps other drivers to get rid of potential duplication of
> > > > devm_regmap_add_irq_chip_fwnode() calls.
> > > >
> > > > Have you researched this path?
> > >
> > > OK, so looking at the code, I believe it would need to:
> > > - Add some flag in gpio_regmap_config structure, so
> > >   gpio_regmap_register() creates a new IRQ domain.
>
> Easy.
>
> > > - Add a function allowing to retrieve this domain out of the gpio_regmap
> > >   structure.
>
> Easy, as there is an API available for regmaps, so it looks like one-liner.
>
> > > - Allow to pass a domain in the regmap_irq_chip structure, so
> > >   regmap_add_irq_chip_fwnode() use this domain instead of calling
> > >   regmap_irq_create_domain().
>
> You need this because of...? (Please, remind me what the obstacle is there
> that requires this to be done)
>

OK, maybe I misunderstood you idea. Or maybe I misunderstood something
about IRQ domains.

So what I understood is, in the probe, we first call
gpio_regmap_register(), that will create a new IRQ domain, and we call
regmap_add_irq_chip_fwnode() in second. But this second call will again
create an IRQ domain, so we would end-up with a second one. We have to
prevent this second creation and make it use the one we created first.

Did I miss something?

> > > - Make sure this domain is still populated with the IRQ data: number of
> > >   IRQs, IRQ base but also a pointer on the regmap_irq_chip_data
> > >   structure in .host_data. I believe this will be a bit tricky.
>
> Hmm... But wouldn't gpio-regmap internals have all this information available?
>

I don't think so. It will not know the IRQ base nor the
regmap_irq_chip_data as it is not created at this point.

> > > - Add a function allowing to retrieve ngpio out of the
> > >   gpio_regmap.gpio_chip structure, so it can be used for IRQ setup and
> > >   other places of the driver.
>
> I'm not sure where it can be needed.
>

Let's discuss this on the next version, but yes, I do need to know ngpio
in the driver.

> > > I'm sorry, but I feel like this is a lot of changes to solve this point.
> > > I've been thinking about it, and I can suggest a different solution.
> > >
> > > For gpios, I will remove the ngpios property of the device tree and use
> > > a fixed value:
> > > - For the today version of the chip, this is always 8.
> > > - I a chip variant or a similar chip ever arise later with a different
> > >   number of gpios, the fixed value can be set according to the
> > >   "compatible" value.
> > > - This removes any issue with the IRQ setup.
> > >
> > > For gpos, we have to keep ngpios, as it depends of the implementation on
> > > the board. That means ngpios will be used:
> > > - For the gpio chip configuration: we let gpiolib retrieve it from the
> > >   device tree.
> > > - In gpio-regmap reg_mask_xlate callback: I can add a function allowing
> > >   to retrieve it from gpio_regmap.gpio_chip, as suggested above.
> > > - In max7360_set_gpos_count() to validate the coherency between
> > >   requested gpios and keypad columns and set the correct configuration
> > >   on the chip:
> > >   - I can also retrieve the value from gpio_regmap.gpio_chip, but that
> > >     means the check is made after the call to
> > >     devm_gpio_regmap_register().
> > >   - Or I will still need to retrieve it using device_property_read_u32()
> > >     here.
> > >
> > > How do you feel about this solution?
> > 
> > Actually there is an additional issue: today, relying on gpiolib to
> > parse the "ngpios" property does not work with gpio-regmap.
> > 
> > The gpiochip_get_ngpios() function in gpiolib is called in
> > gpiochip_add_data_with_key(), but when using gpio_regmap_register(),
> > we first ensure ngpio is set correctly before calling anything.
> > 
> > Yet I believe this check can safely be removed, allowing the magic in
> > gpiolib happen as expected.
>
> Not really. I'm about to send a series to address this issue.
> Please, test.

I will test it today.

>
> ...
>
> P.S.
> Maybe it's time to send a new version based on this discussion even
> if not finished / working, so we can see the exact issues we still have
> and target them.

Sure, I will send a new version, probably today.

Thanks again for your help.
diff mbox series

Patch

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 93ee3aa092f8..efe07e21c442 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1444,6 +1444,17 @@  config GPIO_MADERA
 	help
 	  Support for GPIOs on Cirrus Logic Madera class codecs.
 
+config GPIO_MAX7360
+	tristate "MAX7360 GPIO support"
+	depends on MFD_MAX7360
+	depends on OF_GPIO
+	help
+	  Allows to use MAX7360 I/O Expander PWM lines as GPIO and keypad COL
+	  lines as GPO.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called gpio-max7360.
+
 config GPIO_MAX77620
 	tristate "GPIO support for PMIC MAX77620 and MAX20024"
 	depends on MFD_MAX77620
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index af3ba4d81b58..581341b3e3e4 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -100,6 +100,7 @@  obj-$(CONFIG_GPIO_MAX7300)		+= gpio-max7300.o
 obj-$(CONFIG_GPIO_MAX7301)		+= gpio-max7301.o
 obj-$(CONFIG_GPIO_MAX730X)		+= gpio-max730x.o
 obj-$(CONFIG_GPIO_MAX732X)		+= gpio-max732x.o
+obj-$(CONFIG_GPIO_MAX7360)		+= gpio-max7360.o
 obj-$(CONFIG_GPIO_MAX77620)		+= gpio-max77620.o
 obj-$(CONFIG_GPIO_MAX77650)		+= gpio-max77650.o
 obj-$(CONFIG_GPIO_MB86S7X)		+= gpio-mb86s7x.o
diff --git a/drivers/gpio/gpio-max7360.c b/drivers/gpio/gpio-max7360.c
new file mode 100644
index 000000000000..9b701246689c
--- /dev/null
+++ b/drivers/gpio/gpio-max7360.c
@@ -0,0 +1,454 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2024 Bootlin
+ *
+ * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max7360.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define MAX7360_GPIO_PORT	1
+#define MAX7360_GPIO_COL	2
+
+struct max7360_gpio {
+	struct gpio_chip chip;
+	struct device *dev;
+	struct regmap *regmap;
+	unsigned long gpio_function;
+
+	/*
+	 * Interrupts handling data: only used when gpio_function is
+	 * MAX7360_GPIO_PORT.
+	 */
+	u8 masked_interrupts;
+	u8 in_values;
+	unsigned int irq_types[MAX7360_MAX_GPIO];
+};
+
+static void max7360_gpio_set_value(struct gpio_chip *gc,
+				   unsigned int pin, int state)
+{
+	struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
+	int ret;
+
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_COL) {
+		int off = MAX7360_MAX_GPIO - (gc->ngpio - pin);
+
+		ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_PORTS,
+					BIT(off), state ? BIT(off) : 0);
+	} else {
+		ret = regmap_write(max7360_gpio->regmap,
+				   MAX7360_REG_PWMBASE + pin, state ? 0xFF : 0);
+	}
+
+	if (ret)
+		dev_err(max7360_gpio->dev,
+			"failed to set value %d on gpio-%d", state, pin);
+}
+
+static int max7360_gpio_get_value(struct gpio_chip *gc, unsigned int pin)
+{
+	struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
+	unsigned int val;
+	int off;
+	int ret;
+
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_COL) {
+		off = MAX7360_MAX_GPIO - (gc->ngpio - pin);
+
+		ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_PORTS, &val);
+	} else {
+		off = pin;
+		ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_GPIOIN, &val);
+	}
+
+	if (ret) {
+		dev_err(max7360_gpio->dev, "failed to read gpio-%d", pin);
+		return ret;
+	}
+
+	return !!(val & BIT(off));
+}
+
+static int max7360_gpio_get_direction(struct gpio_chip *gc, unsigned int pin)
+{
+	struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
+	unsigned int val;
+	int ret;
+
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
+		return GPIO_LINE_DIRECTION_OUT;
+
+	ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_GPIOCTRL, &val);
+	if (ret) {
+		dev_err(max7360_gpio->dev, "failed to read gpio-%d direction",
+			pin);
+		return ret;
+	}
+
+	if (val & BIT(pin))
+		return GPIO_LINE_DIRECTION_OUT;
+
+	return GPIO_LINE_DIRECTION_IN;
+}
+
+static int max7360_gpio_direction_input(struct gpio_chip *gc, unsigned int pin)
+{
+	struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
+	int ret;
+
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
+		return -EIO;
+
+	ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_GPIOCTRL,
+				BIT(pin), 0);
+	if (ret) {
+		dev_err(max7360_gpio->dev, "failed to set gpio-%d direction",
+			pin);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int max7360_gpio_direction_output(struct gpio_chip *gc, unsigned int pin,
+					 int state)
+{
+	struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
+	int ret;
+
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_PORT) {
+		ret = regmap_write_bits(max7360_gpio->regmap,
+					MAX7360_REG_GPIOCTRL, BIT(pin),
+					BIT(pin));
+		if (ret) {
+			dev_err(max7360_gpio->dev,
+				"failed to set gpio-%d direction", pin);
+			return ret;
+		}
+	}
+
+	max7360_gpio_set_value(gc, pin, state);
+
+	return 0;
+}
+
+static int max7360_gpio_request(struct gpio_chip *gc, unsigned int pin)
+{
+	struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
+
+	/*
+	 * GPOs on COL pins (keypad columns) can always be requested: this
+	 * driver has full access to them, up to the number set in chip.ngpio.
+	 * GPIOs on PORT pins are shared with the PWM and rotary encoder
+	 * drivers: they have to be requested from the MFD driver.
+	 */
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
+		return 0;
+
+	return max7360_port_pin_request(max7360_gpio->dev->parent, pin, true);
+}
+
+static void max7360_gpio_free(struct gpio_chip *gc, unsigned int pin)
+{
+	struct max7360_gpio *max7360_gpio = gpiochip_get_data(gc);
+
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_COL)
+		return;
+
+	max7360_port_pin_request(max7360_gpio->dev->parent, pin, false);
+}
+
+static struct gpio_chip max7360_gpio_chip = {
+	.label                  = "max7360",
+	.request		= max7360_gpio_request,
+	.free			= max7360_gpio_free,
+	.get_direction		= max7360_gpio_get_direction,
+	.direction_input	= max7360_gpio_direction_input,
+	.direction_output	= max7360_gpio_direction_output,
+	.get                    = max7360_gpio_get_value,
+	.set                    = max7360_gpio_set_value,
+	.base                   = -1,
+	.can_sleep              = true,
+};
+
+static irqreturn_t max7360_gpio_irq(int irq, void *data)
+{
+	struct max7360_gpio *max7360_gpio = data;
+	unsigned long pending;
+	unsigned int gpio_irq;
+	unsigned int type;
+	unsigned int count = 0;
+	int val;
+	int irqn;
+	int ret;
+
+	ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_GPIOIN, &val);
+	if (ret) {
+		dev_err(max7360_gpio->dev, "Failed to read gpio values: %d\n",
+			ret);
+		return IRQ_NONE;
+	}
+
+	/* MAX7360 generates interrupts but does not report which pins changed:
+	 * compare the pin value with the value they had in previous interrupt
+	 * and report interrupt if the change match the set IRQ type.
+	 */
+	pending = val ^ max7360_gpio->in_values;
+	for_each_set_bit(irqn, &pending, max7360_gpio->chip.ngpio) {
+		type = max7360_gpio->irq_types[irqn];
+		if (max7360_gpio->masked_interrupts & BIT(irqn))
+			continue;
+		if ((val & BIT(irqn)) && type == IRQ_TYPE_EDGE_FALLING)
+			continue;
+		if (!(val & BIT(irqn)) && type == IRQ_TYPE_EDGE_RISING)
+			continue;
+		gpio_irq = irq_find_mapping(max7360_gpio->chip.irq.domain, irqn);
+		handle_nested_irq(gpio_irq);
+		count++;
+	}
+
+	max7360_gpio->in_values = val;
+
+	if (count == 0)
+		return IRQ_NONE;
+
+	return IRQ_HANDLED;
+}
+
+static void max7360_gpio_irq_unmask(struct irq_data *data)
+{
+	struct max7360_gpio *max7360_gpio;
+	unsigned int pin = irqd_to_hwirq(data);
+	unsigned int val;
+	int ret;
+
+	max7360_gpio = gpiochip_get_data(irq_data_get_irq_chip_data(data));
+
+	/* Read current pin value, so we know if the pin changed in the next
+	 * interrupt.
+	 * No lock should be needed regarding the interrupt handler: as long as
+	 * the corresponding bit has not been cleared in masked_interrupts, this
+	 * gpio is ignored.
+	 */
+	ret = regmap_read(max7360_gpio->regmap, MAX7360_REG_GPIOIN, &val);
+	if (ret)
+		dev_err(max7360_gpio->dev, "Failed to read gpio values: %d\n",
+			ret);
+
+	max7360_gpio->in_values &= ~BIT(pin);
+	max7360_gpio->in_values |= val & BIT(pin);
+
+	ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_PWMCFG + pin,
+				MAX7360_PORT_CFG_INTERRUPT_MASK, 0);
+
+	if (ret)
+		dev_err(max7360_gpio->dev, "failed to unmask gpio-%d", pin);
+
+	max7360_gpio->masked_interrupts &= ~BIT(pin);
+}
+
+static void max7360_gpio_irq_mask(struct irq_data *data)
+{
+	struct max7360_gpio *max7360_gpio;
+	unsigned int pin = irqd_to_hwirq(data);
+	int ret;
+
+	max7360_gpio = gpiochip_get_data(irq_data_get_irq_chip_data(data));
+
+	max7360_gpio->masked_interrupts |= BIT(pin);
+
+	ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_PWMCFG + pin,
+				MAX7360_PORT_CFG_INTERRUPT_MASK,
+				MAX7360_PORT_CFG_INTERRUPT_MASK);
+
+	if (ret)
+		dev_err(max7360_gpio->dev, "failed to mask gpio-%d", pin);
+}
+
+static void max7360_gpio_irq_enable(struct irq_data *data)
+{
+	max7360_gpio_irq_unmask(data);
+}
+
+static void max7360_gpio_irq_disable(struct irq_data *data)
+{
+	max7360_gpio_irq_mask(data);
+}
+
+static int max7360_gpio_irq_set_type(struct irq_data *data,
+				     unsigned int flow_type)
+{
+	struct max7360_gpio *max7360_gpio;
+	unsigned int pin;
+	unsigned int val;
+	int ret;
+
+	pin = irqd_to_hwirq(data);
+	max7360_gpio = gpiochip_get_data(irq_data_get_irq_chip_data(data));
+
+	switch (flow_type) {
+	case IRQ_TYPE_EDGE_RISING:
+		val = 0;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+	case IRQ_TYPE_EDGE_BOTH:
+		val = MAX7360_PORT_CFG_INTERRUPT_EDGES;
+		break;
+	default:
+		return -EINVAL;
+	}
+	ret = regmap_write_bits(max7360_gpio->regmap, MAX7360_REG_PWMCFG + pin,
+				MAX7360_PORT_CFG_INTERRUPT_EDGES, val);
+
+	if (ret)
+		dev_err(max7360_gpio->dev, "failed to unmask gpio-%d", pin);
+
+	max7360_gpio->irq_types[pin] = flow_type;
+
+	return 0;
+}
+
+static const struct irq_chip max7360_gpio_irqchip = {
+	.name = "max7360",
+	.irq_enable = max7360_gpio_irq_enable,
+	.irq_disable = max7360_gpio_irq_disable,
+	.irq_mask = max7360_gpio_irq_mask,
+	.irq_unmask = max7360_gpio_irq_unmask,
+	.irq_set_type = max7360_gpio_irq_set_type,
+	.flags = IRQCHIP_IMMUTABLE,
+	GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int max7360_gpio_probe(struct platform_device *pdev)
+{
+	struct max7360_gpio *max7360_gpio;
+	struct platform_device *parent;
+	unsigned int ngpios;
+	unsigned int outconf;
+	struct gpio_irq_chip *girq;
+	unsigned long flags;
+	int irq;
+	int ret;
+
+	if (!pdev->dev.parent) {
+		dev_err(&pdev->dev, "no parent device\n");
+		return -ENODEV;
+	}
+	parent = to_platform_device(pdev->dev.parent);
+
+	max7360_gpio = devm_kzalloc(&pdev->dev, sizeof(*max7360_gpio),
+				    GFP_KERNEL);
+	if (!max7360_gpio)
+		return -ENOMEM;
+
+	if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
+		dev_err(&pdev->dev, "Missing ngpios OF property\n");
+		return -ENODEV;
+	}
+
+	max7360_gpio->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!max7360_gpio->regmap) {
+		dev_err(&pdev->dev, "could not get parent regmap\n");
+		return -ENODEV;
+	}
+
+	max7360_gpio->dev = &pdev->dev;
+	max7360_gpio->chip = max7360_gpio_chip;
+	max7360_gpio->chip.ngpio = ngpios;
+	max7360_gpio->chip.parent = &pdev->dev;
+	max7360_gpio->chip.base = -1;
+	max7360_gpio->gpio_function = (uintptr_t)device_get_match_data(&pdev->dev);
+
+	dev_dbg(&pdev->dev, "gpio count: %d\n", max7360_gpio->chip.ngpio);
+
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_PORT) {
+		/* Port GPIOs: set output mode configuration (constant-current
+		 * or not).
+		 * This property is optional.
+		 */
+		outconf = 0;
+		ret = of_property_read_u32(pdev->dev.of_node,
+					   "maxim,constant-current-disable",
+					   &outconf);
+		if (ret && (ret != -EINVAL)) {
+			dev_err(&pdev->dev,
+				"Failed to read maxim,constant-current-disable OF property\n");
+			return -ENODEV;
+		}
+
+	    regmap_write(max7360_gpio->regmap, MAX7360_REG_GPIOOUTM, outconf);
+	}
+
+	if (max7360_gpio->gpio_function == MAX7360_GPIO_PORT &&
+	    of_property_read_bool(pdev->dev.of_node, "interrupt-controller")) {
+		/* Port GPIOs: declare IRQ chip, if configuration was provided.
+		 */
+		irq = platform_get_irq_byname(parent, "inti");
+		if (irq < 0)
+			return dev_err_probe(&pdev->dev, irq,
+					     "Failed to get IRQ\n");
+
+		flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED;
+		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+						max7360_gpio_irq, flags,
+						"max7360-gpio", max7360_gpio);
+		if (ret)
+			return dev_err_probe(&pdev->dev, ret,
+					     "Failed to register interrupt\n");
+
+		girq = &max7360_gpio->chip.irq;
+		gpio_irq_chip_set_chip(girq, &max7360_gpio_irqchip);
+		girq->parent_handler = NULL;
+		girq->num_parents = 0;
+		girq->parents = NULL;
+		girq->threaded = true;
+		girq->default_type = IRQ_TYPE_NONE;
+		girq->handler = handle_simple_irq;
+	}
+
+	ret = devm_gpiochip_add_data(&pdev->dev, &max7360_gpio->chip, max7360_gpio);
+	if (ret) {
+		return dev_err_probe(&pdev->dev, ret,
+				     "unable to add gpiochip\n");
+	}
+
+	return 0;
+}
+
+static const struct of_device_id max7360_gpio_of_match[] = {
+	{
+		.compatible = "maxim,max7360-gpo",
+		.data = (void *)MAX7360_GPIO_COL
+	}, {
+		.compatible = "maxim,max7360-gpio",
+		.data = (void *)MAX7360_GPIO_PORT
+	}, {
+	}
+};
+MODULE_DEVICE_TABLE(of, max7360_gpio_of_match);
+
+static struct platform_driver max7360_gpio_driver = {
+	.driver = {
+		.name	= MAX7360_DRVNAME_GPIO,
+		.of_match_table = of_match_ptr(max7360_gpio_of_match),
+	},
+	.probe		= max7360_gpio_probe,
+};
+module_platform_driver(max7360_gpio_driver);
+
+MODULE_DESCRIPTION("MAX7360 GPIO driver");
+MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_ALIAS("platform:" MAX7360_DRVNAME_GPIO);
+MODULE_LICENSE("GPL");