diff mbox series

[v2,1/2] gpio/aspeed-sgpio: enable access to all 80 input & output sgpios

Message ID 20200911015105.48581-1-jk@codeconstruct.com.au
State New
Headers show
Series [v2,1/2] gpio/aspeed-sgpio: enable access to all 80 input & output sgpios | expand

Commit Message

Jeremy Kerr Sept. 11, 2020, 1:51 a.m. UTC
Currently, the aspeed-sgpio driver exposes up to 80 GPIO lines,
corresponding to the 80 status bits available in hardware. Each of these
lines can be configured as either an input or an output.

However, each of these GPIOs is actually an input *and* an output; we
actually have 80 inputs plus 80 outputs.

This change expands the maximum number of GPIOs to 160; the lower half
of this range are the input-only GPIOs, the upper half are the outputs.
We fix the GPIO directions to correspond to this mapping.

This also fixes a bug when setting GPIOs - we were reading from the
input register, making it impossible to set more than one output GPIO.

Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
Fixes: 7db47faae79b ("gpio: aspeed: Add SGPIO driver")

---
v2:
 - Fix warnings from kbuild test robot
 - Add comment for input/output GPIO numbering
---
 .../devicetree/bindings/gpio/sgpio-aspeed.txt |   5 +-
 drivers/gpio/gpio-aspeed-sgpio.c              | 126 ++++++++++++------
 2 files changed, 87 insertions(+), 44 deletions(-)

Comments

Joel Stanley Sept. 16, 2020, 4:51 a.m. UTC | #1
Hi GPIO maintainers,

On Fri, 11 Sep 2020 at 02:20, Joel Stanley <joel@jms.id.au> wrote:
>

> On Fri, 11 Sep 2020 at 02:11, Jeremy Kerr <jk@codeconstruct.com.au> wrote:

> >

> > Currently, the aspeed-sgpio driver exposes up to 80 GPIO lines,

> > corresponding to the 80 status bits available in hardware. Each of these

> > lines can be configured as either an input or an output.

> >

> > However, each of these GPIOs is actually an input *and* an output; we

> > actually have 80 inputs plus 80 outputs.

> >

> > This change expands the maximum number of GPIOs to 160; the lower half

> > of this range are the input-only GPIOs, the upper half are the outputs.

> > We fix the GPIO directions to correspond to this mapping.

> >

> > This also fixes a bug when setting GPIOs - we were reading from the

> > input register, making it impossible to set more than one output GPIO.

> >

> > Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>

> > Fixes: 7db47faae79b ("gpio: aspeed: Add SGPIO driver")

>

> Reviewed-by: Joel Stanley <joel@jms.id.au>


This series is good to go in for 5.10:

 https://lore.kernel.org/linux-gpio/20200911015105.48581-1-jk@codeconstruct.com.au/
 https://lore.kernel.org/linux-gpio/20200911015105.48581-2-jk@codeconstruct.com.au/

Cheers,

Joel

>

> >

> > ---

> > v2:

> >  - Fix warnings from kbuild test robot

> >  - Add comment for input/output GPIO numbering

> > ---

> >  .../devicetree/bindings/gpio/sgpio-aspeed.txt |   5 +-

> >  drivers/gpio/gpio-aspeed-sgpio.c              | 126 ++++++++++++------

> >  2 files changed, 87 insertions(+), 44 deletions(-)

> >

> > diff --git a/Documentation/devicetree/bindings/gpio/sgpio-aspeed.txt b/Documentation/devicetree/bindings/gpio/sgpio-aspeed.txt

> > index d4d83916c09d..be329ea4794f 100644

> > --- a/Documentation/devicetree/bindings/gpio/sgpio-aspeed.txt

> > +++ b/Documentation/devicetree/bindings/gpio/sgpio-aspeed.txt

> > @@ -20,8 +20,9 @@ Required properties:

> >  - gpio-controller : Marks the device node as a GPIO controller

> >  - interrupts : Interrupt specifier, see interrupt-controller/interrupts.txt

> >  - interrupt-controller : Mark the GPIO controller as an interrupt-controller

> > -- ngpios : number of GPIO lines, see gpio.txt

> > -  (should be multiple of 8, up to 80 pins)

> > +- ngpios : number of *hardware* GPIO lines, see gpio.txt. This will expose

> > +  2 software GPIOs per hardware GPIO: one for hardware input, one for hardware

> > +  output. Up to 80 pins, must be a multiple of 8.

> >  - clocks : A phandle to the APB clock for SGPM clock division

> >  - bus-frequency : SGPM CLK frequency

> >

> > diff --git a/drivers/gpio/gpio-aspeed-sgpio.c b/drivers/gpio/gpio-aspeed-sgpio.c

> > index 3aa45934d60c..a18ca52432e0 100644

> > --- a/drivers/gpio/gpio-aspeed-sgpio.c

> > +++ b/drivers/gpio/gpio-aspeed-sgpio.c

> > @@ -17,7 +17,17 @@

> >  #include <linux/spinlock.h>

> >  #include <linux/string.h>

> >

> > -#define MAX_NR_SGPIO                   80

> > +/*

> > + * MAX_NR_HW_GPIO represents the number of actual hardware-supported GPIOs (ie,

> > + * slots within the clocked serial GPIO data). Since each HW GPIO is both an

> > + * input and an output, we provide MAX_NR_HW_GPIO * 2 lines on our gpiochip

> > + * device.

> > + *

> > + * We use SGPIO_OUTPUT_OFFSET to define the split between the inputs and

> > + * outputs; the inputs start at line 0, the outputs start at OUTPUT_OFFSET.

> > + */

> > +#define MAX_NR_HW_SGPIO                        80

> > +#define SGPIO_OUTPUT_OFFSET            MAX_NR_HW_SGPIO

> >

> >  #define ASPEED_SGPIO_CTRL              0x54

> >

> > @@ -30,8 +40,8 @@ struct aspeed_sgpio {

> >         struct clk *pclk;

> >         spinlock_t lock;

> >         void __iomem *base;

> > -       uint32_t dir_in[3];

> >         int irq;

> > +       int n_sgpio;

> >  };

> >

> >  struct aspeed_sgpio_bank {

> > @@ -111,31 +121,69 @@ static void __iomem *bank_reg(struct aspeed_sgpio *gpio,

> >         }

> >  }

> >

> > -#define GPIO_BANK(x)    ((x) >> 5)

> > -#define GPIO_OFFSET(x)  ((x) & 0x1f)

> > +#define GPIO_BANK(x)    ((x % SGPIO_OUTPUT_OFFSET) >> 5)

> > +#define GPIO_OFFSET(x)  ((x % SGPIO_OUTPUT_OFFSET) & 0x1f)

> >  #define GPIO_BIT(x)     BIT(GPIO_OFFSET(x))

> >

> >  static const struct aspeed_sgpio_bank *to_bank(unsigned int offset)

> >  {

> > -       unsigned int bank = GPIO_BANK(offset);

> > +       unsigned int bank;

> > +

> > +       bank = GPIO_BANK(offset);

> >

> >         WARN_ON(bank >= ARRAY_SIZE(aspeed_sgpio_banks));

> >         return &aspeed_sgpio_banks[bank];

> >  }

> >

> > +static int aspeed_sgpio_init_valid_mask(struct gpio_chip *gc,

> > +               unsigned long *valid_mask, unsigned int ngpios)

> > +{

> > +       struct aspeed_sgpio *sgpio = gpiochip_get_data(gc);

> > +       int n = sgpio->n_sgpio;

> > +       int c = SGPIO_OUTPUT_OFFSET - n;

> > +

> > +       WARN_ON(ngpios < MAX_NR_HW_SGPIO * 2);

> > +

> > +       /* input GPIOs in the lower range */

> > +       bitmap_set(valid_mask, 0, n);

> > +       bitmap_clear(valid_mask, n, c);

> > +

> > +       /* output GPIOS above SGPIO_OUTPUT_OFFSET */

> > +       bitmap_set(valid_mask, SGPIO_OUTPUT_OFFSET, n);

> > +       bitmap_clear(valid_mask, SGPIO_OUTPUT_OFFSET + n, c);

> > +

> > +       return 0;

> > +}

> > +

> > +static void aspeed_sgpio_irq_init_valid_mask(struct gpio_chip *gc,

> > +               unsigned long *valid_mask, unsigned int ngpios)

> > +{

> > +       struct aspeed_sgpio *sgpio = gpiochip_get_data(gc);

> > +       int n = sgpio->n_sgpio;

> > +

> > +       WARN_ON(ngpios < MAX_NR_HW_SGPIO * 2);

> > +

> > +       /* input GPIOs in the lower range */

> > +       bitmap_set(valid_mask, 0, n);

> > +       bitmap_clear(valid_mask, n, ngpios - n);

> > +}

> > +

> > +static bool aspeed_sgpio_is_input(unsigned int offset)

> > +{

> > +       return offset < SGPIO_OUTPUT_OFFSET;

> > +}

> > +

> >  static int aspeed_sgpio_get(struct gpio_chip *gc, unsigned int offset)

> >  {

> >         struct aspeed_sgpio *gpio = gpiochip_get_data(gc);

> >         const struct aspeed_sgpio_bank *bank = to_bank(offset);

> >         unsigned long flags;

> >         enum aspeed_sgpio_reg reg;

> > -       bool is_input;

> >         int rc = 0;

> >

> >         spin_lock_irqsave(&gpio->lock, flags);

> >

> > -       is_input = gpio->dir_in[GPIO_BANK(offset)] & GPIO_BIT(offset);

> > -       reg = is_input ? reg_val : reg_rdata;

> > +       reg = aspeed_sgpio_is_input(offset) ? reg_val : reg_rdata;

> >         rc = !!(ioread32(bank_reg(gpio, bank, reg)) & GPIO_BIT(offset));

> >

> >         spin_unlock_irqrestore(&gpio->lock, flags);

> > @@ -143,22 +191,31 @@ static int aspeed_sgpio_get(struct gpio_chip *gc, unsigned int offset)

> >         return rc;

> >  }

> >

> > -static void sgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val)

> > +static int sgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val)

> >  {

> >         struct aspeed_sgpio *gpio = gpiochip_get_data(gc);

> >         const struct aspeed_sgpio_bank *bank = to_bank(offset);

> > -       void __iomem *addr;

> > +       void __iomem *addr_r, *addr_w;

> >         u32 reg = 0;

> >

> > -       addr = bank_reg(gpio, bank, reg_val);

> > -       reg = ioread32(addr);

> > +       if (aspeed_sgpio_is_input(offset))

> > +               return -EINVAL;

> > +

> > +       /* Since this is an output, read the cached value from rdata, then

> > +        * update val. */

> > +       addr_r = bank_reg(gpio, bank, reg_rdata);

> > +       addr_w = bank_reg(gpio, bank, reg_val);

> > +

> > +       reg = ioread32(addr_r);

> >

> >         if (val)

> >                 reg |= GPIO_BIT(offset);

> >         else

> >                 reg &= ~GPIO_BIT(offset);

> >

> > -       iowrite32(reg, addr);

> > +       iowrite32(reg, addr_w);

> > +

> > +       return 0;

> >  }

> >

> >  static void aspeed_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val)

> > @@ -175,43 +232,28 @@ static void aspeed_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val)

> >

> >  static int aspeed_sgpio_dir_in(struct gpio_chip *gc, unsigned int offset)

> >  {

> > -       struct aspeed_sgpio *gpio = gpiochip_get_data(gc);

> > -       unsigned long flags;

> > -

> > -       spin_lock_irqsave(&gpio->lock, flags);

> > -       gpio->dir_in[GPIO_BANK(offset)] |= GPIO_BIT(offset);

> > -       spin_unlock_irqrestore(&gpio->lock, flags);

> > -

> > -       return 0;

> > +       return aspeed_sgpio_is_input(offset) ? 0 : -EINVAL;

> >  }

> >

> >  static int aspeed_sgpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val)

> >  {

> >         struct aspeed_sgpio *gpio = gpiochip_get_data(gc);

> >         unsigned long flags;

> > +       int rc;

> >

> > -       spin_lock_irqsave(&gpio->lock, flags);

> > -

> > -       gpio->dir_in[GPIO_BANK(offset)] &= ~GPIO_BIT(offset);

> > -       sgpio_set_value(gc, offset, val);

> > +       /* No special action is required for setting the direction; we'll

> > +        * error-out in sgpio_set_value if this isn't an output GPIO */

> >

> > +       spin_lock_irqsave(&gpio->lock, flags);

> > +       rc = sgpio_set_value(gc, offset, val);

> >         spin_unlock_irqrestore(&gpio->lock, flags);

> >

> > -       return 0;

> > +       return rc;

> >  }

> >

> >  static int aspeed_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset)

> >  {

> > -       int dir_status;

> > -       struct aspeed_sgpio *gpio = gpiochip_get_data(gc);

> > -       unsigned long flags;

> > -

> > -       spin_lock_irqsave(&gpio->lock, flags);

> > -       dir_status = gpio->dir_in[GPIO_BANK(offset)] & GPIO_BIT(offset);

> > -       spin_unlock_irqrestore(&gpio->lock, flags);

> > -

> > -       return dir_status;

> > -

> > +       return !!aspeed_sgpio_is_input(offset);

> >  }

> >

> >  static void irqd_to_aspeed_sgpio_data(struct irq_data *d,

> > @@ -402,6 +444,7 @@ static int aspeed_sgpio_setup_irqs(struct aspeed_sgpio *gpio,

> >

> >         irq = &gpio->chip.irq;

> >         irq->chip = &aspeed_sgpio_irqchip;

> > +       irq->init_valid_mask = aspeed_sgpio_irq_init_valid_mask;

> >         irq->handler = handle_bad_irq;

> >         irq->default_type = IRQ_TYPE_NONE;

> >         irq->parent_handler = aspeed_sgpio_irq_handler;

> > @@ -452,11 +495,12 @@ static int __init aspeed_sgpio_probe(struct platform_device *pdev)

> >         if (rc < 0) {

> >                 dev_err(&pdev->dev, "Could not read ngpios property\n");

> >                 return -EINVAL;

> > -       } else if (nr_gpios > MAX_NR_SGPIO) {

> > +       } else if (nr_gpios > MAX_NR_HW_SGPIO) {

> >                 dev_err(&pdev->dev, "Number of GPIOs exceeds the maximum of %d: %d\n",

> > -                       MAX_NR_SGPIO, nr_gpios);

> > +                       MAX_NR_HW_SGPIO, nr_gpios);

> >                 return -EINVAL;

> >         }

> > +       gpio->n_sgpio = nr_gpios;

> >

> >         rc = of_property_read_u32(pdev->dev.of_node, "bus-frequency", &sgpio_freq);

> >         if (rc < 0) {

> > @@ -497,7 +541,8 @@ static int __init aspeed_sgpio_probe(struct platform_device *pdev)

> >         spin_lock_init(&gpio->lock);

> >

> >         gpio->chip.parent = &pdev->dev;

> > -       gpio->chip.ngpio = nr_gpios;

> > +       gpio->chip.ngpio = MAX_NR_HW_SGPIO * 2;

> > +       gpio->chip.init_valid_mask = aspeed_sgpio_init_valid_mask;

> >         gpio->chip.direction_input = aspeed_sgpio_dir_in;

> >         gpio->chip.direction_output = aspeed_sgpio_dir_out;

> >         gpio->chip.get_direction = aspeed_sgpio_get_direction;

> > @@ -509,9 +554,6 @@ static int __init aspeed_sgpio_probe(struct platform_device *pdev)

> >         gpio->chip.label = dev_name(&pdev->dev);

> >         gpio->chip.base = -1;

> >

> > -       /* set all SGPIO pins as input (1). */

> > -       memset(gpio->dir_in, 0xff, sizeof(gpio->dir_in));

> > -

> >         aspeed_sgpio_setup_irqs(gpio, pdev);

> >

> >         rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);

> > --

> > 2.28.0

> >
Joel Stanley Sept. 16, 2020, 12:59 p.m. UTC | #2
On Wed, 16 Sep 2020 at 11:09, Bartosz Golaszewski
<bgolaszewski@baylibre.com> wrote:
>

> On Wed, Sep 16, 2020 at 6:51 AM Joel Stanley <joel@jms.id.au> wrote:

> >

> > Hi GPIO maintainers,

> >

> > On Fri, 11 Sep 2020 at 02:20, Joel Stanley <joel@jms.id.au> wrote:

> > >

> > > On Fri, 11 Sep 2020 at 02:11, Jeremy Kerr <jk@codeconstruct.com.au> wrote:

> > > >

> > > > Currently, the aspeed-sgpio driver exposes up to 80 GPIO lines,

> > > > corresponding to the 80 status bits available in hardware. Each of these

> > > > lines can be configured as either an input or an output.

> > > >

> > > > However, each of these GPIOs is actually an input *and* an output; we

> > > > actually have 80 inputs plus 80 outputs.

> > > >

> > > > This change expands the maximum number of GPIOs to 160; the lower half

> > > > of this range are the input-only GPIOs, the upper half are the outputs.

> > > > We fix the GPIO directions to correspond to this mapping.

> > > >

> > > > This also fixes a bug when setting GPIOs - we were reading from the

> > > > input register, making it impossible to set more than one output GPIO.

> > > >

> > > > Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>

> > > > Fixes: 7db47faae79b ("gpio: aspeed: Add SGPIO driver")

> > >

> > > Reviewed-by: Joel Stanley <joel@jms.id.au>

> >

> > This series is good to go in for 5.10:

> >

>

> Hi Joel,

>

> I don't have this in my inbox. Did you copy me on this series?


I did not; I am not the author of the patches as you can see.

I notice that Jeremy sent them to the linux-gpio list, but you were
not copied. Are you able to grab them from lore, or do you need him to
resend them?

Cheers,

Joel
Rob Herring (Arm) Sept. 22, 2020, 10:39 p.m. UTC | #3
On Fri, 11 Sep 2020 09:51:04 +0800, Jeremy Kerr wrote:
> Currently, the aspeed-sgpio driver exposes up to 80 GPIO lines,

> corresponding to the 80 status bits available in hardware. Each of these

> lines can be configured as either an input or an output.

> 

> However, each of these GPIOs is actually an input *and* an output; we

> actually have 80 inputs plus 80 outputs.

> 

> This change expands the maximum number of GPIOs to 160; the lower half

> of this range are the input-only GPIOs, the upper half are the outputs.

> We fix the GPIO directions to correspond to this mapping.

> 

> This also fixes a bug when setting GPIOs - we were reading from the

> input register, making it impossible to set more than one output GPIO.

> 

> Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>

> Fixes: 7db47faae79b ("gpio: aspeed: Add SGPIO driver")

> 

> ---

> v2:

>  - Fix warnings from kbuild test robot

>  - Add comment for input/output GPIO numbering

> ---

>  .../devicetree/bindings/gpio/sgpio-aspeed.txt |   5 +-

>  drivers/gpio/gpio-aspeed-sgpio.c              | 126 ++++++++++++------

>  2 files changed, 87 insertions(+), 44 deletions(-)

> 


Acked-by: Rob Herring <robh@kernel.org>
Bartosz Golaszewski Sept. 24, 2020, 1:11 p.m. UTC | #4
On Wed, Sep 16, 2020 at 2:59 PM Joel Stanley <joel@jms.id.au> wrote:
>

> On Wed, 16 Sep 2020 at 11:09, Bartosz Golaszewski

> <bgolaszewski@baylibre.com> wrote:

> >

> > On Wed, Sep 16, 2020 at 6:51 AM Joel Stanley <joel@jms.id.au> wrote:

> > >

> > > Hi GPIO maintainers,

> > >

> > > On Fri, 11 Sep 2020 at 02:20, Joel Stanley <joel@jms.id.au> wrote:

> > > >

> > > > On Fri, 11 Sep 2020 at 02:11, Jeremy Kerr <jk@codeconstruct.com.au> wrote:

> > > > >

> > > > > Currently, the aspeed-sgpio driver exposes up to 80 GPIO lines,

> > > > > corresponding to the 80 status bits available in hardware. Each of these

> > > > > lines can be configured as either an input or an output.

> > > > >

> > > > > However, each of these GPIOs is actually an input *and* an output; we

> > > > > actually have 80 inputs plus 80 outputs.

> > > > >

> > > > > This change expands the maximum number of GPIOs to 160; the lower half

> > > > > of this range are the input-only GPIOs, the upper half are the outputs.

> > > > > We fix the GPIO directions to correspond to this mapping.

> > > > >

> > > > > This also fixes a bug when setting GPIOs - we were reading from the

> > > > > input register, making it impossible to set more than one output GPIO.

> > > > >

> > > > > Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>

> > > > > Fixes: 7db47faae79b ("gpio: aspeed: Add SGPIO driver")

> > > >

> > > > Reviewed-by: Joel Stanley <joel@jms.id.au>

> > >

> > > This series is good to go in for 5.10:

> > >

> >

> > Hi Joel,

> >

> > I don't have this in my inbox. Did you copy me on this series?

>

> I did not; I am not the author of the patches as you can see.

>

> I notice that Jeremy sent them to the linux-gpio list, but you were

> not copied. Are you able to grab them from lore, or do you need him to

> resend them?

>

> Cheers,

>

> Joel


Now queued, thanks.

Bartosz
diff mbox series

Patch

diff --git a/Documentation/devicetree/bindings/gpio/sgpio-aspeed.txt b/Documentation/devicetree/bindings/gpio/sgpio-aspeed.txt
index d4d83916c09d..be329ea4794f 100644
--- a/Documentation/devicetree/bindings/gpio/sgpio-aspeed.txt
+++ b/Documentation/devicetree/bindings/gpio/sgpio-aspeed.txt
@@ -20,8 +20,9 @@  Required properties:
 - gpio-controller : Marks the device node as a GPIO controller
 - interrupts : Interrupt specifier, see interrupt-controller/interrupts.txt
 - interrupt-controller : Mark the GPIO controller as an interrupt-controller
-- ngpios : number of GPIO lines, see gpio.txt
-  (should be multiple of 8, up to 80 pins)
+- ngpios : number of *hardware* GPIO lines, see gpio.txt. This will expose
+  2 software GPIOs per hardware GPIO: one for hardware input, one for hardware
+  output. Up to 80 pins, must be a multiple of 8.
 - clocks : A phandle to the APB clock for SGPM clock division
 - bus-frequency : SGPM CLK frequency
 
diff --git a/drivers/gpio/gpio-aspeed-sgpio.c b/drivers/gpio/gpio-aspeed-sgpio.c
index 3aa45934d60c..a18ca52432e0 100644
--- a/drivers/gpio/gpio-aspeed-sgpio.c
+++ b/drivers/gpio/gpio-aspeed-sgpio.c
@@ -17,7 +17,17 @@ 
 #include <linux/spinlock.h>
 #include <linux/string.h>
 
-#define MAX_NR_SGPIO			80
+/*
+ * MAX_NR_HW_GPIO represents the number of actual hardware-supported GPIOs (ie,
+ * slots within the clocked serial GPIO data). Since each HW GPIO is both an
+ * input and an output, we provide MAX_NR_HW_GPIO * 2 lines on our gpiochip
+ * device.
+ *
+ * We use SGPIO_OUTPUT_OFFSET to define the split between the inputs and
+ * outputs; the inputs start at line 0, the outputs start at OUTPUT_OFFSET.
+ */
+#define MAX_NR_HW_SGPIO			80
+#define SGPIO_OUTPUT_OFFSET		MAX_NR_HW_SGPIO
 
 #define ASPEED_SGPIO_CTRL		0x54
 
@@ -30,8 +40,8 @@  struct aspeed_sgpio {
 	struct clk *pclk;
 	spinlock_t lock;
 	void __iomem *base;
-	uint32_t dir_in[3];
 	int irq;
+	int n_sgpio;
 };
 
 struct aspeed_sgpio_bank {
@@ -111,31 +121,69 @@  static void __iomem *bank_reg(struct aspeed_sgpio *gpio,
 	}
 }
 
-#define GPIO_BANK(x)    ((x) >> 5)
-#define GPIO_OFFSET(x)  ((x) & 0x1f)
+#define GPIO_BANK(x)    ((x % SGPIO_OUTPUT_OFFSET) >> 5)
+#define GPIO_OFFSET(x)  ((x % SGPIO_OUTPUT_OFFSET) & 0x1f)
 #define GPIO_BIT(x)     BIT(GPIO_OFFSET(x))
 
 static const struct aspeed_sgpio_bank *to_bank(unsigned int offset)
 {
-	unsigned int bank = GPIO_BANK(offset);
+	unsigned int bank;
+
+	bank = GPIO_BANK(offset);
 
 	WARN_ON(bank >= ARRAY_SIZE(aspeed_sgpio_banks));
 	return &aspeed_sgpio_banks[bank];
 }
 
+static int aspeed_sgpio_init_valid_mask(struct gpio_chip *gc,
+		unsigned long *valid_mask, unsigned int ngpios)
+{
+	struct aspeed_sgpio *sgpio = gpiochip_get_data(gc);
+	int n = sgpio->n_sgpio;
+	int c = SGPIO_OUTPUT_OFFSET - n;
+
+	WARN_ON(ngpios < MAX_NR_HW_SGPIO * 2);
+
+	/* input GPIOs in the lower range */
+	bitmap_set(valid_mask, 0, n);
+	bitmap_clear(valid_mask, n, c);
+
+	/* output GPIOS above SGPIO_OUTPUT_OFFSET */
+	bitmap_set(valid_mask, SGPIO_OUTPUT_OFFSET, n);
+	bitmap_clear(valid_mask, SGPIO_OUTPUT_OFFSET + n, c);
+
+	return 0;
+}
+
+static void aspeed_sgpio_irq_init_valid_mask(struct gpio_chip *gc,
+		unsigned long *valid_mask, unsigned int ngpios)
+{
+	struct aspeed_sgpio *sgpio = gpiochip_get_data(gc);
+	int n = sgpio->n_sgpio;
+
+	WARN_ON(ngpios < MAX_NR_HW_SGPIO * 2);
+
+	/* input GPIOs in the lower range */
+	bitmap_set(valid_mask, 0, n);
+	bitmap_clear(valid_mask, n, ngpios - n);
+}
+
+static bool aspeed_sgpio_is_input(unsigned int offset)
+{
+	return offset < SGPIO_OUTPUT_OFFSET;
+}
+
 static int aspeed_sgpio_get(struct gpio_chip *gc, unsigned int offset)
 {
 	struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
 	const struct aspeed_sgpio_bank *bank = to_bank(offset);
 	unsigned long flags;
 	enum aspeed_sgpio_reg reg;
-	bool is_input;
 	int rc = 0;
 
 	spin_lock_irqsave(&gpio->lock, flags);
 
-	is_input = gpio->dir_in[GPIO_BANK(offset)] & GPIO_BIT(offset);
-	reg = is_input ? reg_val : reg_rdata;
+	reg = aspeed_sgpio_is_input(offset) ? reg_val : reg_rdata;
 	rc = !!(ioread32(bank_reg(gpio, bank, reg)) & GPIO_BIT(offset));
 
 	spin_unlock_irqrestore(&gpio->lock, flags);
@@ -143,22 +191,31 @@  static int aspeed_sgpio_get(struct gpio_chip *gc, unsigned int offset)
 	return rc;
 }
 
-static void sgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val)
+static int sgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val)
 {
 	struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
 	const struct aspeed_sgpio_bank *bank = to_bank(offset);
-	void __iomem *addr;
+	void __iomem *addr_r, *addr_w;
 	u32 reg = 0;
 
-	addr = bank_reg(gpio, bank, reg_val);
-	reg = ioread32(addr);
+	if (aspeed_sgpio_is_input(offset))
+		return -EINVAL;
+
+	/* Since this is an output, read the cached value from rdata, then
+	 * update val. */
+	addr_r = bank_reg(gpio, bank, reg_rdata);
+	addr_w = bank_reg(gpio, bank, reg_val);
+
+	reg = ioread32(addr_r);
 
 	if (val)
 		reg |= GPIO_BIT(offset);
 	else
 		reg &= ~GPIO_BIT(offset);
 
-	iowrite32(reg, addr);
+	iowrite32(reg, addr_w);
+
+	return 0;
 }
 
 static void aspeed_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val)
@@ -175,43 +232,28 @@  static void aspeed_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val)
 
 static int aspeed_sgpio_dir_in(struct gpio_chip *gc, unsigned int offset)
 {
-	struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
-	unsigned long flags;
-
-	spin_lock_irqsave(&gpio->lock, flags);
-	gpio->dir_in[GPIO_BANK(offset)] |= GPIO_BIT(offset);
-	spin_unlock_irqrestore(&gpio->lock, flags);
-
-	return 0;
+	return aspeed_sgpio_is_input(offset) ? 0 : -EINVAL;
 }
 
 static int aspeed_sgpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
 {
 	struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
 	unsigned long flags;
+	int rc;
 
-	spin_lock_irqsave(&gpio->lock, flags);
-
-	gpio->dir_in[GPIO_BANK(offset)] &= ~GPIO_BIT(offset);
-	sgpio_set_value(gc, offset, val);
+	/* No special action is required for setting the direction; we'll
+	 * error-out in sgpio_set_value if this isn't an output GPIO */
 
+	spin_lock_irqsave(&gpio->lock, flags);
+	rc = sgpio_set_value(gc, offset, val);
 	spin_unlock_irqrestore(&gpio->lock, flags);
 
-	return 0;
+	return rc;
 }
 
 static int aspeed_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset)
 {
-	int dir_status;
-	struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
-	unsigned long flags;
-
-	spin_lock_irqsave(&gpio->lock, flags);
-	dir_status = gpio->dir_in[GPIO_BANK(offset)] & GPIO_BIT(offset);
-	spin_unlock_irqrestore(&gpio->lock, flags);
-
-	return dir_status;
-
+	return !!aspeed_sgpio_is_input(offset);
 }
 
 static void irqd_to_aspeed_sgpio_data(struct irq_data *d,
@@ -402,6 +444,7 @@  static int aspeed_sgpio_setup_irqs(struct aspeed_sgpio *gpio,
 
 	irq = &gpio->chip.irq;
 	irq->chip = &aspeed_sgpio_irqchip;
+	irq->init_valid_mask = aspeed_sgpio_irq_init_valid_mask;
 	irq->handler = handle_bad_irq;
 	irq->default_type = IRQ_TYPE_NONE;
 	irq->parent_handler = aspeed_sgpio_irq_handler;
@@ -452,11 +495,12 @@  static int __init aspeed_sgpio_probe(struct platform_device *pdev)
 	if (rc < 0) {
 		dev_err(&pdev->dev, "Could not read ngpios property\n");
 		return -EINVAL;
-	} else if (nr_gpios > MAX_NR_SGPIO) {
+	} else if (nr_gpios > MAX_NR_HW_SGPIO) {
 		dev_err(&pdev->dev, "Number of GPIOs exceeds the maximum of %d: %d\n",
-			MAX_NR_SGPIO, nr_gpios);
+			MAX_NR_HW_SGPIO, nr_gpios);
 		return -EINVAL;
 	}
+	gpio->n_sgpio = nr_gpios;
 
 	rc = of_property_read_u32(pdev->dev.of_node, "bus-frequency", &sgpio_freq);
 	if (rc < 0) {
@@ -497,7 +541,8 @@  static int __init aspeed_sgpio_probe(struct platform_device *pdev)
 	spin_lock_init(&gpio->lock);
 
 	gpio->chip.parent = &pdev->dev;
-	gpio->chip.ngpio = nr_gpios;
+	gpio->chip.ngpio = MAX_NR_HW_SGPIO * 2;
+	gpio->chip.init_valid_mask = aspeed_sgpio_init_valid_mask;
 	gpio->chip.direction_input = aspeed_sgpio_dir_in;
 	gpio->chip.direction_output = aspeed_sgpio_dir_out;
 	gpio->chip.get_direction = aspeed_sgpio_get_direction;
@@ -509,9 +554,6 @@  static int __init aspeed_sgpio_probe(struct platform_device *pdev)
 	gpio->chip.label = dev_name(&pdev->dev);
 	gpio->chip.base = -1;
 
-	/* set all SGPIO pins as input (1). */
-	memset(gpio->dir_in, 0xff, sizeof(gpio->dir_in));
-
 	aspeed_sgpio_setup_irqs(gpio, pdev);
 
 	rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);