diff options
Diffstat (limited to 'drivers/usb/host')
-rw-r--r-- | drivers/usb/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/host/ehci-exynos.c | 51 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 318 | ||||
-rw-r--r-- | drivers/usb/host/ehci-pci.c | 60 | ||||
-rw-r--r-- | drivers/usb/host/ehci-spear.c | 59 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 6 |
6 files changed, 455 insertions, 40 deletions
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 6c94794..9a6f982 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -54,6 +54,7 @@ COBJS-$(CONFIG_USB_EHCI_PPC4XX) += ehci-ppc4xx.o COBJS-$(CONFIG_USB_EHCI_IXP4XX) += ehci-ixp.o COBJS-$(CONFIG_USB_EHCI_MARVELL) += ehci-marvell.o COBJS-$(CONFIG_USB_EHCI_PCI) += ehci-pci.o +COBJS-$(CONFIG_USB_EHCI_SPEAR) += ehci-spear.o COBJS-$(CONFIG_USB_EHCI_TEGRA) += ehci-tegra.o COBJS-$(CONFIG_USB_EHCI_VCT) += ehci-vct.o diff --git a/drivers/usb/host/ehci-exynos.c b/drivers/usb/host/ehci-exynos.c index 3ca4c5c..0c797aa 100644 --- a/drivers/usb/host/ehci-exynos.c +++ b/drivers/usb/host/ehci-exynos.c @@ -42,11 +42,15 @@ DECLARE_GLOBAL_DATA_PTR; */ struct exynos_ehci { struct exynos_usb_phy *usb; - unsigned int *hcd; + struct ehci_hccr *hcd; }; +static struct exynos_ehci exynos; + +#ifdef CONFIG_OF_CONTROL static int exynos_usb_parse_dt(const void *blob, struct exynos_ehci *exynos) { + fdt_addr_t addr; unsigned int node; int depth; @@ -59,12 +63,14 @@ static int exynos_usb_parse_dt(const void *blob, struct exynos_ehci *exynos) /* * Get the base address for EHCI controller from the device node */ - exynos->hcd = (unsigned int *)fdtdec_get_addr(blob, node, "reg"); - if (exynos->hcd == NULL) { + addr = fdtdec_get_addr(blob, node, "reg"); + if (addr == FDT_ADDR_T_NONE) { debug("Can't get the EHCI register address\n"); return -ENXIO; } + exynos->hcd = (struct ehci_hccr *)addr; + depth = 0; node = fdtdec_next_compatible_subnode(blob, node, COMPAT_SAMSUNG_EXYNOS_USB_PHY, &depth); @@ -85,6 +91,7 @@ static int exynos_usb_parse_dt(const void *blob, struct exynos_ehci *exynos) return 0; } +#endif /* Setup the EHCI host controller. */ static void setup_usb_phy(struct exynos_usb_phy *usb) @@ -144,20 +151,21 @@ static void reset_usb_phy(struct exynos_usb_phy *usb) */ int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor) { - struct exynos_ehci *exynos = NULL; + struct exynos_ehci *ctx = &exynos; - exynos = (struct exynos_ehci *) - kzalloc(sizeof(struct exynos_ehci), GFP_KERNEL); - if (!exynos) { - debug("failed to allocate exynos ehci context\n"); - return -ENOMEM; +#ifdef CONFIG_OF_CONTROL + if (exynos_usb_parse_dt(gd->fdt_blob, ctx)) { + debug("Unable to parse device tree for ehci-exynos\n"); + return -ENODEV; } +#else + ctx->usb = (struct exynos_usb_phy *)samsung_get_base_usb_phy(); + ctx->hcd = (struct ehci_hccr *)samsung_get_base_usb_ehci(); +#endif - exynos_usb_parse_dt(gd->fdt_blob, exynos); + setup_usb_phy(ctx->usb); - setup_usb_phy(exynos->usb); - - *hccr = (struct ehci_hccr *)(exynos->hcd); + *hccr = ctx->hcd; *hcor = (struct ehci_hcor *)((uint32_t) *hccr + HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase))); @@ -165,8 +173,6 @@ int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor) (uint32_t)*hccr, (uint32_t)*hcor, (uint32_t)HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase))); - kfree(exynos); - return 0; } @@ -176,20 +182,9 @@ int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor) */ int ehci_hcd_stop(int index) { - struct exynos_ehci *exynos = NULL; - - exynos = (struct exynos_ehci *) - kzalloc(sizeof(struct exynos_ehci), GFP_KERNEL); - if (!exynos) { - debug("failed to allocate exynos ehci context\n"); - return -ENOMEM; - } - - exynos_usb_parse_dt(gd->fdt_blob, exynos); - - reset_usb_phy(exynos->usb); + struct exynos_ehci *ctx = &exynos; - kfree(exynos); + reset_usb_phy(ctx->usb); return 0; } diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 7f98a63..c816878 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -21,12 +21,14 @@ * MA 02111-1307 USA */ #include <common.h> +#include <errno.h> #include <asm/byteorder.h> #include <asm/unaligned.h> #include <usb.h> #include <asm/io.h> #include <malloc.h> #include <watchdog.h> +#include <linux/compiler.h> #include "ehci.h" @@ -39,7 +41,10 @@ static struct ehci_ctrl { struct ehci_hcor *hcor; int rootdev; uint16_t portreset; - struct QH qh_list __attribute__((aligned(USB_DMA_MINALIGN))); + struct QH qh_list __aligned(USB_DMA_MINALIGN); + struct QH periodic_queue __aligned(USB_DMA_MINALIGN); + uint32_t *periodic_list; + int ntds; } ehcic[CONFIG_USB_MAX_CONTROLLER_COUNT]; #define ALIGN_END_ADDR(type, ptr, size) \ @@ -858,6 +863,8 @@ int usb_lowlevel_init(int index, void **controller) uint32_t reg; uint32_t cmd; struct QH *qh_list; + struct QH *periodic; + int i; if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor)) return -1; @@ -870,6 +877,9 @@ int usb_lowlevel_init(int index, void **controller) if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor)) return -1; #endif + /* Set the high address word (aka segment) for 64-bit controller */ + if (ehci_readl(&ehcic[index].hccr->cr_hccparams) & 1) + ehci_writel(ehcic[index].hcor->or_ctrldssegment, 0); qh_list = &ehcic[index].qh_list; @@ -884,6 +894,40 @@ int usb_lowlevel_init(int index, void **controller) qh_list->qh_overlay.qt_token = cpu_to_hc32(QT_TOKEN_STATUS(QT_TOKEN_STATUS_HALTED)); + /* Set async. queue head pointer. */ + ehci_writel(&ehcic[index].hcor->or_asynclistaddr, (uint32_t)qh_list); + + /* + * Set up periodic list + * Step 1: Parent QH for all periodic transfers. + */ + periodic = &ehcic[index].periodic_queue; + memset(periodic, 0, sizeof(*periodic)); + periodic->qh_link = cpu_to_hc32(QH_LINK_TERMINATE); + periodic->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + periodic->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + + /* + * Step 2: Setup frame-list: Every microframe, USB tries the same list. + * In particular, device specifications on polling frequency + * are disregarded. Keyboards seem to send NAK/NYet reliably + * when polled with an empty buffer. + * + * Split Transactions will be spread across microframes using + * S-mask and C-mask. + */ + ehcic[index].periodic_list = memalign(4096, 1024*4); + if (!ehcic[index].periodic_list) + return -ENOMEM; + for (i = 0; i < 1024; i++) { + ehcic[index].periodic_list[i] = (uint32_t)periodic + | QH_LINK_TYPE_QH; + } + + /* Set periodic list base address */ + ehci_writel(&ehcic[index].hcor->or_periodiclistbase, + (uint32_t)ehcic[index].periodic_list); + reg = ehci_readl(&ehcic[index].hccr->cr_hcsparams); descriptor.hub.bNbrPorts = HCS_N_PORTS(reg); debug("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts); @@ -953,10 +997,254 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, return ehci_submit_async(dev, pipe, buffer, length, setup); } +struct int_queue { + struct QH *first; + struct QH *current; + struct QH *last; + struct qTD *tds; +}; + +#define NEXT_QH(qh) (struct QH *)((qh)->qh_link & ~0x1f) + +static int +enable_periodic(struct ehci_ctrl *ctrl) +{ + uint32_t cmd; + struct ehci_hcor *hcor = ctrl->hcor; + int ret; + + cmd = ehci_readl(&hcor->or_usbcmd); + cmd |= CMD_PSE; + ehci_writel(&hcor->or_usbcmd, cmd); + + ret = handshake((uint32_t *)&hcor->or_usbsts, + STS_PSS, STS_PSS, 100 * 1000); + if (ret < 0) { + printf("EHCI failed: timeout when enabling periodic list\n"); + return -ETIMEDOUT; + } + udelay(1000); + return 0; +} + +static int +disable_periodic(struct ehci_ctrl *ctrl) +{ + uint32_t cmd; + struct ehci_hcor *hcor = ctrl->hcor; + int ret; + + cmd = ehci_readl(&hcor->or_usbcmd); + cmd &= ~CMD_PSE; + ehci_writel(&hcor->or_usbcmd, cmd); + + ret = handshake((uint32_t *)&hcor->or_usbsts, + STS_PSS, 0, 100 * 1000); + if (ret < 0) { + printf("EHCI failed: timeout when disabling periodic list\n"); + return -ETIMEDOUT; + } + return 0; +} + +static int periodic_schedules; + +struct int_queue * +create_int_queue(struct usb_device *dev, unsigned long pipe, int queuesize, + int elementsize, void *buffer) +{ + struct ehci_ctrl *ctrl = dev->controller; + struct int_queue *result = NULL; + int i; + + debug("Enter create_int_queue\n"); + if (usb_pipetype(pipe) != PIPE_INTERRUPT) { + debug("non-interrupt pipe (type=%lu)", usb_pipetype(pipe)); + return NULL; + } + + /* limit to 4 full pages worth of data - + * we can safely fit them in a single TD, + * no matter the alignment + */ + if (elementsize >= 16384) { + debug("too large elements for interrupt transfers\n"); + return NULL; + } + + result = malloc(sizeof(*result)); + if (!result) { + debug("ehci intr queue: out of memory\n"); + goto fail1; + } + result->first = memalign(32, sizeof(struct QH) * queuesize); + if (!result->first) { + debug("ehci intr queue: out of memory\n"); + goto fail2; + } + result->current = result->first; + result->last = result->first + queuesize - 1; + result->tds = memalign(32, sizeof(struct qTD) * queuesize); + if (!result->tds) { + debug("ehci intr queue: out of memory\n"); + goto fail3; + } + memset(result->first, 0, sizeof(struct QH) * queuesize); + memset(result->tds, 0, sizeof(struct qTD) * queuesize); + + for (i = 0; i < queuesize; i++) { + struct QH *qh = result->first + i; + struct qTD *td = result->tds + i; + void **buf = &qh->buffer; + + qh->qh_link = (uint32_t)(qh+1) | QH_LINK_TYPE_QH; + if (i == queuesize - 1) + qh->qh_link = QH_LINK_TERMINATE; + + qh->qh_overlay.qt_next = (uint32_t)td; + qh->qh_endpt1 = (0 << 28) | /* No NAK reload (ehci 4.9) */ + (usb_maxpacket(dev, pipe) << 16) | /* MPS */ + (1 << 14) | + QH_ENDPT1_EPS(ehci_encode_speed(dev->speed)) | + (usb_pipeendpoint(pipe) << 8) | /* Endpoint Number */ + (usb_pipedevice(pipe) << 0); + qh->qh_endpt2 = (1 << 30) | /* 1 Tx per mframe */ + (1 << 0); /* S-mask: microframe 0 */ + if (dev->speed == USB_SPEED_LOW || + dev->speed == USB_SPEED_FULL) { + debug("TT: port: %d, hub address: %d\n", + dev->portnr, dev->parent->devnum); + qh->qh_endpt2 |= (dev->portnr << 23) | + (dev->parent->devnum << 16) | + (0x1c << 8); /* C-mask: microframes 2-4 */ + } + + td->qt_next = QT_NEXT_TERMINATE; + td->qt_altnext = QT_NEXT_TERMINATE; + debug("communication direction is '%s'\n", + usb_pipein(pipe) ? "in" : "out"); + td->qt_token = (elementsize << 16) | + ((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */ + 0x80; /* active */ + td->qt_buffer[0] = (uint32_t)buffer + i * elementsize; + td->qt_buffer[1] = (td->qt_buffer[0] + 0x1000) & ~0xfff; + td->qt_buffer[2] = (td->qt_buffer[0] + 0x2000) & ~0xfff; + td->qt_buffer[3] = (td->qt_buffer[0] + 0x3000) & ~0xfff; + td->qt_buffer[4] = (td->qt_buffer[0] + 0x4000) & ~0xfff; + + *buf = buffer + i * elementsize; + } + + if (disable_periodic(ctrl) < 0) { + debug("FATAL: periodic should never fail, but did"); + goto fail3; + } + + /* hook up to periodic list */ + struct QH *list = &ctrl->periodic_queue; + result->last->qh_link = list->qh_link; + list->qh_link = (uint32_t)result->first | QH_LINK_TYPE_QH; + + if (enable_periodic(ctrl) < 0) { + debug("FATAL: periodic should never fail, but did"); + goto fail3; + } + periodic_schedules++; + + debug("Exit create_int_queue\n"); + return result; +fail3: + if (result->tds) + free(result->tds); +fail2: + if (result->first) + free(result->first); + if (result) + free(result); +fail1: + return NULL; +} + +void *poll_int_queue(struct usb_device *dev, struct int_queue *queue) +{ + struct QH *cur = queue->current; + + /* depleted queue */ + if (cur == NULL) { + debug("Exit poll_int_queue with completed queue\n"); + return NULL; + } + /* still active */ + if (cur->qh_overlay.qt_token & 0x80) { + debug("Exit poll_int_queue with no completed intr transfer. " + "token is %x\n", cur->qh_overlay.qt_token); + return NULL; + } + if (!(cur->qh_link & QH_LINK_TERMINATE)) + queue->current++; + else + queue->current = NULL; + debug("Exit poll_int_queue with completed intr transfer. " + "token is %x at %p (first at %p)\n", cur->qh_overlay.qt_token, + &cur->qh_overlay.qt_token, queue->first); + return cur->buffer; +} + +/* Do not free buffers associated with QHs, they're owned by someone else */ +int +destroy_int_queue(struct usb_device *dev, struct int_queue *queue) +{ + struct ehci_ctrl *ctrl = dev->controller; + int result = -1; + unsigned long timeout; + + if (disable_periodic(ctrl) < 0) { + debug("FATAL: periodic should never fail, but did"); + goto out; + } + periodic_schedules--; + + struct QH *cur = &ctrl->periodic_queue; + timeout = get_timer(0) + 500; /* abort after 500ms */ + while (!(cur->qh_link & QH_LINK_TERMINATE)) { + debug("considering %p, with qh_link %x\n", cur, cur->qh_link); + if (NEXT_QH(cur) == queue->first) { + debug("found candidate. removing from chain\n"); + cur->qh_link = queue->last->qh_link; + result = 0; + break; + } + cur = NEXT_QH(cur); + if (get_timer(0) > timeout) { + printf("Timeout destroying interrupt endpoint queue\n"); + result = -1; + goto out; + } + } + + if (periodic_schedules > 0) { + result = enable_periodic(ctrl); + if (result < 0) + debug("FATAL: periodic should never fail, but did"); + } + +out: + free(queue->tds); + free(queue->first); + free(queue); + + return result; +} + int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, int length, int interval) { + void *backbuffer; + struct int_queue *queue; + unsigned long timeout; + int result = 0, ret; + debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d", dev, pipe, buffer, length, interval); @@ -972,9 +1260,31 @@ submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, * not require more than a single qTD. */ if (length > usb_maxpacket(dev, pipe)) { - printf("%s: Interrupt transfers requiring several transactions " - "are not supported.\n", __func__); + printf("%s: Interrupt transfers requiring several " + "transactions are not supported.\n", __func__); return -1; } - return ehci_submit_async(dev, pipe, buffer, length, NULL); + + queue = create_int_queue(dev, pipe, 1, length, buffer); + + timeout = get_timer(0) + USB_TIMEOUT_MS(pipe); + while ((backbuffer = poll_int_queue(dev, queue)) == NULL) + if (get_timer(0) > timeout) { + printf("Timeout poll on interrupt endpoint\n"); + result = -ETIMEDOUT; + break; + } + + if (backbuffer != buffer) { + debug("got wrong buffer back (%x instead of %x)\n", + (uint32_t)backbuffer, (uint32_t)buffer); + return -EINVAL; + } + + ret = destroy_int_queue(dev, queue); + if (ret < 0) + return ret; + + /* everything worked out fine */ + return result; } diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 29af02d..90d7a6f 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -19,6 +19,7 @@ */ #include <common.h> +#include <errno.h> #include <pci.h> #include <usb.h> @@ -32,31 +33,76 @@ static struct pci_device_id ehci_pci_ids[] = { {0x12D8, 0x400F}, /* Pericom */ {0, 0} }; +#else +static pci_dev_t ehci_find_class(int index) +{ + int bus; + int devnum; + pci_dev_t bdf; + uint32_t class; + + for (bus = 0; bus <= pci_last_busno(); bus++) { + for (devnum = 0; devnum < PCI_MAX_PCI_DEVICES-1; devnum++) { + pci_read_config_dword(PCI_BDF(bus, devnum, 0), + PCI_CLASS_REVISION, &class); + if (class >> 16 == 0xffff) + continue; + + for (bdf = PCI_BDF(bus, devnum, 0); + bdf <= PCI_BDF(bus, devnum, + PCI_MAX_PCI_FUNCTIONS - 1); + bdf += PCI_BDF(0, 0, 1)) { + pci_read_config_dword(bdf, PCI_CLASS_REVISION, + &class); + if ((class >> 8 == PCI_CLASS_SERIAL_USB_EHCI) + && !index--) + return bdf; + } + } + } + + return -ENODEV; +} #endif /* * Create the appropriate control structures to manage * a new EHCI host controller. */ -int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor) +int ehci_hcd_init(int index, struct ehci_hccr **ret_hccr, + struct ehci_hcor **ret_hcor) { pci_dev_t pdev; + uint32_t cmd; + struct ehci_hccr *hccr; + struct ehci_hcor *hcor; +#ifdef CONFIG_PCI_EHCI_DEVICE pdev = pci_find_devices(ehci_pci_ids, CONFIG_PCI_EHCI_DEVICE); - if (pdev == -1) { +#else + pdev = ehci_find_class(index); +#endif + if (pdev < 0) { printf("EHCI host controller not found\n"); return -1; } - *hccr = (struct ehci_hccr *)pci_map_bar(pdev, + hccr = (struct ehci_hccr *)pci_map_bar(pdev, PCI_BASE_ADDRESS_0, PCI_REGION_MEM); - *hcor = (struct ehci_hcor *)((uint32_t) *hccr + - HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase))); + hcor = (struct ehci_hcor *)((uint32_t) hccr + + HC_LENGTH(ehci_readl(&hccr->cr_capbase))); debug("EHCI-PCI init hccr 0x%x and hcor 0x%x hc_length %d\n", - (uint32_t)*hccr, (uint32_t)*hcor, - (uint32_t)HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase))); + (uint32_t)hccr, (uint32_t)hcor, + (uint32_t)HC_LENGTH(ehci_readl(&hccr->cr_capbase))); + + *ret_hccr = hccr; + *ret_hcor = hcor; + /* enable busmaster */ + pci_read_config_dword(pdev, PCI_COMMAND, &cmd); + cmd |= PCI_COMMAND_MASTER; + pci_write_config_dword(pdev, PCI_COMMAND, cmd); return 0; } diff --git a/drivers/usb/host/ehci-spear.c b/drivers/usb/host/ehci-spear.c new file mode 100644 index 0000000..f99bd1f --- /dev/null +++ b/drivers/usb/host/ehci-spear.c @@ -0,0 +1,59 @@ +/* + * (C) Copyright 2010 + * Armando Visconti, ST Micoelectronics, <armando.visconti@st.com>. + * + * (C) Copyright 2009 + * Marvell Semiconductor <www.marvell.com> + * Written-by: Prafulla Wadaskar <prafulla@marvell.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 + */ + +#include <common.h> +#include <asm/io.h> +#include <usb.h> +#include "ehci.h" +#include <asm/arch/hardware.h> + + +/* + * Create the appropriate control structures to manage + * a new EHCI host controller. + */ +int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor) +{ + *hccr = (struct ehci_hccr *)(CONFIG_SYS_UHC0_EHCI_BASE + 0x100); + *hcor = (struct ehci_hcor *)((uint32_t)*hccr + + HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase))); + + debug("SPEAr-ehci: init hccr %x and hcor %x hc_length %d\n", + (uint32_t)*hccr, (uint32_t)*hcor, + (uint32_t)HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase))); + + return 0; +} + +/* + * Destroy the appropriate control structures corresponding + * the the EHCI host controller. + */ +int ehci_hcd_stop(int index) +{ + return 0; +} diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 1e3cd79..d090f0a 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -69,6 +69,7 @@ struct ehci_hcor { #define CMD_RUN (1 << 0) /* start/stop HC */ uint32_t or_usbsts; #define STS_ASS (1 << 15) +#define STS_PSS (1 << 14) #define STS_HALT (1 << 12) uint32_t or_usbintr; #define INTR_UE (1 << 0) /* USB interrupt enable */ @@ -245,7 +246,10 @@ struct QH { * Add dummy fill value to make the size of this struct * aligned to 32 bytes */ - uint8_t fill[16]; + union { + uint32_t fill[4]; + void *buffer; + }; }; /* Low level init functions */ |