314 lines
7.4 KiB
C
314 lines
7.4 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 << 0,
|
||
|
1);
|
||
|
hbm_msm_write_reg_field(hbm_ctx->base, USB_OTG_HS_HBM_SB_SW_RST, 1 << 0,
|
||
|
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 = NULL;
|
||
|
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;
|
||
|
|
||
|
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))
|
||
|
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);
|
||
|
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);
|
||
|
}
|