Add a uclass for AXI (Advanced eXtensible Interface) busses, and a driver for the gdsys IHS AXI bus on IHS FPGAs.
Signed-off-by: Mario Six <mario....@gdsys.cc> --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/axi/Kconfig | 24 ++++++ drivers/axi/Makefile | 9 +++ drivers/axi/axi-uclass.c | 40 ++++++++++ drivers/axi/ihs_axi.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++ include/axi.h | 75 ++++++++++++++++++ include/dm/uclass-id.h | 1 + 8 files changed, 351 insertions(+) create mode 100644 drivers/axi/Kconfig create mode 100644 drivers/axi/Makefile create mode 100644 drivers/axi/axi-uclass.c create mode 100644 drivers/axi/ihs_axi.c create mode 100644 include/axi.h diff --git a/drivers/Kconfig b/drivers/Kconfig index c2e813f5ad..eeaaa7575c 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -8,6 +8,8 @@ source "drivers/adc/Kconfig" source "drivers/ata/Kconfig" +source "drivers/axi/Kconfig" + source "drivers/block/Kconfig" source "drivers/bootcount/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 6846d181aa..f54a10f3ad 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -100,6 +100,7 @@ obj-y += input/ obj-y += soc/ obj-$(CONFIG_REMOTEPROC) += remoteproc/ obj-y += thermal/ +obj-y += axi/ obj-$(CONFIG_MACH_PIC32) += ddr/microchip/ endif diff --git a/drivers/axi/Kconfig b/drivers/axi/Kconfig new file mode 100644 index 0000000000..19e1b7fd2f --- /dev/null +++ b/drivers/axi/Kconfig @@ -0,0 +1,24 @@ +menuconfig AXI + bool "AXI bus drivers" + help + Support AXI (Advanced eXtensible Interface) busses, a on-chip + interconnect specification for managing functional blocks in SoC + designs, which is also often used in designs involving FPGAs (e.g. + communication with IP cores in Xilinx FPGAs). + + These types of busses expose a virtual address space that can be + accessed using different address widths (8, 16, and 32 are supported + for now). + + Other similar bus architectures may be compatible as well. + +if AXI + +config IHS_AXI + bool "Enable IHS AXI driver" + depends on DM + help + Support for IHS AXI bus on a gdsys IHS FPGA used to communicate with + IP cores in the FPGA (e.g. video transmitter cores). + +endif diff --git a/drivers/axi/Makefile b/drivers/axi/Makefile new file mode 100644 index 0000000000..18d9380e9b --- /dev/null +++ b/drivers/axi/Makefile @@ -0,0 +1,9 @@ +# +# (C) Copyright 2017 +# Mario Six, Guntermann & Drunck GmbH, mario....@gdsys.cc +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-$(CONFIG_AXI) += axi-uclass.o +obj-$(CONFIG_IHS_AXI) += ihs_axi.o diff --git a/drivers/axi/axi-uclass.c b/drivers/axi/axi-uclass.c new file mode 100644 index 0000000000..3a5ddf3a46 --- /dev/null +++ b/drivers/axi/axi-uclass.c @@ -0,0 +1,40 @@ +/* + * (C) Copyright 2017 + * Mario Six, Guntermann & Drunck GmbH, mario....@gdsys.cc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <axi.h> + +int axi_read(struct udevice *dev, ulong address, void *data, + enum axi_size_t size) +{ + struct axi_ops *ops = axi_get_ops(dev); + + if (!ops->read) + return -ENOSYS; + + return ops->read(dev, address, data, size); +} + +int axi_write(struct udevice *dev, ulong address, void *data, + enum axi_size_t size) +{ + struct axi_ops *ops = axi_get_ops(dev); + + if (!ops->write) + return -ENOSYS; + + return ops->write(dev, address, data, size); +} + +UCLASS_DRIVER(axi) = { + .id = UCLASS_AXI, + .name = "axi", + .post_bind = dm_scan_fdt_dev, + .flags = DM_UC_FLAG_SEQ_ALIAS, +}; + diff --git a/drivers/axi/ihs_axi.c b/drivers/axi/ihs_axi.c new file mode 100644 index 0000000000..71bb67421f --- /dev/null +++ b/drivers/axi/ihs_axi.c @@ -0,0 +1,199 @@ +/* + * (C) Copyright 2016 + * Dirk Eibach, Guntermann & Drunck GmbH, dirk.eib...@gdsys.cc + * + * (C) Copyright 2017 + * Mario Six, Guntermann & Drunck GmbH, mario....@gdsys.cc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <axi.h> +#include <dm.h> +#include <fpgamap.h> +#include "../misc/gdsys_soc.h" + +DECLARE_GLOBAL_DATA_PTR; + +enum { + REG_INTERRUPT_STATUS = 0x00, + REG_INTERRUPT_ENABLE_CONTROL = 0x02, + REG_ADDRESS_LSB = 0x04, + REG_ADDRESS_MSB = 0x06, + REG_WRITE_DATA_LSB = 0x08, + REG_WRITE_DATA_MSB = 0x0A, + REG_READ_DATA_LSB = 0x0C, + REG_READ_DATA_MSB = 0x0E, +}; + +struct ihs_axi_priv { + fdt_addr_t addr; +}; + +enum { + STATUS_EVENT_MASK = GENMASK(15, 12), + + STATUS_READ_COMPLETE_EVENT = BIT(15), + STATUS_WRITE_COMPLETE_EVENT = BIT(14), + STATUS_TIMEOUT_EVENT = BIT(13), + STATUS_ERROR_EVENT = BIT(12), + STATUS_AXI_INT = BIT(11), + STATUS_READ_DATA_AVAILABLE = BIT(7), + STATUS_BUSY = BIT(6), + STATUS_INIT_DONE = BIT(5), +}; + +enum { + CONTROL_EVENT_ENABLE_MASK = GENMASK(15, 11), + CONTROL_CMD_MASK = GENMASK(3, 0), + + CONTROL_READ_COMPLETE_EVENT_ENABLE = BIT(15), + CONTROL_WRITE_COMPLETE_EVENT_ENABLE = BIT(14), + CONTROL_TIMEOUT_EVENT_ENABLE = BIT(13), + CONTROL_ERROR_EVENT_ENABLE = BIT(12), + CONTROL_AXI_INT_ENABLE = BIT(11), + + CONTROL_CMD_NOP = 0x0, + CONTROL_CMD_WRITE = 0x8, + CONTROL_CMD_WRITE_POST_INC = 0x9, + CONTROL_CMD_READ = 0xa, + CONTROL_CMD_READ_POST_INC = 0xb, + +}; + +static int ihs_axi_transfer(struct udevice *bus, ulong address, u16 cmd, + u16 complete_flag) +{ + struct ihs_axi_priv *priv = dev_get_priv(bus); + struct udevice *fpga; + u16 wait_mask = complete_flag | STATUS_TIMEOUT_EVENT | + STATUS_ERROR_EVENT; + u16 status; + u16 addr_low, addr_high; + uint k; + + gdsys_soc_get_fpga(bus, &fpga); + + cmd &= CONTROL_CMD_MASK; + + addr_low = address & 0xffff; + fpgamap_write(fpga, priv->addr + REG_ADDRESS_LSB, &addr_low, + FPGAMAP_SIZE_16); + addr_high = (address >> 16) & 0xffff; + fpgamap_write(fpga, priv->addr + REG_ADDRESS_MSB, &addr_high, + FPGAMAP_SIZE_16); + + fpgamap_write(fpga, priv->addr + REG_INTERRUPT_STATUS, &wait_mask, + FPGAMAP_SIZE_16); + fpgamap_write(fpga, priv->addr + REG_INTERRUPT_ENABLE_CONTROL, &cmd, + FPGAMAP_SIZE_16); + + for (k = 10; k > 0; --k) { + fpgamap_read(fpga, priv->addr + REG_INTERRUPT_STATUS, &status, + FPGAMAP_SIZE_16); + if (status & wait_mask) + break; + udelay(1); + } + + if (!k) + fpgamap_read(fpga, priv->addr + REG_INTERRUPT_STATUS, &status, + FPGAMAP_SIZE_16); + + if (status & complete_flag) + return 0; + + if (status & STATUS_ERROR_EVENT) + return -EIO; + + return -ETIMEDOUT; +} + +/* + * API + */ + +int ihs_axi_read(struct udevice *dev, ulong address, void *data, + enum axi_size_t size) +{ + struct ihs_axi_priv *priv = dev_get_priv(dev); + struct udevice *fpga; + int res = 0; + u16 data_lsb, data_msb; + u32 *p = data; + + if (size != AXI_SIZE_32) + return -ENOSYS; + + gdsys_soc_get_fpga(dev, &fpga); + + res = ihs_axi_transfer(dev, address, CONTROL_CMD_READ, + STATUS_READ_COMPLETE_EVENT); + if (res < 0) + return res; + + fpgamap_read(fpga, priv->addr + REG_READ_DATA_LSB, &data_lsb, + FPGAMAP_SIZE_16); + fpgamap_read(fpga, priv->addr + REG_READ_DATA_MSB, &data_msb, + FPGAMAP_SIZE_16); + + *p = (data_msb << 16) | data_lsb; + + return res; +} + +int ihs_axi_write(struct udevice *dev, ulong address, void *data, + enum axi_size_t size) +{ + struct ihs_axi_priv *priv = dev_get_priv(dev); + struct udevice *fpga; + int res = 0; + u32 *p = data; + u16 data_low, data_high; + + if (size != AXI_SIZE_32) + return -ENOSYS; + + gdsys_soc_get_fpga(dev, &fpga); + + data_low = *p & 0xffff; + fpgamap_write(fpga, priv->addr + REG_WRITE_DATA_LSB, &data_low, + FPGAMAP_SIZE_16); + data_high = (*p >> 16) & 0xffff; + fpgamap_write(fpga, priv->addr + REG_WRITE_DATA_MSB, &data_high, + FPGAMAP_SIZE_16); + + res = ihs_axi_transfer(dev, address, CONTROL_CMD_WRITE, + STATUS_WRITE_COMPLETE_EVENT); + + return res; +} + +static const struct udevice_id ihs_axi_ids[] = { + { .compatible = "gdsys,ihs_axi" }, + { /* sentinel */ } +}; + +static const struct axi_ops ihs_axi_ops = { + .read = ihs_axi_read, + .write = ihs_axi_write, +}; + +int ihs_axi_probe(struct udevice *dev) +{ + struct ihs_axi_priv *priv = dev_get_priv(dev); + + priv->addr = dev_read_u32_default(dev, "reg", -1); + + return 0; +} + +U_BOOT_DRIVER(ihs_axi_bus) = { + .name = "ihs_axi_bus", + .id = UCLASS_AXI, + .of_match = ihs_axi_ids, + .ops = &ihs_axi_ops, + .priv_auto_alloc_size = sizeof(struct ihs_axi_priv), + .probe = ihs_axi_probe, +}; diff --git a/include/axi.h b/include/axi.h new file mode 100644 index 0000000000..317e931a6c --- /dev/null +++ b/include/axi.h @@ -0,0 +1,75 @@ +/* + * (C) Copyright 2017 + * Mario Six, Guntermann & Drunck GmbH, mario....@gdsys.cc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _AXI_H_ +#define _AXI_H_ + +enum axi_size_t { + AXI_SIZE_8, + AXI_SIZE_16, + AXI_SIZE_32, +}; + +/** + * struct axi_ops - driver operations for AXI uclass + * + * Drivers should support these operations unless otherwise noted. These + * operations are intended to be used by uclass code, not directly from + * other code. + */ +struct axi_ops { + /** + * read() - Read a single value from a specified address on a AXI bus + * + * @dev: AXI bus to read from. + * @address: The address to read from. + * @data: Pointer to a variable that takes the data value read + * from the address on the AXI bus. + * @size: The size of the data to be read. + * @return 0 if OK, -ve on error. + */ + int (*read)(struct udevice *dev, ulong address, void *data, enum axi_size_t size); + + /** + * write() - Write a single value to a specified address on a AXI bus + * + * @dev: AXI bus to write to. + * @address: The address to write to. + * @data: Pointer to the data value to be written to the address + * on the AXI bus. + * @size: The size of the data to write. + * @return 0 if OK, -ve on error. + */ + int (*write)(struct udevice *dev, ulong address, void *data, enum axi_size_t size); +}; + +#define axi_get_ops(dev) ((struct axi_ops *)(dev)->driver->ops) + +/** + * axi_read() - Read a single value from a specified address on a AXI bus + * + * @dev: AXI bus to read from. + * @address: The address to read from. + * @data: Pointer to a variable that takes the data value read from the + * address on the AXI bus. + * @size: The size of the data to write. + * @return 0 if OK, -ve on error. + */ +int axi_read(struct udevice *dev, ulong address, void *data, enum axi_size_t size); + +/** + * axi_write() - Write a single value to a specified address on a AXI bus + * + * @dev: AXI bus to write to. + * @address: The address to write to. + * @data: Pointer to the data value to be written to the address on the + * AXI bus. + * @size: The size of the data to write. + * @return 0 if OK, -ve on error. + */ +int axi_write(struct udevice *dev, ulong address, void *data, enum axi_size_t size); +#endif diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 07fabc3ce6..9857796e54 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -43,6 +43,7 @@ enum uclass_id { UCLASS_I2C_GENERIC, /* Generic I2C device */ UCLASS_I2C_MUX, /* I2C multiplexer */ UCLASS_IDE, /* IDE device */ + UCLASS_AXI, /* AXI busses */ UCLASS_IRQ, /* Interrupt controller */ UCLASS_KEYBOARD, /* Keyboard input device */ UCLASS_LED, /* Light-emitting diode (LED) */ -- 2.16.1 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de https://lists.denx.de/listinfo/u-boot