]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[dwgpio] Add driver for the DesignWare GPIO controller coverity_scan
authorMichael Brown <mcb30@ipxe.org>
Tue, 5 Aug 2025 12:55:23 +0000 (13:55 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 5 Aug 2025 13:39:56 +0000 (14:39 +0100)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/Makefile
src/drivers/bus/devtree.c
src/drivers/gpio/dwgpio.c [new file with mode: 0644]
src/drivers/gpio/dwgpio.h [new file with mode: 0644]
src/include/ipxe/devtree.h
src/include/ipxe/errfile.h

index f3d378e058a9ca82a11191c51c3c76e1d221b541..22f413d25cce4e2fb0b7e52b2288b53eb2cd84f2 100644 (file)
@@ -83,6 +83,7 @@ SRCDIRS               += drivers/net/marvell
 SRCDIRS                += drivers/block
 SRCDIRS                += drivers/nvs
 SRCDIRS                += drivers/bitbash
+SRCDIRS                += drivers/gpio
 SRCDIRS                += drivers/infiniband
 SRCDIRS                += drivers/infiniband/mlx_utils_flexboot/src
 SRCDIRS                += drivers/infiniband/mlx_utils/src/public
index cd5bb09a055d870944e0dbef4b3b024013fbcae3..32ab1c2cb99656843d52276f23c51b2ccb25802b 100644 (file)
@@ -40,8 +40,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 static struct dt_driver dt_node_driver __dt_driver;
 
-static void dt_remove_children ( struct dt_device *parent );
-
 /**
  * Map devicetree range
  *
@@ -267,8 +265,7 @@ void dt_remove_node ( struct device *parent ) {
  * @v offset           Starting node offset
  * @ret rc             Return status code
  */
-static int dt_probe_children ( struct dt_device *parent,
-                              unsigned int offset ) {
+int dt_probe_children ( struct dt_device *parent, unsigned int offset ) {
        struct fdt_descriptor desc;
        int depth;
        int rc;
@@ -314,7 +311,7 @@ static int dt_probe_children ( struct dt_device *parent,
  *
  * @v parent           Parent device
  */
-static void dt_remove_children ( struct dt_device *parent ) {
+void dt_remove_children ( struct dt_device *parent ) {
 
        /* Remove all child nodes */
        while ( ! list_empty ( &parent->dev.children ) )
diff --git a/src/drivers/gpio/dwgpio.c b/src/drivers/gpio/dwgpio.c
new file mode 100644 (file)
index 0000000..0b300e0
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ipxe/devtree.h>
+#include <ipxe/fdt.h>
+#include <ipxe/gpio.h>
+#include "dwgpio.h"
+
+/** @file
+ *
+ * Synopsys DesignWare GPIO driver
+ *
+ */
+
+/******************************************************************************
+ *
+ * GPIO port group
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Probe port group
+ *
+ * @v dt               Devicetree device
+ * @v offset           Starting node offset
+ * @ret rc             Return status code
+ */
+static int dwgpio_group_probe ( struct dt_device *dt, unsigned int offset ) {
+       struct dwgpio_group *group;
+       int rc;
+
+       /* Allocate and initialise structure */
+       group = zalloc ( sizeof ( *group ) );
+       if ( ! group ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       dt_set_drvdata ( dt, group );
+
+       /* Map registers */
+       group->base = dt_ioremap ( dt, offset, 0, 0 );
+       if ( ! group->base ) {
+               rc = -ENODEV;
+               goto err_ioremap;
+       }
+
+       /* Get region cell size specification */
+       fdt_reg_cells ( &sysfdt, offset, &group->regs );
+
+       /* Probe child ports */
+       if ( ( rc = dt_probe_children ( dt, offset ) ) != 0 )
+               goto err_children;
+
+       return 0;
+
+       dt_remove_children ( dt );
+ err_children:
+       iounmap ( group->base );
+ err_ioremap:
+       free ( group );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Remove port group
+ *
+ * @v dt               Devicetree device
+ */
+static void dwgpio_group_remove ( struct dt_device *dt ) {
+       struct dwgpio_group *group = dt_get_drvdata ( dt );
+
+       /* Remove child ports */
+       dt_remove_children ( dt );
+
+       /* Unmap registers */
+       iounmap ( group->base );
+
+       /* Free device */
+       free ( group );
+}
+
+/** DesignWare GPIO port group compatible model identifiers */
+static const char * dwgpio_group_ids[] = {
+       "snps,dw-apb-gpio",
+};
+
+/** DesignWare GPIO port group devicetree driver */
+struct dt_driver dwgpio_group_driver __dt_driver = {
+       .name = "dwgpio-group",
+       .ids = dwgpio_group_ids,
+       .id_count = ( sizeof ( dwgpio_group_ids ) /
+                     sizeof ( dwgpio_group_ids[0] ) ),
+       .probe = dwgpio_group_probe,
+       .remove = dwgpio_group_remove,
+};
+
+/******************************************************************************
+ *
+ * GPIO port
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Dump GPIO port status
+ *
+ * @v dwgpio           DesignWare GPIO port
+ */
+static inline void dwgpio_dump ( struct dwgpio *dwgpio ) {
+
+       DBGC2 ( dwgpio, "DWGPIO %s dr %#08x ddr %#08x ctl %#08x\n",
+               dwgpio->name, readl ( dwgpio->swport + DWGPIO_SWPORT_DR ),
+               readl ( dwgpio->swport + DWGPIO_SWPORT_DDR ),
+               readl ( dwgpio->swport + DWGPIO_SWPORT_CTL ) );
+}
+
+/**
+ * Get current GPIO input value
+ *
+ * @v gpios            GPIO controller
+ * @v gpio             GPIO pin
+ * @ret active         Pin is in the active state
+ */
+static int dwgpio_in ( struct gpios *gpios, struct gpio *gpio ) {
+       struct dwgpio *dwgpio = gpios->priv;
+       uint32_t ext;
+
+       /* Read external port status */
+       ext = readl ( dwgpio->ext );
+       return ( ( ( ext >> gpio->index ) ^ gpio->config ) & 1 );
+}
+
+/**
+ * Set current GPIO output value
+ *
+ * @v gpios            GPIO controller
+ * @v gpio             GPIO pin
+ * @v active           Set pin to active state
+ */
+static void dwgpio_out ( struct gpios *gpios, struct gpio *gpio, int active ) {
+       struct dwgpio *dwgpio = gpios->priv;
+       uint32_t mask = ( 1UL << gpio->index );
+       uint32_t dr;
+
+       /* Update data register */
+       dr = readl ( dwgpio->swport + DWGPIO_SWPORT_DR );
+       dr &= ~mask;
+       if ( ( ( !! active ) ^ gpio->config ) & 1 )
+               dr |= mask;
+       writel ( dr, ( dwgpio->swport + DWGPIO_SWPORT_DR ) );
+       dwgpio_dump ( dwgpio );
+}
+
+/**
+ * Configure GPIO pin
+ *
+ * @v gpios            GPIO controller
+ * @v gpio             GPIO pin
+ * @v config           Configuration
+ * @ret rc             Return status code
+ */
+static int dwgpio_config ( struct gpios *gpios, struct gpio *gpio,
+                          unsigned int config ) {
+       struct dwgpio *dwgpio = gpios->priv;
+       uint32_t mask = ( 1UL << gpio->index );
+       uint32_t ddr;
+       uint32_t ctl;
+
+       /* Update data direction and control registers */
+       ddr = readl ( dwgpio->swport + DWGPIO_SWPORT_DDR );
+       ctl = readl ( dwgpio->swport + DWGPIO_SWPORT_CTL );
+       ctl &= ~mask;
+       ddr &= ~mask;
+       if ( config & GPIO_CFG_OUTPUT )
+               ddr |= mask;
+       writel ( ctl, ( dwgpio->swport + DWGPIO_SWPORT_CTL ) );
+       writel ( ddr, ( dwgpio->swport + DWGPIO_SWPORT_DDR ) );
+       dwgpio_dump ( dwgpio );
+
+       return 0;
+}
+
+/** GPIO operations */
+static struct gpio_operations dwgpio_operations = {
+       .in = dwgpio_in,
+       .out = dwgpio_out,
+       .config = dwgpio_config,
+};
+
+/**
+ * Probe port
+ *
+ * @v dt               Devicetree device
+ * @v offset           Starting node offset
+ * @ret rc             Return status code
+ */
+static int dwgpio_probe ( struct dt_device *dt, unsigned int offset ) {
+       struct dt_device *parent;
+       struct dwgpio_group *group;
+       struct dwgpio *dwgpio;
+       struct gpios *gpios;
+       uint32_t count;
+       uint64_t port;
+       int rc;
+
+       /* Get number of GPIOs */
+       if ( ( rc = fdt_u32 ( &sysfdt, offset, "nr-gpios-snps",
+                             &count ) ) != 0 ) {
+               goto err_count;
+       }
+       assert ( count <= DWGPIO_MAX_COUNT );
+
+       /* Allocate and initialise device */
+       gpios = alloc_gpios ( count, sizeof ( *dwgpio ) );
+       if ( ! gpios ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       dt_set_drvdata ( dt, gpios );
+       gpios->dev = &dt->dev;
+       gpios_init ( gpios, &dwgpio_operations );
+       dwgpio = gpios->priv;
+       dwgpio->name = dt->name;
+
+       /* Identify group */
+       parent = dt_parent ( dt );
+       if ( parent->driver != &dwgpio_group_driver ) {
+               DBGC ( dwgpio, "DWGPIO %s has invalid parent %s\n",
+                      dwgpio->name, parent->name );
+               rc = -EINVAL;
+               goto err_parent;
+       }
+       group = dt_get_drvdata ( parent );
+
+       /* Identify port */
+       if ( ( rc = fdt_reg_address ( &sysfdt, offset, &group->regs, 0,
+                                     &port ) ) != 0 ) {
+               DBGC ( dwgpio, "DWGPIO %s could not get port number: %s\n",
+                      dwgpio->name, strerror ( rc ) );
+               goto err_port;
+       }
+       dwgpio->port = port;
+       DBGC ( dwgpio, "DWGPIO %s is %s port %d (%d GPIOs)\n",
+              dwgpio->name, parent->name, dwgpio->port, gpios->count );
+
+       /* Map registers */
+       dwgpio->swport = ( group->base + DWGPIO_SWPORT ( port ) );
+       dwgpio->ext = ( group->base + DWGPIO_EXT_PORT ( port ) );
+       dwgpio_dump ( dwgpio );
+
+       /* Record original register values */
+       dwgpio->dr = readl ( dwgpio->swport + DWGPIO_SWPORT_DR );
+       dwgpio->ddr = readl ( dwgpio->swport + DWGPIO_SWPORT_DDR );
+       dwgpio->ctl = readl ( dwgpio->swport + DWGPIO_SWPORT_CTL );
+
+       /* Register GPIO controller */
+       if ( ( rc = gpios_register ( gpios ) ) != 0 ) {
+               DBGC ( dwgpio, "DWGPIO %s could not register: %s\n",
+                      dwgpio->name, strerror ( rc ) );
+               goto err_register;
+       }
+
+       return 0;
+
+       gpios_unregister ( gpios );
+ err_register:
+ err_port:
+ err_parent:
+       gpios_nullify ( gpios );
+       gpios_put ( gpios );
+ err_alloc:
+ err_count:
+       return rc;
+}
+
+/**
+ * Remove port
+ *
+ * @v dt               Devicetree device
+ */
+static void dwgpio_remove ( struct dt_device *dt ) {
+       struct gpios *gpios = dt_get_drvdata ( dt );
+       struct dwgpio *dwgpio = gpios->priv;
+
+       /* Unregister GPIO controller */
+       gpios_unregister ( gpios );
+
+       /* Restore original register values */
+       writel ( dwgpio->ctl, ( dwgpio->swport + DWGPIO_SWPORT_CTL ) );
+       writel ( dwgpio->ddr, ( dwgpio->swport + DWGPIO_SWPORT_DDR ) );
+       writel ( dwgpio->dr, ( dwgpio->swport + DWGPIO_SWPORT_DR ) );
+
+       /* Free GPIO device */
+       gpios_nullify ( gpios );
+       gpios_put ( gpios );
+}
+
+/** DesignWare GPIO port compatible model identifiers */
+static const char * dwgpio_ids[] = {
+       "snps,dw-apb-gpio-port",
+};
+
+/** DesignWare GPIO port devicetree driver */
+struct dt_driver dwgpio_driver __dt_driver = {
+       .name = "dwgpio",
+       .ids = dwgpio_ids,
+       .id_count = ( sizeof ( dwgpio_ids ) / sizeof ( dwgpio_ids[0] ) ),
+       .probe = dwgpio_probe,
+       .remove = dwgpio_remove,
+};
diff --git a/src/drivers/gpio/dwgpio.h b/src/drivers/gpio/dwgpio.h
new file mode 100644 (file)
index 0000000..4deb622
--- /dev/null
@@ -0,0 +1,81 @@
+#ifndef _DWGPIO_H
+#define _DWGPIO_H
+
+/** @file
+ *
+ * Synopsys DesignWare GPIO driver
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/** Maximum number of GPIOs per port */
+#define DWGPIO_MAX_COUNT 32
+
+/** Software port
+ *
+ * This is the register bank containing the DR, DDR, and CTL bits.
+ */
+#define DWGPIO_SWPORT( x ) ( 0x00 + ( (x) * 0x0c ) )
+
+/** Data register
+ *
+ * Bits written to this register are output if the corresponding DDR
+ * bit is set to 1 (output) and the corresponding CTL bit is set to 0
+ * (software control).
+ *
+ * Bits read from this register reflect the most recently written
+ * value, and do not reflect the actual status of the GPIO pin.
+ */
+#define DWGPIO_SWPORT_DR 0x00
+
+/** Data direction register
+ *
+ * The GPIO is an output if the corresponding bit in this register is
+ * set to 1.
+ */
+#define DWGPIO_SWPORT_DDR 0x04
+
+/** Control register
+ *
+ * The GPIO is under software control (i.e. is functioning as a GPIO,
+ * rather than being controlled by a separate functional block) if the
+ * corresponding bit in this register is set to 0.
+ */
+#define DWGPIO_SWPORT_CTL 0x08
+
+/** External port
+ *
+ * Bits read from this register reflect the current status of the GPIO
+ * pin.
+ */
+#define DWGPIO_EXT_PORT( x ) ( 0x50 + ( (x) * 0x04 ) )
+
+/** A DesignWare GPIO port group */
+struct dwgpio_group {
+       /** Register base */
+       void *base;
+       /** Region cell size specification */
+       struct fdt_reg_cells regs;
+};
+
+/** A DesignWare GPIO port */
+struct dwgpio {
+       /** Device name */
+       const char *name;
+       /** Port index */
+       unsigned int port;
+       /** Software port registers */
+       void *swport;
+       /** External port register */
+       void *ext;
+
+       /** Original data register value */
+       uint32_t dr;
+       /** Original data direction register value */
+       uint32_t ddr;
+       /** Original control register value */
+       uint32_t ctl;
+};
+
+#endif /* _DWGPIO_H */
index cdf173938beca43a804f1f3f836309b51194c312..2bf473a3bbabcc60e199a492a66333fca2707ce6 100644 (file)
@@ -77,9 +77,21 @@ static inline void * dt_get_drvdata ( struct dt_device *dt ) {
        return dt->priv;
 }
 
+/**
+ * Get devicetree parent device
+ *
+ * @v dt               Devicetree device
+ * @ret parent         Parent devicetree device
+ */
+static inline struct dt_device * dt_parent ( struct dt_device *dt ) {
+       return container_of ( dt->dev.parent, struct dt_device, dev );
+}
+
 extern void * dt_ioremap ( struct dt_device *dt, unsigned int offset,
                           unsigned int index, size_t len );
 extern int dt_probe_node ( struct device *parent, unsigned int offset );
 extern void dt_remove_node ( struct device *parent );
+extern int dt_probe_children ( struct dt_device *parent, unsigned int offset );
+extern void dt_remove_children ( struct dt_device *parent );
 
 #endif /* _IPXE_DEVTREE_H */
index b99d2101e2c86fa78963a0e9a4c9aaa124e0d54c..1a8bddbcff09443af60f0f8255276341a77a44b6 100644 (file)
@@ -239,6 +239,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_cgem                ( ERRFILE_DRIVER | 0x00db0000 )
 #define ERRFILE_dwmac               ( ERRFILE_DRIVER | 0x00dc0000 )
 #define ERRFILE_dwusb               ( ERRFILE_DRIVER | 0x00dd0000 )
+#define ERRFILE_dwgpio              ( ERRFILE_DRIVER | 0x00de0000 )
 
 #define ERRFILE_aoe                    ( ERRFILE_NET | 0x00000000 )
 #define ERRFILE_arp                    ( ERRFILE_NET | 0x00010000 )