usb: ehci: add Host Bus Manager (HBM) support
HBM is a new HW unit which transfers USB bulk data packets in
BAM to BAM mode between the USB BAM and a peer BAM in USB
host mode.
This ability reduces the SW interfere in host mode for data
packets transmission. This is done by the HBM HW which generates
"infinite" USB (bulk) transfers against the USB core.
This change adds the HBM SW driver.
Change-Id: I63febd699b1edca71a56a2d7d2b41f25d5e48447
Signed-off-by: Shimrit Malichi <smalichi@codeaurora.org>
diff --git a/drivers/usb/host/hbm.c b/drivers/usb/host/hbm.c
new file mode 100644
index 0000000..f516ad8
--- /dev/null
+++ b/drivers/usb/host/hbm.c
@@ -0,0 +1,282 @@
+/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/usb/hbm.h>
+#include <mach/usb_bam.h>
+
+/**
+ * USB HBM Hardware registers.
+ *
+ */
+#define USB_OTG_HS_HBM_CFG (0x00000290)
+#define USB_OTG_HS_HBM_QH_MAP_PIPE(n) (0x00000294 + 4 * (n))
+#define USB_OTG_HS_HBM_PIPE_PRODUCER (0x00000314)
+#define USB_OTG_HS_HBM_PARK_MODE_DISABLE (0x00000318)
+#define USB_OTG_HS_HBM_PIPE_ZLT_DISABLE (0x0000031C)
+#define USB_OTG_HS_HBM_PIPE_EN (0x00000310)
+#define USB_OTG_HS_HBM_SW_RST (0x00000324)
+#define USB_OTG_HS_HBM_SB_SW_RST (0x00000320)
+#define USB_OTG_HS_USBCMD (0x00000140)
+#define USB_OTG_HS_USBSTS (0x00000144)
+
+/**
+ * USB HBM Hardware registers bitmask.
+ */
+#define HBM_EN 0x00000001
+#define ASE 0x20
+#define AS 0x8000
+#define PIPE_PRODUCER 1
+#define MAX_PIPE_NUM 16
+#define HBM_QH_MAP_PIPE 0xffffffc0
+
+struct hbm_msm {
+ u32 *base;
+ struct usb_hcd *hcd;
+};
+
+static struct hbm_msm *hbm_ctx;
+
+/**
+ * Read register masked field.
+ *
+ * @base - hbm base virtual address.
+ * @offset - register offset.
+ * @mask - register bitmask.
+ *
+ * @return u32
+ */
+static inline u32 hbm_msm_read_reg_field(void *base,
+ u32 offset, const u32 mask)
+{
+ u32 shift = find_first_bit((void *)&mask, 32);
+ u32 val = ioread32(base + offset);
+ val &= mask; /* clear other bits */
+ val >>= shift;
+ return val;
+}
+
+/**
+ * Write register field.
+ *
+ * @base - hbm base virtual address.
+ * @offset - register offset.
+ * @val - value to be written.
+ *
+ */
+static inline void hbm_msm_write_reg(void *base, u32 offset, u32 val)
+{
+ iowrite32(val, base + offset);
+}
+
+/**
+ * Write register masked field.
+ *
+ * @base - hbm base virtual address.
+ * @offset - register offset.
+ * @mask - register bitmask.
+ * @val - value to write.
+ *
+ */
+static inline void hbm_msm_write_reg_field(void *base, u32 offset,
+ const u32 mask, u32 val)
+{
+ u32 shift = find_first_bit((void *)&mask, 32);
+ u32 tmp = ioread32(base + offset);
+
+ tmp &= ~mask;
+ val = tmp | (val << shift);
+ iowrite32(val, base + offset);
+}
+
+/**
+ * Enable/disable park mode. Park mode enables executing up to 3 usb packets
+ * from each QH.
+ *
+ * @pipe_num - Connection index.
+ *
+ * @disable_park_mode - Enable/disable park mode.
+ *
+ */
+int set_disable_park_mode(u8 pipe_num, bool disable_park_mode)
+{
+ if (pipe_num >= MAX_PIPE_NUM) {
+ pr_err("%s: illegal pipe num %d", __func__, pipe_num);
+ return -EINVAL;
+ }
+
+ /* enable/disable park mode */
+ hbm_msm_write_reg_field(hbm_ctx->base,
+ USB_OTG_HS_HBM_PARK_MODE_DISABLE, 1 << pipe_num,
+ (disable_park_mode ? 1 : 0));
+ return 0;
+}
+
+/**
+ * Enable/disable zero length transfer.
+ *
+ * @pipe_num - Connection index.
+ *
+ * @disable_zlt - Enable/disable zlt.
+ *
+ */
+int set_disable_zlt(u8 pipe_num, bool disable_zlt)
+{
+ if (pipe_num >= MAX_PIPE_NUM) {
+ pr_err("%s: illegal pipe num %d", __func__, pipe_num);
+ return -EINVAL;
+ }
+
+ /* enable/disable zlt */
+ hbm_msm_write_reg_field(hbm_ctx->base,
+ USB_OTG_HS_HBM_PIPE_ZLT_DISABLE, 1 << pipe_num,
+ (disable_zlt ? 1 : 0));
+ return 0;
+}
+
+static void hbm_reset(bool reset)
+{
+ hbm_msm_write_reg_field(hbm_ctx->base, USB_OTG_HS_HBM_SW_RST, 1 << 0,
+ reset ? 1 : 0);
+}
+
+static void hbm_config(bool enable)
+{
+ hbm_msm_write_reg_field(hbm_ctx->base, USB_OTG_HS_HBM_CFG, HBM_EN,
+ enable ? 1 : 0);
+}
+
+int hbm_pipe_init(u32 QH_addr, u32 pipe_num, bool is_consumer)
+{
+ if (pipe_num >= MAX_PIPE_NUM) {
+ pr_err("%s: illegal pipe num %d", __func__, pipe_num);
+ return -EINVAL;
+ }
+
+ /* map QH(ep) <> pipe */
+ hbm_msm_write_reg(hbm_ctx->base,
+ USB_OTG_HS_HBM_QH_MAP_PIPE(pipe_num), QH_addr);
+
+ /* set pipe producer/consumer mode - (IN EP is producer) */
+ hbm_msm_write_reg_field(hbm_ctx->base,
+ USB_OTG_HS_HBM_PIPE_PRODUCER, 1 << pipe_num,
+ (is_consumer ? 0 : 1));
+
+ /* disable park mode as default */
+ set_disable_park_mode(pipe_num, true);
+
+ /* enable zlt as default*/
+ set_disable_zlt(pipe_num, false);
+
+ /* activate pipe */
+ hbm_msm_write_reg_field(hbm_ctx->base, USB_OTG_HS_HBM_PIPE_EN,
+ 1 << pipe_num, 1);
+
+ return 0;
+}
+
+void hbm_init(struct usb_hcd *hcd)
+{
+ pr_info("%s\n", __func__);
+
+ hbm_ctx = kzalloc(sizeof(*hbm_ctx), GFP_KERNEL);
+ if (!hbm_ctx) {
+ pr_err("%s: hbm_ctx alloc failed\n", __func__);
+ return;
+ }
+ hbm_ctx->base = kzalloc(sizeof(u32), GFP_KERNEL);
+ if (!hbm_ctx->base) {
+ pr_err("%s: hbm_ctx base alloc failed\n", __func__);
+ kfree(hbm_ctx);
+ return;
+ }
+
+ hbm_ctx->base = hcd->regs;
+ hbm_ctx->hcd = hcd;
+
+ /* reset hbm */
+ hbm_reset(true);
+ /* delay was added to allow the reset process the end */
+ udelay(1000);
+ hbm_reset(false);
+ hbm_config(true);
+}
+
+void hbm_uninit(void)
+{
+ kfree(hbm_ctx->base);
+ kfree(hbm_ctx);
+}
+
+static int hbm_submit_async(struct ehci_hcd *ehci, struct urb *urb,
+ struct list_head *qtd_list, gfp_t mem_flags)
+{
+ int epnum;
+ unsigned long flags;
+ struct ehci_qh *qh = NULL;
+ int rc;
+ struct usb_host_bam_type *bam =
+ (struct usb_host_bam_type *)urb->priv_data;
+
+ epnum = urb->ep->desc.bEndpointAddress;
+
+ spin_lock_irqsave(&ehci->lock, flags);
+ if (unlikely(!HCD_HW_ACCESSIBLE(ehci_to_hcd(ehci)))) {
+ rc = -ESHUTDOWN;
+ goto done;
+ }
+ rc = usb_hcd_link_urb_to_ep(ehci_to_hcd(ehci), urb);
+ if (unlikely(rc))
+ goto done;
+
+ qh = qh_append_tds(ehci, urb, qtd_list, epnum, &urb->ep->hcpriv);
+ if (unlikely(qh == NULL)) {
+ usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb);
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ hbm_pipe_init(qh->qh_dma, bam->pipe_num, bam->dir);
+
+ if (likely(qh->qh_state == QH_STATE_IDLE))
+ qh_link_async(ehci, qh);
+
+done:
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ if (unlikely(qh == NULL))
+ qtd_list_free(ehci, urb, qtd_list);
+ return rc;
+}
+
+int hbm_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ struct list_head qtd_list;
+
+ INIT_LIST_HEAD(&qtd_list);
+
+ if (usb_pipetype(urb->pipe) != PIPE_BULK) {
+ pr_err("%s pipe type is not BULK\n", __func__);
+ return -EINVAL;
+ }
+
+ /*no sg support*/
+ urb->transfer_buffer_length = 0;
+ urb->transfer_dma = 0;
+ urb->transfer_flags |= URB_NO_INTERRUPT;
+
+ if (!qh_urb_transaction(ehci, urb, &qtd_list, mem_flags))
+ return -ENOMEM;
+ return hbm_submit_async(ehci, urb, &qtd_list, mem_flags);
+}