M7350/kernel/drivers/usb/host/hbm.c
2024-09-09 08:54:06 +00:00

324 lines
7.7 KiB
C

/* Copyright (c) 2013-2014, 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
#define QTD_CERR_MASK 0xfffff3ff
struct hbm_msm {
u32 *base;
struct usb_hcd *hcd;
bool disable_park_mode;
};
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;
}
/* Disable HBM pipe */
hbm_msm_write_reg_field(hbm_ctx->base, USB_OTG_HS_HBM_PIPE_EN,
1 << pipe_num, 0);
/* Reset HBM SideBand */
hbm_msm_write_reg_field(hbm_ctx->base, USB_OTG_HS_HBM_SB_SW_RST,
1 << pipe_num, 1);
hbm_msm_write_reg_field(hbm_ctx->base, USB_OTG_HS_HBM_SB_SW_RST,
1 << pipe_num, 0);
/* 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));
/* set park mode */
set_disable_park_mode(pipe_num, hbm_ctx->disable_park_mode);
/* 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, bool disable_park_mode)
{
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 = hcd->regs;
hbm_ctx->hcd = hcd;
hbm_ctx->disable_park_mode = disable_park_mode;
/* 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)
{
hbm_config(false);
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 = (struct ehci_qh *) urb->ep->hcpriv;
int rc;
struct usb_host_bam_type *bam =
(struct usb_host_bam_type *)urb->priv_data;
int cmd;
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;
if (qh != NULL)
pr_debug("%s: QH NOT NULL (%p)\n", __func__, qh);
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;
}
/* Stop Async Scheduler */
cmd = ehci_readl(ehci, &ehci->regs->command);
if (ehci->rh_state != EHCI_RH_HALTED && !ehci->reclaim) {
ehci_writel(ehci, cmd & ~CMD_ASE, &ehci->regs->command);
wmb();
}
hbm_pipe_init(qh->qh_dma, bam->pipe_num, bam->dir);
if (likely(qh->qh_state == QH_STATE_IDLE)) {
pr_debug("%s: QH was IDLE (%p)\n", __func__, qh);
qh_link_async(ehci, qh);
}
/* Start Async Scheduler */
cmd = ehci_readl(ehci, &ehci->regs->command);
if (!(cmd & CMD_ASE)) {
/* in case a clear of CMD_ASE didn't take yet */
(void)handshake(ehci, &ehci->regs->status, STS_ASS, 0, 150);
cmd |= CMD_ASE;
ehci_writel(ehci, cmd, &ehci->regs->command);
}
done:
spin_unlock_irqrestore(&ehci->lock, flags);
pr_debug("QH:%p urb:%p <--> pipe:%u ep:%d(out:%u)\n",
urb->ep->hcpriv, urb, bam->pipe_num, epnum, bam->dir);
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;
struct ehci_qtd *qtd;
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;
/* set err counter in qTD token to zero */
qtd = list_entry(qtd_list.next, struct ehci_qtd, qtd_list);
if (qtd != NULL)
qtd->hw_token &= QTD_CERR_MASK;
return hbm_submit_async(ehci, urb, &qtd_list, mem_flags);
}