From 37e9f785ba6573a5298cf6aea2b04bf4ad674b2f Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 14 Apr 2025 11:34:20 +0100 Subject: [dt] Add basic concept of a devicetree bus Add a basic model for devices instantiated by parsing the system flattened device tree, with drivers matched via the "compatible" property for any non-root node. Signed-off-by: Michael Brown --- src/include/ipxe/devtree.h | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/include/ipxe/devtree.h (limited to 'src/include/ipxe/devtree.h') diff --git a/src/include/ipxe/devtree.h b/src/include/ipxe/devtree.h new file mode 100644 index 000000000..f31ddbd5b --- /dev/null +++ b/src/include/ipxe/devtree.h @@ -0,0 +1,76 @@ +#ifndef _IPXE_DEVTREE_H +#define _IPXE_DEVTREE_H + +/** @file + * + * Devicetree bus + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +/** A devicetree device */ +struct dt_device { + /** Generic device */ + struct device dev; + /** Device path */ + const char *path; + /** Driver for this device */ + struct dt_driver *driver; + /** Driver-private data */ + void *priv; +}; + +/** A devicetree driver */ +struct dt_driver { + /** Driver name */ + const char *name; + /** Compatible programming model identifiers */ + const char **ids; + /** Number of compatible programming model identifiers */ + unsigned int id_count; + /** + * Probe device + * + * @v dt Devicetree device + * @v offset Starting node offset + * @ret rc Return status code + */ + int ( * probe ) ( struct dt_device *dt, unsigned int offset ); + /** + * Remove device + * + * @v dt Devicetree device + */ + void ( * remove ) ( struct dt_device *dt ); +}; + +/** Devicetree driver table */ +#define DT_DRIVERS __table ( struct dt_driver, "dt_drivers" ) + +/** Declare a devicetree driver */ +#define __dt_driver __table_entry ( DT_DRIVERS, 01 ) + +/** + * Set devicetree driver-private data + * + * @v dt Devicetree device + * @v priv Private data + */ +static inline void dt_set_drvdata ( struct dt_device *dt, void *priv ) { + dt->priv = priv; +} + +/** + * Get devicetree driver-private data + * + * @v dt Devicetree device + * @ret priv Private data + */ +static inline void * dt_get_drvdata ( struct dt_device *dt ) { + return dt->priv; +} + +#endif /* _IPXE_DEVTREE_H */ -- cgit v1.2.3-55-g7522 From eeec6442d9d422cbc7473f8dd9229c1acdc4084b Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 15 Apr 2025 20:19:17 +0100 Subject: [dt] Provide dt_ioremap() to map device registers Devicetree devices encode register address ranges within the "reg" property, with the number of cells used for addresses and for sizes determined by the #address-cells and #size-cells properties of the immediate parent device. Record the number of address and size cells for each device, and provide a dt_ioremap() function to allow drivers to map a specified range without having to directly handle the "reg" property. Signed-off-by: Michael Brown --- src/drivers/bus/devtree.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++ src/include/ipxe/devtree.h | 14 +++++++++ 2 files changed, 89 insertions(+) (limited to 'src/include/ipxe/devtree.h') diff --git a/src/drivers/bus/devtree.c b/src/drivers/bus/devtree.c index 35bd35c52..cbd8ea8fa 100644 --- a/src/drivers/bus/devtree.c +++ b/src/drivers/bus/devtree.c @@ -34,6 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include static struct dt_driver dt_node_driver __dt_driver; @@ -41,6 +42,70 @@ struct root_device dt_root_device __root_device; static void dt_remove_children ( struct dt_device *parent ); +/** + * Map devicetree range + * + * @v dt Devicetree device + * @v offset Starting node offset + * @v index Region index + * @v len Length to map, or 0 to map whole region + * @ret io_addr I/O address, or NULL on error + */ +void * dt_ioremap ( struct dt_device *dt, unsigned int offset, + unsigned int index, size_t len ) { + struct dt_device *parent = + container_of ( dt->dev.parent, struct dt_device, dev ); + uint64_t address; + uint64_t size; + unsigned int cell; + void *io_addr; + int rc; + + /* Read address */ + cell = ( index * ( parent->address_cells + parent->size_cells ) ); + if ( ( rc = fdt_cells ( &sysfdt, offset, "reg", cell, + parent->address_cells, &address ) ) != 0 ) { + DBGC ( dt, "DT %s could not read region %d address: %s\n", + dt->path, index, strerror ( rc ) ); + return NULL; + } + cell += parent->address_cells; + + /* Read size (or assume sufficient, if tree specifies no sizes) */ + size = len; + if ( parent->size_cells && + ( rc = fdt_cells ( &sysfdt, offset, "reg", cell, + parent->size_cells, &size ) ) != 0 ) { + DBGC ( dt, "DT %s could not read region %d size: %s\n", + dt->path, index, strerror ( rc ) ); + return NULL; + } + + /* Use region size as length if not specified */ + if ( ! len ) + len = size; + DBGC ( dt, "DT %s region %d at %#08llx+%#04llx\n", + dt->path, index, ( ( unsigned long long ) address ), + ( ( unsigned long long ) size ) ); + + /* Verify size */ + if ( len > size ) { + DBGC ( dt, "DT %s region %d is too small (%#llx/%#zx bytes)\n", + dt->path, index, ( ( unsigned long long ) size ), len ); + return NULL; + } + + /* Map region */ + io_addr = ioremap ( address, len ); + if ( ! io_addr ) { + DBGC ( dt, "DT %s could not map region %d\n", + dt->path, index ); + return NULL; + } + + return io_addr; +} + /** * Find devicetree driver * @@ -158,6 +223,16 @@ static int dt_probe_node ( struct dt_device *parent, unsigned int offset, INIT_LIST_HEAD ( &dt->dev.children ); list_add_tail ( &dt->dev.siblings, &dt->dev.parent->children ); + /* Read #address-cells and #size-cells, if present */ + if ( ( rc = fdt_u32 ( &sysfdt, offset, "#address-cells", + &dt->address_cells ) ) != 0 ) { + dt->address_cells = DT_DEFAULT_ADDRESS_CELLS; + } + if ( ( rc = fdt_u32 ( &sysfdt, offset, "#size-cells", + &dt->size_cells ) ) != 0 ) { + dt->size_cells = DT_DEFAULT_SIZE_CELLS; + } + /* Probe device */ if ( ( rc = dt_probe ( dt, offset ) ) != 0 ) goto err_probe; diff --git a/src/include/ipxe/devtree.h b/src/include/ipxe/devtree.h index f31ddbd5b..911e7ad15 100644 --- a/src/include/ipxe/devtree.h +++ b/src/include/ipxe/devtree.h @@ -21,8 +21,19 @@ struct dt_device { struct dt_driver *driver; /** Driver-private data */ void *priv; + + /** Number of address cells for child devices */ + uint32_t address_cells; + /** Number of size cells for child devices */ + uint32_t size_cells; }; +/** Default number of address cells, if not specified */ +#define DT_DEFAULT_ADDRESS_CELLS 2 + +/** Default number of size cells, if not specified */ +#define DT_DEFAULT_SIZE_CELLS 1 + /** A devicetree driver */ struct dt_driver { /** Driver name */ @@ -73,4 +84,7 @@ static inline void * dt_get_drvdata ( struct dt_device *dt ) { return dt->priv; } +extern void * dt_ioremap ( struct dt_device *dt, unsigned int offset, + unsigned int index, size_t len ); + #endif /* _IPXE_DEVTREE_H */ -- cgit v1.2.3-55-g7522 From 1291dc39fd09807f6b864c6433444d97be2a0c8a Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sat, 19 Apr 2025 11:54:08 +0100 Subject: [cgem] Add a driver for the Cadence GEM NIC Add a basic driver for the Cadence GEM network interface as emulated by QEMU when using the RISC-V "sifive_u" machine type. Signed-off-by: Michael Brown --- src/drivers/net/cgem.c | 711 +++++++++++++++++++++++++++++++++++++++++++++ src/drivers/net/cgem.h | 189 ++++++++++++ src/include/ipxe/devtree.h | 3 + src/include/ipxe/errfile.h | 1 + 4 files changed, 904 insertions(+) create mode 100644 src/drivers/net/cgem.c create mode 100644 src/drivers/net/cgem.h (limited to 'src/include/ipxe/devtree.h') diff --git a/src/drivers/net/cgem.c b/src/drivers/net/cgem.c new file mode 100644 index 000000000..c935c8024 --- /dev/null +++ b/src/drivers/net/cgem.c @@ -0,0 +1,711 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include "cgem.h" + +/** @file + * + * Cadence Gigabit Ethernet MAC (GEM) network driver + * + * Based primarily on the Zynq 7000 SoC Technical Reference Manual, + * available at the time of writing from: + * + * https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM + * + */ + +/****************************************************************************** + * + * Device reset + * + ****************************************************************************** + */ + +/** + * Reset hardware + * + * @v cgem Cadence GEM device + * @ret rc Return status code + */ +static int cgem_reset ( struct cgem_nic *cgem ) { + + /* There is no software-driven reset capability in the + * hardware. Instead we have to write the expected reset + * values to the various registers. + */ + + /* Disable all interrupts */ + writel ( CGEM_IDR_ALL, ( cgem->regs + CGEM_IDR ) ); + + /* Clear network control register */ + writel ( 0, ( cgem->regs + CGEM_NWCTRL ) ); + + /* Clear statistics registers now that TX and RX are stopped */ + writel ( CGEM_NWCTRL_STATCLR, ( cgem->regs + CGEM_NWCTRL ) ); + + /* Clear TX queue base address */ + writel ( 0, ( cgem->regs + CGEM_TXQBASE ) ); + + /* Clear RX queue base address */ + writel ( 0, ( cgem->regs + CGEM_RXQBASE ) ); + + /* Configure DMA */ + writel ( ( CGEM_DMACR_RXBUF ( CGEM_RX_LEN ) | CGEM_DMACR_TXSIZE_MAX | + CGEM_DMACR_RXSIZE_MAX | CGEM_DMACR_BLENGTH_MAX ), + ( cgem->regs + CGEM_DMACR ) ); + + /* Enable MII interface */ + writel ( CGEM_NWCTRL_MDEN, ( cgem->regs + CGEM_NWCTRL ) ); + + return 0; +} + +/****************************************************************************** + * + * PHY access + * + ****************************************************************************** + */ + +/** + * Wait for MII operation to complete + * + * @v cgem Cadence GEM device + * @ret rc Return status code + */ +static int cgem_mii_wait ( struct cgem_nic *cgem ) { + uint32_t nwsr; + unsigned int i; + + /* Wait for MII interface to become idle */ + for ( i = 0 ; i < CGEM_MII_MAX_WAIT_US ; i++ ) { + + /* Check if MII interface is idle */ + nwsr = readl ( cgem->regs + CGEM_NWSR ); + if ( nwsr & CGEM_NWSR_MII_IDLE ) + return 0; + + /* Delay */ + udelay ( 1 ); + } + + DBGC ( cgem, "CGEM %s timed out waiting for MII\n", cgem->name ); + return -ETIMEDOUT; +} + +/** + * Read from MII register + * + * @v mdio MII interface + * @v phy PHY address + * @v reg Register address + * @ret data Data read, or negative error + */ +static int cgem_mii_read ( struct mii_interface *mdio, unsigned int phy, + unsigned int reg ) { + struct cgem_nic *cgem = container_of ( mdio, struct cgem_nic, mdio ); + unsigned int data; + int rc; + + /* Initiate read */ + writel ( ( CGEM_PHYMNTNC_CLAUSE22 | CGEM_PHYMNTNC_OP_READ | + CGEM_PHYMNTNC_ADDR ( phy ) | CGEM_PHYMNTNC_REG ( reg ) | + CGEM_PHYMNTNC_FIXED ), + ( cgem->regs + CGEM_PHYMNTNC ) ); + + /* Wait for read to complete */ + if ( ( rc = cgem_mii_wait ( cgem ) ) != 0 ) + return rc; + + /* Read data */ + data = ( readl ( cgem->regs + CGEM_PHYMNTNC ) & + CGEM_PHYMNTNC_DATA_MASK ); + + return data; +} + +/** + * Write to MII register + * + * @v mdio MII interface + * @v phy PHY address + * @v reg Register address + * @v data Data to write + * @ret rc Return status code + */ +static int cgem_mii_write ( struct mii_interface *mdio, unsigned int phy, + unsigned int reg, unsigned int data ) { + struct cgem_nic *cgem = container_of ( mdio, struct cgem_nic, mdio ); + int rc; + + /* Initiate write */ + writel ( ( CGEM_PHYMNTNC_CLAUSE22 | CGEM_PHYMNTNC_OP_READ | + CGEM_PHYMNTNC_ADDR ( phy ) | CGEM_PHYMNTNC_REG ( reg ) | + CGEM_PHYMNTNC_FIXED | data ), + ( cgem->regs + CGEM_PHYMNTNC ) ); + + /* Wait for write to complete */ + if ( ( rc = cgem_mii_wait ( cgem ) ) != 0 ) + return rc; + + return 0; +} + +/** MII operations */ +static struct mii_operations cgem_mii_operations = { + .read = cgem_mii_read, + .write = cgem_mii_write, +}; + +/****************************************************************************** + * + * Link state + * + ****************************************************************************** + */ + +/** + * Initialise PHY + * + * @v cgem Cadence GEM device + * @ret rc Return status code + */ +static int cgem_init_phy ( struct cgem_nic *cgem ) { + int rc; + + /* Find PHY address */ + if ( ( rc = mii_find ( &cgem->mii ) ) != 0 ) { + DBGC ( cgem, "CGEM %s could not find PHY address: %s\n", + cgem->name, strerror ( rc ) ); + return rc; + } + + /* Reset PHY */ + if ( ( rc = mii_reset ( &cgem->mii ) ) != 0 ) { + DBGC ( cgem, "CGEM %s could not reset PHY: %s\n", + cgem->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Check link state + * + * @v netdev Network device + */ +static int cgem_check_link ( struct net_device *netdev ) { + struct cgem_nic *cgem = netdev->priv; + int rc; + + /* Check link state */ + if ( ( rc = mii_check_link ( &cgem->mii, netdev ) ) != 0 ) { + DBGC ( cgem, "CGEM %s could not check link: %s\n", + cgem->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Check link state periodically + * + * @v retry Link state check timer + * @v over Failure indicator + */ +static void cgem_expired ( struct retry_timer *timer, int over __unused ) { + struct cgem_nic *cgem = container_of ( timer, struct cgem_nic, timer ); + struct net_device *netdev = cgem->netdev; + + /* Restart timer */ + start_timer_fixed ( timer, CGEM_LINK_INTERVAL ); + + /* Check link state */ + cgem_check_link ( netdev ); +} + +/****************************************************************************** + * + * Network device interface + * + ****************************************************************************** + */ + +/** + * Create descriptor ring + * + * @v cgem Cadence GEM device + * @v ring Descriptor ring + * @ret rc Return status code + */ +static int cgem_create_ring ( struct cgem_nic *cgem, struct cgem_ring *ring ) { + struct cgem_descriptor *desc; + unsigned int i; + + /* Allocate descriptor ring (on its own size) */ + ring->desc = dma_alloc ( cgem->dma, &ring->map, ring->len, ring->len ); + if ( ! ring->desc ) + return -ENOMEM; + + /* Initialise descriptor ring */ + for ( i = 0 ; i < ring->count ; i++ ) { + desc = &ring->desc[i]; + desc->addr = cpu_to_le32 ( CGEM_RX_ADDR_OWNED ); + desc->flags = cpu_to_le32 ( CGEM_TX_FL_OWNED ); + } + desc = &ring->desc[ ring->count - 1 ]; + desc->addr |= cpu_to_le32 ( CGEM_RX_ADDR_WRAP ); + desc->flags |= cpu_to_le32 ( CGEM_TX_FL_WRAP ); + + /* Program ring address */ + writel ( dma ( &ring->map, ring->desc ), + ( cgem->regs + ring->qbase ) ); + + DBGC ( cgem, "CGEM %s ring %02x is at [%08lx,%08lx)\n", + cgem->name, ring->qbase, virt_to_phys ( ring->desc ), + ( virt_to_phys ( ring->desc ) + ring->len ) ); + return 0; +} + +/** + * Destroy descriptor ring + * + * @v cgem Cadence GEM device + * @v ring Descriptor ring + */ +static void cgem_destroy_ring ( struct cgem_nic *cgem, + struct cgem_ring *ring ) { + + /* Clear ring address */ + writel ( 0, ( cgem->regs + ring->qbase ) ); + + /* Free descriptor ring */ + dma_free ( &ring->map, ring->desc, ring->len ); + ring->desc = NULL; + ring->prod = 0; + ring->cons = 0; +} + +/** + * Refill receive descriptor ring + * + * @v cgem Cadence GEM device + */ +static void cgem_refill_rx ( struct cgem_nic *cgem ) { + struct cgem_descriptor *rx; + struct io_buffer *iobuf; + unsigned int rx_idx; + uint32_t addr; + + /* Refill ring */ + while ( ( cgem->rx.prod - cgem->rx.cons ) != CGEM_NUM_RX_DESC ) { + + /* Allocate I/O buffer */ + iobuf = alloc_rx_iob ( CGEM_RX_LEN, cgem->dma ); + if ( ! iobuf ) { + /* Wait for next refill */ + break; + } + + /* Get next receive descriptor */ + rx_idx = ( cgem->rx.prod++ % CGEM_NUM_RX_DESC ); + rx = &cgem->rx.desc[rx_idx]; + + /* Populate receive descriptor */ + rx->flags = 0; + addr = 0; + if ( ( cgem->rx.prod % CGEM_NUM_RX_DESC ) == 0 ) + addr |= CGEM_RX_ADDR_WRAP; + rx->addr = cpu_to_le32 ( addr | iob_dma ( iobuf ) ); + + /* Record I/O buffer */ + assert ( cgem->rx_iobuf[rx_idx] == NULL ); + cgem->rx_iobuf[rx_idx] = iobuf; + + DBGC2 ( cgem, "CGEM %s RX %d is [%08lx,%08lx)\n", + cgem->name, rx_idx, virt_to_phys ( iobuf->data ), + ( virt_to_phys ( iobuf->data ) + CGEM_RX_LEN ) ); + } +} + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int cgem_open ( struct net_device *netdev ) { + struct cgem_nic *cgem = netdev->priv; + union cgem_mac mac; + int rc; + + /* Create transmit descriptor ring */ + if ( ( rc = cgem_create_ring ( cgem, &cgem->tx ) ) != 0 ) + goto err_create_tx; + + /* Create receive descriptor ring */ + if ( ( rc = cgem_create_ring ( cgem, &cgem->rx ) ) != 0 ) + goto err_create_rx; + + /* Set MAC address */ + memcpy ( mac.raw, netdev->ll_addr, ETH_ALEN ); + writel ( mac.reg.low, ( cgem->regs + CGEM_LADDRL ) ); + writel ( mac.reg.high, ( cgem->regs + CGEM_LADDRH ) ); + + /* Enable transmit and receive */ + writel ( CGEM_NWCTRL_NORMAL, ( cgem->regs + CGEM_NWCTRL ) ); + + /* Refill receive descriptor ring */ + cgem_refill_rx ( cgem ); + + /* Update link state */ + cgem_check_link ( netdev ); + + /* Start link state timer */ + start_timer_fixed ( &cgem->timer, CGEM_LINK_INTERVAL ); + + return 0; + + cgem_destroy_ring ( cgem, &cgem->rx ); + err_create_rx: + cgem_destroy_ring ( cgem, &cgem->tx ); + err_create_tx: + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void cgem_close ( struct net_device *netdev ) { + struct cgem_nic *cgem = netdev->priv; + unsigned int i; + + /* Stop link state timer */ + stop_timer ( &cgem->timer ); + + /* Reset NIC */ + cgem_reset ( cgem ); + + /* Discard unused receive buffers */ + for ( i = 0 ; i < CGEM_NUM_RX_DESC ; i++ ) { + if ( cgem->rx_iobuf[i] ) + free_rx_iob ( cgem->rx_iobuf[i] ); + cgem->rx_iobuf[i] = NULL; + } + + /* Destroy receive descriptor ring */ + cgem_destroy_ring ( cgem, &cgem->rx ); + + /* Destroy transmit descriptor ring */ + cgem_destroy_ring ( cgem, &cgem->tx ); +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int cgem_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct cgem_nic *cgem = netdev->priv; + struct cgem_descriptor *tx; + unsigned int tx_idx; + uint32_t flags; + int rc; + + /* Get next transmit descriptor */ + if ( ( cgem->tx.prod - cgem->tx.cons ) >= CGEM_NUM_TX_DESC ) { + DBGC ( cgem, "CGEM %s out of transmit descriptors\n", + cgem->name ); + return -ENOBUFS; + } + tx_idx = ( cgem->tx.prod % CGEM_NUM_TX_DESC ); + tx = &cgem->tx.desc[tx_idx]; + + /* Pad to minimum length */ + iob_pad ( iobuf, ETH_ZLEN ); + + /* Map I/O buffer */ + if ( ( rc = iob_map_tx ( iobuf, cgem->dma ) ) != 0 ) + return rc; + + /* Update producer index */ + cgem->tx.prod++; + + /* Populate transmit descriptor */ + flags = CGEM_TX_FL_LAST; + if ( ( cgem->tx.prod % CGEM_NUM_TX_DESC ) == 0 ) + flags |= CGEM_TX_FL_WRAP; + tx->addr = cpu_to_le32 ( iob_dma ( iobuf ) ); + wmb(); + tx->flags = cpu_to_le32 ( flags | iob_len ( iobuf ) ); + wmb(); + + /* Initiate transmission */ + writel ( ( CGEM_NWCTRL_NORMAL | CGEM_NWCTRL_STARTTX ), + ( cgem->regs + CGEM_NWCTRL ) ); + + DBGC2 ( cgem, "CGEM %s TX %d is [%08lx,%08lx)\n", + cgem->name, tx_idx, virt_to_phys ( iobuf->data ), + ( virt_to_phys ( iobuf->data ) + iob_len ( iobuf ) ) ); + return 0; +} + +/** + * Poll for completed packets + * + * @V netdev Network device + */ +static void cgem_poll_tx ( struct net_device *netdev ) { + struct cgem_nic *cgem = netdev->priv; + struct cgem_descriptor *tx; + unsigned int tx_idx; + + /* Check for completed packets */ + while ( cgem->tx.cons != cgem->tx.prod ) { + + /* Get next transmit descriptor */ + tx_idx = ( cgem->tx.cons % CGEM_NUM_TX_DESC ); + tx = &cgem->tx.desc[tx_idx]; + + /* Stop if descriptor is still owned by hardware */ + if ( ! ( tx->flags & cpu_to_le32 ( CGEM_TX_FL_OWNED ) ) ) + return; + DBGC2 ( cgem, "CGEM %s TX %d complete\n", + cgem->name, tx_idx ); + + /* Complete transmit descriptor */ + netdev_tx_complete_next ( netdev ); + cgem->tx.cons++; + } +} + +/** + * Poll for received packets + * + * @v netdev Network device + */ +static void cgem_poll_rx ( struct net_device *netdev ) { + struct cgem_nic *cgem = netdev->priv; + struct cgem_descriptor *rx; + struct io_buffer *iobuf; + unsigned int rx_idx; + uint32_t flags; + size_t len; + + /* Check for received packets */ + while ( cgem->rx.cons != cgem->rx.prod ) { + + /* Get next receive descriptor */ + rx_idx = ( cgem->rx.cons % CGEM_NUM_RX_DESC ); + rx = &cgem->rx.desc[rx_idx]; + + /* Stop if descriptor is still in use */ + if ( ! ( rx->addr & cpu_to_le32 ( CGEM_RX_ADDR_OWNED ) ) ) + return; + + /* Populate I/O buffer */ + iobuf = cgem->rx_iobuf[rx_idx]; + cgem->rx_iobuf[rx_idx] = NULL; + flags = le32_to_cpu ( rx->flags ); + len = CGEM_RX_FL_LEN ( flags ); + iob_put ( iobuf, len ); + DBGC2 ( cgem, "CGEM %s RX %d complete (length %zd)\n", + cgem->name, rx_idx, len ); + + /* Hand off to network stack */ + netdev_rx ( netdev, iobuf ); + cgem->rx.cons++; + } +} + +/** + * Poll for completed and received packets + * + * @v netdev Network device + */ +static void cgem_poll ( struct net_device *netdev ) { + struct cgem_nic *cgem = netdev->priv; + + /* Poll for TX competions */ + cgem_poll_tx ( netdev ); + + /* Poll for RX completions */ + cgem_poll_rx ( netdev ); + + /* Refill RX ring */ + cgem_refill_rx ( cgem ); +} + +/** Cadence GEM network device operations */ +static struct net_device_operations cgem_operations = { + .open = cgem_open, + .close = cgem_close, + .transmit = cgem_transmit, + .poll = cgem_poll, +}; + +/****************************************************************************** + * + * Devicetree interface + * + ****************************************************************************** + */ + +/** + * Probe devicetree device + * + * @v dt Devicetree device + * @v offset Starting node offset + * @ret rc Return status code + */ +static int cgem_probe ( struct dt_device *dt, unsigned int offset ) { + struct net_device *netdev; + struct cgem_nic *cgem; + union cgem_mac mac; + int rc; + + /* Allocate and initialise net device */ + netdev = alloc_etherdev ( sizeof ( *cgem ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &cgem_operations ); + cgem = netdev->priv; + dt_set_drvdata ( dt, netdev ); + netdev->dev = &dt->dev; + memset ( cgem, 0, sizeof ( *cgem ) ); + cgem->dma = &dt->dma; + cgem->netdev = netdev; + cgem->name = netdev->dev->name; + mdio_init ( &cgem->mdio, &cgem_mii_operations ); + mii_init ( &cgem->mii, &cgem->mdio, 0 ); + timer_init ( &cgem->timer, cgem_expired, &netdev->refcnt ); + cgem_init_ring ( &cgem->tx, CGEM_NUM_TX_DESC, CGEM_TXQBASE ); + cgem_init_ring ( &cgem->rx, CGEM_NUM_RX_DESC, CGEM_RXQBASE ); + + /* Map registers */ + cgem->regs = dt_ioremap ( dt, offset, CGEM_REG_IDX, CGEM_REG_LEN ); + if ( ! cgem->regs ) { + rc = -ENODEV; + goto err_ioremap; + } + + /* Reset the NIC */ + if ( ( rc = cgem_reset ( cgem ) ) != 0 ) + goto err_reset; + + /* Initialise the PHY */ + if ( ( rc = cgem_init_phy ( cgem ) ) != 0 ) + goto err_init_phy; + + /* Fetch devicetree MAC address */ + if ( ( rc = fdt_mac ( &sysfdt, offset, netdev ) ) != 0 ) { + DBGC ( cgem, "CGEM %s could not fetch MAC: %s\n", + cgem->name, strerror ( rc ) ); + goto err_mac; + } + + /* Fetch current MAC address, if set */ + mac.reg.low = readl ( cgem->regs + CGEM_LADDRL ); + mac.reg.high = readl ( cgem->regs + CGEM_LADDRH ); + memcpy ( netdev->ll_addr, mac.raw, ETH_ALEN ); + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register_netdev; + + /* Set initial link state */ + cgem_check_link ( netdev ); + + return 0; + + unregister_netdev ( netdev ); + err_register_netdev: + err_mac: + err_init_phy: + cgem_reset ( cgem ); + err_reset: + iounmap ( cgem->regs ); + err_ioremap: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Remove devicetree device + * + * @v dt Devicetree device + */ +static void cgem_remove ( struct dt_device *dt ) { + struct net_device *netdev = dt_get_drvdata ( dt ); + struct cgem_nic *cgem = netdev->priv; + + /* Unregister network device */ + unregister_netdev ( netdev ); + + /* Reset card */ + cgem_reset ( cgem ); + + /* Free network device */ + iounmap ( cgem->regs ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} + +/** Cadence GEM compatible model identifiers */ +static const char * cgem_ids[] = { + "sifive,fu540-c000-gem", +}; + +/** Cadence GEM devicetree driver */ +struct dt_driver cgem_driver __dt_driver = { + .name = "cgem", + .ids = cgem_ids, + .id_count = ( sizeof ( cgem_ids ) / sizeof ( cgem_ids[0] ) ), + .probe = cgem_probe, + .remove = cgem_remove, +}; diff --git a/src/drivers/net/cgem.h b/src/drivers/net/cgem.h new file mode 100644 index 000000000..c91e3677d --- /dev/null +++ b/src/drivers/net/cgem.h @@ -0,0 +1,189 @@ +#ifndef _CGEM_H +#define _CGEM_H + +/** @file + * + * Cadence Gigabit Ethernet MAC (GEM) network driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include + +/** I/O region index */ +#define CGEM_REG_IDX 0 + +/** I/O region length */ +#define CGEM_REG_LEN 0x800 + +/** Network control register */ +#define CGEM_NWCTRL 0x000 +#define CGEM_NWCTRL_STARTTX 0x00000200 /**< Start transmission */ +#define CGEM_NWCTRL_STATCLR 0x00000020 /**< Clear statistics */ +#define CGEM_NWCTRL_MDEN 0x00000010 /**< MII interface enable */ +#define CGEM_NWCTRL_TXEN 0x00000008 /**< Transmit enable */ +#define CGEM_NWCTRL_RXEN 0x00000004 /**< Receive enable */ + +/** Normal value for network control register while up and running */ +#define CGEM_NWCTRL_NORMAL \ + ( CGEM_NWCTRL_MDEN | CGEM_NWCTRL_TXEN | CGEM_NWCTRL_RXEN ) + +/** Network configuration register */ +#define CGEM_NWCFG 0x004 + +/** Network status register */ +#define CGEM_NWSR 0x008 +#define CGEM_NWSR_MII_IDLE 0x00000004 /**< MII interface is idle */ + +/** DMA configuration register */ +#define CGEM_DMACR 0x010 +#define CGEM_DMACR_RXBUF( x ) ( ( (x) / 64 ) << 16 ) /**< RX buffer size */ +#define CGEM_DMACR_TXSIZE( x ) ( (x) << 10 ) /**< TX memory size */ +#define CGEM_DMACR_TXSIZE_MAX \ + CGEM_DMACR_TXSIZE ( 0x1 ) /**< Max TX memory size */ +#define CGEM_DMACR_RXSIZE( x ) ( (x) << 8 ) /**< RX memory size */ +#define CGEM_DMACR_RXSIZE_MAX \ + CGEM_DMACR_RXSIZE ( 0x3 ) /**< Max RX memory size */ +#define CGEM_DMACR_BLENGTH( x ) ( (x) << 0 ) /**< DMA burst length */ +#define CGEM_DMACR_BLENGTH_MAX \ + CGEM_DMACR_BLENGTH ( 0x10 ) /**< Max DMA burst length */ + +/** RX queue base address register */ +#define CGEM_RXQBASE 0x018 + +/** TX queue base address register */ +#define CGEM_TXQBASE 0x01c + +/** Interrupt disable register */ +#define CGEM_IDR 0x02c +#define CGEM_IDR_ALL 0xffffffff /**< Disable all interrupts */ + +/** PHY maintenance register */ +#define CGEM_PHYMNTNC 0x034 +#define CGEM_PHYMNTNC_CLAUSE22 0x40000000 /**< Clause 22 operation */ +#define CGEM_PHYMNTNC_OP_WRITE 0x10000000 /**< Write to PHY register */ +#define CGEM_PHYMNTNC_OP_READ 0x20000000 /**< Read from PHY register */ +#define CGEM_PHYMNTNC_ADDR( x ) ( (x) << 23 ) /**< PHY address */ +#define CGEM_PHYMNTNC_REG( x ) ( (x) << 18 ) /**< Register address */ +#define CGEM_PHYMNTNC_FIXED 0x00020000 /**< Fixed value to write */ +#define CGEM_PHYMNTNC_DATA_MASK 0x0000ffff /**< Data mask */ + +/** Maximum time to wait for PHY access, in microseconds */ +#define CGEM_MII_MAX_WAIT_US 500 + +/** Link state check interval */ +#define CGEM_LINK_INTERVAL ( 2 * TICKS_PER_SEC ) + +/** Local MAC address (low half) register */ +#define CGEM_LADDRL 0x088 + +/** Local MAC address (high half) register */ +#define CGEM_LADDRH 0x08c + +/** A Cadence GEM descriptor */ +struct cgem_descriptor { + /** Buffer address */ + uint32_t addr; + /** Flags */ + uint32_t flags; +} __attribute__ (( packed )); + +/** Transmit flags */ +#define CGEM_TX_FL_OWNED 0x80000000 /**< Owned by software */ +#define CGEM_TX_FL_WRAP 0x40000000 /**< End of descriptor ring */ +#define CGEM_TX_FL_LAST 0x00008000 /**< Last buffer in frame */ + +/** Transmit ring length */ +#define CGEM_NUM_TX_DESC 8 + +/** Receive flags (in buffer address) */ +#define CGEM_RX_ADDR_OWNED 0x00000001 /**< Owned by software */ +#define CGEM_RX_ADDR_WRAP 0x00000002 /**< End of descriptor ring */ + +/** Receive flags */ +#define CGEM_RX_FL_LEN( x ) ( (x) & 0x1fff ) /**< RX packet length */ + +/** Receive ring length */ +#define CGEM_NUM_RX_DESC 8 + +/** Length of receive buffers + * + * Must be a multiple of 64. + */ +#define CGEM_RX_LEN 1536 + +/** A Cadence GEM MAC address */ +union cgem_mac { + struct { + uint32_t low; + uint32_t high; + } __attribute__ (( packed )) reg; + uint8_t raw[ETH_ALEN]; +}; + +/** A Cadence GEM descriptor ring */ +struct cgem_ring { + /** Descriptors */ + struct cgem_descriptor *desc; + /** Descriptor ring DMA mapping */ + struct dma_mapping map; + /** Producer index */ + unsigned int prod; + /** Consumer index */ + unsigned int cons; + + /** Queue base address register */ + uint8_t qbase; + /** Number of descriptors */ + uint8_t count; + /** Length of descriptors */ + uint16_t len; +}; + +/** + * Initialise descriptor ring + * + * @v ring Descriptor ring + * @v count Number of descriptors + * @v qbase Queue base address register + */ +static inline __attribute__ (( always_inline )) void +cgem_init_ring ( struct cgem_ring *ring, unsigned int count, + unsigned int qbase ) { + + ring->qbase = qbase; + ring->count = count; + ring->len = ( count * sizeof ( ring->desc[0] ) ); +} + +/** A Cadence GEM network card */ +struct cgem_nic { + /** Registers */ + void *regs; + /** DMA device */ + struct dma_device *dma; + /** Network device */ + struct net_device *netdev; + /** Device name (for debugging) */ + const char *name; + + /** PHY interface */ + struct mii_interface mdio; + /** PHY device */ + struct mii_device mii; + /** Link state timer */ + struct retry_timer timer; + + /** Transmit ring */ + struct cgem_ring tx; + /** Receive ring */ + struct cgem_ring rx; + /** Receive I/O buffers */ + struct io_buffer *rx_iobuf[CGEM_NUM_RX_DESC]; +}; + +#endif /* _CGEM_H */ diff --git a/src/include/ipxe/devtree.h b/src/include/ipxe/devtree.h index 911e7ad15..04414f370 100644 --- a/src/include/ipxe/devtree.h +++ b/src/include/ipxe/devtree.h @@ -10,11 +10,14 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +#include /** A devicetree device */ struct dt_device { /** Generic device */ struct device dev; + /** DMA device */ + struct dma_device dma; /** Device path */ const char *path; /** Driver for this device */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 2b8806706..37e36c720 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -232,6 +232,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_atl_hw ( ERRFILE_DRIVER | 0x00d80000 ) #define ERRFILE_atl2_hw ( ERRFILE_DRIVER | 0x00d90000 ) #define ERRFILE_devtree ( ERRFILE_DRIVER | 0x00da0000 ) +#define ERRFILE_cgem ( ERRFILE_DRIVER | 0x00db0000 ) #define ERRFILE_aoe ( ERRFILE_NET | 0x00000000 ) #define ERRFILE_arp ( ERRFILE_NET | 0x00010000 ) -- cgit v1.2.3-55-g7522 From f988ec09e01b54d21d1b1fa0e2b3121d926ed7df Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 9 May 2025 16:26:41 +0100 Subject: [fdt] Generalise access to "reg" property The "reg" property is also used by non-device nodes, such as the nodes describing the system memory map. Provide generalised functionality for parsing the "#address-cells", "#size-cells", and "reg" properties. Signed-off-by: Michael Brown --- src/core/fdt.c | 105 +++++++++++++++++++++++++++++++++++++++++++++ src/drivers/bus/devtree.c | 23 +++------- src/include/ipxe/devtree.h | 13 ++---- src/include/ipxe/fdt.h | 26 +++++++++++ 4 files changed, 141 insertions(+), 26 deletions(-) (limited to 'src/include/ipxe/devtree.h') diff --git a/src/core/fdt.c b/src/core/fdt.c index 744e0e705..eee5b47b5 100644 --- a/src/core/fdt.c +++ b/src/core/fdt.c @@ -563,6 +563,111 @@ int fdt_u32 ( struct fdt *fdt, unsigned int offset, const char *name, return 0; } +/** + * Get region cell size specification + * + * @v fdt Device tree + * @v offset Starting (parent) node offset + * @v regs Region cell size specification to fill in + * + * Note that #address-cells and #size-cells are defined on the + * immediate parent node, rather than on the node with the "reg" + * property itself. + */ +void fdt_reg_cells ( struct fdt *fdt, unsigned int offset, + struct fdt_reg_cells *regs ) { + int rc; + + /* Read #address-cells, if present */ + if ( ( rc = fdt_u32 ( fdt, offset, "#address-cells", + ®s->address_cells ) ) != 0 ) { + regs->address_cells = FDT_DEFAULT_ADDRESS_CELLS; + } + + /* Read #size-cells, if present */ + if ( ( rc = fdt_u32 ( fdt, offset, "#size-cells", + ®s->size_cells ) ) != 0 ) { + regs->size_cells = FDT_DEFAULT_SIZE_CELLS; + } + + /* Calculate stride */ + regs->stride = ( regs->address_cells + regs->size_cells ); +} + +/** + * Get number of regions + * + * @v fdt Device tree + * @v offset Starting node offset + * @v regs Region cell size specification + * @ret count Number of regions, or negative error + */ +int fdt_reg_count ( struct fdt *fdt, unsigned int offset, + struct fdt_reg_cells *regs ) { + struct fdt_descriptor desc; + const uint32_t *cell; + unsigned int count; + int rc; + + /* Find property */ + if ( ( rc = fdt_property ( fdt, offset, "reg", &desc ) ) != 0 ) + return rc; + + /* Determine number of regions */ + count = ( desc.len / ( regs->stride * sizeof ( *cell ) ) ); + return count; +} + +/** + * Get region address + * + * @v fdt Device tree + * @v offset Starting node offset + * @v regs Region cell size specification + * @v index Region index + * @v address Region starting address to fill in + * @ret rc Return status code + */ +int fdt_reg_address ( struct fdt *fdt, unsigned int offset, + struct fdt_reg_cells *regs, unsigned int index, + uint64_t *address ) { + unsigned int cell = ( index * regs->stride ); + int rc; + + /* Read relevant portion of region array */ + if ( ( rc = fdt_cells ( fdt, offset, "reg", cell, regs->address_cells, + address ) ) != 0 ) { + return rc; + } + + return 0; +} + +/** + * Get region size + * + * @v fdt Device tree + * @v offset Starting node offset + * @v regs Region cell size specification + * @v index Region index + * @v size Region size to fill in + * @ret rc Return status code + */ +int fdt_reg_size ( struct fdt *fdt, unsigned int offset, + struct fdt_reg_cells *regs, unsigned int index, + uint64_t *size ) { + unsigned int cell = ( ( index * regs->stride ) + regs->address_cells ); + int rc; + + /* Read relevant portion of region array */ + if ( ( rc = fdt_cells ( fdt, offset, "reg", cell, regs->size_cells, + size ) ) != 0 ) { + return rc; + } + + return 0; +} + /** * Get MAC address from property * diff --git a/src/drivers/bus/devtree.c b/src/drivers/bus/devtree.c index 654f8d14f..55f68ee33 100644 --- a/src/drivers/bus/devtree.c +++ b/src/drivers/bus/devtree.c @@ -56,27 +56,25 @@ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, unsigned int index, size_t len ) { struct dt_device *parent = container_of ( dt->dev.parent, struct dt_device, dev ); + struct fdt_reg_cells *regs = &parent->regs; uint64_t address; uint64_t size; - unsigned int cell; void *io_addr; int rc; /* Read address */ - cell = ( index * ( parent->address_cells + parent->size_cells ) ); - if ( ( rc = fdt_cells ( &sysfdt, offset, "reg", cell, - parent->address_cells, &address ) ) != 0 ) { + if ( ( rc = fdt_reg_address ( &sysfdt, offset, regs, index, + &address ) ) != 0 ) { DBGC ( dt, "DT %s could not read region %d address: %s\n", dt->path, index, strerror ( rc ) ); return NULL; } - cell += parent->address_cells; /* Read size (or assume sufficient, if tree specifies no sizes) */ size = len; - if ( parent->size_cells && - ( rc = fdt_cells ( &sysfdt, offset, "reg", cell, - parent->size_cells, &size ) ) != 0 ) { + if ( regs->size_cells && + ( ( rc = fdt_reg_size ( &sysfdt, offset, regs, index, + &size ) ) != 0 ) ) { DBGC ( dt, "DT %s could not read region %d size: %s\n", dt->path, index, strerror ( rc ) ); return NULL; @@ -225,14 +223,7 @@ static int dt_probe_node ( struct dt_device *parent, unsigned int offset, list_add_tail ( &dt->dev.siblings, &dt->dev.parent->children ); /* Read #address-cells and #size-cells, if present */ - if ( ( rc = fdt_u32 ( &sysfdt, offset, "#address-cells", - &dt->address_cells ) ) != 0 ) { - dt->address_cells = DT_DEFAULT_ADDRESS_CELLS; - } - if ( ( rc = fdt_u32 ( &sysfdt, offset, "#size-cells", - &dt->size_cells ) ) != 0 ) { - dt->size_cells = DT_DEFAULT_SIZE_CELLS; - } + fdt_reg_cells ( &sysfdt, offset, &dt->regs ); /* Probe device */ if ( ( rc = dt_probe ( dt, offset ) ) != 0 ) diff --git a/src/include/ipxe/devtree.h b/src/include/ipxe/devtree.h index 04414f370..6e9286af4 100644 --- a/src/include/ipxe/devtree.h +++ b/src/include/ipxe/devtree.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include /** A devicetree device */ struct dt_device { @@ -25,18 +26,10 @@ struct dt_device { /** Driver-private data */ void *priv; - /** Number of address cells for child devices */ - uint32_t address_cells; - /** Number of size cells for child devices */ - uint32_t size_cells; + /** Register cell size specification */ + struct fdt_reg_cells regs; }; -/** Default number of address cells, if not specified */ -#define DT_DEFAULT_ADDRESS_CELLS 2 - -/** Default number of size cells, if not specified */ -#define DT_DEFAULT_SIZE_CELLS 1 - /** A devicetree driver */ struct dt_driver { /** Driver name */ diff --git a/src/include/ipxe/fdt.h b/src/include/ipxe/fdt.h index fb0ae7752..6c58f5687 100644 --- a/src/include/ipxe/fdt.h +++ b/src/include/ipxe/fdt.h @@ -124,6 +124,22 @@ struct fdt_descriptor { int depth; }; +/** A device tree region cell size specification */ +struct fdt_reg_cells { + /** Number of address cells */ + uint32_t address_cells; + /** Number of size cells */ + uint32_t size_cells; + /** Number of address cells plus number of size cells */ + unsigned int stride; +}; + +/** Default number of address cells, if not specified */ +#define FDT_DEFAULT_ADDRESS_CELLS 2 + +/** Default number of size cells, if not specified */ +#define FDT_DEFAULT_SIZE_CELLS 1 + extern struct image_tag fdt_image __image_tag; extern struct fdt sysfdt; @@ -144,6 +160,16 @@ extern int fdt_u64 ( struct fdt *fdt, unsigned int offset, const char *name, uint64_t *value ); extern int fdt_u32 ( struct fdt *fdt, unsigned int offset, const char *name, uint32_t *value ); +extern void fdt_reg_cells ( struct fdt *fdt, unsigned int offset, + struct fdt_reg_cells *regs ); +extern int fdt_reg_count ( struct fdt *fdt, unsigned int offset, + struct fdt_reg_cells *regs ); +extern int fdt_reg_address ( struct fdt *fdt, unsigned int offset, + struct fdt_reg_cells *regs, unsigned int index, + uint64_t *address ); +extern int fdt_reg_size ( struct fdt *fdt, unsigned int offset, + struct fdt_reg_cells *regs, unsigned int index, + uint64_t *size ); extern int fdt_mac ( struct fdt *fdt, unsigned int offset, struct net_device *netdev ); extern int fdt_parse ( struct fdt *fdt, struct fdt_header *hdr, -- cgit v1.2.3-55-g7522 From bb2011241fcf15de4edd962fe9656e513ad19174 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 30 May 2025 16:39:10 +0100 Subject: [dt] Locate parent node at point of use in dt_ioremap() We currently rely on the recursive nature of devicetree bus probing to obtain the region cell size specification from the parent device. This blocks the possibility of creating a standalone console device based on /chosen/stdout-path before probing the whole bus. Fix by using fdt_parent() to locate the parent device at the point of use within dt_ioremap(). Signed-off-by: Michael Brown --- src/drivers/bus/devtree.c | 24 +++++++++++++++--------- src/include/ipxe/devtree.h | 3 --- 2 files changed, 15 insertions(+), 12 deletions(-) (limited to 'src/include/ipxe/devtree.h') diff --git a/src/drivers/bus/devtree.c b/src/drivers/bus/devtree.c index 55f68ee33..796fe56a0 100644 --- a/src/drivers/bus/devtree.c +++ b/src/drivers/bus/devtree.c @@ -54,16 +54,25 @@ static void dt_remove_children ( struct dt_device *parent ); */ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, unsigned int index, size_t len ) { - struct dt_device *parent = - container_of ( dt->dev.parent, struct dt_device, dev ); - struct fdt_reg_cells *regs = &parent->regs; + struct fdt_reg_cells regs; + unsigned int parent; uint64_t address; uint64_t size; void *io_addr; int rc; + /* Get parent node */ + if ( ( rc = fdt_parent ( &sysfdt, offset, &parent ) ) != 0 ) { + DBGC ( dt, "DT %s could not locate parent: %s\n", + dt->path, strerror ( rc ) ); + return NULL; + } + + /* Read #address-cells and #size-cells, if present */ + fdt_reg_cells ( &sysfdt, parent, ®s ); + /* Read address */ - if ( ( rc = fdt_reg_address ( &sysfdt, offset, regs, index, + if ( ( rc = fdt_reg_address ( &sysfdt, offset, ®s, index, &address ) ) != 0 ) { DBGC ( dt, "DT %s could not read region %d address: %s\n", dt->path, index, strerror ( rc ) ); @@ -72,8 +81,8 @@ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, /* Read size (or assume sufficient, if tree specifies no sizes) */ size = len; - if ( regs->size_cells && - ( ( rc = fdt_reg_size ( &sysfdt, offset, regs, index, + if ( regs.size_cells && + ( ( rc = fdt_reg_size ( &sysfdt, offset, ®s, index, &size ) ) != 0 ) ) { DBGC ( dt, "DT %s could not read region %d size: %s\n", dt->path, index, strerror ( rc ) ); @@ -222,9 +231,6 @@ static int dt_probe_node ( struct dt_device *parent, unsigned int offset, INIT_LIST_HEAD ( &dt->dev.children ); list_add_tail ( &dt->dev.siblings, &dt->dev.parent->children ); - /* Read #address-cells and #size-cells, if present */ - fdt_reg_cells ( &sysfdt, offset, &dt->regs ); - /* Probe device */ if ( ( rc = dt_probe ( dt, offset ) ) != 0 ) goto err_probe; diff --git a/src/include/ipxe/devtree.h b/src/include/ipxe/devtree.h index 6e9286af4..46d8b5d86 100644 --- a/src/include/ipxe/devtree.h +++ b/src/include/ipxe/devtree.h @@ -25,9 +25,6 @@ struct dt_device { struct dt_driver *driver; /** Driver-private data */ void *priv; - - /** Register cell size specification */ - struct fdt_reg_cells regs; }; /** A devicetree driver */ -- cgit v1.2.3-55-g7522 From c4a3d438e642e8692e6a94e7d4db75d75744e73f Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 11 Jun 2025 12:51:56 +0100 Subject: [dt] Allow for creation of standalone devices We will want to be able to create the console device as early as possible. Refactor devicetree probing to remove the assumption that a devicetree device must have a devicetree parent, and expose functions to allow a standalone device to be created given only the offset of a node within the tree. The full device path is no longer trivial to construct with this assumption removed. The full path is currently used only for debug messages. Remove the stored full path, use just the node name for debug messages, and ensure that the topology information previously visible in the full path is reconstructible from the combined debug output if needed. Signed-off-by: Michael Brown --- src/drivers/bus/devtree.c | 111 +++++++++++++++++++++------------------------ src/include/ipxe/devtree.h | 6 ++- 2 files changed, 56 insertions(+), 61 deletions(-) (limited to 'src/include/ipxe/devtree.h') diff --git a/src/drivers/bus/devtree.c b/src/drivers/bus/devtree.c index 796fe56a0..59bbc9bf7 100644 --- a/src/drivers/bus/devtree.c +++ b/src/drivers/bus/devtree.c @@ -39,7 +39,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include static struct dt_driver dt_node_driver __dt_driver; -struct root_device dt_root_device __root_device; static void dt_remove_children ( struct dt_device *parent ); @@ -64,7 +63,7 @@ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, /* Get parent node */ if ( ( rc = fdt_parent ( &sysfdt, offset, &parent ) ) != 0 ) { DBGC ( dt, "DT %s could not locate parent: %s\n", - dt->path, strerror ( rc ) ); + dt->name, strerror ( rc ) ); return NULL; } @@ -75,7 +74,7 @@ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, if ( ( rc = fdt_reg_address ( &sysfdt, offset, ®s, index, &address ) ) != 0 ) { DBGC ( dt, "DT %s could not read region %d address: %s\n", - dt->path, index, strerror ( rc ) ); + dt->name, index, strerror ( rc ) ); return NULL; } @@ -85,7 +84,7 @@ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, ( ( rc = fdt_reg_size ( &sysfdt, offset, ®s, index, &size ) ) != 0 ) ) { DBGC ( dt, "DT %s could not read region %d size: %s\n", - dt->path, index, strerror ( rc ) ); + dt->name, index, strerror ( rc ) ); return NULL; } @@ -93,13 +92,13 @@ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, if ( ! len ) len = size; DBGC ( dt, "DT %s region %d at %#08llx+%#04llx\n", - dt->path, index, ( ( unsigned long long ) address ), + dt->name, index, ( ( unsigned long long ) address ), ( ( unsigned long long ) size ) ); /* Verify size */ if ( len > size ) { DBGC ( dt, "DT %s region %d is too small (%#llx/%#zx bytes)\n", - dt->path, index, ( ( unsigned long long ) size ), len ); + dt->name, index, ( ( unsigned long long ) size ), len ); return NULL; } @@ -107,7 +106,7 @@ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, io_addr = ioremap ( address, len ); if ( ! io_addr ) { DBGC ( dt, "DT %s could not map region %d\n", - dt->path, index ); + dt->name, index ); return NULL; } @@ -119,7 +118,7 @@ void * dt_ioremap ( struct dt_device *dt, unsigned int offset, * * @v dt Devicetree device * @v offset Starting node offset - * @ret driver Driver, or NULL + * @ret driver Driver */ static struct dt_driver * dt_find_driver ( struct dt_device *dt, unsigned int offset ) { @@ -135,19 +134,20 @@ static struct dt_driver * dt_find_driver ( struct dt_device *dt, /* Look for a compatible driver */ for ( id = ids ; count-- ; id += ( strlen ( id ) + 1 ) ) { DBGC2 ( &sysfdt, "DT %s is compatible with %s\n", - dt->path, id ); + dt->name, id ); for_each_table_entry ( driver, DT_DRIVERS ) { for ( i = 0 ; i < driver->id_count ; i++ ) { if ( strcmp ( id, driver->ids[i] ) == 0 ) { DBGC ( dt, "DT %s has %s driver %s\n", - dt->path, id, driver->name ); + dt->name, id, driver->name ); return driver; } } } } - return NULL; + /* Use generic node driver if no other driver matches */ + return &dt_node_driver; } /** @@ -158,19 +158,11 @@ static struct dt_driver * dt_find_driver ( struct dt_device *dt, * @ret rc Return status code */ static int dt_probe ( struct dt_device *dt, unsigned int offset ) { - struct dt_driver *driver = NULL; + struct dt_driver *driver; int rc; - /* Identify driver. Use the generic node driver if no other - * driver matches, or if this is the root device (which has no - * valid devicetree parent). - */ - if ( offset > 0 ) - driver = dt_find_driver ( dt, offset ); - if ( ! driver ) - driver = &dt_node_driver; - - /* Record driver */ + /* Identify driver */ + driver = dt_find_driver ( dt, offset ); dt->driver = driver; dt->dev.driver_name = driver->name; @@ -178,7 +170,7 @@ static int dt_probe ( struct dt_device *dt, unsigned int offset ) { if ( ( rc = driver->probe ( dt, offset ) ) != 0 ) { if ( driver != &dt_node_driver ) { DBGC ( dt, "DT %s could not probe: %s\n", - dt->path, strerror ( rc ) ); + dt->name, strerror ( rc ) ); } return rc; } @@ -192,44 +184,44 @@ static int dt_probe ( struct dt_device *dt, unsigned int offset ) { * @v dt Devicetree device */ static void dt_remove ( struct dt_device *dt ) { + struct dt_driver *driver = dt->driver; /* Remove device */ - dt->driver->remove ( dt ); + driver->remove ( dt ); + if ( driver != &dt_node_driver ) + DBGC ( dt, "DT %s removed\n", dt->name ); } /** * Probe devicetree node * - * @v parent Parent device, or NULL for root of tree + * @v parent Parent generic device * @v offset Starting node offset - * @v name Node name * @ret rc Return status code */ -static int dt_probe_node ( struct dt_device *parent, unsigned int offset, - const char *name ) { +int dt_probe_node ( struct device *parent, unsigned int offset ) { + struct fdt_descriptor desc; struct dt_device *dt; - const char *ppath; - size_t path_len; - void *path; + const char *name; int rc; + /* Describe token */ + if ( ( rc = fdt_describe ( &sysfdt, offset, &desc ) ) != 0 ) + goto err_describe; + /* Allocate and initialise device */ - ppath = ( ( parent && parent->path[1] ) ? parent->path : "" ); - path_len = ( strlen ( ppath ) + 1 /* "/" */ + - strlen ( name ) + 1 /* NUL */ ); - dt = zalloc ( sizeof ( *dt ) + path_len ); + dt = zalloc ( sizeof ( *dt ) ); if ( ! dt ) { rc = -ENOMEM; goto err_alloc; } - path = ( ( ( void * ) dt ) + sizeof ( *dt ) ); - sprintf ( path, "%s/%s", ppath, name ); - dt->path = path; + name = ( offset ? desc.name : "root node" ); + dt->name = dt->dev.name; snprintf ( dt->dev.name, sizeof ( dt->dev.name ), "%s", name ); dt->dev.desc.bus_type = BUS_TYPE_DT; - dt->dev.parent = ( parent ? &parent->dev : &dt_root_device.dev ); + dt->dev.parent = parent; INIT_LIST_HEAD ( &dt->dev.children ); - list_add_tail ( &dt->dev.siblings, &dt->dev.parent->children ); + list_add_tail ( &dt->dev.siblings, &parent->children ); /* Probe device */ if ( ( rc = dt_probe ( dt, offset ) ) != 0 ) @@ -242,15 +234,22 @@ static int dt_probe_node ( struct dt_device *parent, unsigned int offset, list_del ( &dt->dev.siblings ); free ( dt ); err_alloc: + err_describe: return rc; } /** * Remove devicetree node * - * @v dt Devicetree device + * @v parent Parent generic device */ -static void dt_remove_node ( struct dt_device *dt ) { +void dt_remove_node ( struct device *parent ) { + struct dt_device *dt; + + /* Identify most recently added child */ + dt = list_last_entry ( &parent->children, struct dt_device, + dev.siblings ); + assert ( dt != NULL ); /* Remove driver */ dt_remove ( dt ); @@ -279,7 +278,7 @@ static int dt_probe_children ( struct dt_device *parent, /* Describe token */ if ( ( rc = fdt_describe ( &sysfdt, offset, &desc ) ) != 0 ) { DBGC ( &sysfdt, "DT %s has malformed node: %s\n", - parent->path, strerror ( rc ) ); + parent->name, strerror ( rc ) ); goto err_describe; } @@ -288,8 +287,11 @@ static int dt_probe_children ( struct dt_device *parent, break; /* Probe child node, if applicable */ - if ( ( depth == 0 ) && desc.name && ( ! desc.data ) ) - dt_probe_node ( parent, desc.offset, desc.name ); + if ( ( depth == 0 ) && desc.name && ( ! desc.data ) ) { + DBGC2 ( &sysfdt, "DT %s is child of %s\n", + desc.name, parent->name ); + dt_probe_node ( &parent->dev, desc.offset ); + } } /* Fail if we have no children (so that this device will be freed) */ @@ -312,15 +314,10 @@ static int dt_probe_children ( struct dt_device *parent, * @v parent Parent device */ static void dt_remove_children ( struct dt_device *parent ) { - struct dt_device *dt; - struct dt_device *tmp; /* Remove all child nodes */ - list_for_each_entry_safe ( dt, tmp, &parent->dev.children, - dev.siblings ) { - dt_remove_node ( dt ); - } - assert ( list_empty ( &parent->dev.children ) ); + while ( ! list_empty ( &parent->dev.children ) ) + dt_remove_node ( &parent->dev ); } /** Generic node driver */ @@ -336,10 +333,10 @@ static struct dt_driver dt_node_driver __dt_driver = { * @v rootdev Devicetree root device * @ret rc Return status code */ -static int dt_probe_all ( struct root_device *rootdev __unused ) { +static int dt_probe_all ( struct root_device *rootdev ) { /* Probe root node */ - return dt_probe_node ( NULL, 0, "" ); + return dt_probe_node ( &rootdev->dev, 0 ); } /** @@ -348,13 +345,9 @@ static int dt_probe_all ( struct root_device *rootdev __unused ) { * @v rootdev Devicetree root device */ static void dt_remove_all ( struct root_device *rootdev ) { - struct dt_device *dt; /* Remove root node */ - dt = list_first_entry ( &rootdev->dev.children, struct dt_device, - dev.siblings ); - assert ( dt != NULL ); - dt_remove_node ( dt ); + dt_remove_node ( &rootdev->dev ); } /** Devicetree bus root device driver */ diff --git a/src/include/ipxe/devtree.h b/src/include/ipxe/devtree.h index 46d8b5d86..cdf173938 100644 --- a/src/include/ipxe/devtree.h +++ b/src/include/ipxe/devtree.h @@ -15,12 +15,12 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** A devicetree device */ struct dt_device { + /** Device name */ + const char *name; /** Generic device */ struct device dev; /** DMA device */ struct dma_device dma; - /** Device path */ - const char *path; /** Driver for this device */ struct dt_driver *driver; /** Driver-private data */ @@ -79,5 +79,7 @@ static inline void * dt_get_drvdata ( struct dt_device *dt ) { 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 ); #endif /* _IPXE_DEVTREE_H */ -- cgit v1.2.3-55-g7522 From 2e4e1f7e9e474f6555009238304780448d3ea238 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 5 Aug 2025 13:55:23 +0100 Subject: [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 (limited to 'src/include/ipxe/devtree.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 ) -- cgit v1.2.3-55-g7522