From bef6959a3746fc8207a0ca75e239c95d7409fd90 Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Tue, 8 Oct 2024 16:14:49 +0800 Subject: [PATCH 01/16] dt-bindings: gpio: aspeed,ast2400-gpio: Support ast2700 The AST2700 is the 7th generation SoC from Aspeed, featuring two GPIO controllers: one with 12 GPIO pins and another with 216 GPIO pins. Acked-by: Conor Dooley Signed-off-by: Billy Tsai Link: https://lore.kernel.org/r/20241008081450.1490955-7-billy_tsai@aspeedtech.com Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/aspeed,ast2400-gpio.yaml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/gpio/aspeed,ast2400-gpio.yaml b/Documentation/devicetree/bindings/gpio/aspeed,ast2400-gpio.yaml index cf11aa7ec8c7..b9afd07a9d24 100644 --- a/Documentation/devicetree/bindings/gpio/aspeed,ast2400-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/aspeed,ast2400-gpio.yaml @@ -15,6 +15,7 @@ properties: - aspeed,ast2400-gpio - aspeed,ast2500-gpio - aspeed,ast2600-gpio + - aspeed,ast2700-gpio reg: maxItems: 1 @@ -25,7 +26,7 @@ properties: gpio-controller: true gpio-line-names: - minItems: 36 + minItems: 12 maxItems: 232 gpio-ranges: true @@ -42,7 +43,7 @@ properties: const: 2 ngpios: - minimum: 36 + minimum: 12 maximum: 232 required: @@ -93,6 +94,20 @@ allOf: enum: [ 36, 208 ] required: - ngpios + - if: + properties: + compatible: + contains: + const: aspeed,ast2700-gpio + then: + properties: + gpio-line-names: + minItems: 12 + maxItems: 216 + ngpios: + enum: [ 12, 216 ] + required: + - ngpios additionalProperties: false -- 2.51.0 From b2e861bd1eaf4c5f75139df9b75dade3334a5b6c Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Tue, 8 Oct 2024 16:14:50 +0800 Subject: [PATCH 02/16] gpio: aspeed: Support G7 Aspeed gpio controller In the 7th generation of the SoC from Aspeed, the control logic of the GPIO controller has been updated to support per-pin control. Each pin now has its own 32-bit register, allowing for individual control of the pin's value, direction, interrupt type, and other settings. The permission for coprocessor access is supported by the hardware but hasn't been implemented in the current patch. Signed-off-by: Billy Tsai Reviewed-by: Andrew Jeffery Link: https://lore.kernel.org/r/20241008081450.1490955-8-billy_tsai@aspeedtech.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aspeed.c | 147 +++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c index 5d583cc9cbc7..208f95fb585e 100644 --- a/drivers/gpio/gpio-aspeed.c +++ b/drivers/gpio/gpio-aspeed.c @@ -30,6 +30,27 @@ #include #include "gpiolib.h" +/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */ +#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1)) +#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask)) + +#define GPIO_G7_IRQ_STS_BASE 0x100 +#define GPIO_G7_IRQ_STS_OFFSET(x) (GPIO_G7_IRQ_STS_BASE + (x) * 0x4) +#define GPIO_G7_CTRL_REG_BASE 0x180 +#define GPIO_G7_CTRL_REG_OFFSET(x) (GPIO_G7_CTRL_REG_BASE + (x) * 0x4) +#define GPIO_G7_CTRL_OUT_DATA BIT(0) +#define GPIO_G7_CTRL_DIR BIT(1) +#define GPIO_G7_CTRL_IRQ_EN BIT(2) +#define GPIO_G7_CTRL_IRQ_TYPE0 BIT(3) +#define GPIO_G7_CTRL_IRQ_TYPE1 BIT(4) +#define GPIO_G7_CTRL_IRQ_TYPE2 BIT(5) +#define GPIO_G7_CTRL_RST_TOLERANCE BIT(6) +#define GPIO_G7_CTRL_DEBOUNCE_SEL1 BIT(7) +#define GPIO_G7_CTRL_DEBOUNCE_SEL2 BIT(8) +#define GPIO_G7_CTRL_INPUT_MASK BIT(9) +#define GPIO_G7_CTRL_IRQ_STS BIT(12) +#define GPIO_G7_CTRL_IN_DATA BIT(13) + struct aspeed_bank_props { unsigned int bank; u32 input; @@ -95,6 +116,22 @@ struct aspeed_gpio_bank { */ static const int debounce_timers[4] = { 0x00, 0x50, 0x54, 0x58 }; +static const int g7_debounce_timers[4] = { 0x00, 0x00, 0x04, 0x08 }; + +/* + * The debounce timers array is used to configure the debounce timer settings.Here’s how it works: + * Array Value: Indicates the offset for configuring the debounce timer. + * Array Index: Corresponds to the debounce setting register. + * The debounce timers array follows this pattern for configuring the debounce setting registers: + * Array Index 0: No debounce timer is set; + * Array Value is irrelevant (don’t care). + * Array Index 1: Debounce setting #2 is set to 1, and debounce setting #1 is set to 0. + * Array Value: offset for configuring debounce timer 0 (g4: 0x50, g7: 0x00) + * Array Index 2: Debounce setting #2 is set to 0, and debounce setting #1 is set to 1. + * Array Value: offset for configuring debounce timer 1 (g4: 0x54, g7: 0x04) + * Array Index 3: Debounce setting #2 is set to 1, and debounce setting #1 is set to 1. + * Array Value: offset for configuring debounce timer 2 (g4: 0x58, g7: 0x8) + */ static const struct aspeed_gpio_copro_ops *copro_ops; static void *copro_data; @@ -250,6 +287,39 @@ static void __iomem *aspeed_gpio_g4_bank_reg(struct aspeed_gpio *gpio, BUG(); } +static u32 aspeed_gpio_g7_reg_mask(const enum aspeed_gpio_reg reg) +{ + switch (reg) { + case reg_val: + return GPIO_G7_CTRL_OUT_DATA; + case reg_dir: + return GPIO_G7_CTRL_DIR; + case reg_irq_enable: + return GPIO_G7_CTRL_IRQ_EN; + case reg_irq_type0: + return GPIO_G7_CTRL_IRQ_TYPE0; + case reg_irq_type1: + return GPIO_G7_CTRL_IRQ_TYPE1; + case reg_irq_type2: + return GPIO_G7_CTRL_IRQ_TYPE2; + case reg_tolerance: + return GPIO_G7_CTRL_RST_TOLERANCE; + case reg_debounce_sel1: + return GPIO_G7_CTRL_DEBOUNCE_SEL1; + case reg_debounce_sel2: + return GPIO_G7_CTRL_DEBOUNCE_SEL2; + case reg_rdata: + return GPIO_G7_CTRL_OUT_DATA; + case reg_irq_status: + return GPIO_G7_CTRL_IRQ_STS; + case reg_cmdsrc0: + case reg_cmdsrc1: + default: + WARN_ON_ONCE(1); + return 0; + } +} + #define GPIO_BANK(x) ((x) >> 5) #define GPIO_OFFSET(x) ((x) & 0x1f) #define GPIO_BIT(x) BIT(GPIO_OFFSET(x)) @@ -1106,6 +1176,59 @@ static const struct aspeed_gpio_llops aspeed_g4_llops = { .copro_request = aspeed_g4_copro_request, .copro_release = aspeed_g4_copro_release, }; + +static void aspeed_g7_reg_bit_set(struct aspeed_gpio *gpio, unsigned int offset, + const enum aspeed_gpio_reg reg, bool val) +{ + u32 mask = aspeed_gpio_g7_reg_mask(reg); + void __iomem *addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset); + u32 write_val; + + if (mask) { + write_val = (ioread32(addr) & ~(mask)) | field_prep(mask, val); + iowrite32(write_val, addr); + } +} + +static bool aspeed_g7_reg_bit_get(struct aspeed_gpio *gpio, unsigned int offset, + const enum aspeed_gpio_reg reg) +{ + u32 mask = aspeed_gpio_g7_reg_mask(reg); + void __iomem *addr; + + addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset); + if (reg == reg_val) + mask = GPIO_G7_CTRL_IN_DATA; + + if (mask) + return field_get(mask, ioread32(addr)); + else + return 0; +} + +static int aspeed_g7_reg_bank_get(struct aspeed_gpio *gpio, unsigned int offset, + const enum aspeed_gpio_reg reg) +{ + void __iomem *addr; + + if (reg == reg_irq_status) { + addr = gpio->base + GPIO_G7_IRQ_STS_OFFSET(offset >> 5); + return ioread32(addr); + } else { + return -EOPNOTSUPP; + } +} + +static const struct aspeed_gpio_llops aspeed_g7_llops = { + .reg_bit_set = aspeed_g7_reg_bit_set, + .reg_bit_get = aspeed_g7_reg_bit_get, + .reg_bank_get = aspeed_g7_reg_bank_get, + .privilege_ctrl = NULL, + .privilege_init = NULL, + .copro_request = NULL, + .copro_release = NULL, +}; + /* * Any banks not specified in a struct aspeed_bank_props array are assumed to * have the properties: @@ -1173,10 +1296,34 @@ static const struct aspeed_gpio_config ast2600_config = .require_dcache = true, }; +static const struct aspeed_bank_props ast2700_bank_props[] = { + /* input output */ + { 1, 0x0fffffff, 0x0fffffff }, /* E/F/G/H, 4-GPIO hole */ + { 6, 0x00ffffff, 0x00ff0000 }, /* Y/Z/AA */ + {}, +}; + +static const struct aspeed_gpio_config ast2700_config = + /* + * ast2700 has two controllers one with 212 GPIOs and one with 16 GPIOs. + * 216 for simplicity, actual number is 212 (4-GPIO hole in GPIOH) + * We expect ngpio being set in the device tree and this is a fallback + * option. + */ + { + .nr_gpios = 216, + .props = ast2700_bank_props, + .llops = &aspeed_g7_llops, + .debounce_timers_array = g7_debounce_timers, + .debounce_timers_num = ARRAY_SIZE(g7_debounce_timers), + .require_dcache = false, + }; + static const struct of_device_id aspeed_gpio_of_table[] = { { .compatible = "aspeed,ast2400-gpio", .data = &ast2400_config, }, { .compatible = "aspeed,ast2500-gpio", .data = &ast2500_config, }, { .compatible = "aspeed,ast2600-gpio", .data = &ast2600_config, }, + { .compatible = "aspeed,ast2700-gpio", .data = &ast2700_config, }, {} }; MODULE_DEVICE_TABLE(of, aspeed_gpio_of_table); -- 2.51.0 From c46a74ff05c0ac76ba11ef21c930c3b447abf31a Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Wed, 9 Oct 2024 09:11:31 -0400 Subject: [PATCH 03/16] gpio: add support for FTDI's MPSSE as GPIO FTDI FT2232H is a USB to GPIO chip. Sealevel produces some devices with this chip. FT2232H presents itself as a composite device with two interfaces (each is an "MPSSE"). Each MPSSE has two banks (high and low) of 8 GPIO each. I believe some MPSSE's have only one bank, but I don't know how to identify them (I don't have any for testing) and as a result are unsupported for the time being. Additionally, this driver provides software polling-based interrupts for edge detection. For the Sealevel device I have to test with, this works well because there is hardware debouncing. From talking to Sealevel's people, this is their preferred way to do edge detection. Signed-off-by: Mary Strodl Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241009131131.1618329-1-mstrodl@csh.rit.edu Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 7 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-mpsse.c | 522 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 530 insertions(+) create mode 100644 drivers/gpio/gpio-mpsse.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index bfa6b5a2c537..3598d5094393 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1853,6 +1853,13 @@ config GPIO_VIPERBOARD River Tech's viperboard.h for detailed meaning of the module parameters. +config GPIO_MPSSE + tristate "FTDI MPSSE GPIO support" + select GPIOLIB_IRQCHIP + help + GPIO driver for FTDI's MPSSE interface. These can do input and + output. Each MPSSE provides 16 IO pins. + endmenu menu "Virtual GPIO drivers" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 3cdfa927ed98..72b79642aa00 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -115,6 +115,7 @@ obj-$(CONFIG_GPIO_MOCKUP) += gpio-mockup.o obj-$(CONFIG_GPIO_MOXTET) += gpio-moxtet.o obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o +obj-$(CONFIG_GPIO_MPSSE) += gpio-mpsse.o obj-$(CONFIG_GPIO_MSC313) += gpio-msc313.o obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c new file mode 100644 index 000000000000..3ab6651d2226 --- /dev/null +++ b/drivers/gpio/gpio-mpsse.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTDI MPSSE GPIO support + * + * Based on code by Anatolij Gustschin + * + * Copyright (C) 2024 Mary Strodl + */ + +#include +#include +#include +#include + +struct mpsse_priv { + struct gpio_chip gpio; + struct usb_device *udev; /* USB device encompassing all MPSSEs */ + struct usb_interface *intf; /* USB interface for this MPSSE */ + u8 intf_id; /* USB interface number for this MPSSE */ + struct work_struct irq_work; /* polling work thread */ + struct mutex irq_mutex; /* lock over irq_data */ + atomic_t irq_type[16]; /* pin -> edge detection type */ + atomic_t irq_enabled; + int id; + + u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */ + u8 gpio_dir[2]; /* Directions for GPIOs [L, H] */ + + u8 *bulk_in_buf; /* Extra recv buffer to grab status bytes */ + + struct usb_endpoint_descriptor *bulk_in; + struct usb_endpoint_descriptor *bulk_out; + + struct mutex io_mutex; /* sync I/O with disconnect */ +}; + +struct bulk_desc { + bool tx; /* direction of bulk transfer */ + u8 *data; /* input (tx) or output (rx) */ + int len; /* Length of `data` if tx, or length of */ + /* Data to read if rx */ + int len_actual; /* Length successfully transferred */ + int timeout; +}; + +static const struct usb_device_id gpio_mpsse_table[] = { + { USB_DEVICE(0x0c52, 0xa064) }, /* SeaLevel Systems, Inc. */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, gpio_mpsse_table); + +static DEFINE_IDA(gpio_mpsse_ida); + +/* MPSSE commands */ +#define SET_BITS_CMD 0x80 +#define GET_BITS_CMD 0x81 + +#define SET_BITMODE_REQUEST 0x0B +#define MODE_MPSSE (2 << 8) +#define MODE_RESET 0 + +/* Arbitrarily decided. This could probably be much less */ +#define MPSSE_WRITE_TIMEOUT 5000 +#define MPSSE_READ_TIMEOUT 5000 + +/* 1 millisecond, also pretty arbitrary */ +#define MPSSE_POLL_INTERVAL 1000 + +static int mpsse_bulk_xfer(struct usb_interface *intf, struct bulk_desc *desc) +{ + struct mpsse_priv *priv = usb_get_intfdata(intf); + struct usb_device *udev = priv->udev; + unsigned int pipe; + int ret; + + if (desc->tx) + pipe = usb_sndbulkpipe(udev, priv->bulk_out->bEndpointAddress); + else + pipe = usb_rcvbulkpipe(udev, priv->bulk_in->bEndpointAddress); + + ret = usb_bulk_msg(udev, pipe, desc->data, desc->len, + &desc->len_actual, desc->timeout); + if (ret) + dev_dbg(&udev->dev, "mpsse: bulk transfer failed: %d\n", ret); + + return ret; +} + +static int mpsse_write(struct usb_interface *intf, + u8 *buf, size_t len) +{ + int ret; + struct bulk_desc desc; + + desc.len_actual = 0; + desc.tx = true; + desc.data = buf; + desc.len = len; + desc.timeout = MPSSE_WRITE_TIMEOUT; + + ret = mpsse_bulk_xfer(intf, &desc); + + return ret; +} + +static int mpsse_read(struct usb_interface *intf, u8 *buf, size_t len) +{ + int ret; + struct bulk_desc desc; + struct mpsse_priv *priv = usb_get_intfdata(intf); + + desc.len_actual = 0; + desc.tx = false; + desc.data = priv->bulk_in_buf; + /* Device sends 2 additional status bytes, read len + 2 */ + desc.len = min_t(size_t, len + 2, usb_endpoint_maxp(priv->bulk_in)); + desc.timeout = MPSSE_READ_TIMEOUT; + + ret = mpsse_bulk_xfer(intf, &desc); + if (ret) + return ret; + + /* Did we get enough data? */ + if (desc.len_actual < desc.len) + return -EIO; + + memcpy(buf, desc.data + 2, desc.len_actual - 2); + + return ret; +} + +static int gpio_mpsse_set_bank(struct mpsse_priv *priv, u8 bank) +{ + int ret; + u8 tx_buf[3] = { + SET_BITS_CMD | (bank << 1), + priv->gpio_outputs[bank], + priv->gpio_dir[bank], + }; + + ret = mpsse_write(priv->intf, tx_buf, 3); + + return ret; +} + +static int gpio_mpsse_get_bank(struct mpsse_priv *priv, u8 bank) +{ + int ret; + u8 buf = GET_BITS_CMD | (bank << 1); + + ret = mpsse_write(priv->intf, &buf, 1); + if (ret) + return ret; + + ret = mpsse_read(priv->intf, &buf, 1); + if (ret) + return ret; + + return buf; +} + +static void gpio_mpsse_set_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + unsigned long i, bank, bank_mask, bank_bits; + int ret; + struct mpsse_priv *priv = gpiochip_get_data(chip); + + guard(mutex)(&priv->io_mutex); + for_each_set_clump8(i, bank_mask, mask, chip->ngpio) { + bank = i / 8; + + if (bank_mask) { + bank_bits = bitmap_get_value8(bits, i); + /* Zero out pins we want to change */ + priv->gpio_outputs[bank] &= ~bank_mask; + /* Set pins we care about */ + priv->gpio_outputs[bank] |= bank_bits & bank_mask; + + ret = gpio_mpsse_set_bank(priv, bank); + if (ret) + dev_err(&priv->intf->dev, + "Couldn't set values for bank %ld!", + bank); + } + } +} + +static int gpio_mpsse_get_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + unsigned long i, bank, bank_mask; + int ret; + struct mpsse_priv *priv = gpiochip_get_data(chip); + + guard(mutex)(&priv->io_mutex); + for_each_set_clump8(i, bank_mask, mask, chip->ngpio) { + bank = i / 8; + + if (bank_mask) { + ret = gpio_mpsse_get_bank(priv, bank); + if (ret < 0) + return ret; + + bitmap_set_value8(bits, ret & bank_mask, i); + } + } + + return 0; +} + +static int gpio_mpsse_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + int err; + unsigned long mask = 0, bits = 0; + + __set_bit(offset, &mask); + err = gpio_mpsse_get_multiple(chip, &mask, &bits); + if (err) + return err; + + /* == is not guaranteed to give 1 if true */ + if (bits) + return 1; + else + return 0; +} + +static void gpio_mpsse_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + unsigned long mask = 0, bits = 0; + + __set_bit(offset, &mask); + if (value) + __set_bit(offset, &bits); + + gpio_mpsse_set_multiple(chip, &mask, &bits); +} + +static int gpio_mpsse_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct mpsse_priv *priv = gpiochip_get_data(chip); + int bank = (offset & 8) >> 3; + int bank_offset = offset & 7; + + scoped_guard(mutex, &priv->io_mutex) + priv->gpio_dir[bank] |= BIT(bank_offset); + + gpio_mpsse_gpio_set(chip, offset, value); + + return 0; +} + +static int gpio_mpsse_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct mpsse_priv *priv = gpiochip_get_data(chip); + int bank = (offset & 8) >> 3; + int bank_offset = offset & 7; + + guard(mutex)(&priv->io_mutex); + priv->gpio_dir[bank] &= ~BIT(bank_offset); + gpio_mpsse_set_bank(priv, bank); + + return 0; +} + +static int gpio_mpsse_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + int ret; + int bank = (offset & 8) >> 3; + int bank_offset = offset & 7; + struct mpsse_priv *priv = gpiochip_get_data(chip); + + guard(mutex)(&priv->io_mutex); + /* MPSSE directions are inverted */ + if (priv->gpio_dir[bank] & BIT(bank_offset)) + ret = GPIO_LINE_DIRECTION_OUT; + else + ret = GPIO_LINE_DIRECTION_IN; + + return ret; +} + +static void gpio_mpsse_poll(struct work_struct *work) +{ + unsigned long pin_mask, pin_states, flags; + int irq_enabled, offset, err, value, fire_irq, + irq, old_value[16], irq_type[16]; + struct mpsse_priv *priv = container_of(work, struct mpsse_priv, + irq_work); + + for (offset = 0; offset < priv->gpio.ngpio; ++offset) + old_value[offset] = -1; + + while ((irq_enabled = atomic_read(&priv->irq_enabled))) { + usleep_range(MPSSE_POLL_INTERVAL, MPSSE_POLL_INTERVAL + 1000); + /* Cleanup will trigger at the end of the loop */ + guard(mutex)(&priv->irq_mutex); + + pin_mask = 0; + pin_states = 0; + for (offset = 0; offset < priv->gpio.ngpio; ++offset) { + irq_type[offset] = atomic_read(&priv->irq_type[offset]); + if (irq_type[offset] != IRQ_TYPE_NONE && + irq_enabled & BIT(offset)) + pin_mask |= BIT(offset); + else + old_value[offset] = -1; + } + + err = gpio_mpsse_get_multiple(&priv->gpio, &pin_mask, + &pin_states); + if (err) { + dev_err_ratelimited(&priv->intf->dev, + "Error polling!\n"); + continue; + } + + /* Check each value */ + for (offset = 0; offset < priv->gpio.ngpio; ++offset) { + if (old_value[offset] == -1) + continue; + + fire_irq = 0; + value = pin_states & BIT(offset); + + switch (irq_type[offset]) { + case IRQ_TYPE_EDGE_RISING: + fire_irq = value > old_value[offset]; + break; + case IRQ_TYPE_EDGE_FALLING: + fire_irq = value < old_value[offset]; + break; + case IRQ_TYPE_EDGE_BOTH: + fire_irq = value != old_value[offset]; + break; + } + if (!fire_irq) + continue; + + irq = irq_find_mapping(priv->gpio.irq.domain, + offset); + local_irq_save(flags); + generic_handle_irq(irq); + local_irq_disable(); + local_irq_restore(flags); + } + + /* Sync back values so we can refer to them next tick */ + for (offset = 0; offset < priv->gpio.ngpio; ++offset) + if (irq_type[offset] != IRQ_TYPE_NONE && + irq_enabled & BIT(offset)) + old_value[offset] = pin_states & BIT(offset); + } +} + +static int gpio_mpsse_set_irq_type(struct irq_data *irqd, unsigned int type) +{ + int offset; + struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd); + + offset = irqd->hwirq; + atomic_set(&priv->irq_type[offset], type & IRQ_TYPE_EDGE_BOTH); + + return 0; +} + +static void gpio_mpsse_irq_disable(struct irq_data *irqd) +{ + struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd); + + atomic_and(~BIT(irqd->hwirq), &priv->irq_enabled); + gpiochip_disable_irq(&priv->gpio, irqd->hwirq); +} + +static void gpio_mpsse_irq_enable(struct irq_data *irqd) +{ + struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd); + + gpiochip_enable_irq(&priv->gpio, irqd->hwirq); + /* If no-one else was using the IRQ, enable it */ + if (!atomic_fetch_or(BIT(irqd->hwirq), &priv->irq_enabled)) { + INIT_WORK(&priv->irq_work, gpio_mpsse_poll); + schedule_work(&priv->irq_work); + } +} + +static const struct irq_chip gpio_mpsse_irq_chip = { + .name = "gpio-mpsse-irq", + .irq_enable = gpio_mpsse_irq_enable, + .irq_disable = gpio_mpsse_irq_disable, + .irq_set_type = gpio_mpsse_set_irq_type, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static void gpio_mpsse_ida_remove(void *data) +{ + struct mpsse_priv *priv = data; + + ida_simple_remove(&gpio_mpsse_ida, priv->id); +} + +static int gpio_mpsse_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct mpsse_priv *priv; + struct device *dev; + int err; + + dev = &interface->dev; + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->udev = usb_get_dev(interface_to_usbdev(interface)); + priv->intf = interface; + priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber; + + priv->id = ida_simple_get(&gpio_mpsse_ida, 0, 0, GFP_KERNEL); + if (priv->id < 0) + return priv->id; + + err = devm_add_action_or_reset(dev, gpio_mpsse_ida_remove, priv); + if (err) + return err; + + devm_mutex_init(dev, &priv->io_mutex); + devm_mutex_init(dev, &priv->irq_mutex); + + priv->gpio.label = devm_kasprintf(dev, GFP_KERNEL, + "gpio-mpsse.%d.%d", + priv->id, priv->intf_id); + if (!priv->gpio.label) + return -ENOMEM; + + priv->gpio.owner = THIS_MODULE; + priv->gpio.parent = interface->usb_dev; + priv->gpio.get_direction = gpio_mpsse_get_direction; + priv->gpio.direction_input = gpio_mpsse_direction_input; + priv->gpio.direction_output = gpio_mpsse_direction_output; + priv->gpio.get = gpio_mpsse_gpio_get; + priv->gpio.set = gpio_mpsse_gpio_set; + priv->gpio.get_multiple = gpio_mpsse_get_multiple; + priv->gpio.set_multiple = gpio_mpsse_set_multiple; + priv->gpio.base = -1; + priv->gpio.ngpio = 16; + priv->gpio.offset = priv->intf_id * priv->gpio.ngpio; + priv->gpio.can_sleep = 1; + + err = usb_find_common_endpoints(interface->cur_altsetting, + &priv->bulk_in, &priv->bulk_out, + NULL, NULL); + if (err) + return err; + + priv->bulk_in_buf = devm_kmalloc(dev, usb_endpoint_maxp(priv->bulk_in), + GFP_KERNEL); + if (!priv->bulk_in_buf) + return -ENOMEM; + + usb_set_intfdata(interface, priv); + + /* Reset mode, needed to correctly enter MPSSE mode */ + err = usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0), + SET_BITMODE_REQUEST, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + MODE_RESET, priv->intf_id + 1, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (err) + return err; + + /* Enter MPSSE mode */ + err = usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0), + SET_BITMODE_REQUEST, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + MODE_MPSSE, priv->intf_id + 1, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (err) + return err; + + gpio_irq_chip_set_chip(&priv->gpio.irq, &gpio_mpsse_irq_chip); + + priv->gpio.irq.parent_handler = NULL; + priv->gpio.irq.num_parents = 0; + priv->gpio.irq.parents = NULL; + priv->gpio.irq.default_type = IRQ_TYPE_NONE; + priv->gpio.irq.handler = handle_simple_irq; + + err = devm_gpiochip_add_data(dev, &priv->gpio, priv); + if (err) + return err; + + return 0; +} + +static void gpio_mpsse_disconnect(struct usb_interface *intf) +{ + struct mpsse_priv *priv = usb_get_intfdata(intf); + + priv->intf = NULL; + usb_set_intfdata(intf, NULL); + usb_put_dev(priv->udev); +} + +static struct usb_driver gpio_mpsse_driver = { + .name = "gpio-mpsse", + .probe = gpio_mpsse_probe, + .disconnect = gpio_mpsse_disconnect, + .id_table = gpio_mpsse_table, +}; + +module_usb_driver(gpio_mpsse_driver); + +MODULE_AUTHOR("Mary Strodl "); +MODULE_DESCRIPTION("MPSSE GPIO driver"); +MODULE_LICENSE("GPL"); -- 2.51.0 From 3a5ca23bb65169f2f79727c51a5495a2d7a5166e Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Oct 2024 10:15:55 +0200 Subject: [PATCH 04/16] gpio: mvebu: drop dependency on OF_GPIO MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This driver doesn't really depend on interfaces from OF_GPIO so the Kconfig dependency can be dropped. Suggested-by: Uwe Kleine-König Reviewed-by: Linus Walleij Acked-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20241008081555.23465-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 3598d5094393..ae0dd0e8fc4d 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -488,7 +488,6 @@ config GPIO_MT7621 config GPIO_MVEBU def_bool y depends on PLAT_ORION || ARCH_MVEBU || COMPILE_TEST - depends on OF_GPIO select GENERIC_IRQ_CHIP select REGMAP_MMIO -- 2.51.0 From 16de489e3a3009e027a86858bfd645126d391502 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Oct 2024 12:40:52 +0200 Subject: [PATCH 05/16] gpio: ts4900: use generic device properties There's no reason to use OF-specific variants of property getters. Switch to using the preferred, generic device property helpers. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241007104052.39374-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ts4900.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-ts4900.c b/drivers/gpio/gpio-ts4900.c index 0f6397b77c9d..5c806140fdf0 100644 --- a/drivers/gpio/gpio-ts4900.c +++ b/drivers/gpio/gpio-ts4900.c @@ -8,8 +8,8 @@ #include #include -#include #include +#include #include #define DEFAULT_PIN_NUMBER 32 @@ -142,7 +142,7 @@ static int ts4900_gpio_probe(struct i2c_client *client) u32 ngpio; int ret; - if (of_property_read_u32(client->dev.of_node, "ngpios", &ngpio)) + if (device_property_read_u32(&client->dev, "ngpios", &ngpio)) ngpio = DEFAULT_PIN_NUMBER; priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); @@ -153,7 +153,7 @@ static int ts4900_gpio_probe(struct i2c_client *client) priv->gpio_chip.label = "ts4900-gpio"; priv->gpio_chip.ngpio = ngpio; priv->gpio_chip.parent = &client->dev; - priv->input_bit = (uintptr_t)of_device_get_match_data(&client->dev); + priv->input_bit = (uintptr_t)device_get_match_data(&client->dev); priv->regmap = devm_regmap_init_i2c(client, &ts4900_regmap_config); if (IS_ERR(priv->regmap)) { -- 2.51.0 From 56d6ff4b8faf1c2d5c850ed8b4e5dfa6cd81413b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Oct 2024 12:34:39 +0200 Subject: [PATCH 06/16] gpio: davinci: allow building the module with COMPILE_TEST=y Make it possible to build the module when COMPILE_TEST is enabled for better build coverage. Stop using of_match_ptr() to avoid build warnings. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241007103440.38416-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- drivers/gpio/gpio-davinci.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index ae0dd0e8fc4d..efddc6455315 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -243,7 +243,7 @@ config GPIO_CLPS711X config GPIO_DAVINCI tristate "TI Davinci/Keystone GPIO support" default y if ARCH_DAVINCI - depends on (ARM || ARM64) && (ARCH_DAVINCI || ARCH_KEYSTONE || ARCH_K3) + depends on ((ARM || ARM64) && (ARCH_DAVINCI || ARCH_KEYSTONE || ARCH_K3)) || COMPILE_TEST help Say yes here to enable GPIO support for TI Davinci/Keystone SoCs. diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c index 76b58c70b257..1da1d7c7ac2d 100644 --- a/drivers/gpio/gpio-davinci.c +++ b/drivers/gpio/gpio-davinci.c @@ -662,7 +662,7 @@ static struct platform_driver davinci_gpio_driver = { .driver = { .name = "davinci_gpio", .pm = pm_sleep_ptr(&davinci_gpio_dev_pm_ops), - .of_match_table = of_match_ptr(davinci_gpio_ids), + .of_match_table = davinci_gpio_ids, }, }; -- 2.51.0 From 5dfdcd9e73119dcb60b0299e96d2d84d23c354fd Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Oct 2024 12:34:40 +0200 Subject: [PATCH 07/16] gpio: davinci: use generic device properties OF-specific routines should not be used unless necessary. Generic device properties are preferred so switch to using them in the driver code. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241007103440.38416-2-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-davinci.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c index 1da1d7c7ac2d..8c033e8cf3c9 100644 --- a/drivers/gpio/gpio-davinci.c +++ b/drivers/gpio/gpio-davinci.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -159,14 +158,13 @@ static int davinci_gpio_probe(struct platform_device *pdev) unsigned int ngpio, nbank, nirq, gpio_unbanked; struct davinci_gpio_controller *chips; struct device *dev = &pdev->dev; - struct device_node *dn = dev_of_node(dev); /* * The gpio banks conceptually expose a segmented bitmap, * and "ngpio" is one more than the largest zero-based * bit index that's valid. */ - ret = of_property_read_u32(dn, "ti,ngpio", &ngpio); + ret = device_property_read_u32(dev, "ti,ngpio", &ngpio); if (ret) return dev_err_probe(dev, ret, "Failed to get the number of GPIOs\n"); if (ngpio == 0) @@ -177,8 +175,8 @@ static int davinci_gpio_probe(struct platform_device *pdev) * interrupts is equal to number of gpios else all are banked so * number of interrupts is equal to number of banks(each with 16 gpios) */ - ret = of_property_read_u32(dn, "ti,davinci-gpio-unbanked", - &gpio_unbanked); + ret = device_property_read_u32(dev, "ti,davinci-gpio-unbanked", + &gpio_unbanked); if (ret) return dev_err_probe(dev, ret, "Failed to get the unbanked GPIOs property\n"); -- 2.51.0 From 1396470c11d4f30b4d4f444adf353848b48c74da Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Oct 2024 12:28:59 +0200 Subject: [PATCH 08/16] gpio: eic-sprd: use generic device_get_match_data() There's no need to use the OF-specific variant to get the match data. Switch to using device_get_match_data() instead. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241007102859.35602-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-eic-sprd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-eic-sprd.c b/drivers/gpio/gpio-eic-sprd.c index 2dd0e46c42ad..d4bf8d187e16 100644 --- a/drivers/gpio/gpio-eic-sprd.c +++ b/drivers/gpio/gpio-eic-sprd.c @@ -10,8 +10,8 @@ #include #include #include -#include #include +#include #include /* EIC registers definition */ @@ -617,7 +617,7 @@ static int sprd_eic_probe(struct platform_device *pdev) u16 num_banks = 0; int ret, i; - pdata = of_device_get_match_data(dev); + pdata = device_get_match_data(dev); if (!pdata) { dev_err(dev, "No matching driver data found.\n"); return -EINVAL; -- 2.51.0 From 1b35c124f961b355dafb1906c591191bd0b37417 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Oct 2024 12:25:49 +0200 Subject: [PATCH 09/16] gpio: vf610: use generic device_get_match_data() There's no need to use the OF-specific variant to get the match data. Switch to using device_get_match_data() and with that remove the of.h include. Also remove of_irq.h as none of its interfaces is used here and order the includes in alphabetical order. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241007102549.34926-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-vf610.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index 27eff741fe9a..c4f34a347cb6 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -15,10 +15,9 @@ #include #include #include -#include -#include -#include #include +#include +#include #define VF610_GPIO_PER_PORT 32 @@ -297,7 +296,7 @@ static int vf610_gpio_probe(struct platform_device *pdev) if (!port) return -ENOMEM; - port->sdata = of_device_get_match_data(dev); + port->sdata = device_get_match_data(dev); dual_base = port->sdata->have_dual_base; -- 2.51.0 From 2707a028c9b9c54a6dff22c9dcfebf3083ea095e Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Oct 2024 18:29:09 +0200 Subject: [PATCH 10/16] gpio: mpc8xxx: use a helper variable to store the address of pdev->dev Instead of repeatedly dereferencing pdev, just store the address of the embedded struct device in a local variable and use it instead for improved readability. While at it: rearrange variable declarations. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241009162910.33477-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpc8xxx.c | 48 +++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c index 685ec31db409..30f36f94ba1b 100644 --- a/drivers/gpio/gpio-mpc8xxx.c +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -300,14 +300,15 @@ static const struct of_device_id mpc8xxx_gpio_ids[] = { static int mpc8xxx_probe(struct platform_device *pdev) { + const struct mpc8xxx_gpio_devtype *devtype = NULL; struct device_node *np = pdev->dev.of_node; struct mpc8xxx_gpio_chip *mpc8xxx_gc; - struct gpio_chip *gc; - const struct mpc8xxx_gpio_devtype *devtype = NULL; + struct device *dev = &pdev->dev; struct fwnode_handle *fwnode; + struct gpio_chip *gc; int ret; - mpc8xxx_gc = devm_kzalloc(&pdev->dev, sizeof(*mpc8xxx_gc), GFP_KERNEL); + mpc8xxx_gc = devm_kzalloc(dev, sizeof(*mpc8xxx_gc), GFP_KERNEL); if (!mpc8xxx_gc) return -ENOMEM; @@ -320,32 +321,28 @@ static int mpc8xxx_probe(struct platform_device *pdev) return PTR_ERR(mpc8xxx_gc->regs); gc = &mpc8xxx_gc->gc; - gc->parent = &pdev->dev; - - if (device_property_read_bool(&pdev->dev, "little-endian")) { - ret = bgpio_init(gc, &pdev->dev, 4, - mpc8xxx_gc->regs + GPIO_DAT, - NULL, NULL, - mpc8xxx_gc->regs + GPIO_DIR, NULL, - BGPIOF_BIG_ENDIAN); + gc->parent = dev; + + if (device_property_read_bool(dev, "little-endian")) { + ret = bgpio_init(gc, dev, 4, mpc8xxx_gc->regs + GPIO_DAT, + NULL, NULL, mpc8xxx_gc->regs + GPIO_DIR, + NULL, BGPIOF_BIG_ENDIAN); if (ret) return ret; - dev_dbg(&pdev->dev, "GPIO registers are LITTLE endian\n"); + dev_dbg(dev, "GPIO registers are LITTLE endian\n"); } else { - ret = bgpio_init(gc, &pdev->dev, 4, - mpc8xxx_gc->regs + GPIO_DAT, - NULL, NULL, - mpc8xxx_gc->regs + GPIO_DIR, NULL, - BGPIOF_BIG_ENDIAN + ret = bgpio_init(gc, dev, 4, mpc8xxx_gc->regs + GPIO_DAT, + NULL, NULL, mpc8xxx_gc->regs + GPIO_DIR, + NULL, BGPIOF_BIG_ENDIAN | BGPIOF_BIG_ENDIAN_BYTE_ORDER); if (ret) return ret; - dev_dbg(&pdev->dev, "GPIO registers are BIG endian\n"); + dev_dbg(dev, "GPIO registers are BIG endian\n"); } mpc8xxx_gc->direction_output = gc->direction_output; - devtype = device_get_match_data(&pdev->dev); + devtype = device_get_match_data(dev); if (!devtype) devtype = &mpc8xxx_gpio_devtype_default; @@ -370,7 +367,7 @@ static int mpc8xxx_probe(struct platform_device *pdev) * associated input enable must be set (GPIOxGPIE[IEn]=1) to propagate * the port value to the GPIO Data Register. */ - fwnode = dev_fwnode(&pdev->dev); + fwnode = dev_fwnode(dev); if (of_device_is_compatible(np, "fsl,qoriq-gpio") || of_device_is_compatible(np, "fsl,ls1028a-gpio") || of_device_is_compatible(np, "fsl,ls1088a-gpio") || @@ -381,9 +378,9 @@ static int mpc8xxx_probe(struct platform_device *pdev) gc->read_reg(mpc8xxx_gc->regs + GPIO_DIR); } - ret = devm_gpiochip_add_data(&pdev->dev, gc, mpc8xxx_gc); + ret = devm_gpiochip_add_data(dev, gc, mpc8xxx_gc); if (ret) { - dev_err(&pdev->dev, + dev_err(dev, "GPIO chip registration failed with status %d\n", ret); return ret; } @@ -404,18 +401,17 @@ static int mpc8xxx_probe(struct platform_device *pdev) gc->write_reg(mpc8xxx_gc->regs + GPIO_IER, 0xffffffff); gc->write_reg(mpc8xxx_gc->regs + GPIO_IMR, 0); - ret = devm_request_irq(&pdev->dev, mpc8xxx_gc->irqn, + ret = devm_request_irq(dev, mpc8xxx_gc->irqn, mpc8xxx_gpio_irq_cascade, IRQF_NO_THREAD | IRQF_SHARED, "gpio-cascade", mpc8xxx_gc); if (ret) { - dev_err(&pdev->dev, - "failed to devm_request_irq(%d), ret = %d\n", + dev_err(dev, "failed to devm_request_irq(%d), ret = %d\n", mpc8xxx_gc->irqn, ret); goto err; } - device_init_wakeup(&pdev->dev, true); + device_init_wakeup(dev, true); return 0; err: -- 2.51.0 From a937ee6d7eba055226fba300e17ade6f65de6d93 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Oct 2024 18:29:10 +0200 Subject: [PATCH 11/16] gpio: mpc8xxx: use generic device_is_compatible() This driver doesn't need to include of.h and use OF-specific interfaces. Use generic property helpers instead. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241009162910.33477-2-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpc8xxx.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c index 30f36f94ba1b..2f66e24127f4 100644 --- a/drivers/gpio/gpio-mpc8xxx.c +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -301,7 +300,6 @@ static const struct of_device_id mpc8xxx_gpio_ids[] = { static int mpc8xxx_probe(struct platform_device *pdev) { const struct mpc8xxx_gpio_devtype *devtype = NULL; - struct device_node *np = pdev->dev.of_node; struct mpc8xxx_gpio_chip *mpc8xxx_gc; struct device *dev = &pdev->dev; struct fwnode_handle *fwnode; @@ -368,9 +366,9 @@ static int mpc8xxx_probe(struct platform_device *pdev) * the port value to the GPIO Data Register. */ fwnode = dev_fwnode(dev); - if (of_device_is_compatible(np, "fsl,qoriq-gpio") || - of_device_is_compatible(np, "fsl,ls1028a-gpio") || - of_device_is_compatible(np, "fsl,ls1088a-gpio") || + if (device_is_compatible(dev, "fsl,qoriq-gpio") || + device_is_compatible(dev, "fsl,ls1028a-gpio") || + device_is_compatible(dev, "fsl,ls1088a-gpio") || is_acpi_node(fwnode)) { gc->write_reg(mpc8xxx_gc->regs + GPIO_IBE, 0xffffffff); /* Also, latch state of GPIOs configured as output by bootloader. */ -- 2.51.0 From 476f18c0895df7b281eb84b3e687e6101c844338 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Oct 2024 18:24:16 +0200 Subject: [PATCH 12/16] gpio: ljca: use devm_mutex_init() to simplify the error path and remove() Destroying the mutexes is done at the end of remove() so switching to devres does not constitute a functional change. Use devm_mutex_init() and remove repetitions of mutex_destroy(). Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241008162416.85111-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ljca.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-ljca.c b/drivers/gpio/gpio-ljca.c index dfec9fbfc7a9..d67b912d884d 100644 --- a/drivers/gpio/gpio-ljca.c +++ b/drivers/gpio/gpio-ljca.c @@ -420,8 +420,14 @@ static int ljca_gpio_probe(struct auxiliary_device *auxdev, if (!ljca_gpio->connect_mode) return -ENOMEM; - mutex_init(&ljca_gpio->irq_lock); - mutex_init(&ljca_gpio->trans_lock); + ret = devm_mutex_init(&auxdev->dev, &ljca_gpio->irq_lock); + if (ret) + return ret; + + ret = devm_mutex_init(&auxdev->dev, &ljca_gpio->trans_lock); + if (ret) + return ret; + ljca_gpio->gc.direction_input = ljca_gpio_direction_input; ljca_gpio->gc.direction_output = ljca_gpio_direction_output; ljca_gpio->gc.get_direction = ljca_gpio_get_direction; @@ -453,11 +459,8 @@ static int ljca_gpio_probe(struct auxiliary_device *auxdev, INIT_WORK(&ljca_gpio->work, ljca_gpio_async); ret = gpiochip_add_data(&ljca_gpio->gc, ljca_gpio); - if (ret) { + if (ret) ljca_unregister_event_cb(ljca); - mutex_destroy(&ljca_gpio->irq_lock); - mutex_destroy(&ljca_gpio->trans_lock); - } return ret; } @@ -469,8 +472,6 @@ static void ljca_gpio_remove(struct auxiliary_device *auxdev) gpiochip_remove(&ljca_gpio->gc); ljca_unregister_event_cb(ljca_gpio->ljca); cancel_work_sync(&ljca_gpio->work); - mutex_destroy(&ljca_gpio->irq_lock); - mutex_destroy(&ljca_gpio->trans_lock); } static const struct auxiliary_device_id ljca_gpio_id_table[] = { -- 2.51.0 From b6f7aeaf1bfd8d78d55868f0857e397b06a01777 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 14 Oct 2024 14:18:30 +0200 Subject: [PATCH 13/16] gpio: sysfs: constify gpio class All class functions used here take a const pointer to the class structure. We can constify gpio_class. While at it: remove a stray newline and use a tab in the struct definition for consistency with the line above. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241014121831.106532-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-sysfs.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 20426d6e04d5..0c713baa7784 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -549,12 +549,11 @@ static struct attribute *gpio_class_attrs[] = { }; ATTRIBUTE_GROUPS(gpio_class); -static struct class gpio_class = { +static const struct class gpio_class = { .name = "gpio", - .class_groups = gpio_class_groups, + .class_groups = gpio_class_groups, }; - /** * gpiod_export - export a GPIO through sysfs * @desc: GPIO to make available, already requested -- 2.51.0 From 3dcf0643fa5cd65e667057cfa11d6e54ac36f2ff Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 14 Oct 2024 13:07:07 +0200 Subject: [PATCH 14/16] gpio: fold the Kconfig prompt into the option type for GPIO CDEV The 'if !EXPERT' bit will work fine if it follows the 'bool "foobar"' declaration. No need to have separate entries for it. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241014110707.101320-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index efddc6455315..346bb3c8e34c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -70,8 +70,7 @@ config GPIO_SYSFS ioctl() operations instead. config GPIO_CDEV - bool - prompt "Character device (/dev/gpiochipN) support" if EXPERT + bool "Character device (/dev/gpiochipN) support" if EXPERT default y help Say Y here to add the character device /dev/gpiochipN interface -- 2.51.0 From 9a94580120bff8040e84e2d500f8f462f8704dc5 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 14 Oct 2024 11:29:09 +0200 Subject: [PATCH 15/16] gpio: amdpt: remove remove() Use the managed variant of gpiochip_add_data() and remove the remove() callback. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241014092909.90607-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-amdpt.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-amdpt.c b/drivers/gpio/gpio-amdpt.c index 0a2ea9db4682..b70036587d9c 100644 --- a/drivers/gpio/gpio-amdpt.c +++ b/drivers/gpio/gpio-amdpt.c @@ -106,7 +106,7 @@ static int pt_gpio_probe(struct platform_device *pdev) pt_gpio->gc.free = pt_gpio_free; pt_gpio->gc.ngpio = (uintptr_t)device_get_match_data(dev); - ret = gpiochip_add_data(&pt_gpio->gc, pt_gpio); + ret = devm_gpiochip_add_data(dev, &pt_gpio->gc, pt_gpio); if (ret) { dev_err(dev, "Failed to register GPIO lib\n"); return ret; @@ -122,13 +122,6 @@ static int pt_gpio_probe(struct platform_device *pdev) return ret; } -static void pt_gpio_remove(struct platform_device *pdev) -{ - struct pt_gpio_chip *pt_gpio = platform_get_drvdata(pdev); - - gpiochip_remove(&pt_gpio->gc); -} - static const struct acpi_device_id pt_gpio_acpi_match[] = { { "AMDF030", PT_TOTAL_GPIO }, { "AMDIF030", PT_TOTAL_GPIO }, @@ -143,7 +136,6 @@ static struct platform_driver pt_gpio_driver = { .acpi_match_table = ACPI_PTR(pt_gpio_acpi_match), }, .probe = pt_gpio_probe, - .remove_new = pt_gpio_remove, }; module_platform_driver(pt_gpio_driver); -- 2.51.0 From 3bd13ae04ccc20e3a312596f89a269b8b6416dca Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 14 Oct 2024 11:22:26 +0200 Subject: [PATCH 16/16] gpio: menz127: simplify error path and remove remove() Use devres to drop all goto labels from probe() and remove the driver remove() callback. While at it: drop the unnecessary dev_info() message as not only should the driver be quiet when successful, the message is also wrong: the device was probed at this point, the driver had been registered earlier. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20241014092227.78886-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-menz127.c | 58 +++++++++++++++---------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/drivers/gpio/gpio-menz127.c b/drivers/gpio/gpio-menz127.c index a035a9bcb57c..3ccd2cb35b9c 100644 --- a/drivers/gpio/gpio-menz127.c +++ b/drivers/gpio/gpio-menz127.c @@ -127,6 +127,13 @@ static int men_z127_set_config(struct gpio_chip *gc, unsigned offset, return -ENOTSUPP; } +static void men_z127_release_mem(void *data) +{ + struct resource *res = data; + + mcb_release_mem(res); +} + static int men_z127_probe(struct mcb_device *mdev, const struct mcb_device_id *id) { @@ -140,17 +147,19 @@ static int men_z127_probe(struct mcb_device *mdev, return -ENOMEM; men_z127_gpio->mem = mcb_request_mem(mdev, dev_name(dev)); - if (IS_ERR(men_z127_gpio->mem)) { - dev_err(dev, "failed to request device memory"); - return PTR_ERR(men_z127_gpio->mem); - } + if (IS_ERR(men_z127_gpio->mem)) + return dev_err_probe(dev, PTR_ERR(men_z127_gpio->mem), + "failed to request device memory"); - men_z127_gpio->reg_base = ioremap(men_z127_gpio->mem->start, - resource_size(men_z127_gpio->mem)); - if (men_z127_gpio->reg_base == NULL) { - ret = -ENXIO; - goto err_release; - } + ret = devm_add_action_or_reset(dev, men_z127_release_mem, + men_z127_gpio->mem); + if (ret) + return ret; + + men_z127_gpio->reg_base = devm_ioremap(dev, men_z127_gpio->mem->start, + resource_size(men_z127_gpio->mem)); + if (men_z127_gpio->reg_base == NULL) + return -ENXIO; mcb_set_drvdata(mdev, men_z127_gpio); @@ -161,34 +170,16 @@ static int men_z127_probe(struct mcb_device *mdev, men_z127_gpio->reg_base + MEN_Z127_GPIODR, NULL, 0); if (ret) - goto err_unmap; + return ret; men_z127_gpio->gc.set_config = men_z127_set_config; - ret = gpiochip_add_data(&men_z127_gpio->gc, men_z127_gpio); - if (ret) { - dev_err(dev, "failed to register MEN 16Z127 GPIO controller"); - goto err_unmap; - } - - dev_info(dev, "MEN 16Z127 GPIO driver registered"); + ret = devm_gpiochip_add_data(dev, &men_z127_gpio->gc, men_z127_gpio); + if (ret) + return dev_err_probe(dev, ret, + "failed to register MEN 16Z127 GPIO controller"); return 0; - -err_unmap: - iounmap(men_z127_gpio->reg_base); -err_release: - mcb_release_mem(men_z127_gpio->mem); - return ret; -} - -static void men_z127_remove(struct mcb_device *mdev) -{ - struct men_z127_gpio *men_z127_gpio = mcb_get_drvdata(mdev); - - gpiochip_remove(&men_z127_gpio->gc); - iounmap(men_z127_gpio->reg_base); - mcb_release_mem(men_z127_gpio->mem); } static const struct mcb_device_id men_z127_ids[] = { @@ -202,7 +193,6 @@ static struct mcb_driver men_z127_driver = { .name = "z127-gpio", }, .probe = men_z127_probe, - .remove = men_z127_remove, .id_table = men_z127_ids, }; module_mcb_driver(men_z127_driver); -- 2.51.0