From 2e4e1f7e9e474f6555009238304780448d3ea238 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 5 Aug 2025 13:55:23 +0100 Subject: [PATCH] [dwgpio] Add driver for the DesignWare GPIO controller Signed-off-by: Michael Brown --- src/Makefile | 1 + src/drivers/bus/devtree.c | 7 +- src/drivers/gpio/dwgpio.c | 337 +++++++++++++++++++++++++++++++++++++ src/drivers/gpio/dwgpio.h | 81 +++++++++ src/include/ipxe/devtree.h | 12 ++ src/include/ipxe/errfile.h | 1 + 6 files changed, 434 insertions(+), 5 deletions(-) create mode 100644 src/drivers/gpio/dwgpio.c create mode 100644 src/drivers/gpio/dwgpio.h diff --git a/src/Makefile b/src/Makefile index f3d378e05..22f413d25 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 diff --git a/src/drivers/bus/devtree.c b/src/drivers/bus/devtree.c index cd5bb09a0..32ab1c2cb 100644 --- a/src/drivers/bus/devtree.c +++ b/src/drivers/bus/devtree.c @@ -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 index 000000000..0b300e0c3 --- /dev/null +++ b/src/drivers/gpio/dwgpio.c @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2025 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#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 index 000000000..4deb6227c --- /dev/null +++ b/src/drivers/gpio/dwgpio.h @@ -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 */ diff --git a/src/include/ipxe/devtree.h b/src/include/ipxe/devtree.h index cdf173938..2bf473a3b 100644 --- a/src/include/ipxe/devtree.h +++ b/src/include/ipxe/devtree.h @@ -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 */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index b99d2101e..1a8bddbcf 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -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 ) -- 2.47.2