Commit 0807c500 authored by Li Yang's avatar Li Yang Committed by Greg Kroah-Hartman

USB: add Freescale USB OTG Transceiver driver

Slightly reworked and cleaned up driver from Freescale LTIB
for MPC5121E. The driver has been ported to the current kernel,
proc interface "/proc/driver/fsl_usb2_otg" has been replaced by
sysfs interface.
Signed-off-by: default avatarLi Yang <leoli@freescale.com>
Signed-off-by: default avatarAnatolij Gustschin <agust@denx.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 2ea6698d
...@@ -122,4 +122,12 @@ config AB8500_USB ...@@ -122,4 +122,12 @@ config AB8500_USB
This transceiver supports high and full speed devices plus, This transceiver supports high and full speed devices plus,
in host mode, low speed. in host mode, low speed.
config FSL_USB2_OTG
bool "Freescale USB OTG Transceiver Driver"
depends on USB_EHCI_FSL && USB_GADGET_FSL_USB2
select USB_OTG
select USB_OTG_UTILS
help
Enable this to support Freescale USB OTG transceiver.
endif # USB || OTG endif # USB || OTG
...@@ -19,3 +19,5 @@ obj-$(CONFIG_USB_ULPI) += ulpi.o ...@@ -19,3 +19,5 @@ obj-$(CONFIG_USB_ULPI) += ulpi.o
obj-$(CONFIG_USB_ULPI_VIEWPORT) += ulpi_viewport.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += ulpi_viewport.o
obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o
obj-$(CONFIG_AB8500_USB) += ab8500-usb.o obj-$(CONFIG_AB8500_USB) += ab8500-usb.o
fsl_usb2_otg-objs := fsl_otg.o otg_fsm.o
obj-$(CONFIG_FSL_USB2_OTG) += fsl_usb2_otg.o
This diff is collapsed.
This diff is collapsed.
/*
* OTG Finite State Machine from OTG spec
*
* Copyright (C) 2007,2008 Freescale Semiconductor, Inc.
*
* Author: Li Yang <LeoLi@freescale.com>
* Jerry Huang <Chang-Ming.Huang@freescale.com>
*
* 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/usb.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/types.h>
#include "otg_fsm.h"
/* Change USB protocol when there is a protocol change */
static int otg_set_protocol(struct otg_fsm *fsm, int protocol)
{
int ret = 0;
if (fsm->protocol != protocol) {
VDBG("Changing role fsm->protocol= %d; new protocol= %d\n",
fsm->protocol, protocol);
/* stop old protocol */
if (fsm->protocol == PROTO_HOST)
ret = fsm->ops->start_host(fsm, 0);
else if (fsm->protocol == PROTO_GADGET)
ret = fsm->ops->start_gadget(fsm, 0);
if (ret)
return ret;
/* start new protocol */
if (protocol == PROTO_HOST)
ret = fsm->ops->start_host(fsm, 1);
else if (protocol == PROTO_GADGET)
ret = fsm->ops->start_gadget(fsm, 1);
if (ret)
return ret;
fsm->protocol = protocol;
return 0;
}
return 0;
}
static int state_changed;
/* Called when leaving a state. Do state clean up jobs here */
void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
{
switch (old_state) {
case OTG_STATE_B_IDLE:
otg_del_timer(fsm, b_se0_srp_tmr);
fsm->b_se0_srp = 0;
break;
case OTG_STATE_B_SRP_INIT:
fsm->b_srp_done = 0;
break;
case OTG_STATE_B_PERIPHERAL:
break;
case OTG_STATE_B_WAIT_ACON:
otg_del_timer(fsm, b_ase0_brst_tmr);
fsm->b_ase0_brst_tmout = 0;
break;
case OTG_STATE_B_HOST:
break;
case OTG_STATE_A_IDLE:
break;
case OTG_STATE_A_WAIT_VRISE:
otg_del_timer(fsm, a_wait_vrise_tmr);
fsm->a_wait_vrise_tmout = 0;
break;
case OTG_STATE_A_WAIT_BCON:
otg_del_timer(fsm, a_wait_bcon_tmr);
fsm->a_wait_bcon_tmout = 0;
break;
case OTG_STATE_A_HOST:
otg_del_timer(fsm, a_wait_enum_tmr);
break;
case OTG_STATE_A_SUSPEND:
otg_del_timer(fsm, a_aidl_bdis_tmr);
fsm->a_aidl_bdis_tmout = 0;
fsm->a_suspend_req = 0;
break;
case OTG_STATE_A_PERIPHERAL:
break;
case OTG_STATE_A_WAIT_VFALL:
otg_del_timer(fsm, a_wait_vrise_tmr);
break;
case OTG_STATE_A_VBUS_ERR:
break;
default:
break;
}
}
/* Called when entering a state */
int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
{
state_changed = 1;
if (fsm->transceiver->state == new_state)
return 0;
VDBG("Set state: %s\n", otg_state_string(new_state));
otg_leave_state(fsm, fsm->transceiver->state);
switch (new_state) {
case OTG_STATE_B_IDLE:
otg_drv_vbus(fsm, 0);
otg_chrg_vbus(fsm, 0);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_UNDEF);
otg_add_timer(fsm, b_se0_srp_tmr);
break;
case OTG_STATE_B_SRP_INIT:
otg_start_pulse(fsm);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_UNDEF);
otg_add_timer(fsm, b_srp_fail_tmr);
break;
case OTG_STATE_B_PERIPHERAL:
otg_chrg_vbus(fsm, 0);
otg_loc_conn(fsm, 1);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_GADGET);
break;
case OTG_STATE_B_WAIT_ACON:
otg_chrg_vbus(fsm, 0);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_HOST);
otg_add_timer(fsm, b_ase0_brst_tmr);
fsm->a_bus_suspend = 0;
break;
case OTG_STATE_B_HOST:
otg_chrg_vbus(fsm, 0);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 1);
otg_set_protocol(fsm, PROTO_HOST);
usb_bus_start_enum(fsm->transceiver->host,
fsm->transceiver->host->otg_port);
break;
case OTG_STATE_A_IDLE:
otg_drv_vbus(fsm, 0);
otg_chrg_vbus(fsm, 0);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_HOST);
break;
case OTG_STATE_A_WAIT_VRISE:
otg_drv_vbus(fsm, 1);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_HOST);
otg_add_timer(fsm, a_wait_vrise_tmr);
break;
case OTG_STATE_A_WAIT_BCON:
otg_drv_vbus(fsm, 1);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_HOST);
otg_add_timer(fsm, a_wait_bcon_tmr);
break;
case OTG_STATE_A_HOST:
otg_drv_vbus(fsm, 1);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 1);
otg_set_protocol(fsm, PROTO_HOST);
/*
* When HNP is triggered while a_bus_req = 0, a_host will
* suspend too fast to complete a_set_b_hnp_en
*/
if (!fsm->a_bus_req || fsm->a_suspend_req)
otg_add_timer(fsm, a_wait_enum_tmr);
break;
case OTG_STATE_A_SUSPEND:
otg_drv_vbus(fsm, 1);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_HOST);
otg_add_timer(fsm, a_aidl_bdis_tmr);
break;
case OTG_STATE_A_PERIPHERAL:
otg_loc_conn(fsm, 1);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_GADGET);
otg_drv_vbus(fsm, 1);
break;
case OTG_STATE_A_WAIT_VFALL:
otg_drv_vbus(fsm, 0);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_HOST);
break;
case OTG_STATE_A_VBUS_ERR:
otg_drv_vbus(fsm, 0);
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_UNDEF);
break;
default:
break;
}
fsm->transceiver->state = new_state;
return 0;
}
/* State change judgement */
int otg_statemachine(struct otg_fsm *fsm)
{
enum usb_otg_state state;
unsigned long flags;
spin_lock_irqsave(&fsm->lock, flags);
state = fsm->transceiver->state;
state_changed = 0;
/* State machine state change judgement */
switch (state) {
case OTG_STATE_UNDEFINED:
VDBG("fsm->id = %d\n", fsm->id);
if (fsm->id)
otg_set_state(fsm, OTG_STATE_B_IDLE);
else
otg_set_state(fsm, OTG_STATE_A_IDLE);
break;
case OTG_STATE_B_IDLE:
if (!fsm->id)
otg_set_state(fsm, OTG_STATE_A_IDLE);
else if (fsm->b_sess_vld && fsm->transceiver->gadget)
otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
else if (fsm->b_bus_req && fsm->b_sess_end && fsm->b_se0_srp)
otg_set_state(fsm, OTG_STATE_B_SRP_INIT);
break;
case OTG_STATE_B_SRP_INIT:
if (!fsm->id || fsm->b_srp_done)
otg_set_state(fsm, OTG_STATE_B_IDLE);
break;
case OTG_STATE_B_PERIPHERAL:
if (!fsm->id || !fsm->b_sess_vld)
otg_set_state(fsm, OTG_STATE_B_IDLE);
else if (fsm->b_bus_req && fsm->transceiver->
gadget->b_hnp_enable && fsm->a_bus_suspend)
otg_set_state(fsm, OTG_STATE_B_WAIT_ACON);
break;
case OTG_STATE_B_WAIT_ACON:
if (fsm->a_conn)
otg_set_state(fsm, OTG_STATE_B_HOST);
else if (!fsm->id || !fsm->b_sess_vld)
otg_set_state(fsm, OTG_STATE_B_IDLE);
else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) {
fsm->b_ase0_brst_tmout = 0;
otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
}
break;
case OTG_STATE_B_HOST:
if (!fsm->id || !fsm->b_sess_vld)
otg_set_state(fsm, OTG_STATE_B_IDLE);
else if (!fsm->b_bus_req || !fsm->a_conn)
otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
break;
case OTG_STATE_A_IDLE:
if (fsm->id)
otg_set_state(fsm, OTG_STATE_B_IDLE);
else if (!fsm->a_bus_drop && (fsm->a_bus_req || fsm->a_srp_det))
otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE);
break;
case OTG_STATE_A_WAIT_VRISE:
if (fsm->id || fsm->a_bus_drop || fsm->a_vbus_vld ||
fsm->a_wait_vrise_tmout) {
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
}
break;
case OTG_STATE_A_WAIT_BCON:
if (!fsm->a_vbus_vld)
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
else if (fsm->b_conn)
otg_set_state(fsm, OTG_STATE_A_HOST);
else if (fsm->id | fsm->a_bus_drop | fsm->a_wait_bcon_tmout)
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
break;
case OTG_STATE_A_HOST:
if ((!fsm->a_bus_req || fsm->a_suspend_req) &&
fsm->transceiver->host->b_hnp_enable)
otg_set_state(fsm, OTG_STATE_A_SUSPEND);
else if (fsm->id || !fsm->b_conn || fsm->a_bus_drop)
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
else if (!fsm->a_vbus_vld)
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
break;
case OTG_STATE_A_SUSPEND:
if (!fsm->b_conn && fsm->transceiver->host->b_hnp_enable)
otg_set_state(fsm, OTG_STATE_A_PERIPHERAL);
else if (!fsm->b_conn && !fsm->transceiver->host->b_hnp_enable)
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
else if (fsm->a_bus_req || fsm->b_bus_resume)
otg_set_state(fsm, OTG_STATE_A_HOST);
else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout)
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
else if (!fsm->a_vbus_vld)
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
break;
case OTG_STATE_A_PERIPHERAL:
if (fsm->id || fsm->a_bus_drop)
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
else if (fsm->b_bus_suspend)
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
else if (!fsm->a_vbus_vld)
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
break;
case OTG_STATE_A_WAIT_VFALL:
if (fsm->id || fsm->a_bus_req || (!fsm->a_sess_vld &&
!fsm->b_conn))
otg_set_state(fsm, OTG_STATE_A_IDLE);
break;
case OTG_STATE_A_VBUS_ERR:
if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err)
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
break;
default:
break;
}
spin_unlock_irqrestore(&fsm->lock, flags);
VDBG("quit statemachine, changed = %d\n", state_changed);
return state_changed;
}
/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc.
*
* 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#undef DEBUG
#undef VERBOSE
#ifdef DEBUG
#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt , \
__func__, ## args)
#else
#define DBG(fmt, args...) do {} while (0)
#endif
#ifdef VERBOSE
#define VDBG DBG
#else
#define VDBG(stuff...) do {} while (0)
#endif
#ifdef VERBOSE
#define MPC_LOC printk("Current Location [%s]:[%d]\n", __FILE__, __LINE__)
#else
#define MPC_LOC do {} while (0)
#endif
#define PROTO_UNDEF (0)
#define PROTO_HOST (1)
#define PROTO_GADGET (2)
/* OTG state machine according to the OTG spec */
struct otg_fsm {
/* Input */
int a_bus_resume;
int a_bus_suspend;
int a_conn;
int a_sess_vld;
int a_srp_det;
int a_vbus_vld;
int b_bus_resume;
int b_bus_suspend;
int b_conn;
int b_se0_srp;
int b_sess_end;
int b_sess_vld;
int id;
/* Internal variables */
int a_set_b_hnp_en;
int b_srp_done;
int b_hnp_enable;
/* Timeout indicator for timers */
int a_wait_vrise_tmout;
int a_wait_bcon_tmout;
int a_aidl_bdis_tmout;
int b_ase0_brst_tmout;
/* Informative variables */
int a_bus_drop;
int a_bus_req;
int a_clr_err;
int a_suspend_req;
int b_bus_req;
/* Output */
int drv_vbus;
int loc_conn;
int loc_sof;
struct otg_fsm_ops *ops;
struct otg_transceiver *transceiver;
/* Current usb protocol used: 0:undefine; 1:host; 2:client */
int protocol;
spinlock_t lock;
};
struct otg_fsm_ops {
void (*chrg_vbus)(int on);
void (*drv_vbus)(int on);
void (*loc_conn)(int on);
void (*loc_sof)(int on);
void (*start_pulse)(void);
void (*add_timer)(void *timer);
void (*del_timer)(void *timer);
int (*start_host)(struct otg_fsm *fsm, int on);
int (*start_gadget)(struct otg_fsm *fsm, int on);
};
static inline void otg_chrg_vbus(struct otg_fsm *fsm, int on)
{
fsm->ops->chrg_vbus(on);
}
static inline void otg_drv_vbus(struct otg_fsm *fsm, int on)
{
if (fsm->drv_vbus != on) {
fsm->drv_vbus = on;
fsm->ops->drv_vbus(on);
}
}
static inline void otg_loc_conn(struct otg_fsm *fsm, int on)
{
if (fsm->loc_conn != on) {
fsm->loc_conn = on;
fsm->ops->loc_conn(on);
}
}
static inline void otg_loc_sof(struct otg_fsm *fsm, int on)
{
if (fsm->loc_sof != on) {
fsm->loc_sof = on;
fsm->ops->loc_sof(on);
}
}
static inline void otg_start_pulse(struct otg_fsm *fsm)
{
fsm->ops->start_pulse();
}
static inline void otg_add_timer(struct otg_fsm *fsm, void *timer)
{
fsm->ops->add_timer(timer);
}
static inline void otg_del_timer(struct otg_fsm *fsm, void *timer)
{
fsm->ops->del_timer(timer);
}
int otg_statemachine(struct otg_fsm *fsm);
/* Defined by device specific driver, for different timer implementation */
extern struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr,
*a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr,
*a_wait_enum_tmr;
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment