diff options
| author | Serge Semin <fancer.lancer@gmail.com> | 2025-09-12 13:33:57 +0300 |
|---|---|---|
| committer | Serge Semin <fancer.lancer@gmail.com> | 2026-08-13 20:36:54 +0300 |
| commit | a8c19a421893fca887060cd525f3e86371fd1801 (patch) | |
| tree | d3a568fb3fb515014b520e89ed918b66129865e4 | |
| parent | 8d149a427127d12b7ed259c824641968e705b231 (diff) | |
| download | linux-a8c19a421893fca887060cd525f3e86371fd1801.tar.gz linux-a8c19a421893fca887060cd525f3e86371fd1801.zip | |
net: stmmac: Add GPIO-chip driver sub-module
Synopsys DesignWare Ethernet controllers can be synthesized with
General-Purpose IOs support. GPIOs are divided into two ports: gpi_i -
inputs and gpo_o - outputs. While the GPIOs semantics is identical on all
DW Ethernet controllers the ports width (number of possible
inputs/outputs) and the configuration registers layout depend on the
IP-core version. For instance, DW GMAC can have from 0 to 4 GPIs and from
0 to 4 GPOs, while DW QoS Ether/xGMAC/etc have a wider ports width up to
16 pins of each one. In the framework of provided implementation any
DesignWare Ethernet controllers GPIO block can be supported as soon as the
GPIO registers accessors and the cleanup methods are defined for the
respective IP-core.
Total number of GPIOs MAC supports is supposed to be passed via the
platform descriptor. If it's an OF-based platform, then the standard
"ngpios" DT-property will be parsed for it as well.
Before registering the GPIO-chip in the kernel, the driver will try to
auto-detect the number of GPIs and GPOs by writing 1s into the GPI type
config register. Reading the written value back and calculating the number
of actually set bits will give the GPI port width the device has been
synthesized with.
If GPIs have been detected then GPIO IRQ-chip will be also initialized and
only in that case the GPIO IRQs handling will be activated. Since the
pending events are cleared just by reading from the GPI event status
register, only the edged IRQs type can be implemented. For the same reason
and for the reason of having the rest of GPIO configs reside in the same
CSR, the GPI type, GPI mask and GPO state caches have been added. So the
driver would avoid RMW CSR update code to minimize the pending GPI events
clearing.
Note in case of GPIOs being available the core soft-resetting mustn't be
executed otherwise the GPIO configs will be reset to the initial state. Instead
MAC/DMA stop and CSRs cleanup has been added.
Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
| -rw-r--r-- | Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst | 4 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/Makefile | 2 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/hwif.c | 14 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/stmmac.h | 5 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c | 519 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h | 39 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 9 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 5 | ||||
| -rw-r--r-- | include/linux/stmmac.h | 1 |
10 files changed, 599 insertions, 1 deletions
diff --git a/Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst b/Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst index 5d46e5036129..c0e6f1e7538c 100644 --- a/Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst +++ b/Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst @@ -495,6 +495,10 @@ is used to configure the AMBA bridge to generate more efficient STBus traffic:: int has_xgmac; +37) Total number of supported GPIOs:: + + u32 ngpios; + :: } diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index 07088d03dbab..71c1a21ba969 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -10,6 +10,8 @@ config STMMAC_ETH select PHYLINK select CRC32 select RESET_CONTROLLER + select GPIOLIB + select GPIOLIB_IRQCHIP select NET_DEVLINK help This is the driver for the Ethernet IPs built around a diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index 9f24db4cf7a8..cd3c62b4a964 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -7,7 +7,7 @@ stmmac-objs:= stmmac_main.o stmmac_ethtool.o stmmac_mdio.o \ dwmac4_dma.o dwmac4_lib.o dwmac4_core.o dwmac5.o hwif.o \ stmmac_tc.o dwxgmac2_core.o dwxgmac2_dma.o dwxgmac2_descs.o \ stmmac_xdp.o stmmac_est.o stmmac_fpe.o stmmac_vlan.o \ - stmmac_pcs.o $(stmmac-y) + stmmac_pcs.o stmmac_gpio.o $(stmmac-y) stmmac-$(CONFIG_STMMAC_SELFTESTS) += stmmac_selftests.o diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 7d443c274950..ddf1f201969b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -7,6 +7,7 @@ #include "common.h" #include "stmmac.h" #include "stmmac_fpe.h" +#include "stmmac_gpio.h" #include "stmmac_ptp.h" #include "stmmac_est.h" #include "stmmac_vlan.h" @@ -90,10 +91,23 @@ int stmmac_reset(struct stmmac_priv *priv) { struct plat_stmmacenet_data *plat = priv->plat; void __iomem *ioaddr = priv->ioaddr; + int ret; if (plat && plat->fix_soc_reset) return plat->fix_soc_reset(priv); + if (stmmac_gpio_is_available(priv)) { + ret = stmmac_core_stop(priv, priv->hw); + if (ret) + return ret; + + ret = stmmac_core_clean(priv, priv->hw); + if (ret) + return ret; + + return stmmac_dma_clean(priv, ioaddr); + } + return stmmac_do_callback(priv, dma, reset, ioaddr); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 2053f62c67f2..9ebfd26d44b8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -27,6 +27,8 @@ #include <net/xdp.h> #include <uapi/linux/bpf.h> +#include "stmmac_gpio.h" + struct stmmac_pcs; struct stmmac_resources { @@ -418,6 +420,9 @@ struct stmmac_priv { /* Receive Side Scaling */ struct stmmac_rss rss; + /* General Purpose IO */ + struct stmmac_gpio gpio; + /* XDP BPF Program */ unsigned long *af_xdp_zc_qps; struct bpf_prog *xdp_prog; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c new file mode 100644 index 000000000000..80df614a2662 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * stmmac GPIO handling + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/gpio/driver.h> +#include <linux/irq.h> +#include <linux/spinlock.h> + +#include "common.h" +#include "hwif.h" +#include "stmmac.h" +#include "stmmac_gpio.h" +#include "stmmac_platform.h" + +/* Synopsys DesignWare Ethernet controllers can be synthesized with + * General-Purpose IOs support. GPIOs can work either as inputs or as + * outputs depending on whether they belong to the gpi_i and gpo_o ports + * respectively. The ports width (number of possible inputs/outputs) and + * the configuration registers layout depend on the IP-core version and + * the GIW/GOW synthesize parameters. For instance, DW GMAC can have from + * 0 to 4 GPIs and from 0 to 4 GPOs, while DW QoS/xGMAC have a wider ports + * width up to 16 pins of each one. Here are the DW MAC GPIO block + * implementation specifics: + * + * Note 1. The GPIs state is multiplexed with the edge-triggered interrupt + * status in the GPIO control/status register. That is in case of the falling + * or rising edge event the GPI status register field preserves the latched + * state of the input pin until the next read from the register. So in order to + * read the actual state of the input pin we'd need to read the GPIO status + * register twice. But that also causes the race condition between the ordinary + * GPI state reads and the GPI IRQ handler. In order to fix that the + * IRQ-pending value cache is implemented. + * + * Note 2. The GPIs state configuration fields are mapped either to the + * GPIO control or the GPIO status register, which other than that may + * also provide the settings like GPOs state, GPIs IRQ type/mask/unmask. + * Due to that we have no choice but to cache the registers state and use + * the cached values to update the denoted settings, so to prevent the + * racy reads of the GPIs. + * + * Note 3. Due to the multiplexed GPIs state and interrupt status, the + * driver may experience false repeated IRQs detection. That is if after + * reading the GPI status register and calling the interrupt handler a client + * device doesn't revert the IRQ lane state and some other GPI raises an + * interrupt, the driver will get to detect the previous IRQ again. So the + * GPIO client device must return the GPI state back to idling after the + * IRQ is handled. + * + * Note 4. The GPIOs state is cleared together with the DW *MAC controller + * reset. So if IP-core isn't fixed to prevent that behavior the core + * resets mustn't be performed while the GPIO-chip is being registered in + * the system to have a stable GPIs/GPOs interface. + */ + +/* IRQs are acked in the parental ISR, but the irq_ack() callback is still + * required for the edge-triggered IRQ handling by the IRQ-chip core. + */ +static void stmmac_gpio_irq_ack(struct irq_data *data) +{ +} + +static void stmmac_gpio_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags; + u32 mask; + + mask = ~BIT(irqd_to_hwirq(d)); + + spin_lock_irqsave(&priv->gpio.lock, flags); + + priv->gpio.cache.gpie &= mask; + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + priv->gpio.cache.gpip &= mask; + + spin_unlock_irqrestore(&priv->gpio.lock, flags); +} + +static void stmmac_gpio_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags; + u32 mask; + + mask = BIT(irqd_to_hwirq(d)); + + spin_lock_irqsave(&priv->gpio.lock, flags); + + priv->gpio.cache.gpie |= mask; + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + spin_unlock_irqrestore(&priv->gpio.lock, flags); +} + +static int stmmac_gpio_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct stmmac_priv *priv = gpiochip_get_data(gc); + + return irq_set_irq_wake(priv->dev->irq, on); +} + +static int stmmac_gpio_irq_set_type(struct irq_data *d, u32 type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct stmmac_priv *priv = gpiochip_get_data(gc); + irq_hw_number_t offset = irqd_to_hwirq(d); + unsigned long flags; + int ret = 0; + u32 mask; + + if (offset >= priv->gpio.ngpis) + return -EINVAL; + + mask = BIT(offset); + + spin_lock_irqsave(&priv->gpio.lock, flags); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + priv->gpio.cache.gpit &= ~mask; + break; + case IRQ_TYPE_EDGE_FALLING: + priv->gpio.cache.gpit |= mask; + break; + default: + ret = -EINVAL; + goto err_unlock; + } + + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + priv->gpio.cache.gpip &= ~mask; + +err_unlock: + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + return ret; +} + +static void stmmac_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + + seq_printf(p, dev_name(gc->parent)); +} + +static irqreturn_t stmmac_gpio_interrupt(int irq, void *dev_id) +{ + struct stmmac_gpio *gpio = dev_id; + struct gpio_chip *gc = &gpio->gc; + struct stmmac_priv *priv; + irq_hw_number_t hwirq; + unsigned long status; + + priv = container_of(gpio, struct stmmac_priv, gpio); + + /* Make sure we get to handle only the GPIs with unmasked + * latched-low/-high events and the GPI is latched in accordance with + * the type of the event. If for some reason the IRQ cause hasn't been + * cleared on the previous IRQ handler execution or the client device + * hasn't got the GPI state back, here we'll get a repeated false IRQ. + */ + spin_lock(&gpio->lock); + + status = stmmac_gpio_get_gpis(priv); + status = (status ^ gpio->cache.gpit) | gpio->cache.gpip; + status &= gpio->cache.gpie; + + gpio->cache.gpip = 0; + + spin_unlock(&gpio->lock); + + for_each_set_bit(hwirq, &status, gpio->ngpis) + generic_handle_irq(irq_find_mapping(gc->irq.domain, hwirq)); + + return IRQ_RETVAL(status); +} + +static const struct irq_chip stmmac_gpio_irq_chip = { + .name = "dwmac", + .irq_ack = stmmac_gpio_irq_ack, + .irq_mask = stmmac_gpio_irq_mask, + .irq_unmask = stmmac_gpio_irq_unmask, + .irq_set_wake = stmmac_gpio_irq_set_wake, + .irq_set_type = stmmac_gpio_irq_set_type, + .irq_print_chip = stmmac_gpio_irq_print_chip, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int stmmac_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + + if (offset < priv->gpio.ngpis) + return GPIO_LINE_DIRECTION_IN; + else if (offset < priv->plat->ngpios) + return GPIO_LINE_DIRECTION_OUT; + + return -EINVAL; +} + +static int stmmac_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags; + int ret; + + if (offset >= priv->plat->ngpios) + return -EINVAL; + + spin_lock_irqsave(&priv->gpio.lock, flags); + + if (offset < priv->gpio.ngpis) { + /* Read twice to clear the latched state out and get the + * current input pin state. + */ + ret = stmmac_gpio_get_gpis(priv); + priv->gpio.cache.gpip |= (ret ^ priv->gpio.cache.gpit) & + priv->gpio.cache.gpie; + + ret = stmmac_gpio_get_gpis(priv); + priv->gpio.cache.gpip |= (ret ^ priv->gpio.cache.gpit) & + priv->gpio.cache.gpie; + } else { + ret = priv->gpio.cache.gpo << priv->gpio.ngpis; + } + + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + return !!(ret & BIT(offset)); +} + +static int stmmac_gpio_get_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags, fs, val = 0; + + fs = __ffs(*mask); + + spin_lock_irqsave(&priv->gpio.lock, flags); + + if (fs <= priv->gpio.ngpis) { + /* Read twice to clear the latched state out and get the + * current input pin state. + */ + val = stmmac_gpio_get_gpis(priv); + priv->gpio.cache.gpip |= (val ^ priv->gpio.cache.gpit) & + priv->gpio.cache.gpie; + + val = stmmac_gpio_get_gpis(priv); + priv->gpio.cache.gpip |= (val ^ priv->gpio.cache.gpit) & + priv->gpio.cache.gpie; + } + + val |= (priv->gpio.cache.gpo << priv->gpio.ngpis); + + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + bitmap_replace(bits, bits, &val, mask, priv->plat->ngpios); + + return 0; +} + +static int stmmac_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags; + + if (offset < priv->gpio.ngpis || offset >= priv->plat->ngpios) + return -EINVAL; + + offset -= priv->gpio.ngpis; + + spin_lock_irqsave(&priv->gpio.lock, flags); + + if (value) + priv->gpio.cache.gpo |= BIT(offset); + else + priv->gpio.cache.gpo &= ~BIT(offset); + + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + return 0; +} + +static int stmmac_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags, gpom, gpob; + + gpom = *mask >> priv->gpio.ngpis; + gpob = *bits >> priv->gpio.ngpis; + + spin_lock_irqsave(&priv->gpio.lock, flags); + + priv->gpio.cache.gpo = (priv->gpio.cache.gpo & ~gpom) | gpob; + + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + return 0; +} + +static int stmmac_gpio_set_config(struct gpio_chip *gc, unsigned int offset, + unsigned long cfg) +{ + enum pin_config_param mode = pinconf_to_config_param(cfg); + + /* All output pins operate as open source */ + if (mode != PIN_CONFIG_DRIVE_OPEN_SOURCE) + return -ENOTSUPP; + + return gpiochip_generic_config(gc, offset, cfg); +} + +static int stmmac_gpio_data_init(struct stmmac_priv *priv) +{ + u32 tmp; + + if (priv->plat->ngpios > STMMAC_GPI_MAX) { + dev_err(priv->device, "Invalid ngpios specified\n"); + return -EINVAL; + } + + /* GPIs auto-detection: save GPO state, set as many GPI type bits + * as possible, then read the written value back. The number of set + * bits will be the actual number of GPIs. Leave the GPI type field + * being initialized with ones (i.e. the inputs having latched-low + * type) to have the falling-edge IRQs by default. + */ + priv->gpio.cache.gpit = ~0; + stmmac_gpio_get_ctrl(priv, &tmp, &tmp, &priv->gpio.cache.gpo); + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + stmmac_gpio_get_ctrl(priv, &priv->gpio.cache.gpie, + &priv->gpio.cache.gpit, &priv->gpio.cache.gpo); + + priv->gpio.ngpis = hweight_long(priv->gpio.cache.gpit); + + if (priv->gpio.ngpis > priv->plat->ngpios) { + dev_err(priv->device, "ngpios less than detected\n"); + return -EINVAL; + } + + priv->gpio.ngpos = priv->plat->ngpios - priv->gpio.ngpis; + + spin_lock_init(&priv->gpio.lock); + + dev_info(priv->device, "GPI: %u, GPO: %u\n", priv->gpio.ngpis, + priv->gpio.ngpos); + + return 0; +} + +static void stmmac_gpio_chip_init(struct stmmac_priv *priv) +{ + struct gpio_chip *gc = &priv->gpio.gc; + + gc->label = dev_name(priv->device); + gc->parent = priv->device; + gc->owner = THIS_MODULE; + gc->request = gpiochip_generic_request, + gc->free = gpiochip_generic_free, + gc->get_direction = stmmac_gpio_get_direction; + gc->get = stmmac_gpio_get; + gc->get_multiple = stmmac_gpio_get_multiple; + gc->set = stmmac_gpio_set; + gc->set_multiple = stmmac_gpio_set_multiple; + gc->set_config = stmmac_gpio_set_config; + gc->base = -1; + gc->ngpio = priv->plat->ngpios; + gc->can_sleep = false; +} + +static int stmmac_gpio_irq_init(struct stmmac_priv *priv) +{ + struct gpio_chip *gc = &priv->gpio.gc; + struct gpio_irq_chip *girq = &gc->irq; + int ret; + + /* Skip GPIO IRQ-chip initialization if no GPI detected */ + if (!priv->gpio.ngpis) + return 0; + + /* Make sure no MAC activity enabled before requesting IRQ so not + * to end up with spurious IRQs flood left pending after boot up. + */ + ret = stmmac_core_stop(priv, priv->hw); + if (ret) { + dev_err(priv->device, "Failed to stop MAC before GPIO init\n"); + return ret; + } + + girq->handler = handle_edge_irq; + girq->default_type = IRQ_TYPE_NONE; + girq->num_parents = 0; + girq->parents = NULL; + girq->parent_handler = NULL; + + gpio_irq_chip_set_chip(girq, &stmmac_gpio_irq_chip); + + ret = request_irq(priv->dev->irq, stmmac_gpio_interrupt, + IRQF_SHARED, priv->dev->name, &priv->gpio); + if (ret) { + dev_err(priv->device, "Failed to request GPIO IRQ\n"); + return ret; + } + + return 0; +} + +static void stmmac_gpio_irq_free(struct stmmac_priv *priv) +{ + if (!priv->gpio.ngpis) + return; + + free_irq(priv->dev->irq, &priv->gpio); +} + +static int stmmac_gpio_chip_register(struct stmmac_priv *priv) +{ + struct gpio_chip *gc = &priv->gpio.gc; + int ret; + + ret = gpiochip_add_data(gc, priv); + if (ret) + dev_err(priv->device, "Failed to register GPIO-chip\n"); + + return ret; +} + +static void stmmac_gpio_chip_remove(struct stmmac_priv *priv) +{ + gpiochip_remove(&priv->gpio.gc); +} + +/** + * stmmac_gpio_is_available - test whether DW MAC GPIO is detected + * @priv: driver private structure + * Description: check whether the DW MAC has been synthesized with some + * GPIOs enabled. + * Return: + * true if the DW MAC has GPIO capability, otherwise - false + */ +bool stmmac_gpio_is_available(struct stmmac_priv *priv) +{ + return !!priv->plat->ngpios; +} + +/** + * stmmac_gpio_add - add DW MAC GPIO chip + * @priv: driver private structure + * Description: register GPIO-chip handling the DW *MAC GPIs and GPOs + * Return: + * returns 0 on success, otherwise negative errno. + */ +int stmmac_gpio_setup(struct stmmac_priv *priv) +{ + int ret; + + if (!priv->plat->ngpios) + return 0; + + ret = stmmac_gpio_data_init(priv); + if (ret) + return ret; + + stmmac_gpio_chip_init(priv); + + ret = stmmac_gpio_irq_init(priv); + if (ret) + return ret; + + ret = stmmac_gpio_chip_register(priv); + if (ret) + goto err_irq_free; + + return 0; + +err_irq_free: + stmmac_gpio_irq_free(priv); + + return ret; +} + +/** + * stmmac_gpio_clean - remove DW MAC GPIO-chip + * @priv: driver private structure + * Description: remove GPIO-chip registered for the DW *MAC GPIs and GPOs + */ +void stmmac_gpio_clean(struct stmmac_priv *priv) +{ + if (!priv->plat->ngpios) + return; + + stmmac_gpio_chip_remove(priv); + + stmmac_gpio_irq_free(priv); +} diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h new file mode 100644 index 000000000000..17888e0b64d5 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * stmmac GPIO handling + */ + +#ifndef __STMMAC_GPIO_H__ +#define __STMMAC_GPIO_H__ + +#include <linux/container_of.h> +#include <linux/gpio/driver.h> +#include <linux/spinlock.h> + +#include "stmmac.h" + +#define STMMAC_GPI_MAX 16 +#define STMMAC_GPO_MAX 16 +#define STMMAC_GPIO_MAX (STMMAC_GPI_MAX + STMMAC_GPO_MAX) + +struct stmmac_gpio { + struct gpio_chip gc; + + u32 ngpis; + u32 ngpos; + + struct { + u32 gpie; + u32 gpit; + u32 gpip; + u32 gpo; + } cache; + spinlock_t lock; +}; + +bool stmmac_gpio_is_available(struct stmmac_priv *priv); +int stmmac_gpio_setup(struct stmmac_priv *priv); +void stmmac_gpio_clean(struct stmmac_priv *priv); + +#endif /* __STMMAC_GPIO_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index e0d94e7d7a00..fd8bffe9e7d1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -49,6 +49,7 @@ #include "stmmac_ptp.h" #include "stmmac_fpe.h" #include "stmmac.h" +#include "stmmac_gpio.h" #include "stmmac_pcs.h" #include "stmmac_xdp.h" #include <linux/reset.h> @@ -8852,6 +8853,10 @@ static int __stmmac_dvr_probe(struct device *device, if (!pm_runtime_enabled(device)) pm_runtime_enable(device); + ret = stmmac_gpio_setup(priv); + if (ret) + goto error_gpio_setup; + ret = stmmac_mdio_register(ndev); if (ret < 0) { dev_err_probe(priv->device, ret, @@ -8904,6 +8909,8 @@ error_phy_setup: error_pcs_setup: stmmac_mdio_unregister(ndev); error_mdio_register: + stmmac_gpio_clean(priv); +error_gpio_setup: pm_runtime_put_noidle(device); pm_runtime_disable(device); stmmac_napi_del(ndev); @@ -8975,6 +8982,8 @@ void stmmac_dvr_remove(struct device *dev) stmmac_pcs_clean(ndev); stmmac_mdio_unregister(ndev); + stmmac_gpio_clean(priv); + stmmac_state_clear(priv); if (priv->plat->stmmac_rst) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index 03f1498eca3b..b02a557c67e5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -492,6 +492,11 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) goto error_put_phy; } + /* Retrieve total number of supported GPIOs from the STMMAC DT-node. + * Amount of GPIs and GPOs will be auto-detected by the driver later. + */ + of_property_read_u32(np, "ngpios", &plat->ngpios); + of_property_read_u32(np, "tx-fifo-depth", &plat->tx_fifo_size); of_property_read_u32(np, "rx-fifo-depth", &plat->rx_fifo_size); diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index d9c9ef8a933f..4e53b946d63e 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -226,6 +226,7 @@ struct plat_stmmacenet_data { struct device_node *mdio_node; struct stmmac_dma_cfg *dma_cfg; struct stmmac_safety_feature_cfg *safety_feat_cfg; + u32 ngpios; int clk_csr; int enh_desc; int tx_coe; |
