diff mbox series

[v3,09/11] serial: sc16is7xx: add I/O register translation offset

Message ID 20230525040324.3773741-10-hugo@hugovil.com
State New
Headers show
Series serial: sc16is7xx: fix GPIO regression and rs485 improvements | expand

Commit Message

Hugo Villeneuve May 25, 2023, 4:03 a.m. UTC
From: Hugo Villeneuve <hvilleneuve@dimonoff.com>

If the shared GPIO pins on a dual port/channel variant like the
SC16IS752 are configured as GPIOs for port A, and modem control lines
on port A, we need to translate the Linux GPIO offset to an offset
that is compatible with the I/O registers of the SC16IS7XX (IOState,
IODir and IOIntEna).

Add a new variable to store that offset and set it when we detect that
special case.

Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
---
 drivers/tty/serial/sc16is7xx.c | 54 +++++++++++++++++++++++++++++++++-
 1 file changed, 53 insertions(+), 1 deletion(-)

Comments

Andy Shevchenko May 25, 2023, 11:22 a.m. UTC | #1
Thu, May 25, 2023 at 12:03:23AM -0400, Hugo Villeneuve kirjoitti:
> From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> 
> If the shared GPIO pins on a dual port/channel variant like the
> SC16IS752 are configured as GPIOs for port A, and modem control lines
> on port A, we need to translate the Linux GPIO offset to an offset
> that is compatible with the I/O registers of the SC16IS7XX (IOState,
> IODir and IOIntEna).
> 
> Add a new variable to store that offset and set it when we detect that
> special case.

...

> +/*
> + * We may need to translate the Linux GPIO offset to a SC16IS7XX offset.
> + * This is needed only for the case where a dual port variant is configured to
> + * have only port B as modem status lines.
> + *
> + * Example for SC16IS752/762 with upper bank (port A) set as GPIOs, and
> + * lower bank (port B) set as modem status lines (special case described above):
> + *
> + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> + * name        function     offset         offset
> + * --------------------------------------------------
> + * GPIO7/RIA    GPIO7          3              7
> + * GPIO6/CDA    GPIO6          2              6
> + * GPIO5/DTRA   GPIO5          1              5
> + * GPIO4/DSRA   GPIO4          0              4
> + * GPIO3/RIB    RIB           N/A            N/A
> + * GPIO2/CDB    CDB           N/A            N/A
> + * GPIO1/DTRB   DTRB          N/A            N/A
> + * GPIO0/DSRB   DSRB          N/A            N/A
> + *
> + * Example  for SC16IS750/760 with upper bank (7..4) set as modem status lines,

Single space is enough.

> + * and lower bank (3..0) as GPIOs:
> + *
> + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> + * name        function     offset         offset
> + * --------------------------------------------------
> + * GPIO7/RI     RI            N/A            N/A
> + * GPIO6/CD     CD            N/A            N/A
> + * GPIO5/DTR    DTR           N/A            N/A
> + * GPIO4/DSR    DSR           N/A            N/A
> + * GPIO3        GPIO3          3              3
> + * GPIO2        GPIO2          2              2
> + * GPIO1        GPIO1          1              1
> + * GPIO0        GPIO0          0              0
> + */

Wondering if you can always register 8 pins and use valid mask to define which
one are in use?
Ilpo Järvinen May 25, 2023, 12:10 p.m. UTC | #2
On Thu, 25 May 2023, Hugo Villeneuve wrote:

> From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> 
> If the shared GPIO pins on a dual port/channel variant like the
> SC16IS752 are configured as GPIOs for port A, and modem control lines
> on port A, we need to translate the Linux GPIO offset to an offset
> that is compatible with the I/O registers of the SC16IS7XX (IOState,
> IODir and IOIntEna).
> 
> Add a new variable to store that offset and set it when we detect that
> special case.
> 
> Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> ---
>  drivers/tty/serial/sc16is7xx.c | 54 +++++++++++++++++++++++++++++++++-
>  1 file changed, 53 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
> index 97ec532a0a19..c2cfd057ed9a 100644
> --- a/drivers/tty/serial/sc16is7xx.c
> +++ b/drivers/tty/serial/sc16is7xx.c
> @@ -338,6 +338,7 @@ struct sc16is7xx_port {
>  #ifdef CONFIG_GPIOLIB
>  	struct gpio_chip		gpio;
>  	int				gpio_configured;
> +	int				gpio_offset;
>  #endif
>  	unsigned char			buf[SC16IS7XX_FIFO_SIZE];
>  	struct kthread_worker		kworker;
> @@ -1298,12 +1299,50 @@ static const struct uart_ops sc16is7xx_ops = {
>  };
>  
>  #ifdef CONFIG_GPIOLIB
> +
> +/*
> + * We may need to translate the Linux GPIO offset to a SC16IS7XX offset.
> + * This is needed only for the case where a dual port variant is configured to
> + * have only port B as modem status lines.
> + *
> + * Example for SC16IS752/762 with upper bank (port A) set as GPIOs, and
> + * lower bank (port B) set as modem status lines (special case described above):
> + *
> + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> + * name        function     offset         offset
> + * --------------------------------------------------
> + * GPIO7/RIA    GPIO7          3              7
> + * GPIO6/CDA    GPIO6          2              6
> + * GPIO5/DTRA   GPIO5          1              5
> + * GPIO4/DSRA   GPIO4          0              4
> + * GPIO3/RIB    RIB           N/A            N/A
> + * GPIO2/CDB    CDB           N/A            N/A
> + * GPIO1/DTRB   DTRB          N/A            N/A
> + * GPIO0/DSRB   DSRB          N/A            N/A
> + *
> + * Example  for SC16IS750/760 with upper bank (7..4) set as modem status lines,
> + * and lower bank (3..0) as GPIOs:
> + *
> + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> + * name        function     offset         offset
> + * --------------------------------------------------
> + * GPIO7/RI     RI            N/A            N/A
> + * GPIO6/CD     CD            N/A            N/A
> + * GPIO5/DTR    DTR           N/A            N/A
> + * GPIO4/DSR    DSR           N/A            N/A
> + * GPIO3        GPIO3          3              3
> + * GPIO2        GPIO2          2              2
> + * GPIO1        GPIO1          1              1
> + * GPIO0        GPIO0          0              0
> + */
> +
>  static int sc16is7xx_gpio_get(struct gpio_chip *chip, unsigned offset)
>  {
>  	unsigned int val;
>  	struct sc16is7xx_port *s = gpiochip_get_data(chip);
>  	struct uart_port *port = &s->p[0].port;
>  
> +	offset += s->gpio_offset;
>  	val = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG);
>  
>  	return !!(val & BIT(offset));
> @@ -1314,6 +1353,7 @@ static void sc16is7xx_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
>  	struct sc16is7xx_port *s = gpiochip_get_data(chip);
>  	struct uart_port *port = &s->p[0].port;
>  
> +	offset += s->gpio_offset;
>  	sc16is7xx_port_update(port, SC16IS7XX_IOSTATE_REG, BIT(offset),
>  			      val ? BIT(offset) : 0);
>  }
> @@ -1324,6 +1364,7 @@ static int sc16is7xx_gpio_direction_input(struct gpio_chip *chip,
>  	struct sc16is7xx_port *s = gpiochip_get_data(chip);
>  	struct uart_port *port = &s->p[0].port;
>  
> +	offset += s->gpio_offset;
>  	sc16is7xx_port_update(port, SC16IS7XX_IODIR_REG, BIT(offset), 0);
>  
>  	return 0;
> @@ -1336,6 +1377,8 @@ static int sc16is7xx_gpio_direction_output(struct gpio_chip *chip,
>  	struct uart_port *port = &s->p[0].port;
>  	u8 state = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG);
>  
> +	offset += s->gpio_offset;
> +
>  	if (val)
>  		state |= BIT(offset);
>  	else
> @@ -1395,6 +1438,7 @@ static int sc16is7xx_probe(struct device *dev,
>  
>  #ifdef CONFIG_GPIOLIB
>  	s->gpio_configured = devtype->nr_gpio;
> +	s->gpio_offset = 0;
>  #endif /* CONFIG_GPIOLIB */
>  
>  	/* Always ask for fixed clock rate from a property. */
> @@ -1529,16 +1573,24 @@ static int sc16is7xx_probe(struct device *dev,
>  #endif /* CONFIG_GPIOLIB */
>  			}
>  
> -		if (val)
> +		if (val) {
> +#ifdef CONFIG_GPIOLIB
> +			/* Additional I/O regs offset. */
> +			if (val == SC16IS7XX_IOCONTROL_MODEM_B_BIT)
> +				s->gpio_offset = SC16IS7XX_GPIOS_PER_BANK;
> +#endif /* CONFIG_GPIOLIB */
> +
>  			regmap_update_bits(
>  				s->regmap,
>  				SC16IS7XX_IOCONTROL_REG << SC16IS7XX_REG_SHIFT,
>  				SC16IS7XX_IOCONTROL_MODEM_A_BIT |
>  				SC16IS7XX_IOCONTROL_MODEM_B_BIT, val);
> +		}
>  	}
>  
>  #ifdef CONFIG_GPIOLIB
>  	dev_dbg(dev, "GPIOs to configure: %d\n", s->gpio_configured);
> +	dev_dbg(dev, "GPIOs offset: %d\n", s->gpio_offset);
>  
>  	if (s->gpio_configured) {
>  		/* Setup GPIO controller */
> 

Is the order of this and 8/11 patch correct or should this precede that 
other patch? That is, is 8/11 alone useful enough or would this also be 
wanted? (I'm aware that 8/11 has a Fixes tag).
Hugo Villeneuve May 25, 2023, 3:25 p.m. UTC | #3
On Thu, 25 May 2023 15:10:00 +0300 (EEST)
Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> wrote:

> On Thu, 25 May 2023, Hugo Villeneuve wrote:
> 
> > From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> > 
> > If the shared GPIO pins on a dual port/channel variant like the
> > SC16IS752 are configured as GPIOs for port A, and modem control lines
> > on port A, we need to translate the Linux GPIO offset to an offset
> > that is compatible with the I/O registers of the SC16IS7XX (IOState,
> > IODir and IOIntEna).
> > 
> > Add a new variable to store that offset and set it when we detect that
> > special case.
> > 
> > Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> > ---
> >  drivers/tty/serial/sc16is7xx.c | 54 +++++++++++++++++++++++++++++++++-
> >  1 file changed, 53 insertions(+), 1 deletion(-)
> > 
> > diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
> > index 97ec532a0a19..c2cfd057ed9a 100644
> > --- a/drivers/tty/serial/sc16is7xx.c
> > +++ b/drivers/tty/serial/sc16is7xx.c
> > @@ -338,6 +338,7 @@ struct sc16is7xx_port {
> >  #ifdef CONFIG_GPIOLIB
> >  	struct gpio_chip		gpio;
> >  	int				gpio_configured;
> > +	int				gpio_offset;
> >  #endif
> >  	unsigned char			buf[SC16IS7XX_FIFO_SIZE];
> >  	struct kthread_worker		kworker;
> > @@ -1298,12 +1299,50 @@ static const struct uart_ops sc16is7xx_ops = {
> >  };
> >  
> >  #ifdef CONFIG_GPIOLIB
> > +
> > +/*
> > + * We may need to translate the Linux GPIO offset to a SC16IS7XX offset.
> > + * This is needed only for the case where a dual port variant is configured to
> > + * have only port B as modem status lines.
> > + *
> > + * Example for SC16IS752/762 with upper bank (port A) set as GPIOs, and
> > + * lower bank (port B) set as modem status lines (special case described above):
> > + *
> > + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> > + * name        function     offset         offset
> > + * --------------------------------------------------
> > + * GPIO7/RIA    GPIO7          3              7
> > + * GPIO6/CDA    GPIO6          2              6
> > + * GPIO5/DTRA   GPIO5          1              5
> > + * GPIO4/DSRA   GPIO4          0              4
> > + * GPIO3/RIB    RIB           N/A            N/A
> > + * GPIO2/CDB    CDB           N/A            N/A
> > + * GPIO1/DTRB   DTRB          N/A            N/A
> > + * GPIO0/DSRB   DSRB          N/A            N/A
> > + *
> > + * Example  for SC16IS750/760 with upper bank (7..4) set as modem status lines,
> > + * and lower bank (3..0) as GPIOs:
> > + *
> > + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> > + * name        function     offset         offset
> > + * --------------------------------------------------
> > + * GPIO7/RI     RI            N/A            N/A
> > + * GPIO6/CD     CD            N/A            N/A
> > + * GPIO5/DTR    DTR           N/A            N/A
> > + * GPIO4/DSR    DSR           N/A            N/A
> > + * GPIO3        GPIO3          3              3
> > + * GPIO2        GPIO2          2              2
> > + * GPIO1        GPIO1          1              1
> > + * GPIO0        GPIO0          0              0
> > + */
> > +
> >  static int sc16is7xx_gpio_get(struct gpio_chip *chip, unsigned offset)
> >  {
> >  	unsigned int val;
> >  	struct sc16is7xx_port *s = gpiochip_get_data(chip);
> >  	struct uart_port *port = &s->p[0].port;
> >  
> > +	offset += s->gpio_offset;
> >  	val = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG);
> >  
> >  	return !!(val & BIT(offset));
> > @@ -1314,6 +1353,7 @@ static void sc16is7xx_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
> >  	struct sc16is7xx_port *s = gpiochip_get_data(chip);
> >  	struct uart_port *port = &s->p[0].port;
> >  
> > +	offset += s->gpio_offset;
> >  	sc16is7xx_port_update(port, SC16IS7XX_IOSTATE_REG, BIT(offset),
> >  			      val ? BIT(offset) : 0);
> >  }
> > @@ -1324,6 +1364,7 @@ static int sc16is7xx_gpio_direction_input(struct gpio_chip *chip,
> >  	struct sc16is7xx_port *s = gpiochip_get_data(chip);
> >  	struct uart_port *port = &s->p[0].port;
> >  
> > +	offset += s->gpio_offset;
> >  	sc16is7xx_port_update(port, SC16IS7XX_IODIR_REG, BIT(offset), 0);
> >  
> >  	return 0;
> > @@ -1336,6 +1377,8 @@ static int sc16is7xx_gpio_direction_output(struct gpio_chip *chip,
> >  	struct uart_port *port = &s->p[0].port;
> >  	u8 state = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG);
> >  
> > +	offset += s->gpio_offset;
> > +
> >  	if (val)
> >  		state |= BIT(offset);
> >  	else
> > @@ -1395,6 +1438,7 @@ static int sc16is7xx_probe(struct device *dev,
> >  
> >  #ifdef CONFIG_GPIOLIB
> >  	s->gpio_configured = devtype->nr_gpio;
> > +	s->gpio_offset = 0;
> >  #endif /* CONFIG_GPIOLIB */
> >  
> >  	/* Always ask for fixed clock rate from a property. */
> > @@ -1529,16 +1573,24 @@ static int sc16is7xx_probe(struct device *dev,
> >  #endif /* CONFIG_GPIOLIB */
> >  			}
> >  
> > -		if (val)
> > +		if (val) {
> > +#ifdef CONFIG_GPIOLIB
> > +			/* Additional I/O regs offset. */
> > +			if (val == SC16IS7XX_IOCONTROL_MODEM_B_BIT)
> > +				s->gpio_offset = SC16IS7XX_GPIOS_PER_BANK;
> > +#endif /* CONFIG_GPIOLIB */
> > +
> >  			regmap_update_bits(
> >  				s->regmap,
> >  				SC16IS7XX_IOCONTROL_REG << SC16IS7XX_REG_SHIFT,
> >  				SC16IS7XX_IOCONTROL_MODEM_A_BIT |
> >  				SC16IS7XX_IOCONTROL_MODEM_B_BIT, val);
> > +		}
> >  	}
> >  
> >  #ifdef CONFIG_GPIOLIB
> >  	dev_dbg(dev, "GPIOs to configure: %d\n", s->gpio_configured);
> > +	dev_dbg(dev, "GPIOs offset: %d\n", s->gpio_offset);
> >  
> >  	if (s->gpio_configured) {
> >  		/* Setup GPIO controller */
> > 
> 
> Is the order of this and 8/11 patch correct or should this precede that 
> other patch? That is, is 8/11 alone useful enough or would this also be 
> wanted? (I'm aware that 8/11 has a Fixes tag).

In fact, this patch (9/11) could be considered to be part of 8/11. I decided to separate it in order to facilitate the review process.

Maybe I should merge them...

Hugo.
Hugo Villeneuve May 25, 2023, 3:31 p.m. UTC | #4
On Thu, 25 May 2023 14:22:46 +0300
andy.shevchenko@gmail.com wrote:

> Thu, May 25, 2023 at 12:03:23AM -0400, Hugo Villeneuve kirjoitti:
> > From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> > 
> > If the shared GPIO pins on a dual port/channel variant like the
> > SC16IS752 are configured as GPIOs for port A, and modem control lines
> > on port A, we need to translate the Linux GPIO offset to an offset
> > that is compatible with the I/O registers of the SC16IS7XX (IOState,
> > IODir and IOIntEna).
> > 
> > Add a new variable to store that offset and set it when we detect that
> > special case.
> 
> ...
> 
> > +/*
> > + * We may need to translate the Linux GPIO offset to a SC16IS7XX offset.
> > + * This is needed only for the case where a dual port variant is configured to
> > + * have only port B as modem status lines.
> > + *
> > + * Example for SC16IS752/762 with upper bank (port A) set as GPIOs, and
> > + * lower bank (port B) set as modem status lines (special case described above):
> > + *
> > + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> > + * name        function     offset         offset
> > + * --------------------------------------------------
> > + * GPIO7/RIA    GPIO7          3              7
> > + * GPIO6/CDA    GPIO6          2              6
> > + * GPIO5/DTRA   GPIO5          1              5
> > + * GPIO4/DSRA   GPIO4          0              4
> > + * GPIO3/RIB    RIB           N/A            N/A
> > + * GPIO2/CDB    CDB           N/A            N/A
> > + * GPIO1/DTRB   DTRB          N/A            N/A
> > + * GPIO0/DSRB   DSRB          N/A            N/A
> > + *
> > + * Example  for SC16IS750/760 with upper bank (7..4) set as modem status lines,
> 
> Single space is enough.

Fixed.

 
> > + * and lower bank (3..0) as GPIOs:
> > + *
> > + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> > + * name        function     offset         offset
> > + * --------------------------------------------------
> > + * GPIO7/RI     RI            N/A            N/A
> > + * GPIO6/CD     CD            N/A            N/A
> > + * GPIO5/DTR    DTR           N/A            N/A
> > + * GPIO4/DSR    DSR           N/A            N/A
> > + * GPIO3        GPIO3          3              3
> > + * GPIO2        GPIO2          2              2
> > + * GPIO1        GPIO1          1              1
> > + * GPIO0        GPIO0          0              0
> > + */
> 
> Wondering if you can always register 8 pins and use valid mask to define which
> one are in use?

I will look into it, I think it may be a good idea and could help to simplify things a bit.

Hugo.
Hugo Villeneuve May 25, 2023, 5:20 p.m. UTC | #5
On Thu, 25 May 2023 11:31:45 -0400
Hugo Villeneuve <hugo@hugovil.com> wrote:

> On Thu, 25 May 2023 14:22:46 +0300
> andy.shevchenko@gmail.com wrote:
> 
> > Thu, May 25, 2023 at 12:03:23AM -0400, Hugo Villeneuve kirjoitti:
> > > From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> > > 
> > > If the shared GPIO pins on a dual port/channel variant like the
> > > SC16IS752 are configured as GPIOs for port A, and modem control lines
> > > on port A, we need to translate the Linux GPIO offset to an offset
> > > that is compatible with the I/O registers of the SC16IS7XX (IOState,
> > > IODir and IOIntEna).
> > > 
> > > Add a new variable to store that offset and set it when we detect that
> > > special case.
> > 
> > ...
> > 
> > > +/*
> > > + * We may need to translate the Linux GPIO offset to a SC16IS7XX offset.
> > > + * This is needed only for the case where a dual port variant is configured to
> > > + * have only port B as modem status lines.
> > > + *
> > > + * Example for SC16IS752/762 with upper bank (port A) set as GPIOs, and
> > > + * lower bank (port B) set as modem status lines (special case described above):
> > > + *
> > > + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> > > + * name        function     offset         offset
> > > + * --------------------------------------------------
> > > + * GPIO7/RIA    GPIO7          3              7
> > > + * GPIO6/CDA    GPIO6          2              6
> > > + * GPIO5/DTRA   GPIO5          1              5
> > > + * GPIO4/DSRA   GPIO4          0              4
> > > + * GPIO3/RIB    RIB           N/A            N/A
> > > + * GPIO2/CDB    CDB           N/A            N/A
> > > + * GPIO1/DTRB   DTRB          N/A            N/A
> > > + * GPIO0/DSRB   DSRB          N/A            N/A
> > > + *
> > > + * Example  for SC16IS750/760 with upper bank (7..4) set as modem status lines,
> > 
> > Single space is enough.
> 
> Fixed.
> 
>  
> > > + * and lower bank (3..0) as GPIOs:
> > > + *
> > > + * Pin         GPIO pin     Linux GPIO     SC16IS7XX
> > > + * name        function     offset         offset
> > > + * --------------------------------------------------
> > > + * GPIO7/RI     RI            N/A            N/A
> > > + * GPIO6/CD     CD            N/A            N/A
> > > + * GPIO5/DTR    DTR           N/A            N/A
> > > + * GPIO4/DSR    DSR           N/A            N/A
> > > + * GPIO3        GPIO3          3              3
> > > + * GPIO2        GPIO2          2              2
> > > + * GPIO1        GPIO1          1              1
> > > + * GPIO0        GPIO0          0              0
> > > + */
> > 
> > Wondering if you can always register 8 pins and use valid mask to define which
> > one are in use?
> 
> I will look into it, I think it may be a good idea and could help to simplify things a bit.

Hi,
finally, this was the way to go. The resulting code/patch is much simpler and elegant this way. Thank you for the suggestion.

I will submit a V4 with all the changes.

Hugo.
Andy Shevchenko May 26, 2023, 6:36 p.m. UTC | #6
Thu, May 25, 2023 at 01:20:46PM -0400, Hugo Villeneuve kirjoitti:
> On Thu, 25 May 2023 11:31:45 -0400
> Hugo Villeneuve <hugo@hugovil.com> wrote:
> > On Thu, 25 May 2023 14:22:46 +0300
> > andy.shevchenko@gmail.com wrote:
> > > Thu, May 25, 2023 at 12:03:23AM -0400, Hugo Villeneuve kirjoitti:

...

> > > Wondering if you can always register 8 pins and use valid mask to define which
> > > one are in use?
> > 
> > I will look into it, I think it may be a good idea and could help to
> > simplify things a bit.
> 
> finally, this was the way to go. The resulting code/patch is much simpler and
> elegant this way. Thank you for the suggestion.
> 
> I will submit a V4 with all the changes.

Thank you for trying!
diff mbox series

Patch

diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
index 97ec532a0a19..c2cfd057ed9a 100644
--- a/drivers/tty/serial/sc16is7xx.c
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -338,6 +338,7 @@  struct sc16is7xx_port {
 #ifdef CONFIG_GPIOLIB
 	struct gpio_chip		gpio;
 	int				gpio_configured;
+	int				gpio_offset;
 #endif
 	unsigned char			buf[SC16IS7XX_FIFO_SIZE];
 	struct kthread_worker		kworker;
@@ -1298,12 +1299,50 @@  static const struct uart_ops sc16is7xx_ops = {
 };
 
 #ifdef CONFIG_GPIOLIB
+
+/*
+ * We may need to translate the Linux GPIO offset to a SC16IS7XX offset.
+ * This is needed only for the case where a dual port variant is configured to
+ * have only port B as modem status lines.
+ *
+ * Example for SC16IS752/762 with upper bank (port A) set as GPIOs, and
+ * lower bank (port B) set as modem status lines (special case described above):
+ *
+ * Pin         GPIO pin     Linux GPIO     SC16IS7XX
+ * name        function     offset         offset
+ * --------------------------------------------------
+ * GPIO7/RIA    GPIO7          3              7
+ * GPIO6/CDA    GPIO6          2              6
+ * GPIO5/DTRA   GPIO5          1              5
+ * GPIO4/DSRA   GPIO4          0              4
+ * GPIO3/RIB    RIB           N/A            N/A
+ * GPIO2/CDB    CDB           N/A            N/A
+ * GPIO1/DTRB   DTRB          N/A            N/A
+ * GPIO0/DSRB   DSRB          N/A            N/A
+ *
+ * Example  for SC16IS750/760 with upper bank (7..4) set as modem status lines,
+ * and lower bank (3..0) as GPIOs:
+ *
+ * Pin         GPIO pin     Linux GPIO     SC16IS7XX
+ * name        function     offset         offset
+ * --------------------------------------------------
+ * GPIO7/RI     RI            N/A            N/A
+ * GPIO6/CD     CD            N/A            N/A
+ * GPIO5/DTR    DTR           N/A            N/A
+ * GPIO4/DSR    DSR           N/A            N/A
+ * GPIO3        GPIO3          3              3
+ * GPIO2        GPIO2          2              2
+ * GPIO1        GPIO1          1              1
+ * GPIO0        GPIO0          0              0
+ */
+
 static int sc16is7xx_gpio_get(struct gpio_chip *chip, unsigned offset)
 {
 	unsigned int val;
 	struct sc16is7xx_port *s = gpiochip_get_data(chip);
 	struct uart_port *port = &s->p[0].port;
 
+	offset += s->gpio_offset;
 	val = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG);
 
 	return !!(val & BIT(offset));
@@ -1314,6 +1353,7 @@  static void sc16is7xx_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
 	struct sc16is7xx_port *s = gpiochip_get_data(chip);
 	struct uart_port *port = &s->p[0].port;
 
+	offset += s->gpio_offset;
 	sc16is7xx_port_update(port, SC16IS7XX_IOSTATE_REG, BIT(offset),
 			      val ? BIT(offset) : 0);
 }
@@ -1324,6 +1364,7 @@  static int sc16is7xx_gpio_direction_input(struct gpio_chip *chip,
 	struct sc16is7xx_port *s = gpiochip_get_data(chip);
 	struct uart_port *port = &s->p[0].port;
 
+	offset += s->gpio_offset;
 	sc16is7xx_port_update(port, SC16IS7XX_IODIR_REG, BIT(offset), 0);
 
 	return 0;
@@ -1336,6 +1377,8 @@  static int sc16is7xx_gpio_direction_output(struct gpio_chip *chip,
 	struct uart_port *port = &s->p[0].port;
 	u8 state = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG);
 
+	offset += s->gpio_offset;
+
 	if (val)
 		state |= BIT(offset);
 	else
@@ -1395,6 +1438,7 @@  static int sc16is7xx_probe(struct device *dev,
 
 #ifdef CONFIG_GPIOLIB
 	s->gpio_configured = devtype->nr_gpio;
+	s->gpio_offset = 0;
 #endif /* CONFIG_GPIOLIB */
 
 	/* Always ask for fixed clock rate from a property. */
@@ -1529,16 +1573,24 @@  static int sc16is7xx_probe(struct device *dev,
 #endif /* CONFIG_GPIOLIB */
 			}
 
-		if (val)
+		if (val) {
+#ifdef CONFIG_GPIOLIB
+			/* Additional I/O regs offset. */
+			if (val == SC16IS7XX_IOCONTROL_MODEM_B_BIT)
+				s->gpio_offset = SC16IS7XX_GPIOS_PER_BANK;
+#endif /* CONFIG_GPIOLIB */
+
 			regmap_update_bits(
 				s->regmap,
 				SC16IS7XX_IOCONTROL_REG << SC16IS7XX_REG_SHIFT,
 				SC16IS7XX_IOCONTROL_MODEM_A_BIT |
 				SC16IS7XX_IOCONTROL_MODEM_B_BIT, val);
+		}
 	}
 
 #ifdef CONFIG_GPIOLIB
 	dev_dbg(dev, "GPIOs to configure: %d\n", s->gpio_configured);
+	dev_dbg(dev, "GPIOs offset: %d\n", s->gpio_offset);
 
 	if (s->gpio_configured) {
 		/* Setup GPIO controller */