1498 lines
40 KiB
C
1498 lines
40 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/io.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/completion.h>
|
|
#include <mach/eint.h>
|
|
#include <linux/gpio.h>
|
|
#include "musb_core.h"
|
|
#ifdef CONFIG_OF
|
|
#include <linux/of_address.h>
|
|
#endif
|
|
#define DRIVER_AUTHOR "Mediatek"
|
|
#define DRIVER_DESC "driver for OTG USB-IF test"
|
|
#define MUSB_OTG_CSR0 0x102
|
|
#define MUSB_OTG_COUNT0 0x108
|
|
|
|
#define TEST_DRIVER_NAME "mt_otg_test"
|
|
|
|
#define DX_DBG
|
|
|
|
#define TEST_IS_STOP 0xfff1
|
|
#define DEV_NOT_CONNECT 0xfff2
|
|
#define DEV_HNP_TIMEOUT 0xfff3
|
|
#define DEV_NOT_RESET 0xfff4
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
/*for USB-IF OTG test*/
|
|
/*
|
|
* when this func is called in EM, it will reset the USB hw.
|
|
* and tester should not connet the uut to PC or connect a A-cable to it
|
|
* macro for USB-IF for OTG driver
|
|
*/
|
|
#define OTG_CMD_E_ENABLE_VBUS 0x00
|
|
#define OTG_CMD_E_ENABLE_SRP 0x01
|
|
#define OTG_CMD_E_START_DET_SRP 0x02
|
|
#define OTG_CMD_E_START_DET_VBUS 0x03
|
|
#define OTG_CMD_P_A_UUT 0x04
|
|
#define OTG_CMD_P_B_UUT 0x05
|
|
#define HOST_CMD_TEST_SE0_NAK 0x6
|
|
#define HOST_CMD_TEST_J 0x7
|
|
#define HOST_CMD_TEST_K 0x8
|
|
#define HOST_CMD_TEST_PACKET 0x9
|
|
#define HOST_CMD_SUSPEND_RESUME 0xa
|
|
#define HOST_CMD_GET_DESCRIPTOR 0xb
|
|
#define HOST_CMD_SET_FEATURE 0xc
|
|
#define OTG_CMD_P_B_UUT_TD59 0xd
|
|
#define HOST_CMD_ENV_INIT 0xe
|
|
#define HOST_CMD_ENV_EXIT 0xf
|
|
|
|
#define OTG_MSG_DEV_NOT_SUPPORT 0x01
|
|
#define OTG_MSG_DEV_NOT_RESPONSE 0x02
|
|
#define OTG_MSG_HUB_NOT_SUPPORT 0x03
|
|
|
|
#define OTG_STOP_CMD 0x10
|
|
#define OTG_INIT_MSG 0x20
|
|
|
|
struct otg_message {
|
|
spinlock_t lock;
|
|
unsigned int msg;
|
|
};
|
|
|
|
static struct otg_message g_otg_message;
|
|
static atomic_t g_exec;
|
|
|
|
unsigned long usb_l1intm_store;
|
|
unsigned short usb_intrrxe_store;
|
|
unsigned short usb_intrtxe_store;
|
|
unsigned char usb_intrusbe_store;
|
|
unsigned long pericfg_base;
|
|
bool device_enumed;
|
|
bool set_hnp;
|
|
bool high_speed;
|
|
bool is_td_59;
|
|
|
|
struct completion stop_event;
|
|
|
|
void musb_otg_reset_usb(void)
|
|
{
|
|
/* reset all of the USB IP, including PHY and MAC */
|
|
unsigned int usb_reset;
|
|
|
|
usb_reset = __raw_readl((void __iomem *)pericfg_base);
|
|
usb_reset |= 1 << 29;
|
|
__raw_writel(usb_reset, (void __iomem *)pericfg_base);
|
|
mdelay(10);
|
|
usb_reset &= ~(1 << 29);
|
|
__raw_writel(usb_reset, (void __iomem *)pericfg_base);
|
|
/* power on the USB */
|
|
usb_phy_poweron();
|
|
/* enable interrupt */
|
|
musb_writel(mtk_musb->mregs, USB_L1INTM, 0x105);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTXE, 1);
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSBE, 0xf7);
|
|
}
|
|
|
|
int musb_otg_env_init(void)
|
|
{
|
|
u8 power;
|
|
/* u8 intrusb; */
|
|
/* step1: mask the PMU/PMIC EINT */
|
|
mtk_musb->usb_if = true;
|
|
/* workaround for PMIC charger detection */
|
|
mtk_musb->is_host = true;
|
|
/* mt65xx_eint_mask(EINT_CHR_DET_NUM); */
|
|
|
|
pmic_chrdet_int_en(0);
|
|
|
|
mt_usb_init_drvvbus();
|
|
|
|
/* step5: make sure to power on the USB module */
|
|
if (mtk_musb->power)
|
|
mtk_musb->power = FALSE;
|
|
|
|
musb_platform_enable(mtk_musb);
|
|
/* step6: clear session bit */
|
|
musb_writeb(mtk_musb->mregs, MUSB_DEVCTL, 0);
|
|
/* step7: disable and enable usb interrupt */
|
|
usb_l1intm_store = musb_readl(mtk_musb->mregs, USB_L1INTM);
|
|
usb_intrrxe_store = musb_readw(mtk_musb->mregs, MUSB_INTRRXE);
|
|
usb_intrtxe_store = musb_readw(mtk_musb->mregs, MUSB_INTRTXE);
|
|
usb_intrusbe_store = musb_readb(mtk_musb->mregs, MUSB_INTRUSBE);
|
|
|
|
musb_writel(mtk_musb->mregs, USB_L1INTM, 0);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRRXE, 0);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTXE, 0);
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSBE, 0);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRRX, 0xffff);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTX, 0xffff);
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSB, 0xff);
|
|
free_irq(mtk_musb->nIrq, mtk_musb);
|
|
musb_writel(mtk_musb->mregs, USB_L1INTM, 0x105);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTXE, 1);
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSBE, 0xf7);
|
|
/* setp8: set the index to 0 for ep0, maybe no need.
|
|
* Designers said it is better not to use the index register.
|
|
*/
|
|
musb_writeb(mtk_musb->mregs, MUSB_INDEX, 0);
|
|
/* setp9: init message */
|
|
g_otg_message.msg = 0;
|
|
spin_lock_init(&g_otg_message.lock);
|
|
|
|
init_completion(&stop_event);
|
|
#ifdef DX_DBG
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
DBG(0, "start the USB-IF test in EM,power=0x%x!\n", power);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int musb_otg_env_exit(void)
|
|
{
|
|
DBG(0, "stop the USB-IF test in EM!\n");
|
|
musb_writel(mtk_musb->mregs, USB_L1INTM, 0);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRRXE, 0);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTXE, 0);
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSBE, 0);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRRX, 0xffff);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTX, 0xffff);
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSB, 0xff);
|
|
musb_writel(mtk_musb->mregs, USB_L1INTM, usb_l1intm_store);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRRXE, usb_intrrxe_store);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTXE, usb_intrtxe_store);
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSBE, usb_intrusbe_store);
|
|
mtk_musb->usb_if = false;
|
|
mtk_musb->is_host = false;
|
|
pmic_chrdet_int_en(1);
|
|
return 0;
|
|
}
|
|
|
|
void musb_otg_write_fifo(u16 len, u8 *buf)
|
|
{
|
|
int i;
|
|
|
|
DBG(0, "%s,len=%d\n", __func__, len);
|
|
for (i = 0; i < len; i++)
|
|
musb_writeb(mtk_musb->mregs, 0x20, *(buf + i));
|
|
}
|
|
|
|
void musb_otg_read_fifo(u16 len, u8 *buf)
|
|
{
|
|
int i;
|
|
|
|
DBG(0, "%s,len=%d\n", __func__, len);
|
|
for (i = 0; i < len; i++)
|
|
*(buf + i) = musb_readb(mtk_musb->mregs, 0x20);
|
|
}
|
|
|
|
unsigned int musb_polling_ep0_interrupt(void)
|
|
{
|
|
unsigned short intrtx;
|
|
|
|
DBG(0, "polling ep0 interrupt\n");
|
|
do {
|
|
intrtx = musb_readw(mtk_musb->mregs, MUSB_INTRTX);
|
|
/* sync status */
|
|
mb();
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTX, intrtx);
|
|
if (intrtx & 0x1) { /* ep0 interrupt happen */
|
|
DBG(0, "get ep0 interrupt,csr0=0x%x\n",
|
|
musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0));
|
|
break;
|
|
}
|
|
DBG(0, "polling ep0 interrupt,csr0=0x%x\n",
|
|
musb_readb(mtk_musb->mregs, MUSB_OTG_CSR0));
|
|
wait_for_completion_timeout(&stop_event, 1);
|
|
if (atomic_read(&g_exec) == 0)
|
|
return TEST_IS_STOP;
|
|
} while (atomic_read(&g_exec) == 1);
|
|
return 0;
|
|
}
|
|
|
|
void musb_h_setup(struct usb_ctrlrequest *setup)
|
|
{
|
|
unsigned short csr0;
|
|
|
|
DBG(0, "%s++\n", __func__);
|
|
musb_otg_write_fifo(sizeof(struct usb_ctrlrequest), (u8 *) setup);
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
DBG(0, "%s,csr0=0x%x\n", __func__, csr0);
|
|
csr0 |= MUSB_CSR0_H_SETUPPKT | MUSB_CSR0_TXPKTRDY;
|
|
musb_writew(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
|
|
/* polling the Tx interrupt */
|
|
if (musb_polling_ep0_interrupt())
|
|
return;
|
|
DBG(0, "%s--\n", __func__);
|
|
}
|
|
|
|
void musb_h_in_data(unsigned char *buf, u16 len)
|
|
{
|
|
/* will receive all of the data in this transfer. */
|
|
unsigned short csr0;
|
|
u16 received = 0;
|
|
bool bshort = false;
|
|
|
|
DBG(0, "%s++\n", __func__);
|
|
while ((received < len) && (!bshort)) {
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
csr0 |= MUSB_CSR0_H_REQPKT;
|
|
musb_writew(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
|
|
if (musb_polling_ep0_interrupt())
|
|
return;
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
DBG(0, "csr0 = 0x%x!\n", csr0);
|
|
if (csr0 & MUSB_CSR0_RXPKTRDY) {
|
|
/* get the data from ep fifo */
|
|
u8 count = musb_readb(mtk_musb->mregs, MUSB_OTG_COUNT0);
|
|
|
|
if (count < 64)
|
|
bshort = true;
|
|
|
|
if (received + count > len) {
|
|
DBG(0, "Data is too large\n");
|
|
|
|
/* read FIFO until data end (maximum size of len) */
|
|
musb_otg_read_fifo(len - received, buf);
|
|
} else {
|
|
musb_otg_read_fifo(count, buf + received);
|
|
}
|
|
|
|
received += count;
|
|
csr0 &= ~MUSB_CSR0_RXPKTRDY;
|
|
musb_writew(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
|
|
} else
|
|
DBG(0, "error, not receive the rxpktrdy interrupt!\n");
|
|
DBG(0, "%s--\n", __func__);
|
|
}
|
|
}
|
|
|
|
void musb_h_in_status(void)
|
|
{
|
|
unsigned short csr0;
|
|
|
|
DBG(0, "%s++\n", __func__);
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
csr0 |= MUSB_CSR0_H_REQPKT | MUSB_CSR0_H_STATUSPKT;
|
|
musb_writew(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
|
|
if (musb_polling_ep0_interrupt())
|
|
return;
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
DBG(0, "csr0 = 0x%x!\n", csr0);
|
|
|
|
if (csr0 & MUSB_CSR0_RXPKTRDY) {
|
|
csr0 &= ~MUSB_CSR0_RXPKTRDY;
|
|
/* whether this bit will be cleared auto,
|
|
* need to clear by sw??
|
|
*/
|
|
if (csr0 & MUSB_CSR0_H_STATUSPKT)
|
|
csr0 &= ~MUSB_CSR0_H_STATUSPKT;
|
|
musb_writew(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
|
|
} else if (csr0 & MUSB_CSR0_H_RXSTALL) {
|
|
DBG(0, "stall!\n");
|
|
if (set_hnp) {
|
|
DBG(0, "will pop up:DEV_NOT_RESPONSE!\n");
|
|
g_otg_message.msg = OTG_MSG_DEV_NOT_RESPONSE;
|
|
set_hnp = false;
|
|
msleep(1000);
|
|
}
|
|
}
|
|
DBG(0, "%s--\n", __func__);
|
|
}
|
|
|
|
void musb_h_out_status(void)
|
|
{
|
|
unsigned short csr0;
|
|
|
|
DBG(0, "%s++\n", __func__);
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
csr0 |= MUSB_CSR0_H_STATUSPKT | MUSB_CSR0_TXPKTRDY;
|
|
musb_writew(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
|
|
if (musb_polling_ep0_interrupt())
|
|
return;
|
|
#ifdef DX_DBG
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
DBG(0, "csr0 = 0x%x!\n", csr0);
|
|
#endif
|
|
DBG(0, "%s--\n", __func__);
|
|
}
|
|
|
|
void musb_d_reset(void)
|
|
{
|
|
unsigned short swrst;
|
|
|
|
swrst = musb_readw(mtk_musb->mregs, 0x74);
|
|
swrst |= 0x2;
|
|
musb_writew(mtk_musb->mregs, 0x74, swrst);
|
|
}
|
|
|
|
void musb_d_setup(struct usb_ctrlrequest *setup_packet, u16 len)
|
|
{
|
|
musb_otg_read_fifo(len, (u8 *) setup_packet);
|
|
DBG(0,
|
|
"receive setup packet:0x%x 0x%x 0x%x 0x%x 0x%x\n",
|
|
setup_packet->bRequest,
|
|
setup_packet->bRequestType,
|
|
setup_packet->wIndex,
|
|
setup_packet->wValue,
|
|
setup_packet->wLength);
|
|
}
|
|
|
|
void musb_d_out_data(struct usb_ctrlrequest *setup_packet)
|
|
{
|
|
unsigned short csr0;
|
|
|
|
static struct usb_device_descriptor device_descriptor = {
|
|
0x12,
|
|
0x01,
|
|
0x0200,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x40,
|
|
0x0951,
|
|
0x1603,
|
|
0x0200,
|
|
0x01,
|
|
0x02,
|
|
0x03,
|
|
0x01
|
|
};
|
|
static struct usb_config_descriptor configuration_descriptor = {
|
|
0x09,
|
|
0x02,
|
|
0x0023,
|
|
0x01,
|
|
0x01,
|
|
0x00,
|
|
0x80,
|
|
0x32
|
|
};
|
|
static struct usb_interface_descriptor interface_descriptor = {
|
|
0x09,
|
|
0x04,
|
|
0x00,
|
|
0x00,
|
|
0x02,
|
|
0x08,
|
|
0x06,
|
|
0x50,
|
|
0x00
|
|
};
|
|
static struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
|
0x07,
|
|
0x05,
|
|
0x81,
|
|
0x02,
|
|
0x0200,
|
|
0x00
|
|
};
|
|
static struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
|
0x07,
|
|
0x05,
|
|
0x02,
|
|
0x02,
|
|
0x0200,
|
|
0x00
|
|
};
|
|
static struct usb_otg_descriptor usb_otg_descriptor = {
|
|
0x03,
|
|
0x09,
|
|
0x03
|
|
};
|
|
|
|
if (setup_packet->wValue == 0x0100) {
|
|
musb_otg_write_fifo(sizeof(struct usb_device_descriptor),
|
|
(u8 *) &device_descriptor);
|
|
} else if (setup_packet->wValue == 0x0200) {
|
|
if (setup_packet->wLength == 9) {
|
|
musb_otg_write_fifo(
|
|
sizeof(struct usb_config_descriptor),
|
|
(u8 *) &configuration_descriptor);
|
|
} else {
|
|
musb_otg_write_fifo(
|
|
sizeof(struct usb_config_descriptor),
|
|
(u8 *) &configuration_descriptor);
|
|
musb_otg_write_fifo(
|
|
sizeof(struct usb_interface_descriptor),
|
|
(u8 *) &interface_descriptor);
|
|
musb_otg_write_fifo(7, (u8 *) &endpoint_descriptor_in);
|
|
musb_otg_write_fifo(7, (u8 *) &endpoint_descriptor_out);
|
|
musb_otg_write_fifo(sizeof(struct usb_otg_descriptor),
|
|
(u8 *) &usb_otg_descriptor);
|
|
}
|
|
}
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
csr0 |= MUSB_CSR0_TXPKTRDY | MUSB_CSR0_P_DATAEND;
|
|
musb_writew(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
|
|
if (musb_polling_ep0_interrupt())
|
|
return;
|
|
}
|
|
|
|
unsigned int musb_polling_bus_interrupt(unsigned int intr)
|
|
{
|
|
unsigned char intrusb;
|
|
unsigned long timeout;
|
|
|
|
if (intr == MUSB_INTR_CONNECT)
|
|
timeout = jiffies + 15 * HZ;
|
|
if (intr == (MUSB_INTR_CONNECT | MUSB_INTR_RESUME))
|
|
timeout = jiffies + 1;
|
|
if (intr == MUSB_INTR_RESET)
|
|
timeout = jiffies + 2 * HZ;
|
|
|
|
do {
|
|
intrusb = musb_readb(mtk_musb->mregs, MUSB_INTRUSB);
|
|
/* sync status */
|
|
mb();
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSB, intrusb);
|
|
if (intrusb & intr) {
|
|
DBG(0,
|
|
"interrupt happen, intrusb=0x%x, intr=0x%x\n",
|
|
intrusb, intr);
|
|
break;
|
|
}
|
|
|
|
/* check the timeout */
|
|
if ((intr == MUSB_INTR_CONNECT) &&
|
|
time_after(jiffies, timeout)) {
|
|
DBG(0, "time out for MUSB_INTR_CONNECT\n");
|
|
return DEV_NOT_CONNECT;
|
|
}
|
|
if ((intr == (MUSB_INTR_CONNECT | MUSB_INTR_RESUME))
|
|
&& time_after(jiffies, timeout)) {
|
|
DBG(0,
|
|
"time out for MUSB_INTR_CONNECT|MUSB_INTR_RESUME\n");
|
|
return DEV_HNP_TIMEOUT;
|
|
}
|
|
if ((intr == MUSB_INTR_RESET) && time_after(jiffies, timeout)) {
|
|
DBG(0, "time out for MUSB_INTR_RESET\n");
|
|
return DEV_NOT_RESET;
|
|
}
|
|
/* delay for the interrupt */
|
|
if (intr != MUSB_INTR_RESET) {
|
|
wait_for_completion_timeout(&stop_event, 1);
|
|
if (atomic_read(&g_exec) == 0)
|
|
break;
|
|
}
|
|
} while (atomic_read(&g_exec) == 1);
|
|
if (atomic_read(&g_exec) == 0) {
|
|
DBG(0, "TEST_IS_STOP\n");
|
|
return TEST_IS_STOP;
|
|
}
|
|
if (intrusb & MUSB_INTR_RESUME) { /* for TD.4.8, remote wakeup */
|
|
DBG(0, "MUSB_INTR_RESUME\n");
|
|
return MUSB_INTR_RESUME;
|
|
} else {
|
|
return intrusb;
|
|
}
|
|
}
|
|
|
|
void musb_h_suspend(void)
|
|
{
|
|
unsigned char power;
|
|
/* before suspend, should to send SOF for a while (USB-IF plan need) */
|
|
/* mdelay(100); */
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
DBG(0, "before suspend,power=0x%x\n", power);
|
|
if (high_speed)
|
|
power = 0x63;
|
|
else
|
|
power = 0x43;
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, power);
|
|
}
|
|
|
|
void musb_h_remote_wakeup(void)
|
|
{
|
|
unsigned char power;
|
|
|
|
msleep(25);
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
power &= ~MUSB_POWER_RESUME;
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, power);
|
|
}
|
|
|
|
bool musb_h_reset(void)
|
|
{
|
|
unsigned char power;
|
|
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
power |= MUSB_POWER_RESET | MUSB_POWER_HSENAB;
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, power);
|
|
msleep(60);
|
|
power &= ~MUSB_POWER_RESET;
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, power);
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
if (power & MUSB_POWER_HSMODE) {
|
|
DBG(0, "the device is a hs device!\n");
|
|
high_speed = true;
|
|
return true;
|
|
}
|
|
DBG(0, "the device is a fs device!\n");
|
|
high_speed = false;
|
|
return false;
|
|
}
|
|
|
|
void musb_d_soft_connect(bool connect)
|
|
{
|
|
unsigned char power;
|
|
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
if (connect)
|
|
power |= MUSB_POWER_SOFTCONN;
|
|
else
|
|
power &= ~MUSB_POWER_SOFTCONN;
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, power);
|
|
}
|
|
|
|
void musb_otg_set_session(bool set)
|
|
{
|
|
unsigned char devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
|
|
if (set)
|
|
devctl |= MUSB_DEVCTL_SESSION;
|
|
else
|
|
devctl &= ~MUSB_DEVCTL_SESSION;
|
|
musb_writeb(mtk_musb->mregs, MUSB_DEVCTL, devctl);
|
|
}
|
|
|
|
void musb_h_enumerate(void)
|
|
{
|
|
struct usb_ctrlrequest setup_packet;
|
|
struct usb_device_descriptor device_descriptor;
|
|
struct usb_config_descriptor configuration_descriptor;
|
|
struct usb_otg_descriptor *otg_descriptor;
|
|
unsigned char descriptor[65535];
|
|
|
|
/* set address */
|
|
musb_writew(mtk_musb->mregs, MUSB_TXFUNCADDR, 0);
|
|
setup_packet.bRequestType = USB_DIR_OUT |
|
|
USB_TYPE_STANDARD |
|
|
USB_RECIP_DEVICE;
|
|
setup_packet.bRequest = USB_REQ_SET_ADDRESS;
|
|
setup_packet.wIndex = 0;
|
|
setup_packet.wValue = 1;
|
|
setup_packet.wLength = 0;
|
|
musb_h_setup(&setup_packet);
|
|
musb_h_in_status();
|
|
musb_writew(mtk_musb->mregs, MUSB_TXFUNCADDR, 1);
|
|
DBG(0, "set address OK!\n");
|
|
/* get device descriptor */
|
|
setup_packet.bRequestType = USB_DIR_IN |
|
|
USB_TYPE_STANDARD |
|
|
USB_RECIP_DEVICE;
|
|
setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR;
|
|
setup_packet.wIndex = 0;
|
|
setup_packet.wValue = 0x0100;
|
|
setup_packet.wLength = 0x40;
|
|
musb_h_setup(&setup_packet);
|
|
musb_h_in_data((char *)&device_descriptor,
|
|
sizeof(struct usb_device_descriptor));
|
|
musb_h_out_status();
|
|
|
|
if (device_descriptor.idProduct == 0x1234) {
|
|
pr_debug("device pid not match!\n");
|
|
g_otg_message.msg = OTG_MSG_DEV_NOT_SUPPORT;
|
|
/* msleep(1000); */
|
|
}
|
|
|
|
DBG(0,
|
|
"get device descriptor OK!device class=0x%x PID=0x%x VID=0x%x\n",
|
|
device_descriptor.bDeviceClass, device_descriptor.idProduct,
|
|
device_descriptor.idVendor);
|
|
DBG(0,
|
|
"get device descriptor OK!DescriptorType=0x%x DeviceSubClass=0x%x\n",
|
|
device_descriptor.bDescriptorType,
|
|
device_descriptor.bDeviceSubClass);
|
|
/* get configuration descriptor */
|
|
setup_packet.bRequestType = USB_DIR_IN
|
|
| USB_TYPE_STANDARD
|
|
| USB_RECIP_DEVICE;
|
|
setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR;
|
|
setup_packet.wIndex = 0;
|
|
setup_packet.wValue = 0x0200;
|
|
setup_packet.wLength = 0x9;
|
|
musb_h_setup(&setup_packet);
|
|
musb_h_in_data((char *)&configuration_descriptor,
|
|
sizeof(struct usb_config_descriptor));
|
|
musb_h_out_status();
|
|
DBG(0, "get configuration descriptor OK!\n");
|
|
/* get all configuration descriptor */
|
|
setup_packet.bRequestType = USB_DIR_IN
|
|
| USB_TYPE_STANDARD
|
|
| USB_RECIP_DEVICE;
|
|
setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR;
|
|
setup_packet.wIndex = 0;
|
|
setup_packet.wValue = 0x0200;
|
|
setup_packet.wLength = configuration_descriptor.wTotalLength;
|
|
musb_h_setup(&setup_packet);
|
|
|
|
/*
|
|
* According to USB specification,
|
|
* the maximum length of wTotalLength is 65535 bytes
|
|
*/
|
|
if (configuration_descriptor.wTotalLength <= sizeof(descriptor))
|
|
musb_h_in_data(descriptor,
|
|
configuration_descriptor.wTotalLength);
|
|
musb_h_out_status();
|
|
DBG(0, "get all configuration descriptor OK!\n");
|
|
/* get otg descriptor */
|
|
otg_descriptor =
|
|
(struct usb_otg_descriptor *)
|
|
(descriptor + configuration_descriptor.wTotalLength - 3);
|
|
DBG(0, "otg descriptor::bLegth=%d,bDescriptorTye=%d,bmAttr=%d\n",
|
|
otg_descriptor->bLength,
|
|
otg_descriptor->bDescriptorType, otg_descriptor->bmAttributes);
|
|
if (otg_descriptor->bLength == 3 &&
|
|
otg_descriptor->bDescriptorType == 9) {
|
|
|
|
DBG(0, "get an otg descriptor!\n");
|
|
} else {
|
|
DBG(0, "not an otg device, will pop Unsupported Device\n");
|
|
g_otg_message.msg = OTG_MSG_DEV_NOT_SUPPORT;
|
|
msleep(1000);
|
|
}
|
|
/* set hnp, need before set_configuration */
|
|
set_hnp = true;
|
|
setup_packet.bRequestType = USB_DIR_OUT |
|
|
USB_TYPE_STANDARD |
|
|
USB_RECIP_DEVICE;
|
|
setup_packet.bRequest = USB_REQ_SET_FEATURE;
|
|
setup_packet.wIndex = 0;
|
|
setup_packet.wValue = 0x3; /* b_hnp_enable */
|
|
setup_packet.wLength = 0;
|
|
musb_h_setup(&setup_packet);
|
|
musb_h_in_status();
|
|
DBG(0, "set hnp OK!\n");
|
|
/* set configuration */
|
|
setup_packet.bRequestType = USB_DIR_OUT |
|
|
USB_TYPE_STANDARD |
|
|
USB_RECIP_DEVICE;
|
|
setup_packet.bRequest = USB_REQ_SET_CONFIGURATION;
|
|
setup_packet.wIndex = 0;
|
|
setup_packet.wValue = configuration_descriptor.iConfiguration;
|
|
setup_packet.wLength = 0;
|
|
musb_h_setup(&setup_packet);
|
|
musb_h_in_status();
|
|
DBG(0, "set configuration OK!\n");
|
|
}
|
|
|
|
void musb_d_enumerated(void)
|
|
{
|
|
unsigned char devctl;
|
|
unsigned short csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
|
|
DBG(0, "csr0=0x%x\n", csr0);
|
|
if (csr0 & MUSB_CSR0_P_SETUPEND) {
|
|
DBG(0, "SETUPEND\n");
|
|
csr0 |= MUSB_CSR0_P_SVDSETUPEND;
|
|
musb_writeb(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
|
|
csr0 &= ~MUSB_CSR0_P_SVDSETUPEND;
|
|
}
|
|
if (csr0 & MUSB_CSR0_RXPKTRDY) {
|
|
u8 count0;
|
|
|
|
count0 = musb_readb(mtk_musb->mregs, MUSB_OTG_COUNT0);
|
|
if (count0 == 8) {
|
|
struct usb_ctrlrequest setup_packet;
|
|
/* get the setup packet */
|
|
musb_d_setup(&setup_packet, count0);
|
|
|
|
if (setup_packet.bRequest ==
|
|
USB_REQ_SET_ADDRESS) {
|
|
device_enumed = false;
|
|
csr0 |= MUSB_CSR0_P_SVDRXPKTRDY
|
|
| MUSB_CSR0_P_DATAEND;
|
|
/* clear the RXPKTRDY */
|
|
musb_writew(mtk_musb->mregs,
|
|
MUSB_OTG_CSR0, csr0);
|
|
if (musb_polling_ep0_interrupt()) {
|
|
DBG(0,
|
|
"B-UUT:when set address, do not detect ep0 interrupt\n");
|
|
return;
|
|
}
|
|
musb_writeb(mtk_musb->mregs,
|
|
MUSB_FADDR,
|
|
(u8) setup_packet.wValue);
|
|
} else if (setup_packet.bRequest ==
|
|
USB_REQ_GET_DESCRIPTOR) {
|
|
csr0 |= MUSB_CSR0_P_SVDRXPKTRDY;
|
|
/* clear the RXPKTRDY */
|
|
musb_writew(mtk_musb->mregs,
|
|
MUSB_OTG_CSR0, csr0);
|
|
/* device --> host */
|
|
musb_d_out_data(&setup_packet);
|
|
} else if (setup_packet.bRequest ==
|
|
USB_REQ_SET_CONFIGURATION) {
|
|
csr0 |= MUSB_CSR0_P_SVDRXPKTRDY
|
|
| MUSB_CSR0_P_DATAEND;
|
|
/* clear the RXPKTRDY */
|
|
musb_writew(mtk_musb->mregs,
|
|
MUSB_OTG_CSR0, csr0);
|
|
if (musb_polling_ep0_interrupt())
|
|
return;
|
|
device_enumed = true;
|
|
/* will set host_req for B-device */
|
|
devctl = musb_readb(mtk_musb->mregs,
|
|
MUSB_DEVCTL);
|
|
if (devctl & MUSB_DEVCTL_BDEVICE) {
|
|
devctl |= MUSB_DEVCTL_HR;
|
|
musb_writeb(mtk_musb->mregs,
|
|
MUSB_DEVCTL, devctl);
|
|
}
|
|
} else if (setup_packet.bRequest ==
|
|
USB_REQ_SET_FEATURE) {
|
|
csr0 |= MUSB_CSR0_P_SVDRXPKTRDY
|
|
| MUSB_CSR0_P_DATAEND;
|
|
/* clear the RXPKTRDY */
|
|
musb_writew(mtk_musb->mregs,
|
|
MUSB_OTG_CSR0, csr0);
|
|
if (musb_polling_ep0_interrupt())
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void musb_otg_test_return(void)
|
|
{
|
|
}
|
|
|
|
static int musb_host_test_mode(unsigned char cmd);
|
|
|
|
void otg_cmd_a_uut(void)
|
|
{
|
|
unsigned long timeout;
|
|
unsigned char devctl;
|
|
bool timeout_flag = false;
|
|
unsigned int ret;
|
|
unsigned char power;
|
|
unsigned short csr0;
|
|
unsigned char intrusb;
|
|
unsigned short intrtx;
|
|
|
|
/* polling the session req from B-OPT and start a new session */
|
|
device_enumed = false;
|
|
TD_4_6:
|
|
musb_otg_reset_usb();
|
|
DBG(0, "A-UUT reset success\n");
|
|
timeout = jiffies + 5 * HZ;
|
|
musb_writeb(mtk_musb->mregs, MUSB_DEVCTL, 0);
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
while ((atomic_read(&g_exec) == 1) && (devctl & 0x18)) {
|
|
DBG(0, "musb::not below session end!\n");
|
|
msleep(100);
|
|
if (time_after(jiffies, timeout)) {
|
|
timeout_flag = true;
|
|
return TEST_IS_STOP;
|
|
}
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
}
|
|
if (timeout_flag) {
|
|
timeout_flag = false;
|
|
musb_otg_reset_usb();
|
|
BG(0,
|
|
"timeout for below session end, after reset usb, devctl=0x%x\n",
|
|
musb_readb(mtk_musb->mregs, MUSB_DEVCTL));
|
|
}
|
|
BG(0, "polling session request,begin\n");
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_SESSREQ);
|
|
pBG(0, "polling session request,done,ret=0x%x\n", ret);
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
/* session is set and VBUS will be out. */
|
|
musb_otg_set_session(true);
|
|
#if 1
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
power &= ~MUSB_POWER_SOFTCONN;
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, power);
|
|
#endif
|
|
/* polling the connect interrupt from B-OPT */
|
|
DBG(0, "polling connect interrupt,begin\n");
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_CONNECT);
|
|
DBG(0, "polling connect interrupt,done,ret=0x%x\n", ret);
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
if (ret == DEV_NOT_CONNECT) {
|
|
DBG(0, "device is not connected in 15s\n");
|
|
g_otg_message.msg = OTG_MSG_DEV_NOT_RESPONSE;
|
|
return TEST_IS_STOP;
|
|
}
|
|
DBG(0, "musb::connect interrupt is detected!\n");
|
|
/* the test is fail because the reset starts less than100 ms
|
|
* from the B-OPT connect. the IF test needs
|
|
*/
|
|
msleep(100);
|
|
/* reset the bus,check whether it is a hs device */
|
|
musb_h_reset(); /* should last for more than 50ms, TD.4.2 */
|
|
musb_h_enumerate();
|
|
/* suspend the bus */
|
|
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
|
|
DBG(0, "after enum B-OPT,csr0=0x%x\n", csr0);
|
|
musb_h_suspend();
|
|
|
|
/* polling the disconnect interrupt from B-OPT,
|
|
* and remote wakeup(TD.4.8)
|
|
*/
|
|
DBG(0, "polling disconnect or remote wakeup,begin\n");
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_DISCONNECT
|
|
| MUSB_INTR_RESUME);
|
|
DBG(0, "polling disconnect or remote wakeup,done,ret=0x%x\n", ret);
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
if (ret == MUSB_INTR_RESUME) {
|
|
/* for TD4.8 */
|
|
musb_h_remote_wakeup();
|
|
/* maybe need to access the B-OPT, get device descriptor */
|
|
if (atomic_read(&g_exec) == 1)
|
|
wait_for_completion(&stop_event);
|
|
return TEST_IS_STOP;
|
|
}
|
|
/* polling the reset interrupt from B-OPT */
|
|
if (!(ret & MUSB_INTR_RESET)) {
|
|
DBG(0, "polling reset for B-OPT,begin\n");
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_RESET);
|
|
DBG(0, "polling reset for B-OPT,done,ret=0x%x\n", ret);
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
if (ret == DEV_NOT_RESET) {
|
|
if (atomic_read(&g_exec) == 1)
|
|
wait_for_completion(&stop_event);
|
|
return TEST_IS_STOP;
|
|
}
|
|
}
|
|
|
|
DBG(0, "after receive reset,devctl=0x%x,csr0=0x%x\n",
|
|
musb_readb(mtk_musb->mregs, MUSB_DEVCTL),
|
|
musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0));
|
|
|
|
/* enumerate and polling the suspend interrupt form B-OPT */
|
|
|
|
do {
|
|
intrtx = musb_readw(mtk_musb->mregs, MUSB_INTRTX);
|
|
/* sync status */
|
|
mb();
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTX, intrtx);
|
|
intrusb = musb_readb(mtk_musb->mregs, MUSB_INTRUSB);
|
|
/* sync status */
|
|
mb();
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSB, intrusb);
|
|
if (intrtx || (intrusb & MUSB_INTR_SUSPEND)) {
|
|
if (intrtx) {
|
|
if (intrtx & 0x1)
|
|
musb_d_enumerated();
|
|
}
|
|
if (intrusb) {
|
|
/* maybe receive disconnect interrupt when the session is end */
|
|
if (intrusb & MUSB_INTR_SUSPEND) {
|
|
if (device_enumed) {
|
|
/* return form the while loop */
|
|
break;
|
|
}
|
|
/* TD.4.6 */
|
|
musb_d_soft_connect(false);
|
|
goto TD_4_6;
|
|
}
|
|
}
|
|
} else
|
|
wait_for_completion_timeout(&stop_event, 1);
|
|
/* the enum will be repeated for 5 times */
|
|
} while (atomic_read(&g_exec) == 1);
|
|
if (atomic_read(&g_exec) == 0) {
|
|
/* return form the switch-case */
|
|
return TEST_IS_STOP;
|
|
}
|
|
DBG(0, "polling connect form B-OPT,begin\n");
|
|
/* B-OPT will connect again 100ms after A disconnect */
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_CONNECT);
|
|
DBG(0, "polling connect form B-OPT,done,ret=0x%x\n", ret);
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
musb_h_reset(); /* should reset bus again, TD.4.7 */
|
|
wait_for_completion(&stop_event);
|
|
}
|
|
|
|
void otg_cmd_b_uut(void)
|
|
{
|
|
unsigned long timeout;
|
|
unsigned char devctl;
|
|
bool timeout_flag = false;
|
|
unsigned int ret;
|
|
unsigned char power;
|
|
unsigned char intrusb;
|
|
unsigned short intrtx;
|
|
|
|
musb_otg_reset_usb();
|
|
/* The B-UUT issues an SRP to start a session with the A-OPT */
|
|
musb_otg_set_session(true);
|
|
/* 100ms after VBUS begins to decay the A-OPT powers VBUS */
|
|
timeout = jiffies + 5 * HZ;
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
|
|
while (((devctl & MUSB_DEVCTL_VBUS) >> MUSB_DEVCTL_VBUS_SHIFT) < 0x3) {
|
|
if (time_after(jiffies, timeout)) {
|
|
timeout_flag = true;
|
|
break;
|
|
}
|
|
msleep(100);
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
}
|
|
if (timeout_flag) {
|
|
DBG(0, "B-UUT set vbus timeout\n");
|
|
g_otg_message.msg = OTG_MSG_DEV_NOT_RESPONSE;
|
|
timeout_flag = false;
|
|
return TEST_IS_STOP;
|
|
}
|
|
|
|
/* After detecting the VBUS, B-UUT should connect to the A_OPT */
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
power |= MUSB_POWER_HSENAB;
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, power);
|
|
/* TD5_5: */
|
|
musb_d_soft_connect(true);
|
|
|
|
device_enumed = false;
|
|
/* polling the reset single form the A-OPT */
|
|
DBG(0, "polling reset form A-OPT,begin\n");
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_RESET);
|
|
DBG(0, "polling reset form A-OPT,done,ret=0x%x\n", ret);
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
if (power & MUSB_POWER_HSMODE)
|
|
high_speed = true;
|
|
else
|
|
high_speed = false;
|
|
/* The A-OPT enumerates the B-UUT */
|
|
TD6_13:
|
|
do {
|
|
intrtx = musb_readw(mtk_musb->mregs, MUSB_INTRTX);
|
|
/* sync status */
|
|
mb();
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTX, intrtx);
|
|
intrusb = musb_readb(mtk_musb->mregs, MUSB_INTRUSB);
|
|
/* sync status */
|
|
mb();
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSB, intrusb);
|
|
if (intrtx || (intrusb & 0xf7)) {
|
|
if (intrtx) {
|
|
/* DBG(0,"B-enum,intrtx=0x%x\n",intrtx); */
|
|
if (intrtx & 0x1)
|
|
DBG(0, "ep0 interrupt\n");
|
|
musb_d_enumerated();
|
|
}
|
|
if (intrusb) {
|
|
if (intrusb & 0xf7)
|
|
DBG(0,
|
|
"B-enum,intrusb=0x%x,power=0x%x\n",
|
|
intrusb,
|
|
musb_readb(mtk_musb->mregs,
|
|
MUSB_POWER));
|
|
if ((device_enumed) &&
|
|
(intrusb & MUSB_INTR_SUSPEND)) {
|
|
DBG(0,
|
|
"suspend interrupt is received,power=0x%x,devctl=0x%x\n",
|
|
musb_readb(mtk_musb->mregs, MUSB_POWER),
|
|
musb_readb(mtk_musb->mregs, MUSB_DEVCTL));
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
DBG(0,
|
|
"power=0x%x,devctl=0x%x,intrtx=0x%x,intrusb=0x%x\n",
|
|
musb_readb(mtk_musb->mregs, MUSB_POWER),
|
|
musb_readb(mtk_musb->mregs,
|
|
MUSB_DEVCTL),
|
|
musb_readw(mtk_musb->mregs,
|
|
MUSB_INTRTX),
|
|
musb_readb(mtk_musb->mregs,
|
|
MUSB_INTRUSB));
|
|
wait_for_completion_timeout(&stop_event, 1);
|
|
}
|
|
} while (atomic_read(&g_exec) == 1);
|
|
if (atomic_read(&g_exec) == 0)
|
|
return TEST_IS_STOP;
|
|
DBG(0, "hnp start\n");
|
|
if (intrusb & MUSB_INTR_RESUME)
|
|
goto TD6_13;
|
|
if (!(intrusb & MUSB_INTR_CONNECT)) {
|
|
/* polling the connect from A-OPT, the UUT acts as host */
|
|
DBG(0, "polling connect or resume form A-OPT,begin\n");
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_CONNECT
|
|
| MUSB_INTR_RESUME);
|
|
DBG(0, "polling connect or resume form A-OPT,done,ret=0x%x\n",
|
|
ret);
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
if (ret == MUSB_INTR_RESUME)
|
|
goto TD6_13;
|
|
if (ret == DEV_HNP_TIMEOUT) {
|
|
DBG(0, "B-UUT HNP timeout\n");
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
|
|
devctl &= ~MUSB_DEVCTL_HR;
|
|
musb_writeb(mtk_musb->mregs, MUSB_DEVCTL, devctl);
|
|
if (is_td_59)
|
|
g_otg_message.msg = OTG_MSG_DEV_NOT_RESPONSE;
|
|
return TEST_IS_STOP;
|
|
}
|
|
}
|
|
/* reset the bus and check whether it is a hs device */
|
|
musb_h_reset();
|
|
musb_h_enumerate();
|
|
/* suspend the bus */
|
|
musb_h_suspend();
|
|
/* polling the disconnect interrupt from A-OPT */
|
|
DBG(0, "polling disconnect form A-OPT,begin\n");
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_DISCONNECT);
|
|
DBG(0, "polling disconnect form A-OPT,done,ret=0x%x\n", ret);
|
|
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
DBG(0, "A-OPT is disconnected, UUT will be back to device\n");
|
|
if (!(ret & MUSB_INTR_RESET)) {
|
|
musb_d_soft_connect(true);
|
|
/* polling the reset single form the A-OPT */
|
|
DBG(0, "polling reset form A-OPT,begin\n");
|
|
ret = musb_polling_bus_interrupt(MUSB_INTR_RESET);
|
|
/* musb_d_reset (); */
|
|
DBG(0, "polling reset form A-OPT,done,ret=0x%x\n", ret);
|
|
if (ret == TEST_IS_STOP)
|
|
return TEST_IS_STOP;
|
|
}
|
|
device_enumed = false;
|
|
if (atomic_read(&g_exec) == 1)
|
|
goto TD6_13; /* TD5_5 */
|
|
wait_for_completion(&stop_event);
|
|
}
|
|
|
|
int musb_otg_exec_cmd(unsigned int cmd)
|
|
{
|
|
|
|
unsigned char devctl;
|
|
unsigned char intrusb;
|
|
unsigned char power;
|
|
unsigned short csr0;
|
|
unsigned int usb_l1intp;
|
|
unsigned int usb_l1ints;
|
|
|
|
if (!mtk_musb) {
|
|
DBG(0, "mtk_musb is NULL,error!\n");
|
|
return false;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case HOST_CMD_ENV_INIT:
|
|
musb_otg_env_init();
|
|
return 0;
|
|
case HOST_CMD_ENV_EXIT:
|
|
musb_otg_env_exit();
|
|
return 0;
|
|
}
|
|
|
|
/* init */
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, 0x21);
|
|
musb_writeb(mtk_musb->mregs, MUSB_DEVCTL, 0);
|
|
msleep(300);
|
|
|
|
#ifdef DX_DBG
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
intrusb = musb_readb(mtk_musb->mregs, MUSB_INTRUSB);
|
|
DBG(0, "1:cmd=%d,devctl=0x%x,power=0x%x,intrusb=0x%x\n",
|
|
cmd, devctl, power, intrusb);
|
|
#endif
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRRX, 0xffff);
|
|
musb_writew(mtk_musb->mregs, MUSB_INTRTX, 0xffff);
|
|
musb_writeb(mtk_musb->mregs, MUSB_INTRUSB, 0xff);
|
|
mdelay(10);
|
|
#ifdef DX_DBG
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
intrusb = musb_readb(mtk_musb->mregs, MUSB_INTRUSB);
|
|
DBG(0, "2:cmd=%d,devctl=0x%x,power=0x%x,intrusb=0x%x\n",
|
|
cmd, devctl, power, intrusb);
|
|
#endif
|
|
high_speed = false;
|
|
atomic_set(&g_exec, 1);
|
|
|
|
DBG(0, "before exec:cmd=%d\n", cmd);
|
|
|
|
switch (cmd) {
|
|
/* electrical */
|
|
case OTG_CMD_E_ENABLE_VBUS:
|
|
DBG(0, "musb::enable VBUS!\n");
|
|
musb_otg_set_session(true);
|
|
musb_platform_set_vbus(mtk_musb, 1);
|
|
while (atomic_read(&g_exec) == 1)
|
|
msleep(100);
|
|
musb_otg_set_session(false);
|
|
musb_platform_set_vbus(mtk_musb, 0);
|
|
break;
|
|
case OTG_CMD_E_ENABLE_SRP: /* need to clear session? */
|
|
DBG(0, "musb::enable srp!\n");
|
|
musb_otg_reset_usb();
|
|
{
|
|
u32 val = 0;
|
|
|
|
val = USBPHY_READ32(0x6c);
|
|
val = (val & ~(0xff<<0)) | (0x1<<0);
|
|
USBPHY_WRITE32(0x6c, val);
|
|
|
|
val = USBPHY_READ32(0x6c);
|
|
val = (val & ~(0xff<<8)) | (0x1<<8);
|
|
USBPHY_WRITE32(0x6c, val);
|
|
}
|
|
musb_writeb(mtk_musb->mregs, 0x7B, 1);
|
|
musb_otg_set_session(true);
|
|
while (atomic_read(&g_exec) == 1)
|
|
msleep(100);
|
|
musb_otg_set_session(false);
|
|
break;
|
|
case OTG_CMD_E_START_DET_SRP:
|
|
/* need as a A-device */
|
|
musb_writeb(mtk_musb->mregs, MUSB_DEVCTL, 0);
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
while ((atomic_read(&g_exec) == 1) && (devctl & 0x18)) {
|
|
DBG(0, "musb::not below session end!\n");
|
|
msleep(100);
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
}
|
|
while ((atomic_read(&g_exec) == 1) && (!(devctl & 0x10))) {
|
|
DBG(0, "musb::not above session end!\n");
|
|
msleep(100);
|
|
devctl = musb_readb(mtk_musb->mregs, MUSB_DEVCTL);
|
|
}
|
|
devctl |= MUSB_DEVCTL_SESSION;
|
|
musb_writeb(mtk_musb->mregs, MUSB_DEVCTL, devctl);
|
|
while (atomic_read(&g_exec) == 1)
|
|
msleep(100);
|
|
musb_writeb(mtk_musb->mregs, MUSB_DEVCTL, 0);
|
|
break;
|
|
case OTG_CMD_E_START_DET_VBUS:
|
|
usb_l1intp = musb_readl(mtk_musb->mregs, USB_L1INTP);
|
|
usb_l1intp &= ~(1 << 10);
|
|
musb_writel(mtk_musb->mregs, USB_L1INTP, usb_l1intp);
|
|
usb_l1ints = musb_readl(mtk_musb->mregs, USB_L1INTS);
|
|
while ((usb_l1ints & (1 << 8)) == 0) {
|
|
DBG(0, "musb::vbus is 0!\n");
|
|
msleep(100);
|
|
usb_l1ints = musb_readl(mtk_musb->mregs, USB_L1INTS);
|
|
}
|
|
DBG(0, "musb::vbus is detected!\n");
|
|
power = musb_readb(mtk_musb->mregs, MUSB_POWER);
|
|
power |= MUSB_POWER_SOFTCONN;
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, power);
|
|
while (atomic_read(&g_exec) == 1)
|
|
msleep(100);
|
|
musb_writeb(mtk_musb->mregs, MUSB_POWER, 0x21);
|
|
break;
|
|
|
|
case OTG_CMD_P_B_UUT_TD59:
|
|
is_td_59 = true;
|
|
if (is_td_59)
|
|
DBG(0, "TD5.9 will be tested!\n");
|
|
break;
|
|
|
|
/* protocal */
|
|
case OTG_CMD_P_A_UUT:
|
|
DBG(0, "A-UUT starts...\n");
|
|
otg_cmd_a_uut();
|
|
DBG(0, "the test as A-UUT is done\n");
|
|
break;
|
|
|
|
case OTG_CMD_P_B_UUT:
|
|
DBG(0, "B-UUT starts...\n");
|
|
otg_cmd_b_uut();
|
|
DBG(0, "the test as B_UUT is done\n");
|
|
break;
|
|
|
|
case HOST_CMD_TEST_SE0_NAK:
|
|
case HOST_CMD_TEST_J:
|
|
case HOST_CMD_TEST_K:
|
|
case HOST_CMD_TEST_PACKET:
|
|
case HOST_CMD_SUSPEND_RESUME:
|
|
case HOST_CMD_GET_DESCRIPTOR:
|
|
case HOST_CMD_SET_FEATURE:
|
|
musb_host_test_mode(cmd);
|
|
while (atomic_read(&g_exec) == 1)
|
|
msleep(100);
|
|
break;
|
|
}
|
|
DBG(0, "%s--\n", __func__);
|
|
return 0;
|
|
|
|
}
|
|
|
|
void musb_otg_stop_cmd(void)
|
|
{
|
|
DBG(0, "%s++\n", __func__);
|
|
atomic_set(&g_exec, 0);
|
|
is_td_59 = false;
|
|
complete(&stop_event);
|
|
}
|
|
|
|
unsigned int musb_otg_message(void)
|
|
{
|
|
/* for EM to pop the message */
|
|
unsigned int msg;
|
|
|
|
msg = g_otg_message.msg;
|
|
g_otg_message.msg = 0;
|
|
return msg;
|
|
}
|
|
|
|
void musb_otg_message_cb(void)
|
|
{
|
|
/* when the OK button is clicked on EM, this func is called. */
|
|
spin_lock(&g_otg_message.lock);
|
|
g_otg_message.msg = 0;
|
|
spin_unlock(&g_otg_message.lock);
|
|
}
|
|
|
|
static int musb_otg_test_open(struct inode *inode, struct file *file)
|
|
{
|
|
DBG(0, "%s++\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int musb_otg_test_release(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ssize_t musb_otg_test_read(struct file *filp,
|
|
char __user *buf, size_t count, loff_t *ppos)
|
|
{
|
|
int ret = 0;
|
|
unsigned int message = musb_otg_message();
|
|
|
|
if (message)
|
|
DBG(0, "%s:message=0x%x\n", __func__, message);
|
|
if (put_user((unsigned int)message, (unsigned int *)buf))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
}
|
|
|
|
ssize_t musb_otg_test_write(struct file *filp,
|
|
const char __user *buf, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
int ret = 0;
|
|
unsigned char value;
|
|
|
|
if (get_user(value, (unsigned char *)buf))
|
|
ret = -EFAULT;
|
|
else {
|
|
if (value == OTG_STOP_CMD) {
|
|
DBG(0, "%s::OTG_STOP_CMD\n", __func__);
|
|
musb_otg_stop_cmd();
|
|
} else if (value == OTG_INIT_MSG) {
|
|
DBG(0, "%s::OTG_INIT_MSG\n", __func__);
|
|
musb_otg_message_cb();
|
|
} else {
|
|
DBG(0, "musb_otg_test_write::the
|
|
value is invalid,0x%x\n",
|
|
value);
|
|
ret = -EFAULT;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static long musb_otg_test_ioctl
|
|
(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
|
|
DBG(0, "%s :cmd=0x%x\n", __func__, cmd);
|
|
ret = musb_otg_exec_cmd(cmd);
|
|
return (long)ret;
|
|
}
|
|
|
|
|
|
static const struct file_operations musb_otg_test_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = musb_otg_test_open,
|
|
.release = musb_otg_test_release,
|
|
.read = musb_otg_test_read,
|
|
.write = musb_otg_test_write,
|
|
.unlocked_ioctl = musb_otg_test_ioctl,
|
|
};
|
|
|
|
static struct miscdevice musb_otg_test_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
/* .minor = 254, */
|
|
.name = TEST_DRIVER_NAME,
|
|
.fops = &musb_otg_test_fops,
|
|
.mode = 0666,
|
|
};
|
|
|
|
|
|
static const u8 musb_host_test_packet[53] = {
|
|
/* implicit SYNC then DATA0 to start */
|
|
|
|
/* JKJKJKJK x9 */
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* JJKKJJKK x8 */
|
|
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
|
/* JJJJKKKK x8 */
|
|
0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
|
|
/* JJJJJJJKKKKKKK x8 */
|
|
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
/* JJJJJJJK x8 */
|
|
0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd,
|
|
/* JKKKKKKK x10, JK */
|
|
0xfc, 0x7e, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0x7e
|
|
/* implicit CRC16 then EOP to end */
|
|
};
|
|
|
|
void musb_host_load_testpacket(struct musb *musb)
|
|
{
|
|
unsigned short csr0 = musb_readw(musb->mregs, 0x102);
|
|
|
|
DBG(0, "csr0=0x%x\n", csr0);
|
|
musb->ignore_disconnect = 1;
|
|
musb_otg_write_fifo(53, (u8 *) musb_host_test_packet);
|
|
}
|
|
|
|
|
|
void host_test_mode(struct musb *musb, unsigned int wIndex)
|
|
{
|
|
unsigned char temp;
|
|
unsigned char power;
|
|
struct usb_ctrlrequest setup_packet;
|
|
struct usb_device_descriptor device_descriptor;
|
|
|
|
setup_packet.bRequestType = USB_DIR_IN |
|
|
USB_TYPE_STANDARD | USB_RECIP_DEVICE;
|
|
setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR;
|
|
setup_packet.wIndex = 0;
|
|
setup_packet.wValue = 0x0100;
|
|
setup_packet.wLength = 0x40;
|
|
musb_otg_set_session(true);
|
|
msleep(200);
|
|
pr_debug("devctl = 0x%x\n", musb_readb(musb->mregs, MUSB_DEVCTL));
|
|
switch (wIndex) {
|
|
case HOST_CMD_TEST_SE0_NAK:
|
|
DBG(0, "TEST_SE0_NAK\n");
|
|
temp = MUSB_TEST_SE0_NAK;
|
|
musb_writeb(musb->mregs, MUSB_TESTMODE, temp);
|
|
|
|
break;
|
|
case HOST_CMD_TEST_J:
|
|
DBG(0, "TEST_J\n");
|
|
temp = MUSB_TEST_J;
|
|
musb_writeb(musb->mregs, MUSB_TESTMODE, temp);
|
|
|
|
break;
|
|
case HOST_CMD_TEST_K:
|
|
DBG(0, "TEST_K\n");
|
|
temp = MUSB_TEST_K;
|
|
musb_writeb(musb->mregs, MUSB_TESTMODE, temp);
|
|
|
|
break;
|
|
case HOST_CMD_TEST_PACKET:
|
|
DBG(0, "TEST_PACKET\n");
|
|
temp = MUSB_TEST_PACKET;
|
|
musb_host_load_testpacket(musb);
|
|
musb_writeb(musb->mregs, MUSB_TESTMODE, temp);
|
|
musb_writew(musb->mregs, 0x102, MUSB_CSR0_TXPKTRDY);
|
|
break;
|
|
|
|
case HOST_CMD_SUSPEND_RESUME:
|
|
/* HS_HOST_PORT_SUSPEND_RESUME */
|
|
DBG(0, "HS_HOST_PORT_SUSPEND_RESUME\n");
|
|
msleep(5000);
|
|
/* the host must continue sending SOFs for 15s */
|
|
DBG(0, "please begin to trigger suspend!\n");
|
|
msleep(10000);
|
|
power = musb_readb(musb->mregs, MUSB_POWER);
|
|
power |= MUSB_POWER_SUSPENDM | MUSB_POWER_ENSUSPEND;
|
|
musb_writeb(musb->mregs, MUSB_POWER, power);
|
|
msleep(5000);
|
|
DBG(0, "please begin to trigger resume!\n");
|
|
msleep(10000);
|
|
power &= ~MUSB_POWER_SUSPENDM;
|
|
power |= MUSB_POWER_RESUME;
|
|
musb_writeb(musb->mregs, MUSB_POWER, power);
|
|
mdelay(25);
|
|
power &= ~MUSB_POWER_RESUME;
|
|
musb_writeb(musb->mregs, MUSB_POWER, power);
|
|
/* SOF continue */
|
|
musb_h_setup(&setup_packet);
|
|
break;
|
|
case HOST_CMD_GET_DESCRIPTOR:
|
|
/* SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup */
|
|
DBG(0, "SINGLE_STEP_GET_DEVICE_DESCRIPTOR\n");
|
|
/* the host issues SOFs for 15s allowing the test engineer
|
|
* to raise the scope trigger just above the SOF voltage level.
|
|
*/
|
|
msleep(15000);
|
|
musb_h_setup(&setup_packet);
|
|
break;
|
|
case HOST_CMD_SET_FEATURE:
|
|
/* SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute */
|
|
DBG(0, "SINGLE_STEP_GET_DEVICE_DESCRIPTOR\n");
|
|
/* get device descriptor */
|
|
musb_h_setup(&setup_packet);
|
|
msleep(15000);
|
|
musb_h_in_data((char *)&device_descriptor,
|
|
sizeof(struct usb_device_descriptor));
|
|
musb_h_out_status();
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
/* while(1); */
|
|
}
|
|
|
|
static int musb_host_test_mode(unsigned char cmd)
|
|
{
|
|
musb_platform_set_vbus(mtk_musb, 1);
|
|
musb_otg_reset_usb();
|
|
host_test_mode(mtk_musb, cmd);
|
|
return 0;
|
|
}
|
|
|
|
static int __init musb_otg_test_init(void)
|
|
{
|
|
#ifdef CONFIG_OF
|
|
struct device_node *np;
|
|
|
|
np = of_find_compatible_node(NULL, NULL, "mediatek,PERICFG");
|
|
if (!np)
|
|
pr_debug("get PERICFG node fail");
|
|
pericfg_base = (unsigned long)of_iomap(np, 0);
|
|
#else
|
|
pericfg_base = PERICFG_BASE;
|
|
#endif
|
|
misc_register(&musb_otg_test_dev);
|
|
return 0;
|
|
}
|
|
|
|
static void __exit musb_otg_test_exit(void)
|
|
{
|
|
misc_deregister(&musb_otg_test_dev);
|
|
}
|
|
|
|
|
|
module_init(musb_otg_test_init);
|
|
module_exit(musb_otg_test_exit);
|