diff options
Diffstat (limited to 'drivers/usb/otg')
-rw-r--r-- | drivers/usb/otg/Kconfig | 8 | ||||
-rw-r--r-- | drivers/usb/otg/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/otg/fsl_otg.c | 1169 | ||||
-rw-r--r-- | drivers/usb/otg/fsl_otg.h | 406 | ||||
-rw-r--r-- | drivers/usb/otg/gpio_vbus.c | 14 | ||||
-rw-r--r-- | drivers/usb/otg/isp1301_omap.c | 26 | ||||
-rw-r--r-- | drivers/usb/otg/langwell_otg.c | 40 | ||||
-rw-r--r-- | drivers/usb/otg/msm_otg.c | 671 | ||||
-rw-r--r-- | drivers/usb/otg/otg.c | 35 | ||||
-rw-r--r-- | drivers/usb/otg/otg_fsm.c | 349 | ||||
-rw-r--r-- | drivers/usb/otg/otg_fsm.h | 154 | ||||
-rw-r--r-- | drivers/usb/otg/twl4030-usb.c | 9 | ||||
-rw-r--r-- | drivers/usb/otg/twl6030-usb.c | 17 |
13 files changed, 2814 insertions, 86 deletions
diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index daf3e5f1a0e7..c66481ad98d7 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -122,4 +122,12 @@ config AB8500_USB This transceiver supports high and full speed devices plus, 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 diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index e22d917de017..566655c53331 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -19,3 +19,5 @@ obj-$(CONFIG_USB_ULPI) += ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += ulpi_viewport.o obj-$(CONFIG_USB_MSM_OTG) += msm_otg.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 diff --git a/drivers/usb/otg/fsl_otg.c b/drivers/usb/otg/fsl_otg.c new file mode 100644 index 000000000000..0f420b25e9a9 --- /dev/null +++ b/drivers/usb/otg/fsl_otg.c @@ -0,0 +1,1169 @@ +/* + * Copyright (C) 2007,2008 Freescale semiconductor, Inc. + * + * Author: Li Yang <LeoLi@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * + * Initialization based on code from Shlomi Gridish. + * + * 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/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/timer.h> +#include <linux/usb.h> +#include <linux/device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/workqueue.h> +#include <linux/time.h> +#include <linux/fsl_devices.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> + +#include <asm/unaligned.h> + +#include "fsl_otg.h" + +#define DRIVER_VERSION "Rev. 1.55" +#define DRIVER_AUTHOR "Jerry Huang/Li Yang" +#define DRIVER_DESC "Freescale USB OTG Transceiver Driver" +#define DRIVER_INFO DRIVER_DESC " " DRIVER_VERSION + +static const char driver_name[] = "fsl-usb2-otg"; + +const pm_message_t otg_suspend_state = { + .event = 1, +}; + +#define HA_DATA_PULSE + +static struct usb_dr_mmap *usb_dr_regs; +static struct fsl_otg *fsl_otg_dev; +static int srp_wait_done; + +/* FSM timers */ +struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr; + +/* Driver specific timers */ +struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, + *b_srp_wait_tmr, *a_wait_enum_tmr; + +static struct list_head active_timers; + +static struct fsl_otg_config fsl_otg_initdata = { + .otg_port = 1, +}; + +#ifdef CONFIG_PPC32 +static u32 _fsl_readl_be(const unsigned __iomem *p) +{ + return in_be32(p); +} + +static u32 _fsl_readl_le(const unsigned __iomem *p) +{ + return in_le32(p); +} + +static void _fsl_writel_be(u32 v, unsigned __iomem *p) +{ + out_be32(p, v); +} + +static void _fsl_writel_le(u32 v, unsigned __iomem *p) +{ + out_le32(p, v); +} + +static u32 (*_fsl_readl)(const unsigned __iomem *p); +static void (*_fsl_writel)(u32 v, unsigned __iomem *p); + +#define fsl_readl(p) (*_fsl_readl)((p)) +#define fsl_writel(v, p) (*_fsl_writel)((v), (p)) + +#else +#define fsl_readl(addr) readl(addr) +#define fsl_writel(val, addr) writel(val, addr) +#endif /* CONFIG_PPC32 */ + +/* Routines to access transceiver ULPI registers */ +u8 view_ulpi(u8 addr) +{ + u32 temp; + + temp = 0x40000000 | (addr << 16); + fsl_writel(temp, &usb_dr_regs->ulpiview); + udelay(1000); + while (temp & 0x40) + temp = fsl_readl(&usb_dr_regs->ulpiview); + return (le32_to_cpu(temp) & 0x0000ff00) >> 8; +} + +int write_ulpi(u8 addr, u8 data) +{ + u32 temp; + + temp = 0x60000000 | (addr << 16) | data; + fsl_writel(temp, &usb_dr_regs->ulpiview); + return 0; +} + +/* -------------------------------------------------------------*/ +/* Operations that will be called from OTG Finite State Machine */ + +/* Charge vbus for vbus pulsing in SRP */ +void fsl_otg_chrg_vbus(int on) +{ + u32 tmp; + + tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + + if (on) + /* stop discharging, start charging */ + tmp = (tmp & ~OTGSC_CTRL_VBUS_DISCHARGE) | + OTGSC_CTRL_VBUS_CHARGE; + else + /* stop charging */ + tmp &= ~OTGSC_CTRL_VBUS_CHARGE; + + fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* Discharge vbus through a resistor to ground */ +void fsl_otg_dischrg_vbus(int on) +{ + u32 tmp; + + tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + + if (on) + /* stop charging, start discharging */ + tmp = (tmp & ~OTGSC_CTRL_VBUS_CHARGE) | + OTGSC_CTRL_VBUS_DISCHARGE; + else + /* stop discharging */ + tmp &= ~OTGSC_CTRL_VBUS_DISCHARGE; + + fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* A-device driver vbus, controlled through PP bit in PORTSC */ +void fsl_otg_drv_vbus(int on) +{ + u32 tmp; + + if (on) { + tmp = fsl_readl(&usb_dr_regs->portsc) & ~PORTSC_W1C_BITS; + fsl_writel(tmp | PORTSC_PORT_POWER, &usb_dr_regs->portsc); + } else { + tmp = fsl_readl(&usb_dr_regs->portsc) & + ~PORTSC_W1C_BITS & ~PORTSC_PORT_POWER; + fsl_writel(tmp, &usb_dr_regs->portsc); + } +} + +/* + * Pull-up D+, signalling connect by periperal. Also used in + * data-line pulsing in SRP + */ +void fsl_otg_loc_conn(int on) +{ + u32 tmp; + + tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + + if (on) + tmp |= OTGSC_CTRL_DATA_PULSING; + else + tmp &= ~OTGSC_CTRL_DATA_PULSING; + + fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* + * Generate SOF by host. This is controlled through suspend/resume the + * port. In host mode, controller will automatically send SOF. + * Suspend will block the data on the port. + */ +void fsl_otg_loc_sof(int on) +{ + u32 tmp; + + tmp = fsl_readl(&fsl_otg_dev->dr_mem_map->portsc) & ~PORTSC_W1C_BITS; + if (on) + tmp |= PORTSC_PORT_FORCE_RESUME; + else + tmp |= PORTSC_PORT_SUSPEND; + + fsl_writel(tmp, &fsl_otg_dev->dr_mem_map->portsc); + +} + +/* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ +void fsl_otg_start_pulse(void) +{ + u32 tmp; + + srp_wait_done = 0; +#ifdef HA_DATA_PULSE + tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + tmp |= OTGSC_HA_DATA_PULSE; + fsl_writel(tmp, &usb_dr_regs->otgsc); +#else + fsl_otg_loc_conn(1); +#endif + + fsl_otg_add_timer(b_data_pulse_tmr); +} + +void b_data_pulse_end(unsigned long foo) +{ +#ifdef HA_DATA_PULSE +#else + fsl_otg_loc_conn(0); +#endif + + /* Do VBUS pulse after data pulse */ + fsl_otg_pulse_vbus(); +} + +void fsl_otg_pulse_vbus(void) +{ + srp_wait_done = 0; + fsl_otg_chrg_vbus(1); + /* start the timer to end vbus charge */ + fsl_otg_add_timer(b_vbus_pulse_tmr); +} + +void b_vbus_pulse_end(unsigned long foo) +{ + fsl_otg_chrg_vbus(0); + + /* + * As USB3300 using the same a_sess_vld and b_sess_vld voltage + * we need to discharge the bus for a while to distinguish + * residual voltage of vbus pulsing and A device pull up + */ + fsl_otg_dischrg_vbus(1); + fsl_otg_add_timer(b_srp_wait_tmr); +} + +void b_srp_end(unsigned long foo) +{ + fsl_otg_dischrg_vbus(0); + srp_wait_done = 1; + + if ((fsl_otg_dev->otg.state == OTG_STATE_B_SRP_INIT) && + fsl_otg_dev->fsm.b_sess_vld) + fsl_otg_dev->fsm.b_srp_done = 1; +} + +/* + * Workaround for a_host suspending too fast. When a_bus_req=0, + * a_host will start by SRP. It needs to set b_hnp_enable before + * actually suspending to start HNP + */ +void a_wait_enum(unsigned long foo) +{ + VDBG("a_wait_enum timeout\n"); + if (!fsl_otg_dev->otg.host->b_hnp_enable) + fsl_otg_add_timer(a_wait_enum_tmr); + else + otg_statemachine(&fsl_otg_dev->fsm); +} + +/* The timeout callback function to set time out bit */ +void set_tmout(unsigned long indicator) +{ + *(int *)indicator = 1; +} + +/* Initialize timers */ +int fsl_otg_init_timers(struct otg_fsm *fsm) +{ + /* FSM used timers */ + a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, + (unsigned long)&fsm->a_wait_vrise_tmout); + if (!a_wait_vrise_tmr) + return -ENOMEM; + + a_wait_bcon_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_BCON, + (unsigned long)&fsm->a_wait_bcon_tmout); + if (!a_wait_bcon_tmr) + return -ENOMEM; + + a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout, TA_AIDL_BDIS, + (unsigned long)&fsm->a_aidl_bdis_tmout); + if (!a_aidl_bdis_tmr) + return -ENOMEM; + + b_ase0_brst_tmr = otg_timer_initializer(&set_tmout, TB_ASE0_BRST, + (unsigned long)&fsm->b_ase0_brst_tmout); + if (!b_ase0_brst_tmr) + return -ENOMEM; + + b_se0_srp_tmr = otg_timer_initializer(&set_tmout, TB_SE0_SRP, + (unsigned long)&fsm->b_se0_srp); + if (!b_se0_srp_tmr) + return -ENOMEM; + + b_srp_fail_tmr = otg_timer_initializer(&set_tmout, TB_SRP_FAIL, + (unsigned long)&fsm->b_srp_done); + if (!b_srp_fail_tmr) + return -ENOMEM; + + a_wait_enum_tmr = otg_timer_initializer(&a_wait_enum, 10, + (unsigned long)&fsm); + if (!a_wait_enum_tmr) + return -ENOMEM; + + /* device driver used timers */ + b_srp_wait_tmr = otg_timer_initializer(&b_srp_end, TB_SRP_WAIT, 0); + if (!b_srp_wait_tmr) + return -ENOMEM; + + b_data_pulse_tmr = otg_timer_initializer(&b_data_pulse_end, + TB_DATA_PLS, 0); + if (!b_data_pulse_tmr) + return -ENOMEM; + + b_vbus_pulse_tmr = otg_timer_initializer(&b_vbus_pulse_end, + TB_VBUS_PLS, 0); + if (!b_vbus_pulse_tmr) + return -ENOMEM; + + return 0; +} + +/* Uninitialize timers */ +void fsl_otg_uninit_timers(void) +{ + /* FSM used timers */ + if (a_wait_vrise_tmr != NULL) + kfree(a_wait_vrise_tmr); + if (a_wait_bcon_tmr != NULL) + kfree(a_wait_bcon_tmr); + if (a_aidl_bdis_tmr != NULL) + kfree(a_aidl_bdis_tmr); + if (b_ase0_brst_tmr != NULL) + kfree(b_ase0_brst_tmr); + if (b_se0_srp_tmr != NULL) + kfree(b_se0_srp_tmr); + if (b_srp_fail_tmr != NULL) + kfree(b_srp_fail_tmr); + if (a_wait_enum_tmr != NULL) + kfree(a_wait_enum_tmr); + + /* device driver used timers */ + if (b_srp_wait_tmr != NULL) + kfree(b_srp_wait_tmr); + if (b_data_pulse_tmr != NULL) + kfree(b_data_pulse_tmr); + if (b_vbus_pulse_tmr != NULL) + kfree(b_vbus_pulse_tmr); +} + +/* Add timer to timer list */ +void fsl_otg_add_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = gtimer; + struct fsl_otg_timer *tmp_timer; + + /* + * Check if the timer is already in the active list, + * if so update timer count + */ + list_for_each_entry(tmp_timer, &active_timers, list) + if (tmp_timer == timer) { + timer->count = timer->expires; + return; + } + timer->count = timer->expires; + list_add_tail(&timer->list, &active_timers); +} + +/* Remove timer from the timer list; clear timeout status */ +void fsl_otg_del_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = gtimer; + struct fsl_otg_timer *tmp_timer, *del_tmp; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) + if (tmp_timer == timer) + list_del(&timer->list); +} + +/* + * Reduce timer count by 1, and find timeout conditions. + * Called by fsl_otg 1ms timer interrupt + */ +int fsl_otg_tick_timer(void) +{ + struct fsl_otg_timer *tmp_timer, *del_tmp; + int expired = 0; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { + tmp_timer->count--; + /* check if timer expires */ + if (!tmp_timer->count) { + list_del(&tmp_timer->list); + tmp_timer->function(tmp_timer->data); + expired = 1; + } + } + + return expired; +} + +/* Reset controller, not reset the bus */ +void otg_reset_controller(void) +{ + u32 command; + + command = fsl_readl(&usb_dr_regs->usbcmd); + command |= (1 << 1); + fsl_writel(command, &usb_dr_regs->usbcmd); + while (fsl_readl(&usb_dr_regs->usbcmd) & (1 << 1)) + ; +} + +/* Call suspend/resume routines in host driver */ +int fsl_otg_start_host(struct otg_fsm *fsm, int on) +{ + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *dev; + struct fsl_otg *otg_dev = container_of(xceiv, struct fsl_otg, otg); + u32 retval = 0; + + if (!xceiv->host) + return -ENODEV; + dev = xceiv->host->controller; + + /* + * Update a_vbus_vld state as a_vbus_vld int is disabled + * in device mode + */ + fsm->a_vbus_vld = + !!(fsl_readl(&usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID); + if (on) { + /* start fsl usb host controller */ + if (otg_dev->host_working) + goto end; + else { + otg_reset_controller(); + VDBG("host on......\n"); + if (dev->driver->pm && dev->driver->pm->resume) { + retval = dev->driver->pm->resume(dev); + if (fsm->id) { + /* default-b */ + fsl_otg_drv_vbus(1); + /* + * Workaround: b_host can't driver + * vbus, but PP in PORTSC needs to + * be 1 for host to work. + * So we set drv_vbus bit in + * transceiver to 0 thru ULPI. + */ + write_ulpi(0x0c, 0x20); + } + } + + otg_dev->host_working = 1; + } + } else { + /* stop fsl usb host controller */ + if (!otg_dev->host_working) + goto end; + else { + VDBG("host off......\n"); + if (dev && dev->driver) { + if (dev->driver->pm && dev->driver->pm->suspend) + retval = dev->driver->pm->suspend(dev); + if (fsm->id) + /* default-b */ + fsl_otg_drv_vbus(0); + } + otg_dev->host_working = 0; + } + } +end: + return retval; +} + +/* + * Call suspend and resume function in udc driver + * to stop and start udc driver. + */ +int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) +{ + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *dev; + + if (!xceiv->gadget || !xceiv->gadget->dev.parent) + return -ENODEV; + + VDBG("gadget %s\n", on ? "on" : "off"); + dev = xceiv->gadget->dev.parent; + + if (on) { + if (dev->driver->resume) + dev->driver->resume(dev); + } else { + if (dev->driver->suspend) + dev->driver->suspend(dev, otg_suspend_state); + } + + return 0; +} + +/* + * Called by initialization code of host driver. Register host controller + * to the OTG. Suspend host for OTG role detection. + */ +static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + otg_p->host = host; + + otg_dev->fsm.a_bus_drop = 0; + otg_dev->fsm.a_bus_req = 1; + + if (host) { + VDBG("host off......\n"); + + otg_p->host->otg_port = fsl_otg_initdata.otg_port; + otg_p->host->is_b_host = otg_dev->fsm.id; + /* + * must leave time for khubd to finish its thing + * before yanking the host driver out from under it, + * so suspend the host after a short delay. + */ + otg_dev->host_working = 1; + schedule_delayed_work(&otg_dev->otg_event, 100); + return 0; + } else { + /* host driver going away */ + if (!(fsl_readl(&otg_dev->dr_mem_map->otgsc) & + OTGSC_STS_USB_ID)) { + /* Mini-A cable connected */ + struct otg_fsm *fsm = &otg_dev->fsm; + + otg_p->state = OTG_STATE_UNDEFINED; + fsm->protocol = PROTO_UNDEF; + } + } + + otg_dev->host_working = 0; + + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* Called by initialization code of udc. Register udc to OTG. */ +static int fsl_otg_set_peripheral(struct otg_transceiver *otg_p, + struct usb_gadget *gadget) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + VDBG("otg_dev 0x%x\n", (int)otg_dev); + VDBG("fsl_otg_dev 0x%x\n", (int)fsl_otg_dev); + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + if (!gadget) { + if (!otg_dev->otg.default_a) + otg_p->gadget->ops->vbus_draw(otg_p->gadget, 0); + usb_gadget_vbus_disconnect(otg_dev->otg.gadget); + otg_dev->otg.gadget = 0; + otg_dev->fsm.b_bus_req = 0; + otg_statemachine(&otg_dev->fsm); + return 0; + } + + otg_p->gadget = gadget; + otg_p->gadget->is_a_peripheral = !otg_dev->fsm.id; + + otg_dev->fsm.b_bus_req = 1; + + /* start the gadget right away if the ID pin says Mini-B */ + DBG("ID pin=%d\n", otg_dev->fsm.id); + if (otg_dev->fsm.id == 1) { + fsl_otg_start_host(&otg_dev->fsm, 0); + otg_drv_vbus(&otg_dev->fsm, 0); + fsl_otg_start_gadget(&otg_dev->fsm, 1); + } + + return 0; +} + +/* Set OTG port power, only for B-device */ +static int fsl_otg_set_power(struct otg_transceiver *otg_p, unsigned mA) +{ + if (!fsl_otg_dev) + return -ENODEV; + if (otg_p->state == OTG_STATE_B_PERIPHERAL) + pr_info("FSL OTG: Draw %d mA\n", mA); + + return 0; +} + +/* + * Delayed pin detect interrupt processing. + * + * When the Mini-A cable is disconnected from the board, + * the pin-detect interrupt happens before the disconnnect + * interrupts for the connected device(s). In order to + * process the disconnect interrupt(s) prior to switching + * roles, the pin-detect interrupts are delayed, and handled + * by this routine. + */ +static void fsl_otg_event(struct work_struct *work) +{ + struct fsl_otg *og = container_of(work, struct fsl_otg, otg_event.work); + struct otg_fsm *fsm = &og->fsm; + + if (fsm->id) { /* switch to gadget */ + fsl_otg_start_host(fsm, 0); + otg_drv_vbus(fsm, 0); + fsl_otg_start_gadget(fsm, 1); + } +} + +/* B-device start SRP */ +static int fsl_otg_start_srp(struct otg_transceiver *otg_p) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + if (!otg_p || otg_dev != fsl_otg_dev + || otg_p->state != OTG_STATE_B_IDLE) + return -ENODEV; + + otg_dev->fsm.b_bus_req = 1; + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* A_host suspend will call this function to start hnp */ +static int fsl_otg_start_hnp(struct otg_transceiver *otg_p) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + DBG("start_hnp...n"); + + /* clear a_bus_req to enter a_suspend state */ + otg_dev->fsm.a_bus_req = 0; + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* + * Interrupt handler. OTG/host/peripheral share the same int line. + * OTG driver clears OTGSC interrupts and leaves USB interrupts + * intact. It needs to have knowledge of some USB interrupts + * such as port change. + */ +irqreturn_t fsl_otg_isr(int irq, void *dev_id) +{ + struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; + struct otg_transceiver *otg = &((struct fsl_otg *)dev_id)->otg; + u32 otg_int_src, otg_sc; + + otg_sc = fsl_readl(&usb_dr_regs->otgsc); + otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8); + + /* Only clear otg interrupts */ + fsl_writel(otg_sc, &usb_dr_regs->otgsc); + + /*FIXME: ID change not generate when init to 0 */ + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + + /* process OTG interrupts */ + if (otg_int_src) { + if (otg_int_src & OTGSC_INTSTS_USB_ID) { + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + /* clear conn information */ + if (fsm->id) + fsm->b_conn = 0; + else + fsm->a_conn = 0; + + if (otg->host) + otg->host->is_b_host = fsm->id; + if (otg->gadget) + otg->gadget->is_a_peripheral = !fsm->id; + VDBG("ID int (ID is %d)\n", fsm->id); + + if (fsm->id) { /* switch to gadget */ + schedule_delayed_work( + &((struct fsl_otg *)dev_id)->otg_event, + 100); + } else { /* switch to host */ + cancel_delayed_work(& + ((struct fsl_otg *)dev_id)-> + otg_event); + fsl_otg_start_gadget(fsm, 0); + otg_drv_vbus(fsm, 1); + fsl_otg_start_host(fsm, 1); + } + return IRQ_HANDLED; + } + } + return IRQ_NONE; +} + +static struct otg_fsm_ops fsl_otg_ops = { + .chrg_vbus = fsl_otg_chrg_vbus, + .drv_vbus = fsl_otg_drv_vbus, + .loc_conn = fsl_otg_loc_conn, + .loc_sof = fsl_otg_loc_sof, + .start_pulse = fsl_otg_start_pulse, + + .add_timer = fsl_otg_add_timer, + .del_timer = fsl_otg_del_timer, + + .start_host = fsl_otg_start_host, + .start_gadget = fsl_otg_start_gadget, +}; + +/* Initialize the global variable fsl_otg_dev and request IRQ for OTG */ +static int fsl_otg_conf(struct platform_device *pdev) +{ + struct fsl_otg *fsl_otg_tc; + int status; + + if (fsl_otg_dev) + return 0; + + /* allocate space to fsl otg device */ + fsl_otg_tc = kzalloc(sizeof(struct fsl_otg), GFP_KERNEL); + if (!fsl_otg_tc) + return -ENOMEM; + + INIT_DELAYED_WORK(&fsl_otg_tc->otg_event, fsl_otg_event); + + INIT_LIST_HEAD(&active_timers); + status = fsl_otg_init_timers(&fsl_otg_tc->fsm); + if (status) { + pr_info("Couldn't init OTG timers\n"); + goto err; + } + spin_lock_init(&fsl_otg_tc->fsm.lock); + + /* Set OTG state machine operations */ + fsl_otg_tc->fsm.ops = &fsl_otg_ops; + + /* initialize the otg structure */ + fsl_otg_tc->otg.label = DRIVER_DESC; + fsl_otg_tc->otg.set_host = fsl_otg_set_host; + fsl_otg_tc->otg.set_peripheral = fsl_otg_set_peripheral; + fsl_otg_tc->otg.set_power = fsl_otg_set_power; + fsl_otg_tc->otg.start_hnp = fsl_otg_start_hnp; + fsl_otg_tc->otg.start_srp = fsl_otg_start_srp; + + fsl_otg_dev = fsl_otg_tc; + + /* Store the otg transceiver */ + status = otg_set_transceiver(&fsl_otg_tc->otg); + if (status) { + pr_warn(FSL_OTG_NAME ": unable to register OTG transceiver.\n"); + goto err; + } + + return 0; +err: + fsl_otg_uninit_timers(); + kfree(fsl_otg_tc); + return status; +} + +/* OTG Initialization */ +int usb_otg_start(struct platform_device *pdev) +{ + struct fsl_otg *p_otg; + struct otg_transceiver *otg_trans = otg_get_transceiver(); + struct otg_fsm *fsm; + int status; + struct resource *res; + u32 temp; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + p_otg = container_of(otg_trans, struct fsl_otg, otg); + fsm = &p_otg->fsm; + + /* Initialize the state machine structure with default values */ + SET_OTG_STATE(otg_trans, OTG_STATE_UNDEFINED); + fsm->transceiver = &p_otg->otg; + + /* We don't require predefined MEM/IRQ resource index */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + /* We don't request_mem_region here to enable resource sharing + * with host/device */ + + usb_dr_regs = ioremap(res->start, sizeof(struct usb_dr_mmap)); + p_otg->dr_mem_map = (struct usb_dr_mmap *)usb_dr_regs; + pdata->regs = (void *)usb_dr_regs; + + if (pdata->init && pdata->init(pdev) != 0) + return -EINVAL; + + if (pdata->big_endian_mmio) { + _fsl_readl = _fsl_readl_be; + _fsl_writel = _fsl_writel_be; + } else { + _fsl_readl = _fsl_readl_le; + _fsl_writel = _fsl_writel_le; + } + + /* request irq */ + p_otg->irq = platform_get_irq(pdev, 0); + status = request_irq(p_otg->irq, fsl_otg_isr, + IRQF_SHARED, driver_name, p_otg); + if (status) { + dev_dbg(p_otg->otg.dev, "can't get IRQ %d, error %d\n", + p_otg->irq, status); + iounmap(p_otg->dr_mem_map); + kfree(p_otg); + return status; + } + + /* stop the controller */ + temp = fsl_readl(&p_otg->dr_mem_map->usbcmd); + temp &= ~USB_CMD_RUN_STOP; + fsl_writel(temp, &p_otg->dr_mem_map->usbcmd); + + /* reset the controller */ + temp = fsl_readl(&p_otg->dr_mem_map->usbcmd); + temp |= USB_CMD_CTRL_RESET; + fsl_writel(temp, &p_otg->dr_mem_map->usbcmd); + + /* wait reset completed */ + while (fsl_readl(&p_otg->dr_mem_map->usbcmd) & USB_CMD_CTRL_RESET) + ; + + /* configure the VBUSHS as IDLE(both host and device) */ + temp = USB_MODE_STREAM_DISABLE | (pdata->es ? USB_MODE_ES : 0); + fsl_writel(temp, &p_otg->dr_mem_map->usbmode); + + /* configure PHY interface */ + temp = fsl_readl(&p_otg->dr_mem_map->portsc); + temp &= ~(PORTSC_PHY_TYPE_SEL | PORTSC_PTW); + switch (pdata->phy_mode) { + case FSL_USB2_PHY_ULPI: + temp |= PORTSC_PTS_ULPI; + break; + case FSL_USB2_PHY_UTMI_WIDE: + temp |= PORTSC_PTW_16BIT; + /* fall through */ + case FSL_USB2_PHY_UTMI: + temp |= PORTSC_PTS_UTMI; + /* fall through */ + default: + break; + } + fsl_writel(temp, &p_otg->dr_mem_map->portsc); + + if (pdata->have_sysif_regs) { + /* configure control enable IO output, big endian register */ + temp = __raw_readl(&p_otg->dr_mem_map->control); + temp |= USB_CTRL_IOENB; + __raw_writel(temp, &p_otg->dr_mem_map->control); + } + + /* disable all interrupt and clear all OTGSC status */ + temp = fsl_readl(&p_otg->dr_mem_map->otgsc); + temp &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; + temp |= OTGSC_INTERRUPT_STATUS_BITS_MASK | OTGSC_CTRL_VBUS_DISCHARGE; + fsl_writel(temp, &p_otg->dr_mem_map->otgsc); + + /* + * The identification (id) input is FALSE when a Mini-A plug is inserted + * in the devices Mini-AB receptacle. Otherwise, this input is TRUE. + * Also: record initial state of ID pin + */ + if (fsl_readl(&p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) { + p_otg->otg.state = OTG_STATE_UNDEFINED; + p_otg->fsm.id = 1; + } else { + p_otg->otg.state = OTG_STATE_A_IDLE; + p_otg->fsm.id = 0; + } + + DBG("initial ID pin=%d\n", p_otg->fsm.id); + + /* enable OTG ID pin interrupt */ + temp = fsl_readl(&p_otg->dr_mem_map->otgsc); + temp |= OTGSC_INTR_USB_ID_EN; + temp &= ~(OTGSC_CTRL_VBUS_DISCHARGE | OTGSC_INTR_1MS_TIMER_EN); + fsl_writel(temp, &p_otg->dr_mem_map->otgsc); + + return 0; +} + +/* + * state file in sysfs + */ +static int show_fsl_usb2_otg_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct otg_fsm *fsm = &fsl_otg_dev->fsm; + char *next = buf; + unsigned size = PAGE_SIZE; + unsigned long flags; + int t; + + spin_lock_irqsave(&fsm->lock, flags); + + /* basic driver infomation */ + t = scnprintf(next, size, + DRIVER_DESC "\n" "fsl_usb2_otg version: %s\n\n", + DRIVER_VERSION); + size -= t; + next += t; + + /* Registers */ + t = scnprintf(next, size, + "OTGSC: 0x%08x\n" + "PORTSC: 0x%08x\n" + "USBMODE: 0x%08x\n" + "USBCMD: 0x%08x\n" + "USBSTS: 0x%08x\n" + "USBINTR: 0x%08x\n", + fsl_readl(&usb_dr_regs->otgsc), + fsl_readl(&usb_dr_regs->portsc), + fsl_readl(&usb_dr_regs->usbmode), + fsl_readl(&usb_dr_regs->usbcmd), + fsl_readl(&usb_dr_regs->usbsts), + fsl_readl(&usb_dr_regs->usbintr)); + size -= t; + next += t; + + /* State */ + t = scnprintf(next, size, + "OTG state: %s\n\n", + otg_state_string(fsl_otg_dev->otg.state)); + size -= t; + next += t; + + /* State Machine Variables */ + t = scnprintf(next, size, + "a_bus_req: %d\n" + "b_bus_req: %d\n" + "a_bus_resume: %d\n" + "a_bus_suspend: %d\n" + "a_conn: %d\n" + "a_sess_vld: %d\n" + "a_srp_det: %d\n" + "a_vbus_vld: %d\n" + "b_bus_resume: %d\n" + "b_bus_suspend: %d\n" + "b_conn: %d\n" + "b_se0_srp: %d\n" + "b_sess_end: %d\n" + "b_sess_vld: %d\n" + "id: %d\n", + fsm->a_bus_req, + fsm->b_bus_req, + fsm->a_bus_resume, + fsm->a_bus_suspend, + fsm->a_conn, + fsm->a_sess_vld, + fsm->a_srp_det, + fsm->a_vbus_vld, + fsm->b_bus_resume, + fsm->b_bus_suspend, + fsm->b_conn, + fsm->b_se0_srp, + fsm->b_sess_end, + fsm->b_sess_vld, + fsm->id); + size -= t; + next += t; + + spin_unlock_irqrestore(&fsm->lock, flags); + + return PAGE_SIZE - size; +} + +static DEVICE_ATTR(fsl_usb2_otg_state, S_IRUGO, show_fsl_usb2_otg_state, NULL); + + +/* Char driver interface to control some OTG input */ + +/* + * Handle some ioctl command, such as get otg + * status and set host suspend + */ +static long fsl_otg_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + u32 retval = 0; + + switch (cmd) { + case GET_OTG_STATUS: + retval = fsl_otg_dev->host_working; + break; + + case SET_A_SUSPEND_REQ: + fsl_otg_dev->fsm.a_suspend_req = arg; + break; + + case SET_A_BUS_DROP: + fsl_otg_dev->fsm.a_bus_drop = arg; + break; + + case SET_A_BUS_REQ: + fsl_otg_dev->fsm.a_bus_req = arg; + break; + + case SET_B_BUS_REQ: + fsl_otg_dev->fsm.b_bus_req = arg; + break; + + default: + break; + } + + otg_statemachine(&fsl_otg_dev->fsm); + + return retval; +} + +static int fsl_otg_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int fsl_otg_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations otg_fops = { + .owner = THIS_MODULE, + .llseek = NULL, + .read = NULL, + .write = NULL, + .unlocked_ioctl = fsl_otg_ioctl, + .open = fsl_otg_open, + .release = fsl_otg_release, +}; + +static int __devinit fsl_otg_probe(struct platform_device *pdev) +{ + int ret; + + if (!pdev->dev.platform_data) + return -ENODEV; + + /* configure the OTG */ + ret = fsl_otg_conf(pdev); + if (ret) { + dev_err(&pdev->dev, "Couldn't configure OTG module\n"); + return ret; + } + + /* start OTG */ + ret = usb_otg_start(pdev); + if (ret) { + dev_err(&pdev->dev, "Can't init FSL OTG device\n"); + return ret; + } + + ret = register_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME, &otg_fops); + if (ret) { + dev_err(&pdev->dev, "unable to register FSL OTG device\n"); + return ret; + } + + ret = device_create_file(&pdev->dev, &dev_attr_fsl_usb2_otg_state); + if (ret) + dev_warn(&pdev->dev, "Can't register sysfs attribute\n"); + + return ret; +} + +static int __devexit fsl_otg_remove(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + otg_set_transceiver(NULL); + free_irq(fsl_otg_dev->irq, fsl_otg_dev); + + iounmap((void *)usb_dr_regs); + + fsl_otg_uninit_timers(); + kfree(fsl_otg_dev); + + device_remove_file(&pdev->dev, &dev_attr_fsl_usb2_otg_state); + + unregister_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME); + + if (pdata->exit) + pdata->exit(pdev); + + return 0; +} + +struct platform_driver fsl_otg_driver = { + .probe = fsl_otg_probe, + .remove = __devexit_p(fsl_otg_remove), + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + }, +}; + +static int __init fsl_usb_otg_init(void) +{ + pr_info(DRIVER_INFO "\n"); + return platform_driver_register(&fsl_otg_driver); +} +module_init(fsl_usb_otg_init); + +static void __exit fsl_usb_otg_exit(void) +{ + platform_driver_unregister(&fsl_otg_driver); +} +module_exit(fsl_usb_otg_exit); + +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/otg/fsl_otg.h b/drivers/usb/otg/fsl_otg.h new file mode 100644 index 000000000000..3f8ef731aac9 --- /dev/null +++ b/drivers/usb/otg/fsl_otg.h @@ -0,0 +1,406 @@ +/* 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. + */ + +#include "otg_fsm.h" +#include <linux/usb/otg.h> +#include <linux/ioctl.h> + +/* USB Command Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x1<<0) +#define USB_CMD_CTRL_RESET (0x1<<1) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x1<<4) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x1<<5) +#define USB_CMD_INT_AA_DOORBELL (0x1<<6) +#define USB_CMD_ASP (0x3<<8) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x1<<11) +#define USB_CMD_SUTW (0x1<<13) +#define USB_CMD_ATDTW (0x1<<14) +#define USB_CMD_ITC (0xFF<<16) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x0<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_512 (0x0<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_256 (0x0<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_128 (0x0<<15 | 0x3<<2) +#define USB_CMD_FRAME_SIZE_64 (0x1<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_32 (0x1<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_16 (0x1<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_8 (0x1<<15 | 0x3<<2) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x0<<8) +#define USB_CMD_ASP_01 (0x1<<8) +#define USB_CMD_ASP_10 (0x2<<8) +#define USB_CMD_ASP_11 (0x3<<8) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00<<16) +#define USB_CMD_ITC_1_MICRO_FRM (0x01<<16) +#define USB_CMD_ITC_2_MICRO_FRM (0x02<<16) +#define USB_CMD_ITC_4_MICRO_FRM (0x04<<16) +#define USB_CMD_ITC_8_MICRO_FRM (0x08<<16) +#define USB_CMD_ITC_16_MICRO_FRM (0x10<<16) +#define USB_CMD_ITC_32_MICRO_FRM (0x20<<16) +#define USB_CMD_ITC_64_MICRO_FRM (0x40<<16) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB Status Register Bit Masks */ +#define USB_STS_INT (0x1<<0) +#define USB_STS_ERR (0x1<<1) +#define USB_STS_PORT_CHANGE (0x1<<2) +#define USB_STS_FRM_LST_ROLL (0x1<<3) +#define USB_STS_SYS_ERR (0x1<<4) +#define USB_STS_IAA (0x1<<5) +#define USB_STS_RESET_RECEIVED (0x1<<6) +#define USB_STS_SOF (0x1<<7) +#define USB_STS_DCSUSPEND (0x1<<8) +#define USB_STS_HC_HALTED (0x1<<12) +#define USB_STS_RCL (0x1<<13) +#define USB_STS_PERIODIC_SCHEDULE (0x1<<14) +#define USB_STS_ASYNC_SCHEDULE (0x1<<15) + +/* USB Interrupt Enable Register Bit Masks */ +#define USB_INTR_INT_EN (0x1<<0) +#define USB_INTR_ERR_INT_EN (0x1<<1) +#define USB_INTR_PC_DETECT_EN (0x1<<2) +#define USB_INTR_FRM_LST_ROLL_EN (0x1<<3) +#define USB_INTR_SYS_ERR_EN (0x1<<4) +#define USB_INTR_ASYN_ADV_EN (0x1<<5) +#define USB_INTR_RESET_EN (0x1<<6) +#define USB_INTR_SOF_EN (0x1<<7) +#define USB_INTR_DEVICE_SUSPEND (0x1<<8) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0x7F<<25) +#define USB_DEVICE_ADDRESS_BIT_POS (25) +/* PORTSC Register Bit Masks,Only one PORT in OTG mode*/ +#define PORTSC_CURRENT_CONNECT_STATUS (0x1<<0) +#define PORTSC_CONNECT_STATUS_CHANGE (0x1<<1) +#define PORTSC_PORT_ENABLE (0x1<<2) +#define PORTSC_PORT_EN_DIS_CHANGE (0x1<<3) +#define PORTSC_OVER_CURRENT_ACT (0x1<<4) +#define PORTSC_OVER_CUURENT_CHG (0x1<<5) +#define PORTSC_PORT_FORCE_RESUME (0x1<<6) +#define PORTSC_PORT_SUSPEND (0x1<<7) +#define PORTSC_PORT_RESET (0x1<<8) +#define PORTSC_LINE_STATUS_BITS (0x3<<10) +#define PORTSC_PORT_POWER (0x1<<12) +#define PORTSC_PORT_INDICTOR_CTRL (0x3<<14) +#define PORTSC_PORT_TEST_CTRL (0xF<<16) +#define PORTSC_WAKE_ON_CONNECT_EN (0x1<<20) +#define PORTSC_WAKE_ON_CONNECT_DIS (0x1<<21) +#define PORTSC_WAKE_ON_OVER_CURRENT (0x1<<22) +#define PORTSC_PHY_LOW_POWER_SPD (0x1<<23) +#define PORTSC_PORT_FORCE_FULL_SPEED (0x1<<24) +#define PORTSC_PORT_SPEED_MASK (0x3<<26) +#define PORTSC_TRANSCEIVER_WIDTH (0x1<<28) +#define PORTSC_PHY_TYPE_SEL (0x3<<30) +/* bit 11-10 are line status */ +#define PORTSC_LINE_STATUS_SE0 (0x0<<10) +#define PORTSC_LINE_STATUS_JSTATE (0x1<<10) +#define PORTSC_LINE_STATUS_KSTATE (0x2<<10) +#define PORTSC_LINE_STATUS_UNDEF (0x3<<10) +#define PORTSC_LINE_STATUS_BIT_POS (10) + +/* bit 15-14 are port indicator control */ +#define PORTSC_PIC_OFF (0x0<<14) +#define PORTSC_PIC_AMBER (0x1<<14) +#define PORTSC_PIC_GREEN (0x2<<14) +#define PORTSC_PIC_UNDEF (0x3<<14) +#define PORTSC_PIC_BIT_POS (14) + +/* bit 19-16 are port test control */ +#define PORTSC_PTC_DISABLE (0x0<<16) +#define PORTSC_PTC_JSTATE (0x1<<16) +#define PORTSC_PTC_KSTATE (0x2<<16) +#define PORTSC_PTC_SEQNAK (0x3<<16) +#define PORTSC_PTC_PACKET (0x4<<16) +#define PORTSC_PTC_FORCE_EN (0x5<<16) +#define PORTSC_PTC_BIT_POS (16) + +/* bit 27-26 are port speed */ +#define PORTSC_PORT_SPEED_FULL (0x0<<26) +#define PORTSC_PORT_SPEED_LOW (0x1<<26) +#define PORTSC_PORT_SPEED_HIGH (0x2<<26) +#define PORTSC_PORT_SPEED_UNDEF (0x3<<26) +#define PORTSC_SPEED_BIT_POS (26) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSC_PTW (0x1<<28) +#define PORTSC_PTW_8BIT (0x0<<28) +#define PORTSC_PTW_16BIT (0x1<<28) + +/* bit 31-30 are port transceiver select */ +#define PORTSC_PTS_UTMI (0x0<<30) +#define PORTSC_PTS_ULPI (0x2<<30) +#define PORTSC_PTS_FSLS_SERIAL (0x3<<30) +#define PORTSC_PTS_BIT_POS (30) + +#define PORTSC_W1C_BITS \ + (PORTSC_CONNECT_STATUS_CHANGE | \ + PORTSC_PORT_EN_DIS_CHANGE | \ + PORTSC_OVER_CUURENT_CHG) + +/* OTG Status Control Register Bit Masks */ +#define OTGSC_CTRL_VBUS_DISCHARGE (0x1<<0) +#define OTGSC_CTRL_VBUS_CHARGE (0x1<<1) +#define OTGSC_CTRL_OTG_TERMINATION (0x1<<3) +#define OTGSC_CTRL_DATA_PULSING (0x1<<4) +#define OTGSC_CTRL_ID_PULL_EN (0x1<<5) +#define OTGSC_HA_DATA_PULSE (0x1<<6) +#define OTGSC_HA_BA (0x1<<7) +#define OTGSC_STS_USB_ID (0x1<<8) +#define OTGSC_STS_A_VBUS_VALID (0x1<<9) +#define OTGSC_STS_A_SESSION_VALID (0x1<<10) +#define OTGSC_STS_B_SESSION_VALID (0x1<<11) +#define OTGSC_STS_B_SESSION_END (0x1<<12) +#define OTGSC_STS_1MS_TOGGLE (0x1<<13) +#define OTGSC_STS_DATA_PULSING (0x1<<14) +#define OTGSC_INTSTS_USB_ID (0x1<<16) +#define OTGSC_INTSTS_A_VBUS_VALID (0x1<<17) +#define OTGSC_INTSTS_A_SESSION_VALID (0x1<<18) +#define OTGSC_INTSTS_B_SESSION_VALID (0x1<<19) +#define OTGSC_INTSTS_B_SESSION_END (0x1<<20) +#define OTGSC_INTSTS_1MS (0x1<<21) +#define OTGSC_INTSTS_DATA_PULSING (0x1<<22) +#define OTGSC_INTR_USB_ID_EN (0x1<<24) +#define OTGSC_INTR_A_VBUS_VALID_EN (0x1<<25) +#define OTGSC_INTR_A_SESSION_VALID_EN (0x1<<26) +#define OTGSC_INTR_B_SESSION_VALID_EN (0x1<<27) +#define OTGSC_INTR_B_SESSION_END_EN (0x1<<28) +#define OTGSC_INTR_1MS_TIMER_EN (0x1<<29) +#define OTGSC_INTR_DATA_PULSING_EN (0x1<<30) +#define OTGSC_INTSTS_MASK (0x00ff0000) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x0<<0) +#define USB_MODE_CTRL_MODE_DEVICE (0x2<<0) +#define USB_MODE_CTRL_MODE_HOST (0x3<<0) +#define USB_MODE_CTRL_MODE_RSV (0x1<<0) +#define USB_MODE_SETUP_LOCK_OFF (0x1<<3) +#define USB_MODE_STREAM_DISABLE (0x1<<4) +#define USB_MODE_ES (0x1<<2) /* Endian Select */ + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB (0x1<<2) +#define USB_CTRL_ULPI_INT0EN (0x1<<0) + +/* BCSR5 */ +#define BCSR5_INT_USB (0x02) + +/* USB module clk cfg */ +#define SCCR_OFFS (0xA08) +#define SCCR_USB_CLK_DISABLE (0x00000000) /* USB clk disable */ +#define SCCR_USB_MPHCM_11 (0x00c00000) +#define SCCR_USB_MPHCM_01 (0x00400000) +#define SCCR_USB_MPHCM_10 (0x00800000) +#define SCCR_USB_DRCM_11 (0x00300000) +#define SCCR_USB_DRCM_01 (0x00100000) +#define SCCR_USB_DRCM_10 (0x00200000) + +#define SICRL_OFFS (0x114) +#define SICRL_USB0 (0x40000000) +#define SICRL_USB1 (0x20000000) + +#define SICRH_OFFS (0x118) +#define SICRH_USB_UTMI (0x00020000) + +/* OTG interrupt enable bit masks */ +#define OTGSC_INTERRUPT_ENABLE_BITS_MASK \ + (OTGSC_INTR_USB_ID_EN | \ + OTGSC_INTR_1MS_TIMER_EN | \ + OTGSC_INTR_A_VBUS_VALID_EN | \ + OTGSC_INTR_A_SESSION_VALID_EN | \ + OTGSC_INTR_B_SESSION_VALID_EN | \ + OTGSC_INTR_B_SESSION_END_EN | \ + OTGSC_INTR_DATA_PULSING_EN) + +/* OTG interrupt status bit masks */ +#define OTGSC_INTERRUPT_STATUS_BITS_MASK \ + (OTGSC_INTSTS_USB_ID | \ + OTGSC_INTR_1MS_TIMER_EN | \ + OTGSC_INTSTS_A_VBUS_VALID | \ + OTGSC_INTSTS_A_SESSION_VALID | \ + OTGSC_INTSTS_B_SESSION_VALID | \ + OTGSC_INTSTS_B_SESSION_END | \ + OTGSC_INTSTS_DATA_PULSING) + +/* + * A-DEVICE timing constants + */ + +/* Wait for VBUS Rise */ +#define TA_WAIT_VRISE (100) /* a_wait_vrise 100 ms, section: 6.6.5.1 */ + +/* Wait for B-Connect */ +#define TA_WAIT_BCON (10000) /* a_wait_bcon > 1 sec, section: 6.6.5.2 + * This is only used to get out of + * OTG_STATE_A_WAIT_BCON state if there was + * no connection for these many milliseconds + */ + +/* A-Idle to B-Disconnect */ +/* It is necessary for this timer to be more than 750 ms because of a bug in OPT + * test 5.4 in which B OPT disconnects after 750 ms instead of 75ms as stated + * in the test description + */ +#define TA_AIDL_BDIS (5000) /* a_suspend minimum 200 ms, section: 6.6.5.3 */ + +/* B-Idle to A-Disconnect */ +#define TA_BIDL_ADIS (12) /* 3 to 200 ms */ + +/* B-device timing constants */ + + +/* Data-Line Pulse Time*/ +#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms, section:5.3.3 */ +#define TB_DATA_PLS_MIN (5) /* minimum 5 ms */ +#define TB_DATA_PLS_MAX (10) /* maximum 10 ms */ + +/* SRP Initiate Time */ +#define TB_SRP_INIT (100) /* b_srp_init,maximum 100 ms, section:5.3.8 */ + +/* SRP Fail Time */ +#define TB_SRP_FAIL (7000) /* b_srp_init,Fail time 5~30s, section:6.8.2.2*/ + +/* SRP result wait time */ +#define TB_SRP_WAIT (60) + +/* VBus time */ +#define TB_VBUS_PLS (30) /* time to keep vbus pulsing asserted */ + +/* Discharge time */ +/* This time should be less than 10ms. It varies from system to system. */ +#define TB_VBUS_DSCHRG (8) + +/* A-SE0 to B-Reset */ +#define TB_ASE0_BRST (20) /* b_wait_acon, mini 3.125 ms,section:6.8.2.4 */ + +/* A bus suspend timer before we can switch to b_wait_aconn */ +#define TB_A_SUSPEND (7) +#define TB_BUS_RESUME (12) + +/* SE0 Time Before SRP */ +#define TB_SE0_SRP (2) /* b_idle,minimum 2 ms, section:5.3.2 */ + +#define SET_OTG_STATE(otg_ptr, newstate) ((otg_ptr)->state = newstate) + +struct usb_dr_mmap { + /* Capability register */ + u8 res1[256]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u8 res2[20]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u8 res3[24]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u8 res4[4]; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u8 res5[4]; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u8 res6[8]; + u32 ulpiview; /* ULPI register access */ + u8 res7[12]; + u32 configflag; /* Configure Flag Register */ + u32 portsc; /* Port 1 Status and Control Register */ + u8 res8[28]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[6]; /* Endpoint Control Registers */ + u8 res9[552]; + u32 snoop1; + u32 snoop2; + u32 age_cnt_thresh; /* Age Count Threshold Register */ + u32 pri_ctrl; /* Priority Control Register */ + u32 si_ctrl; /* System Interface Control Register */ + u8 res10[236]; + u32 control; /* General Purpose Control Register */ +}; + +struct fsl_otg_timer { + unsigned long expires; /* Number of count increase to timeout */ + unsigned long count; /* Tick counter */ + void (*function)(unsigned long); /* Timeout function */ + unsigned long data; /* Data passed to function */ + struct list_head list; +}; + +inline struct fsl_otg_timer *otg_timer_initializer +(void (*function)(unsigned long), unsigned long expires, unsigned long data) +{ + struct fsl_otg_timer *timer; + + timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL); + if (!timer) + return NULL; + timer->function = function; + timer->expires = expires; + timer->data = data; + return timer; +} + +struct fsl_otg { + struct otg_transceiver otg; + struct otg_fsm fsm; + struct usb_dr_mmap *dr_mem_map; + struct delayed_work otg_event; + + /* used for usb host */ + struct work_struct work_wq; + u8 host_working; + + int irq; +}; + +struct fsl_otg_config { + u8 otg_port; +}; + +/* For SRP and HNP handle */ +#define FSL_OTG_MAJOR 240 +#define FSL_OTG_NAME "fsl-usb2-otg" +/* Command to OTG driver ioctl */ +#define OTG_IOCTL_MAGIC FSL_OTG_MAJOR +/* if otg work as host, it should return 1, otherwise return 0 */ +#define GET_OTG_STATUS _IOR(OTG_IOCTL_MAGIC, 1, int) +#define SET_A_SUSPEND_REQ _IOW(OTG_IOCTL_MAGIC, 2, int) +#define SET_A_BUS_DROP _IOW(OTG_IOCTL_MAGIC, 3, int) +#define SET_A_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 4, int) +#define SET_B_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 5, int) +#define GET_A_SUSPEND_REQ _IOR(OTG_IOCTL_MAGIC, 6, int) +#define GET_A_BUS_DROP _IOR(OTG_IOCTL_MAGIC, 7, int) +#define GET_A_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 8, int) +#define GET_B_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 9, int) + +void fsl_otg_add_timer(void *timer); +void fsl_otg_del_timer(void *timer); +void fsl_otg_pulse_vbus(void); diff --git a/drivers/usb/otg/gpio_vbus.c b/drivers/usb/otg/gpio_vbus.c index 221c44444ec6..52733d9959b4 100644 --- a/drivers/usb/otg/gpio_vbus.c +++ b/drivers/usb/otg/gpio_vbus.c @@ -279,6 +279,13 @@ static int __init gpio_vbus_probe(struct platform_device *pdev) } INIT_WORK(&gpio_vbus->work, gpio_vbus_work); + gpio_vbus->vbus_draw = regulator_get(&pdev->dev, "vbus_draw"); + if (IS_ERR(gpio_vbus->vbus_draw)) { + dev_dbg(&pdev->dev, "can't get vbus_draw regulator, err: %ld\n", + PTR_ERR(gpio_vbus->vbus_draw)); + gpio_vbus->vbus_draw = NULL; + } + /* only active when a gadget is registered */ err = otg_set_transceiver(&gpio_vbus->otg); if (err) { @@ -287,13 +294,6 @@ static int __init gpio_vbus_probe(struct platform_device *pdev) goto err_otg; } - gpio_vbus->vbus_draw = regulator_get(&pdev->dev, "vbus_draw"); - if (IS_ERR(gpio_vbus->vbus_draw)) { - dev_dbg(&pdev->dev, "can't get vbus_draw regulator, err: %ld\n", - PTR_ERR(gpio_vbus->vbus_draw)); - gpio_vbus->vbus_draw = NULL; - } - return 0; err_otg: free_irq(irq, &pdev->dev); diff --git a/drivers/usb/otg/isp1301_omap.c b/drivers/usb/otg/isp1301_omap.c index e25700f44b6f..8c282258e1bd 100644 --- a/drivers/usb/otg/isp1301_omap.c +++ b/drivers/usb/otg/isp1301_omap.c @@ -234,29 +234,9 @@ isp1301_clear_bits(struct isp1301 *isp, u8 reg, u8 bits) /*-------------------------------------------------------------------------*/ -static const char *state_string(enum usb_otg_state state) -{ - switch (state) { - case OTG_STATE_A_IDLE: return "a_idle"; - case OTG_STATE_A_WAIT_VRISE: return "a_wait_vrise"; - case OTG_STATE_A_WAIT_BCON: return "a_wait_bcon"; - case OTG_STATE_A_HOST: return "a_host"; - case OTG_STATE_A_SUSPEND: return "a_suspend"; - case OTG_STATE_A_PERIPHERAL: return "a_peripheral"; - case OTG_STATE_A_WAIT_VFALL: return "a_wait_vfall"; - case OTG_STATE_A_VBUS_ERR: return "a_vbus_err"; - case OTG_STATE_B_IDLE: return "b_idle"; - case OTG_STATE_B_SRP_INIT: return "b_srp_init"; - case OTG_STATE_B_PERIPHERAL: return "b_peripheral"; - case OTG_STATE_B_WAIT_ACON: return "b_wait_acon"; - case OTG_STATE_B_HOST: return "b_host"; - default: return "UNDEFINED"; - } -} - static inline const char *state_name(struct isp1301 *isp) { - return state_string(isp->otg.state); + return otg_state_string(isp->otg.state); } /*-------------------------------------------------------------------------*/ @@ -501,7 +481,7 @@ static void check_state(struct isp1301 *isp, const char *tag) if (isp->otg.state == state && !extra) return; pr_debug("otg: %s FSM %s/%02x, %s, %06x\n", tag, - state_string(state), fsm, state_name(isp), + otg_state_string(state), fsm, state_name(isp), omap_readl(OTG_CTRL)); } @@ -1095,7 +1075,7 @@ static void isp_update_otg(struct isp1301 *isp, u8 stat) if (state != isp->otg.state) pr_debug(" isp, %s -> %s\n", - state_string(state), state_name(isp)); + otg_state_string(state), state_name(isp)); #ifdef CONFIG_USB_OTG /* update the OTG controller state to match the isp1301; may diff --git a/drivers/usb/otg/langwell_otg.c b/drivers/usb/otg/langwell_otg.c index e973ff19c55a..f08f784086f7 100644 --- a/drivers/usb/otg/langwell_otg.c +++ b/drivers/usb/otg/langwell_otg.c @@ -82,40 +82,6 @@ static struct pci_driver otg_pci_driver = { .resume = langwell_otg_resume, }; -static const char *state_string(enum usb_otg_state state) -{ - switch (state) { - case OTG_STATE_A_IDLE: - return "a_idle"; - case OTG_STATE_A_WAIT_VRISE: - return "a_wait_vrise"; - case OTG_STATE_A_WAIT_BCON: - return "a_wait_bcon"; - case OTG_STATE_A_HOST: - return "a_host"; - case OTG_STATE_A_SUSPEND: - return "a_suspend"; - case OTG_STATE_A_PERIPHERAL: - return "a_peripheral"; - case OTG_STATE_A_WAIT_VFALL: - return "a_wait_vfall"; - case OTG_STATE_A_VBUS_ERR: - return "a_vbus_err"; - case OTG_STATE_B_IDLE: - return "b_idle"; - case OTG_STATE_B_SRP_INIT: - return "b_srp_init"; - case OTG_STATE_B_PERIPHERAL: - return "b_peripheral"; - case OTG_STATE_B_WAIT_ACON: - return "b_wait_acon"; - case OTG_STATE_B_HOST: - return "b_host"; - default: - return "UNDEFINED"; - } -} - /* HSM timers */ static inline struct langwell_otg_timer *otg_timer_initializer (void (*function)(unsigned long), unsigned long expires, unsigned long data) @@ -968,7 +934,7 @@ static void langwell_otg_work(struct work_struct *work) pdev = to_pci_dev(lnw->dev); dev_dbg(lnw->dev, "%s: old state = %s\n", __func__, - state_string(iotg->otg.state)); + otg_state_string(iotg->otg.state)); switch (iotg->otg.state) { case OTG_STATE_UNDEFINED: @@ -1703,7 +1669,7 @@ static void langwell_otg_work(struct work_struct *work) } dev_dbg(lnw->dev, "%s: new state = %s\n", __func__, - state_string(iotg->otg.state)); + otg_state_string(iotg->otg.state)); } static ssize_t @@ -1789,7 +1755,7 @@ show_hsm(struct device *_dev, struct device_attribute *attr, char *buf) "b_bus_req = \t%d\n" "b_bus_suspend_tmout = \t%d\n" "b_bus_suspend_vld = \t%d\n", - state_string(iotg->otg.state), + otg_state_string(iotg->otg.state), iotg->hsm.a_bus_resume, iotg->hsm.a_bus_suspend, iotg->hsm.a_conn, diff --git a/drivers/usb/otg/msm_otg.c b/drivers/usb/otg/msm_otg.c index 296598628b85..b276f8fcdeba 100644 --- a/drivers/usb/otg/msm_otg.c +++ b/drivers/usb/otg/msm_otg.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -38,6 +38,7 @@ #include <linux/usb/hcd.h> #include <linux/usb/msm_hsusb.h> #include <linux/usb/msm_hsusb_hw.h> +#include <linux/regulator/consumer.h> #include <mach/clk.h> @@ -45,6 +46,195 @@ #define DRIVER_NAME "msm_otg" #define ULPI_IO_TIMEOUT_USEC (10 * 1000) + +#define USB_PHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_PHY_3P3_VOL_MAX 3300000 /* uV */ +#define USB_PHY_3P3_HPM_LOAD 50000 /* uA */ +#define USB_PHY_3P3_LPM_LOAD 4000 /* uA */ + +#define USB_PHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_PHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_PHY_1P8_HPM_LOAD 50000 /* uA */ +#define USB_PHY_1P8_LPM_LOAD 4000 /* uA */ + +#define USB_PHY_VDD_DIG_VOL_MIN 1000000 /* uV */ +#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */ + +static struct regulator *hsusb_3p3; +static struct regulator *hsusb_1p8; +static struct regulator *hsusb_vddcx; + +static int msm_hsusb_init_vddcx(struct msm_otg *motg, int init) +{ + int ret = 0; + + if (init) { + hsusb_vddcx = regulator_get(motg->otg.dev, "HSUSB_VDDCX"); + if (IS_ERR(hsusb_vddcx)) { + dev_err(motg->otg.dev, "unable to get hsusb vddcx\n"); + return PTR_ERR(hsusb_vddcx); + } + + ret = regulator_set_voltage(hsusb_vddcx, + USB_PHY_VDD_DIG_VOL_MIN, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret) { + dev_err(motg->otg.dev, "unable to set the voltage " + "for hsusb vddcx\n"); + regulator_put(hsusb_vddcx); + return ret; + } + + ret = regulator_enable(hsusb_vddcx); + if (ret) { + dev_err(motg->otg.dev, "unable to enable hsusb vddcx\n"); + regulator_put(hsusb_vddcx); + } + } else { + ret = regulator_set_voltage(hsusb_vddcx, 0, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret) + dev_err(motg->otg.dev, "unable to set the voltage " + "for hsusb vddcx\n"); + ret = regulator_disable(hsusb_vddcx); + if (ret) + dev_err(motg->otg.dev, "unable to disable hsusb vddcx\n"); + + regulator_put(hsusb_vddcx); + } + + return ret; +} + +static int msm_hsusb_ldo_init(struct msm_otg *motg, int init) +{ + int rc = 0; + + if (init) { + hsusb_3p3 = regulator_get(motg->otg.dev, "HSUSB_3p3"); + if (IS_ERR(hsusb_3p3)) { + dev_err(motg->otg.dev, "unable to get hsusb 3p3\n"); + return PTR_ERR(hsusb_3p3); + } + + rc = regulator_set_voltage(hsusb_3p3, USB_PHY_3P3_VOL_MIN, + USB_PHY_3P3_VOL_MAX); + if (rc) { + dev_err(motg->otg.dev, "unable to set voltage level " + "for hsusb 3p3\n"); + goto put_3p3; + } + rc = regulator_enable(hsusb_3p3); + if (rc) { + dev_err(motg->otg.dev, "unable to enable the hsusb 3p3\n"); + goto put_3p3; + } + hsusb_1p8 = regulator_get(motg->otg.dev, "HSUSB_1p8"); + if (IS_ERR(hsusb_1p8)) { + dev_err(motg->otg.dev, "unable to get hsusb 1p8\n"); + rc = PTR_ERR(hsusb_1p8); + goto disable_3p3; + } + rc = regulator_set_voltage(hsusb_1p8, USB_PHY_1P8_VOL_MIN, + USB_PHY_1P8_VOL_MAX); + if (rc) { + dev_err(motg->otg.dev, "unable to set voltage level " + "for hsusb 1p8\n"); + goto put_1p8; + } + rc = regulator_enable(hsusb_1p8); + if (rc) { + dev_err(motg->otg.dev, "unable to enable the hsusb 1p8\n"); + goto put_1p8; + } + + return 0; + } + + regulator_disable(hsusb_1p8); +put_1p8: + regulator_put(hsusb_1p8); +disable_3p3: + regulator_disable(hsusb_3p3); +put_3p3: + regulator_put(hsusb_3p3); + return rc; +} + +#ifdef CONFIG_PM_SLEEP +#define USB_PHY_SUSP_DIG_VOL 500000 +static int msm_hsusb_config_vddcx(int high) +{ + int max_vol = USB_PHY_VDD_DIG_VOL_MAX; + int min_vol; + int ret; + + if (high) + min_vol = USB_PHY_VDD_DIG_VOL_MIN; + else + min_vol = USB_PHY_SUSP_DIG_VOL; + + ret = regulator_set_voltage(hsusb_vddcx, min_vol, max_vol); + if (ret) { + pr_err("%s: unable to set the voltage for regulator " + "HSUSB_VDDCX\n", __func__); + return ret; + } + + pr_debug("%s: min_vol:%d max_vol:%d\n", __func__, min_vol, max_vol); + + return ret; +} +#endif + +static int msm_hsusb_ldo_set_mode(int on) +{ + int ret = 0; + + if (!hsusb_1p8 || IS_ERR(hsusb_1p8)) { + pr_err("%s: HSUSB_1p8 is not initialized\n", __func__); + return -ENODEV; + } + + if (!hsusb_3p3 || IS_ERR(hsusb_3p3)) { + pr_err("%s: HSUSB_3p3 is not initialized\n", __func__); + return -ENODEV; + } + + if (on) { + ret = regulator_set_optimum_mode(hsusb_1p8, + USB_PHY_1P8_HPM_LOAD); + if (ret < 0) { + pr_err("%s: Unable to set HPM of the regulator " + "HSUSB_1p8\n", __func__); + return ret; + } + ret = regulator_set_optimum_mode(hsusb_3p3, + USB_PHY_3P3_HPM_LOAD); + if (ret < 0) { + pr_err("%s: Unable to set HPM of the regulator " + "HSUSB_3p3\n", __func__); + regulator_set_optimum_mode(hsusb_1p8, + USB_PHY_1P8_LPM_LOAD); + return ret; + } + } else { + ret = regulator_set_optimum_mode(hsusb_1p8, + USB_PHY_1P8_LPM_LOAD); + if (ret < 0) + pr_err("%s: Unable to set LPM of the regulator " + "HSUSB_1p8\n", __func__); + ret = regulator_set_optimum_mode(hsusb_3p3, + USB_PHY_3P3_LPM_LOAD); + if (ret < 0) + pr_err("%s: Unable to set LPM of the regulator " + "HSUSB_3p3\n", __func__); + } + + pr_debug("reg (%s)\n", on ? "HPM" : "LPM"); + return ret < 0 ? ret : 0; +} + static int ulpi_read(struct otg_transceiver *otg, u32 reg) { struct msm_otg *motg = container_of(otg, struct msm_otg, otg); @@ -268,27 +458,28 @@ static int msm_otg_suspend(struct msm_otg *motg) disable_irq(motg->irq); /* + * Chipidea 45-nm PHY suspend sequence: + * * Interrupt Latch Register auto-clear feature is not present * in all PHY versions. Latch register is clear on read type. * Clear latch register to avoid spurious wakeup from * low power mode (LPM). - */ - ulpi_read(otg, 0x14); - - /* + * * PHY comparators are disabled when PHY enters into low power * mode (LPM). Keep PHY comparators ON in LPM only when we expect * VBUS/Id notifications from USB PHY. Otherwise turn off USB * PHY comparators. This save significant amount of power. - */ - if (pdata->otg_control == OTG_PHY_CONTROL) - ulpi_write(otg, 0x01, 0x30); - - /* + * * PLL is not turned off when PHY enters into low power mode (LPM). * Disable PLL for maximum power savings. */ - ulpi_write(otg, 0x08, 0x09); + + if (motg->pdata->phy_type == CI_45NM_INTEGRATED_PHY) { + ulpi_read(otg, 0x14); + if (pdata->otg_control == OTG_PHY_CONTROL) + ulpi_write(otg, 0x01, 0x30); + ulpi_write(otg, 0x08, 0x09); + } /* * PHY may take some time or even fail to enter into low power @@ -319,11 +510,24 @@ static int msm_otg_suspend(struct msm_otg *motg) */ writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); + if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && + motg->pdata->otg_control == OTG_PMIC_CONTROL) + writel(readl(USB_PHY_CTRL) | PHY_RETEN, USB_PHY_CTRL); + clk_disable(motg->pclk); clk_disable(motg->clk); if (motg->core_clk) clk_disable(motg->core_clk); + if (!IS_ERR(motg->pclk_src)) + clk_disable(motg->pclk_src); + + if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && + motg->pdata->otg_control == OTG_PMIC_CONTROL) { + msm_hsusb_ldo_set_mode(0); + msm_hsusb_config_vddcx(0); + } + if (device_may_wakeup(otg->dev)) enable_irq_wake(motg->irq); if (bus) @@ -347,11 +551,21 @@ static int msm_otg_resume(struct msm_otg *motg) if (!atomic_read(&motg->in_lpm)) return 0; + if (!IS_ERR(motg->pclk_src)) + clk_enable(motg->pclk_src); + clk_enable(motg->pclk); clk_enable(motg->clk); if (motg->core_clk) clk_enable(motg->core_clk); + if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && + motg->pdata->otg_control == OTG_PMIC_CONTROL) { + msm_hsusb_ldo_set_mode(1); + msm_hsusb_config_vddcx(1); + writel(readl(USB_PHY_CTRL) & ~PHY_RETEN, USB_PHY_CTRL); + } + temp = readl(USB_USBCMD); temp &= ~ASYNC_INTR_CTRL; temp &= ~ULPI_STP_CTRL; @@ -389,20 +603,47 @@ skip_phy_resume: if (bus) set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + atomic_set(&motg->in_lpm, 0); + if (motg->async_int) { motg->async_int = 0; pm_runtime_put(otg->dev); enable_irq(motg->irq); } - atomic_set(&motg->in_lpm, 0); - dev_info(otg->dev, "USB exited from low power mode\n"); return 0; } #endif +static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA) +{ + if (motg->cur_power == mA) + return; + + /* TODO: Notify PMIC about available current */ + dev_info(motg->otg.dev, "Avail curr from USB = %u\n", mA); + motg->cur_power = mA; +} + +static int msm_otg_set_power(struct otg_transceiver *otg, unsigned mA) +{ + struct msm_otg *motg = container_of(otg, struct msm_otg, otg); + + /* + * Gadget driver uses set_power method to notify about the + * available current based on suspend/configured states. + * + * IDEV_CHG can be drawn irrespective of suspend/un-configured + * states when CDP/ACA is connected. + */ + if (motg->chg_type == USB_SDP_CHARGER) + msm_otg_notify_charger(motg, mA); + + return 0; +} + static void msm_otg_start_host(struct otg_transceiver *otg, int on) { struct msm_otg *motg = container_of(otg, struct msm_otg, otg); @@ -557,6 +798,306 @@ static int msm_otg_set_peripheral(struct otg_transceiver *otg, return 0; } +static bool msm_chg_check_secondary_det(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 chg_det; + bool ret = false; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x34); + ret = chg_det & (1 << 4); + break; + case SNPS_28NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x87); + ret = chg_det & 1; + break; + default: + break; + } + return ret; +} + +static void msm_chg_enable_secondary_det(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x34); + /* Turn off charger block */ + chg_det |= ~(1 << 1); + ulpi_write(otg, chg_det, 0x34); + udelay(20); + /* control chg block via ULPI */ + chg_det &= ~(1 << 3); + ulpi_write(otg, chg_det, 0x34); + /* put it in host mode for enabling D- source */ + chg_det &= ~(1 << 2); + ulpi_write(otg, chg_det, 0x34); + /* Turn on chg detect block */ + chg_det &= ~(1 << 1); + ulpi_write(otg, chg_det, 0x34); + udelay(20); + /* enable chg detection */ + chg_det &= ~(1 << 0); + ulpi_write(otg, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* + * Configure DM as current source, DP as current sink + * and enable battery charging comparators. + */ + ulpi_write(otg, 0x8, 0x85); + ulpi_write(otg, 0x2, 0x85); + ulpi_write(otg, 0x1, 0x85); + break; + default: + break; + } +} + +static bool msm_chg_check_primary_det(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 chg_det; + bool ret = false; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x34); + ret = chg_det & (1 << 4); + break; + case SNPS_28NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x87); + ret = chg_det & 1; + break; + default: + break; + } + return ret; +} + +static void msm_chg_enable_primary_det(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x34); + /* enable chg detection */ + chg_det &= ~(1 << 0); + ulpi_write(otg, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* + * Configure DP as current source, DM as current sink + * and enable battery charging comparators. + */ + ulpi_write(otg, 0x2, 0x85); + ulpi_write(otg, 0x1, 0x85); + break; + default: + break; + } +} + +static bool msm_chg_check_dcd(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 line_state; + bool ret = false; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + line_state = ulpi_read(otg, 0x15); + ret = !(line_state & 1); + break; + case SNPS_28NM_INTEGRATED_PHY: + line_state = ulpi_read(otg, 0x87); + ret = line_state & 2; + break; + default: + break; + } + return ret; +} + +static void msm_chg_disable_dcd(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x34); + chg_det &= ~(1 << 5); + ulpi_write(otg, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + ulpi_write(otg, 0x10, 0x86); + break; + default: + break; + } +} + +static void msm_chg_enable_dcd(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x34); + /* Turn on D+ current source */ + chg_det |= (1 << 5); + ulpi_write(otg, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* Data contact detection enable */ + ulpi_write(otg, 0x10, 0x85); + break; + default: + break; + } +} + +static void msm_chg_block_on(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 func_ctrl, chg_det; + + /* put the controller in non-driving mode */ + func_ctrl = ulpi_read(otg, ULPI_FUNC_CTRL); + func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; + ulpi_write(otg, func_ctrl, ULPI_FUNC_CTRL); + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x34); + /* control chg block via ULPI */ + chg_det &= ~(1 << 3); + ulpi_write(otg, chg_det, 0x34); + /* Turn on chg detect block */ + chg_det &= ~(1 << 1); + ulpi_write(otg, chg_det, 0x34); + udelay(20); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* Clear charger detecting control bits */ + ulpi_write(otg, 0x3F, 0x86); + /* Clear alt interrupt latch and enable bits */ + ulpi_write(otg, 0x1F, 0x92); + ulpi_write(otg, 0x1F, 0x95); + udelay(100); + break; + default: + break; + } +} + +static void msm_chg_block_off(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 func_ctrl, chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(otg, 0x34); + /* Turn off charger block */ + chg_det |= ~(1 << 1); + ulpi_write(otg, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* Clear charger detecting control bits */ + ulpi_write(otg, 0x3F, 0x86); + /* Clear alt interrupt latch and enable bits */ + ulpi_write(otg, 0x1F, 0x92); + ulpi_write(otg, 0x1F, 0x95); + break; + default: + break; + } + + /* put the controller in normal mode */ + func_ctrl = ulpi_read(otg, ULPI_FUNC_CTRL); + func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL; + ulpi_write(otg, func_ctrl, ULPI_FUNC_CTRL); +} + +#define MSM_CHG_DCD_POLL_TIME (100 * HZ/1000) /* 100 msec */ +#define MSM_CHG_DCD_MAX_RETRIES 6 /* Tdcd_tmout = 6 * 100 msec */ +#define MSM_CHG_PRIMARY_DET_TIME (40 * HZ/1000) /* TVDPSRC_ON */ +#define MSM_CHG_SECONDARY_DET_TIME (40 * HZ/1000) /* TVDMSRC_ON */ +static void msm_chg_detect_work(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work); + struct otg_transceiver *otg = &motg->otg; + bool is_dcd, tmout, vout; + unsigned long delay; + + dev_dbg(otg->dev, "chg detection work\n"); + switch (motg->chg_state) { + case USB_CHG_STATE_UNDEFINED: + pm_runtime_get_sync(otg->dev); + msm_chg_block_on(motg); + msm_chg_enable_dcd(motg); + motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; + motg->dcd_retries = 0; + delay = MSM_CHG_DCD_POLL_TIME; + break; + case USB_CHG_STATE_WAIT_FOR_DCD: + is_dcd = msm_chg_check_dcd(motg); + tmout = ++motg->dcd_retries == MSM_CHG_DCD_MAX_RETRIES; + if (is_dcd || tmout) { + msm_chg_disable_dcd(motg); + msm_chg_enable_primary_det(motg); + delay = MSM_CHG_PRIMARY_DET_TIME; + motg->chg_state = USB_CHG_STATE_DCD_DONE; + } else { + delay = MSM_CHG_DCD_POLL_TIME; + } + break; + case USB_CHG_STATE_DCD_DONE: + vout = msm_chg_check_primary_det(motg); + if (vout) { + msm_chg_enable_secondary_det(motg); + delay = MSM_CHG_SECONDARY_DET_TIME; + motg->chg_state = USB_CHG_STATE_PRIMARY_DONE; + } else { + motg->chg_type = USB_SDP_CHARGER; + motg->chg_state = USB_CHG_STATE_DETECTED; + delay = 0; + } + break; + case USB_CHG_STATE_PRIMARY_DONE: + vout = msm_chg_check_secondary_det(motg); + if (vout) + motg->chg_type = USB_DCP_CHARGER; + else + motg->chg_type = USB_CDP_CHARGER; + motg->chg_state = USB_CHG_STATE_SECONDARY_DONE; + /* fall through */ + case USB_CHG_STATE_SECONDARY_DONE: + motg->chg_state = USB_CHG_STATE_DETECTED; + case USB_CHG_STATE_DETECTED: + msm_chg_block_off(motg); + dev_dbg(otg->dev, "charger = %d\n", motg->chg_type); + schedule_work(&motg->sm_work); + return; + default: + return; + } + + schedule_delayed_work(&motg->chg_work, delay); +} + /* * We support OTG, Peripheral only and Host only configurations. In case * of OTG, mode switch (host-->peripheral/peripheral-->host) can happen @@ -627,9 +1168,48 @@ static void msm_otg_sm_work(struct work_struct *w) writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); msm_otg_start_host(otg, 1); otg->state = OTG_STATE_A_HOST; - } else if (test_bit(B_SESS_VLD, &motg->inputs) && otg->gadget) { - msm_otg_start_peripheral(otg, 1); - otg->state = OTG_STATE_B_PERIPHERAL; + } else if (test_bit(B_SESS_VLD, &motg->inputs)) { + switch (motg->chg_state) { + case USB_CHG_STATE_UNDEFINED: + msm_chg_detect_work(&motg->chg_work.work); + break; + case USB_CHG_STATE_DETECTED: + switch (motg->chg_type) { + case USB_DCP_CHARGER: + msm_otg_notify_charger(motg, + IDEV_CHG_MAX); + break; + case USB_CDP_CHARGER: + msm_otg_notify_charger(motg, + IDEV_CHG_MAX); + msm_otg_start_peripheral(otg, 1); + otg->state = OTG_STATE_B_PERIPHERAL; + break; + case USB_SDP_CHARGER: + msm_otg_notify_charger(motg, IUNIT); + msm_otg_start_peripheral(otg, 1); + otg->state = OTG_STATE_B_PERIPHERAL; + break; + default: + break; + } + break; + default: + break; + } + } else { + /* + * If charger detection work is pending, decrement + * the pm usage counter to balance with the one that + * is incremented in charger detection work. + */ + if (cancel_delayed_work_sync(&motg->chg_work)) { + pm_runtime_put_sync(otg->dev); + msm_otg_reset(otg); + } + msm_otg_notify_charger(motg, 0); + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; } pm_runtime_put_sync(otg->dev); break; @@ -637,7 +1217,10 @@ static void msm_otg_sm_work(struct work_struct *w) dev_dbg(otg->dev, "OTG_STATE_B_PERIPHERAL state\n"); if (!test_bit(B_SESS_VLD, &motg->inputs) || !test_bit(ID, &motg->inputs)) { + msm_otg_notify_charger(motg, 0); msm_otg_start_peripheral(otg, 0); + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; otg->state = OTG_STATE_B_IDLE; msm_otg_reset(otg); schedule_work(w); @@ -862,12 +1445,31 @@ static int __init msm_otg_probe(struct platform_device *pdev) ret = PTR_ERR(motg->clk); goto put_phy_reset_clk; } + clk_set_rate(motg->clk, 60000000); + + /* + * If USB Core is running its protocol engine based on CORE CLK, + * CORE CLK must be running at >55Mhz for correct HSUSB + * operation and USB core cannot tolerate frequency changes on + * CORE CLK. For such USB cores, vote for maximum clk frequency + * on pclk source + */ + if (motg->pdata->pclk_src_name) { + motg->pclk_src = clk_get(&pdev->dev, + motg->pdata->pclk_src_name); + if (IS_ERR(motg->pclk_src)) + goto put_clk; + clk_set_rate(motg->pclk_src, INT_MAX); + clk_enable(motg->pclk_src); + } else + motg->pclk_src = ERR_PTR(-ENOENT); + motg->pclk = clk_get(&pdev->dev, "usb_hs_pclk"); if (IS_ERR(motg->pclk)) { dev_err(&pdev->dev, "failed to get usb_hs_pclk\n"); ret = PTR_ERR(motg->pclk); - goto put_clk; + goto put_pclk_src; } /* @@ -903,6 +1505,24 @@ static int __init msm_otg_probe(struct platform_device *pdev) clk_enable(motg->clk); clk_enable(motg->pclk); + + ret = msm_hsusb_init_vddcx(motg, 1); + if (ret) { + dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); + goto free_regs; + } + + ret = msm_hsusb_ldo_init(motg, 1); + if (ret) { + dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); + goto vddcx_exit; + } + ret = msm_hsusb_ldo_set_mode(1); + if (ret) { + dev_err(&pdev->dev, "hsusb vreg enable failed\n"); + goto ldo_exit; + } + if (motg->core_clk) clk_enable(motg->core_clk); @@ -910,6 +1530,7 @@ static int __init msm_otg_probe(struct platform_device *pdev) writel(0, USB_OTGSC); INIT_WORK(&motg->sm_work, msm_otg_sm_work); + INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); ret = request_irq(motg->irq, msm_otg_irq, IRQF_SHARED, "msm_otg", motg); if (ret) { @@ -920,6 +1541,7 @@ static int __init msm_otg_probe(struct platform_device *pdev) otg->init = msm_otg_reset; otg->set_host = msm_otg_set_host; otg->set_peripheral = msm_otg_set_peripheral; + otg->set_power = msm_otg_set_power; otg->io_ops = &msm_otg_io_ops; @@ -949,12 +1571,21 @@ free_irq: disable_clks: clk_disable(motg->pclk); clk_disable(motg->clk); +ldo_exit: + msm_hsusb_ldo_init(motg, 0); +vddcx_exit: + msm_hsusb_init_vddcx(motg, 0); free_regs: iounmap(motg->regs); put_core_clk: if (motg->core_clk) clk_put(motg->core_clk); clk_put(motg->pclk); +put_pclk_src: + if (!IS_ERR(motg->pclk_src)) { + clk_disable(motg->pclk_src); + clk_put(motg->pclk_src); + } put_clk: clk_put(motg->clk); put_phy_reset_clk: @@ -974,6 +1605,7 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) return -EBUSY; msm_otg_debugfs_cleanup(); + cancel_delayed_work_sync(&motg->chg_work); cancel_work_sync(&motg->sm_work); pm_runtime_resume(&pdev->dev); @@ -1004,6 +1636,11 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) clk_disable(motg->clk); if (motg->core_clk) clk_disable(motg->core_clk); + if (!IS_ERR(motg->pclk_src)) { + clk_disable(motg->pclk_src); + clk_put(motg->pclk_src); + } + msm_hsusb_ldo_init(motg, 0); iounmap(motg->regs); pm_runtime_set_suspended(&pdev->dev); diff --git a/drivers/usb/otg/otg.c b/drivers/usb/otg/otg.c index 0a43a7db750f..fb7adeff9ffa 100644 --- a/drivers/usb/otg/otg.c +++ b/drivers/usb/otg/otg.c @@ -64,3 +64,38 @@ int otg_set_transceiver(struct otg_transceiver *x) return 0; } EXPORT_SYMBOL(otg_set_transceiver); + +const char *otg_state_string(enum usb_otg_state state) +{ + switch (state) { + case OTG_STATE_A_IDLE: + return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: + return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: + return "a_wait_bcon"; + case OTG_STATE_A_HOST: + return "a_host"; + case OTG_STATE_A_SUSPEND: + return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: + return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: + return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: + return "a_vbus_err"; + case OTG_STATE_B_IDLE: + return "b_idle"; + case OTG_STATE_B_SRP_INIT: + return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: + return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: + return "b_wait_acon"; + case OTG_STATE_B_HOST: + return "b_host"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(otg_state_string); diff --git a/drivers/usb/otg/otg_fsm.c b/drivers/usb/otg/otg_fsm.c new file mode 100644 index 000000000000..b0cc422f2ff9 --- /dev/null +++ b/drivers/usb/otg/otg_fsm.c @@ -0,0 +1,349 @@ +/* + * 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; +} diff --git a/drivers/usb/otg/otg_fsm.h b/drivers/usb/otg/otg_fsm.h new file mode 100644 index 000000000000..0cecf1d593a0 --- /dev/null +++ b/drivers/usb/otg/otg_fsm.h @@ -0,0 +1,154 @@ +/* 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; diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c index e01b073cc489..efeb4d1517ff 100644 --- a/drivers/usb/otg/twl4030-usb.c +++ b/drivers/usb/otg/twl4030-usb.c @@ -160,6 +160,7 @@ struct twl4030_usb { int irq; u8 linkstat; + bool vbus_supplied; u8 asleep; bool irq_enabled; }; @@ -250,6 +251,8 @@ static enum usb_xceiv_events twl4030_usb_linkstat(struct twl4030_usb *twl) int status; int linkstat = USB_EVENT_NONE; + twl->vbus_supplied = false; + /* * For ID/VBUS sensing, see manual section 15.4.8 ... * except when using only battery backup power, two @@ -265,6 +268,9 @@ static enum usb_xceiv_events twl4030_usb_linkstat(struct twl4030_usb *twl) if (status < 0) dev_err(twl->dev, "USB link status err %d\n", status); else if (status & (BIT(7) | BIT(2))) { + if (status & (BIT(7))) + twl->vbus_supplied = true; + if (status & BIT(2)) linkstat = USB_EVENT_ID; else @@ -484,7 +490,7 @@ static ssize_t twl4030_usb_vbus_show(struct device *dev, spin_lock_irqsave(&twl->lock, flags); ret = sprintf(buf, "%s\n", - (twl->linkstat == USB_EVENT_VBUS) ? "on" : "off"); + twl->vbus_supplied ? "on" : "off"); spin_unlock_irqrestore(&twl->lock, flags); return ret; @@ -608,6 +614,7 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev) twl->otg.set_peripheral = twl4030_set_peripheral; twl->otg.set_suspend = twl4030_set_suspend; twl->usb_mode = pdata->usb_mode; + twl->vbus_supplied = false; twl->asleep = 1; /* init spinlock for workqueue */ diff --git a/drivers/usb/otg/twl6030-usb.c b/drivers/usb/otg/twl6030-usb.c index 8a91b4b832a1..3f2e07011a48 100644 --- a/drivers/usb/otg/twl6030-usb.c +++ b/drivers/usb/otg/twl6030-usb.c @@ -31,6 +31,7 @@ #include <linux/err.h> #include <linux/notifier.h> #include <linux/slab.h> +#include <linux/delay.h> /* usb register definitions */ #define USB_VENDOR_ID_LSB 0x00 @@ -101,7 +102,7 @@ struct twl6030_usb { bool irq_enabled; }; -#define xceiv_to_twl(x) container_of((x), struct twl6030_usb, otg); +#define xceiv_to_twl(x) container_of((x), struct twl6030_usb, otg) /*-------------------------------------------------------------------------*/ @@ -188,6 +189,19 @@ static int twl6030_phy_suspend(struct otg_transceiver *x, int suspend) return 0; } +static int twl6030_start_srp(struct otg_transceiver *x) +{ + struct twl6030_usb *twl = xceiv_to_twl(x); + + twl6030_writeb(twl, TWL_MODULE_USB, 0x24, USB_VBUS_CTRL_SET); + twl6030_writeb(twl, TWL_MODULE_USB, 0x84, USB_VBUS_CTRL_SET); + + mdelay(100); + twl6030_writeb(twl, TWL_MODULE_USB, 0xa0, USB_VBUS_CTRL_CLR); + + return 0; +} + static int twl6030_usb_ldo_init(struct twl6030_usb *twl) { @@ -403,6 +417,7 @@ static int __devinit twl6030_usb_probe(struct platform_device *pdev) twl->otg.init = twl6030_phy_init; twl->otg.shutdown = twl6030_phy_shutdown; twl->otg.set_suspend = twl6030_phy_suspend; + twl->otg.start_srp = twl6030_start_srp; /* init spinlock for workqueue */ spin_lock_init(&twl->lock); |