2005-04-17 00:20:36 +02:00
|
|
|
/*
|
|
|
|
* isp1301_omap - ISP 1301 USB transceiver, talking to OMAP OTG controller
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004 Texas Instruments
|
|
|
|
* Copyright (C) 2004 David Brownell
|
|
|
|
*
|
|
|
|
* 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/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/interrupt.h>
|
2005-10-29 20:07:23 +02:00
|
|
|
#include <linux/platform_device.h>
|
2006-12-17 00:34:53 +01:00
|
|
|
#include <linux/usb/ch9.h>
|
2007-12-12 13:45:25 +01:00
|
|
|
#include <linux/usb/gadget.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <linux/usb.h>
|
2006-06-29 21:27:23 +02:00
|
|
|
#include <linux/usb/otg.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
|
|
|
|
#include <asm/irq.h>
|
|
|
|
#include <asm/arch/usb.h>
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef DEBUG
|
|
|
|
#undef VERBOSE
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#define DRIVER_VERSION "24 August 2004"
|
2007-08-14 18:37:14 +02:00
|
|
|
#define DRIVER_NAME (isp1301_driver.driver.name)
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
MODULE_DESCRIPTION("ISP1301 USB OTG Transceiver Driver");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
|
|
struct isp1301 {
|
|
|
|
struct otg_transceiver otg;
|
|
|
|
struct i2c_client client;
|
|
|
|
void (*i2c_release)(struct device *dev);
|
|
|
|
|
|
|
|
int irq;
|
2007-08-14 18:37:14 +02:00
|
|
|
int irq_type;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
u32 last_otg_ctrl;
|
|
|
|
unsigned working:1;
|
|
|
|
|
|
|
|
struct timer_list timer;
|
|
|
|
|
|
|
|
/* use keventd context to change the state for us */
|
|
|
|
struct work_struct work;
|
2007-08-14 18:37:14 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
unsigned long todo;
|
|
|
|
# define WORK_UPDATE_ISP 0 /* update ISP from OTG */
|
|
|
|
# define WORK_UPDATE_OTG 1 /* update OTG from ISP */
|
|
|
|
# define WORK_HOST_RESUME 4 /* resume host */
|
|
|
|
# define WORK_TIMER 6 /* timer fired */
|
|
|
|
# define WORK_STOP 7 /* don't resubmit */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
/* bits in OTG_CTRL */
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#define OTG_XCEIV_OUTPUTS \
|
|
|
|
(OTG_ASESSVLD|OTG_BSESSEND|OTG_BSESSVLD|OTG_VBUSVLD|OTG_ID)
|
|
|
|
#define OTG_XCEIV_INPUTS \
|
|
|
|
(OTG_PULLDOWN|OTG_PULLUP|OTG_DRV_VBUS|OTG_PD_VBUS|OTG_PU_VBUS|OTG_PU_ID)
|
|
|
|
#define OTG_CTRL_BITS \
|
|
|
|
(OTG_A_BUSREQ|OTG_A_SETB_HNPEN|OTG_B_BUSREQ|OTG_B_HNPEN|OTG_BUSDROP)
|
|
|
|
/* and OTG_PULLUP is sometimes written */
|
|
|
|
|
|
|
|
#define OTG_CTRL_MASK (OTG_DRIVER_SEL| \
|
|
|
|
OTG_XCEIV_OUTPUTS|OTG_XCEIV_INPUTS| \
|
|
|
|
OTG_CTRL_BITS)
|
|
|
|
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
#ifdef CONFIG_MACH_OMAP_H2
|
|
|
|
|
|
|
|
/* board-specific PM hooks */
|
|
|
|
|
2007-08-14 18:37:14 +02:00
|
|
|
#include <asm/gpio.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <asm/arch/mux.h>
|
|
|
|
#include <asm/mach-types.h>
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(CONFIG_TPS65010) || defined(CONFIG_TPS65010_MODULE)
|
|
|
|
|
2008-01-27 18:14:49 +01:00
|
|
|
#include <linux/i2c/tps65010.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static inline int tps65010_set_vbus_draw(unsigned mA)
|
|
|
|
{
|
|
|
|
pr_debug("tps65010: draw %d mA (STUB)\n", mA);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void enable_vbus_draw(struct isp1301 *isp, unsigned mA)
|
|
|
|
{
|
|
|
|
int status = tps65010_set_vbus_draw(mA);
|
|
|
|
if (status < 0)
|
|
|
|
pr_debug(" VBUS %d mA error %d\n", mA, status);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void enable_vbus_source(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
/* this board won't supply more than 8mA vbus power.
|
|
|
|
* some boards can switch a 100ma "unit load" (or more).
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* products will deliver OTG messages with LEDs, GUI, etc */
|
|
|
|
static inline void notresponding(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
printk(KERN_NOTICE "OTG device not responding.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* only two addresses possible */
|
|
|
|
#define ISP_BASE 0x2c
|
|
|
|
static unsigned short normal_i2c[] = {
|
|
|
|
ISP_BASE, ISP_BASE + 1,
|
|
|
|
I2C_CLIENT_END };
|
|
|
|
|
|
|
|
I2C_CLIENT_INSMOD;
|
|
|
|
|
|
|
|
static struct i2c_driver isp1301_driver;
|
|
|
|
|
|
|
|
/* smbus apis are used for portability */
|
|
|
|
|
|
|
|
static inline u8
|
|
|
|
isp1301_get_u8(struct isp1301 *isp, u8 reg)
|
|
|
|
{
|
|
|
|
return i2c_smbus_read_byte_data(&isp->client, reg + 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
isp1301_get_u16(struct isp1301 *isp, u8 reg)
|
|
|
|
{
|
|
|
|
return i2c_smbus_read_word_data(&isp->client, reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
isp1301_set_bits(struct isp1301 *isp, u8 reg, u8 bits)
|
|
|
|
{
|
|
|
|
return i2c_smbus_write_byte_data(&isp->client, reg + 0, bits);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
isp1301_clear_bits(struct isp1301 *isp, u8 reg, u8 bits)
|
|
|
|
{
|
|
|
|
return i2c_smbus_write_byte_data(&isp->client, reg + 1, bits);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* identification */
|
|
|
|
#define ISP1301_VENDOR_ID 0x00 /* u16 read */
|
|
|
|
#define ISP1301_PRODUCT_ID 0x02 /* u16 read */
|
|
|
|
#define ISP1301_BCD_DEVICE 0x14 /* u16 read */
|
|
|
|
|
|
|
|
#define I2C_VENDOR_ID_PHILIPS 0x04cc
|
|
|
|
#define I2C_PRODUCT_ID_PHILIPS_1301 0x1301
|
|
|
|
|
|
|
|
/* operational registers */
|
|
|
|
#define ISP1301_MODE_CONTROL_1 0x04 /* u8 read, set, +1 clear */
|
2008-07-03 11:24:43 +02:00
|
|
|
# define MC1_SPEED (1 << 0)
|
|
|
|
# define MC1_SUSPEND (1 << 1)
|
2005-04-17 00:20:36 +02:00
|
|
|
# define MC1_DAT_SE0 (1 << 2)
|
|
|
|
# define MC1_TRANSPARENT (1 << 3)
|
|
|
|
# define MC1_BDIS_ACON_EN (1 << 4)
|
|
|
|
# define MC1_OE_INT_EN (1 << 5)
|
|
|
|
# define MC1_UART_EN (1 << 6)
|
|
|
|
# define MC1_MASK 0x7f
|
|
|
|
#define ISP1301_MODE_CONTROL_2 0x12 /* u8 read, set, +1 clear */
|
|
|
|
# define MC2_GLOBAL_PWR_DN (1 << 0)
|
|
|
|
# define MC2_SPD_SUSP_CTRL (1 << 1)
|
|
|
|
# define MC2_BI_DI (1 << 2)
|
|
|
|
# define MC2_TRANSP_BDIR0 (1 << 3)
|
|
|
|
# define MC2_TRANSP_BDIR1 (1 << 4)
|
|
|
|
# define MC2_AUDIO_EN (1 << 5)
|
|
|
|
# define MC2_PSW_EN (1 << 6)
|
|
|
|
# define MC2_EN2V7 (1 << 7)
|
|
|
|
#define ISP1301_OTG_CONTROL_1 0x06 /* u8 read, set, +1 clear */
|
|
|
|
# define OTG1_DP_PULLUP (1 << 0)
|
|
|
|
# define OTG1_DM_PULLUP (1 << 1)
|
|
|
|
# define OTG1_DP_PULLDOWN (1 << 2)
|
|
|
|
# define OTG1_DM_PULLDOWN (1 << 3)
|
|
|
|
# define OTG1_ID_PULLDOWN (1 << 4)
|
|
|
|
# define OTG1_VBUS_DRV (1 << 5)
|
|
|
|
# define OTG1_VBUS_DISCHRG (1 << 6)
|
|
|
|
# define OTG1_VBUS_CHRG (1 << 7)
|
|
|
|
#define ISP1301_OTG_STATUS 0x10 /* u8 readonly */
|
|
|
|
# define OTG_B_SESS_END (1 << 6)
|
|
|
|
# define OTG_B_SESS_VLD (1 << 7)
|
|
|
|
|
|
|
|
#define ISP1301_INTERRUPT_SOURCE 0x08 /* u8 read */
|
|
|
|
#define ISP1301_INTERRUPT_LATCH 0x0A /* u8 read, set, +1 clear */
|
|
|
|
|
|
|
|
#define ISP1301_INTERRUPT_FALLING 0x0C /* u8 read, set, +1 clear */
|
|
|
|
#define ISP1301_INTERRUPT_RISING 0x0E /* u8 read, set, +1 clear */
|
|
|
|
|
|
|
|
/* same bitfields in all interrupt registers */
|
|
|
|
# define INTR_VBUS_VLD (1 << 0)
|
|
|
|
# define INTR_SESS_VLD (1 << 1)
|
|
|
|
# define INTR_DP_HI (1 << 2)
|
|
|
|
# define INTR_ID_GND (1 << 3)
|
|
|
|
# define INTR_DM_HI (1 << 4)
|
|
|
|
# define INTR_ID_FLOAT (1 << 5)
|
|
|
|
# define INTR_BDIS_ACON (1 << 6)
|
|
|
|
# define INTR_CR_INT (1 << 7)
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* NOTE: some of this ISP1301 setup is specific to H2 boards;
|
|
|
|
* not everything is guarded by board-specific checks, or even using
|
|
|
|
* omap_usb_config data to deduce MC1_DAT_SE0 and MC2_BI_DI.
|
|
|
|
*
|
|
|
|
* ALSO: this currently doesn't use ISP1301 low-power modes
|
|
|
|
* while OTG is running.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void power_down(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
isp->otg.state = OTG_STATE_UNDEFINED;
|
|
|
|
|
|
|
|
// isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN);
|
2008-07-03 11:24:43 +02:00
|
|
|
isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_ID_PULLDOWN);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void power_up(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
// isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN);
|
2008-07-03 11:24:43 +02:00
|
|
|
isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND);
|
2007-08-14 18:37:14 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* do this only when cpu is driving transceiver,
|
|
|
|
* so host won't see a low speed device...
|
|
|
|
*/
|
|
|
|
isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define NO_HOST_SUSPEND
|
|
|
|
|
|
|
|
static int host_suspend(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
#ifdef NO_HOST_SUSPEND
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
struct device *dev;
|
|
|
|
|
|
|
|
if (!isp->otg.host)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
/* Currently ASSUMES only the OTG port matters;
|
|
|
|
* other ports could be active...
|
|
|
|
*/
|
|
|
|
dev = isp->otg.host->controller;
|
|
|
|
return dev->driver->suspend(dev, 3, 0);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static int host_resume(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
#ifdef NO_HOST_SUSPEND
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
struct device *dev;
|
|
|
|
|
|
|
|
if (!isp->otg.host)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
dev = isp->otg.host->controller;
|
|
|
|
return dev->driver->resume(dev, 0);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gadget_suspend(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
isp->otg.gadget->b_hnp_enable = 0;
|
|
|
|
isp->otg.gadget->a_hnp_support = 0;
|
|
|
|
isp->otg.gadget->a_alt_hnp_support = 0;
|
|
|
|
return usb_gadget_vbus_disconnect(isp->otg.gadget);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
#define TIMER_MINUTES 10
|
|
|
|
#define TIMER_JIFFIES (TIMER_MINUTES * 60 * HZ)
|
|
|
|
|
|
|
|
/* Almost all our I2C messaging comes from a work queue's task context.
|
|
|
|
* NOTE: guaranteeing certain response times might mean we shouldn't
|
|
|
|
* share keventd's work queue; a realtime task might be safest.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
isp1301_defer_work(struct isp1301 *isp, int work)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (isp && !test_and_set_bit(work, &isp->todo)) {
|
|
|
|
(void) get_device(&isp->client.dev);
|
|
|
|
status = schedule_work(&isp->work);
|
|
|
|
if (!status && !isp->working)
|
|
|
|
dev_vdbg(&isp->client.dev,
|
|
|
|
"work item %d may be lost\n", work);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* called from irq handlers */
|
|
|
|
static void a_idle(struct isp1301 *isp, const char *tag)
|
|
|
|
{
|
2008-07-03 11:24:43 +02:00
|
|
|
u32 l;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if (isp->otg.state == OTG_STATE_A_IDLE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
isp->otg.default_a = 1;
|
|
|
|
if (isp->otg.host) {
|
|
|
|
isp->otg.host->is_b_host = 0;
|
|
|
|
host_suspend(isp);
|
|
|
|
}
|
|
|
|
if (isp->otg.gadget) {
|
|
|
|
isp->otg.gadget->is_a_peripheral = 1;
|
|
|
|
gadget_suspend(isp);
|
|
|
|
}
|
|
|
|
isp->otg.state = OTG_STATE_A_IDLE;
|
2008-07-03 11:24:43 +02:00
|
|
|
l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS;
|
|
|
|
omap_writel(l, OTG_CTRL);
|
|
|
|
isp->last_otg_ctrl = l;
|
2005-04-17 00:20:36 +02:00
|
|
|
pr_debug(" --> %s/%s\n", state_name(isp), tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* called from irq handlers */
|
|
|
|
static void b_idle(struct isp1301 *isp, const char *tag)
|
|
|
|
{
|
2008-07-03 11:24:43 +02:00
|
|
|
u32 l;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if (isp->otg.state == OTG_STATE_B_IDLE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
isp->otg.default_a = 0;
|
|
|
|
if (isp->otg.host) {
|
|
|
|
isp->otg.host->is_b_host = 1;
|
|
|
|
host_suspend(isp);
|
|
|
|
}
|
|
|
|
if (isp->otg.gadget) {
|
|
|
|
isp->otg.gadget->is_a_peripheral = 0;
|
|
|
|
gadget_suspend(isp);
|
|
|
|
}
|
|
|
|
isp->otg.state = OTG_STATE_B_IDLE;
|
2008-07-03 11:24:43 +02:00
|
|
|
l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS;
|
|
|
|
omap_writel(l, OTG_CTRL);
|
|
|
|
isp->last_otg_ctrl = l;
|
2005-04-17 00:20:36 +02:00
|
|
|
pr_debug(" --> %s/%s\n", state_name(isp), tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_regs(struct isp1301 *isp, const char *label)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
u8 ctrl = isp1301_get_u8(isp, ISP1301_OTG_CONTROL_1);
|
|
|
|
u8 status = isp1301_get_u8(isp, ISP1301_OTG_STATUS);
|
|
|
|
u8 src = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE);
|
|
|
|
|
|
|
|
pr_debug("otg: %06x, %s %s, otg/%02x stat/%02x.%02x\n",
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_readl(OTG_CTRL), label, state_name(isp),
|
2005-04-17 00:20:36 +02:00
|
|
|
ctrl, status, src);
|
|
|
|
/* mode control and irq enables don't change much */
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The OMAP OTG controller handles most of the OTG state transitions.
|
|
|
|
*
|
|
|
|
* We translate isp1301 outputs (mostly voltage comparator status) into
|
|
|
|
* OTG inputs; OTG outputs (mostly pullup/pulldown controls) and HNP state
|
|
|
|
* flags into isp1301 inputs ... and infer state transitions.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef VERBOSE
|
|
|
|
|
|
|
|
static void check_state(struct isp1301 *isp, const char *tag)
|
|
|
|
{
|
|
|
|
enum usb_otg_state state = OTG_STATE_UNDEFINED;
|
2008-07-03 11:24:43 +02:00
|
|
|
u8 fsm = omap_readw(OTG_TEST) & 0x0ff;
|
2005-04-17 00:20:36 +02:00
|
|
|
unsigned extra = 0;
|
|
|
|
|
|
|
|
switch (fsm) {
|
|
|
|
|
|
|
|
/* default-b */
|
|
|
|
case 0x0:
|
|
|
|
state = OTG_STATE_B_IDLE;
|
|
|
|
break;
|
|
|
|
case 0x3:
|
|
|
|
case 0x7:
|
|
|
|
extra = 1;
|
|
|
|
case 0x1:
|
|
|
|
state = OTG_STATE_B_PERIPHERAL;
|
|
|
|
break;
|
|
|
|
case 0x11:
|
|
|
|
state = OTG_STATE_B_SRP_INIT;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* extra dual-role default-b states */
|
|
|
|
case 0x12:
|
|
|
|
case 0x13:
|
|
|
|
case 0x16:
|
|
|
|
extra = 1;
|
|
|
|
case 0x17:
|
|
|
|
state = OTG_STATE_B_WAIT_ACON;
|
|
|
|
break;
|
|
|
|
case 0x34:
|
|
|
|
state = OTG_STATE_B_HOST;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* default-a */
|
|
|
|
case 0x36:
|
|
|
|
state = OTG_STATE_A_IDLE;
|
|
|
|
break;
|
|
|
|
case 0x3c:
|
|
|
|
state = OTG_STATE_A_WAIT_VFALL;
|
|
|
|
break;
|
|
|
|
case 0x7d:
|
|
|
|
state = OTG_STATE_A_VBUS_ERR;
|
|
|
|
break;
|
|
|
|
case 0x9e:
|
|
|
|
case 0x9f:
|
|
|
|
extra = 1;
|
|
|
|
case 0x89:
|
|
|
|
state = OTG_STATE_A_PERIPHERAL;
|
|
|
|
break;
|
|
|
|
case 0xb7:
|
|
|
|
state = OTG_STATE_A_WAIT_VRISE;
|
|
|
|
break;
|
|
|
|
case 0xb8:
|
|
|
|
state = OTG_STATE_A_WAIT_BCON;
|
|
|
|
break;
|
|
|
|
case 0xb9:
|
|
|
|
state = OTG_STATE_A_HOST;
|
|
|
|
break;
|
|
|
|
case 0xba:
|
|
|
|
state = OTG_STATE_A_SUSPEND;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (isp->otg.state == state && !extra)
|
|
|
|
return;
|
|
|
|
pr_debug("otg: %s FSM %s/%02x, %s, %06x\n", tag,
|
2008-07-03 11:24:43 +02:00
|
|
|
state_string(state), fsm, state_name(isp),
|
|
|
|
omap_readl(OTG_CTRL));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static inline void check_state(struct isp1301 *isp, const char *tag) { }
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* outputs from ISP1301_INTERRUPT_SOURCE */
|
|
|
|
static void update_otg1(struct isp1301 *isp, u8 int_src)
|
|
|
|
{
|
|
|
|
u32 otg_ctrl;
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL) & OTG_CTRL_MASK;
|
|
|
|
otg_ctrl &= ~OTG_XCEIV_INPUTS;
|
|
|
|
otg_ctrl &= ~(OTG_ID|OTG_ASESSVLD|OTG_VBUSVLD);
|
|
|
|
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if (int_src & INTR_SESS_VLD)
|
|
|
|
otg_ctrl |= OTG_ASESSVLD;
|
|
|
|
else if (isp->otg.state == OTG_STATE_A_WAIT_VFALL) {
|
|
|
|
a_idle(isp, "vfall");
|
|
|
|
otg_ctrl &= ~OTG_CTRL_BITS;
|
|
|
|
}
|
|
|
|
if (int_src & INTR_VBUS_VLD)
|
|
|
|
otg_ctrl |= OTG_VBUSVLD;
|
|
|
|
if (int_src & INTR_ID_GND) { /* default-A */
|
|
|
|
if (isp->otg.state == OTG_STATE_B_IDLE
|
|
|
|
|| isp->otg.state == OTG_STATE_UNDEFINED) {
|
|
|
|
a_idle(isp, "init");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else { /* default-B */
|
|
|
|
otg_ctrl |= OTG_ID;
|
|
|
|
if (isp->otg.state == OTG_STATE_A_IDLE
|
|
|
|
|| isp->otg.state == OTG_STATE_UNDEFINED) {
|
|
|
|
b_idle(isp, "init");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* outputs from ISP1301_OTG_STATUS */
|
|
|
|
static void update_otg2(struct isp1301 *isp, u8 otg_status)
|
|
|
|
{
|
|
|
|
u32 otg_ctrl;
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL) & OTG_CTRL_MASK;
|
|
|
|
otg_ctrl &= ~OTG_XCEIV_INPUTS;
|
|
|
|
otg_ctrl &= ~(OTG_BSESSVLD | OTG_BSESSEND);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (otg_status & OTG_B_SESS_VLD)
|
|
|
|
otg_ctrl |= OTG_BSESSVLD;
|
|
|
|
else if (otg_status & OTG_B_SESS_END)
|
|
|
|
otg_ctrl |= OTG_BSESSEND;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* inputs going to ISP1301 */
|
|
|
|
static void otg_update_isp(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
u32 otg_ctrl, otg_change;
|
|
|
|
u8 set = OTG1_DM_PULLDOWN, clr = OTG1_DM_PULLUP;
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
otg_change = otg_ctrl ^ isp->last_otg_ctrl;
|
|
|
|
isp->last_otg_ctrl = otg_ctrl;
|
|
|
|
otg_ctrl = otg_ctrl & OTG_XCEIV_INPUTS;
|
|
|
|
|
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_B_IDLE:
|
|
|
|
case OTG_STATE_B_PERIPHERAL:
|
|
|
|
case OTG_STATE_B_SRP_INIT:
|
|
|
|
if (!(otg_ctrl & OTG_PULLUP)) {
|
|
|
|
// if (otg_ctrl & OTG_B_HNPEN) {
|
|
|
|
if (isp->otg.gadget->b_hnp_enable) {
|
|
|
|
isp->otg.state = OTG_STATE_B_WAIT_ACON;
|
|
|
|
pr_debug(" --> b_wait_acon\n");
|
|
|
|
}
|
|
|
|
goto pulldown;
|
|
|
|
}
|
|
|
|
pullup:
|
|
|
|
set |= OTG1_DP_PULLUP;
|
|
|
|
clr |= OTG1_DP_PULLDOWN;
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_SUSPEND:
|
|
|
|
case OTG_STATE_A_PERIPHERAL:
|
|
|
|
if (otg_ctrl & OTG_PULLUP)
|
|
|
|
goto pullup;
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
// case OTG_STATE_B_WAIT_ACON:
|
|
|
|
default:
|
|
|
|
pulldown:
|
|
|
|
set |= OTG1_DP_PULLDOWN;
|
|
|
|
clr |= OTG1_DP_PULLUP;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
# define toggle(OTG,ISP) do { \
|
|
|
|
if (otg_ctrl & OTG) set |= ISP; \
|
|
|
|
else clr |= ISP; \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
if (!(isp->otg.host))
|
|
|
|
otg_ctrl &= ~OTG_DRV_VBUS;
|
|
|
|
|
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_A_SUSPEND:
|
|
|
|
if (otg_ctrl & OTG_DRV_VBUS) {
|
|
|
|
set |= OTG1_VBUS_DRV;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* HNP failed for some reason (A_AIDL_BDIS timeout) */
|
|
|
|
notresponding(isp);
|
|
|
|
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case OTG_STATE_A_VBUS_ERR:
|
|
|
|
isp->otg.state = OTG_STATE_A_WAIT_VFALL;
|
|
|
|
pr_debug(" --> a_wait_vfall\n");
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case OTG_STATE_A_WAIT_VFALL:
|
|
|
|
/* FIXME usbcore thinks port power is still on ... */
|
|
|
|
clr |= OTG1_VBUS_DRV;
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_IDLE:
|
|
|
|
if (otg_ctrl & OTG_DRV_VBUS) {
|
|
|
|
isp->otg.state = OTG_STATE_A_WAIT_VRISE;
|
|
|
|
pr_debug(" --> a_wait_vrise\n");
|
|
|
|
}
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
default:
|
|
|
|
toggle(OTG_DRV_VBUS, OTG1_VBUS_DRV);
|
|
|
|
}
|
|
|
|
|
|
|
|
toggle(OTG_PU_VBUS, OTG1_VBUS_CHRG);
|
|
|
|
toggle(OTG_PD_VBUS, OTG1_VBUS_DISCHRG);
|
|
|
|
|
|
|
|
# undef toggle
|
|
|
|
|
|
|
|
isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, set);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, clr);
|
|
|
|
|
|
|
|
/* HNP switch to host or peripheral; and SRP */
|
|
|
|
if (otg_change & OTG_PULLUP) {
|
2008-07-03 11:24:43 +02:00
|
|
|
u32 l;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_B_IDLE:
|
|
|
|
if (clr & OTG1_DP_PULLUP)
|
|
|
|
break;
|
|
|
|
isp->otg.state = OTG_STATE_B_PERIPHERAL;
|
|
|
|
pr_debug(" --> b_peripheral\n");
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_SUSPEND:
|
|
|
|
if (clr & OTG1_DP_PULLUP)
|
|
|
|
break;
|
|
|
|
isp->otg.state = OTG_STATE_A_PERIPHERAL;
|
|
|
|
pr_debug(" --> a_peripheral\n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2008-07-03 11:24:43 +02:00
|
|
|
l = omap_readl(OTG_CTRL);
|
|
|
|
l |= OTG_PULLUP;
|
|
|
|
omap_writel(l, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2008-04-22 22:16:47 +02:00
|
|
|
check_state(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
dump_regs(isp, "otg->isp1301");
|
|
|
|
}
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
static irqreturn_t omap_otg_irq(int irq, void *_isp)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2008-07-03 11:24:43 +02:00
|
|
|
u16 otg_irq = omap_readw(OTG_IRQ_SRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
u32 otg_ctrl;
|
|
|
|
int ret = IRQ_NONE;
|
|
|
|
struct isp1301 *isp = _isp;
|
|
|
|
|
|
|
|
/* update ISP1301 transciever from OTG controller */
|
|
|
|
if (otg_irq & OPRT_CHG) {
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(OPRT_CHG, OTG_IRQ_SRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
isp1301_defer_work(isp, WORK_UPDATE_ISP);
|
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
|
|
|
|
/* SRP to become b_peripheral failed */
|
|
|
|
} else if (otg_irq & B_SRP_TMROUT) {
|
2008-07-03 11:24:43 +02:00
|
|
|
pr_debug("otg: B_SRP_TIMEOUT, %06x\n", omap_readl(OTG_CTRL));
|
2005-04-17 00:20:36 +02:00
|
|
|
notresponding(isp);
|
|
|
|
|
|
|
|
/* gadget drivers that care should monitor all kinds of
|
|
|
|
* remote wakeup (SRP, normal) using their own timer
|
|
|
|
* to give "check cable and A-device" messages.
|
|
|
|
*/
|
|
|
|
if (isp->otg.state == OTG_STATE_B_SRP_INIT)
|
|
|
|
b_idle(isp, "srp_timeout");
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(B_SRP_TMROUT, OTG_IRQ_SRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
|
|
|
|
/* HNP to become b_host failed */
|
|
|
|
} else if (otg_irq & B_HNP_FAIL) {
|
|
|
|
pr_debug("otg: %s B_HNP_FAIL, %06x\n",
|
2008-07-03 11:24:43 +02:00
|
|
|
state_name(isp), omap_readl(OTG_CTRL));
|
2005-04-17 00:20:36 +02:00
|
|
|
notresponding(isp);
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
otg_ctrl |= OTG_BUSDROP;
|
|
|
|
otg_ctrl &= OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* subset of b_peripheral()... */
|
|
|
|
isp->otg.state = OTG_STATE_B_PERIPHERAL;
|
|
|
|
pr_debug(" --> b_peripheral\n");
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(B_HNP_FAIL, OTG_IRQ_SRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
|
|
|
|
/* detect SRP from B-device ... */
|
|
|
|
} else if (otg_irq & A_SRP_DETECT) {
|
|
|
|
pr_debug("otg: %s SRP_DETECT, %06x\n",
|
2008-07-03 11:24:43 +02:00
|
|
|
state_name(isp), omap_readl(OTG_CTRL));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
isp1301_defer_work(isp, WORK_UPDATE_OTG);
|
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_A_IDLE:
|
|
|
|
if (!isp->otg.host)
|
|
|
|
break;
|
|
|
|
isp1301_defer_work(isp, WORK_HOST_RESUME);
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
otg_ctrl |= OTG_A_BUSREQ;
|
|
|
|
otg_ctrl &= ~(OTG_BUSDROP|OTG_B_BUSREQ)
|
|
|
|
& ~OTG_XCEIV_INPUTS
|
|
|
|
& OTG_CTRL_MASK;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(A_SRP_DETECT, OTG_IRQ_SRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
|
|
|
|
/* timer expired: T(a_wait_bcon) and maybe T(a_wait_vrise)
|
|
|
|
* we don't track them separately
|
|
|
|
*/
|
|
|
|
} else if (otg_irq & A_REQ_TMROUT) {
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
pr_info("otg: BCON_TMOUT from %s, %06x\n",
|
|
|
|
state_name(isp), otg_ctrl);
|
|
|
|
notresponding(isp);
|
|
|
|
|
|
|
|
otg_ctrl |= OTG_BUSDROP;
|
|
|
|
otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
isp->otg.state = OTG_STATE_A_WAIT_VFALL;
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(A_REQ_TMROUT, OTG_IRQ_SRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
|
|
|
|
/* A-supplied voltage fell too low; overcurrent */
|
|
|
|
} else if (otg_irq & A_VBUS_ERR) {
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
printk(KERN_ERR "otg: %s, VBUS_ERR %04x ctrl %06x\n",
|
|
|
|
state_name(isp), otg_irq, otg_ctrl);
|
|
|
|
|
|
|
|
otg_ctrl |= OTG_BUSDROP;
|
|
|
|
otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
isp->otg.state = OTG_STATE_A_VBUS_ERR;
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(A_VBUS_ERR, OTG_IRQ_SRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
|
|
|
|
/* switch driver; the transciever code activates it,
|
|
|
|
* ungating the udc clock or resuming OHCI.
|
|
|
|
*/
|
|
|
|
} else if (otg_irq & DRIVER_SWITCH) {
|
|
|
|
int kick = 0;
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
printk(KERN_NOTICE "otg: %s, SWITCH to %s, ctrl %06x\n",
|
|
|
|
state_name(isp),
|
|
|
|
(otg_ctrl & OTG_DRIVER_SEL)
|
|
|
|
? "gadget" : "host",
|
|
|
|
otg_ctrl);
|
|
|
|
isp1301_defer_work(isp, WORK_UPDATE_ISP);
|
|
|
|
|
|
|
|
/* role is peripheral */
|
|
|
|
if (otg_ctrl & OTG_DRIVER_SEL) {
|
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_A_IDLE:
|
2008-04-22 22:16:47 +02:00
|
|
|
b_idle(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
isp1301_defer_work(isp, WORK_UPDATE_ISP);
|
|
|
|
|
|
|
|
/* role is host */
|
|
|
|
} else {
|
|
|
|
if (!(otg_ctrl & OTG_ID)) {
|
2007-08-14 18:37:14 +02:00
|
|
|
otg_ctrl &= OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl | OTG_A_BUSREQ, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isp->otg.host) {
|
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_B_WAIT_ACON:
|
|
|
|
isp->otg.state = OTG_STATE_B_HOST;
|
|
|
|
pr_debug(" --> b_host\n");
|
|
|
|
kick = 1;
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_WAIT_BCON:
|
|
|
|
isp->otg.state = OTG_STATE_A_HOST;
|
|
|
|
pr_debug(" --> a_host\n");
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_PERIPHERAL:
|
|
|
|
isp->otg.state = OTG_STATE_A_WAIT_BCON;
|
|
|
|
pr_debug(" --> a_wait_bcon\n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
isp1301_defer_work(isp, WORK_HOST_RESUME);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(DRIVER_SWITCH, OTG_IRQ_SRC);
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
|
|
|
|
if (kick)
|
|
|
|
usb_bus_start_enum(isp->otg.host,
|
|
|
|
isp->otg.host->otg_port);
|
|
|
|
}
|
|
|
|
|
2008-04-22 22:16:47 +02:00
|
|
|
check_state(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct platform_device *otg_dev;
|
|
|
|
|
|
|
|
static int otg_init(struct isp1301 *isp)
|
|
|
|
{
|
2008-07-03 11:24:43 +02:00
|
|
|
u32 l;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!otg_dev)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2008-04-22 22:16:47 +02:00
|
|
|
dump_regs(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
/* some of these values are board-specific... */
|
2008-07-03 11:24:43 +02:00
|
|
|
l = omap_readl(OTG_SYSCON_2);
|
|
|
|
l |= OTG_EN
|
2005-04-17 00:20:36 +02:00
|
|
|
/* for B-device: */
|
|
|
|
| SRP_GPDATA /* 9msec Bdev D+ pulse */
|
|
|
|
| SRP_GPDVBUS /* discharge after VBUS pulse */
|
|
|
|
// | (3 << 24) /* 2msec VBUS pulse */
|
|
|
|
/* for A-device: */
|
|
|
|
| (0 << 20) /* 200ms nominal A_WAIT_VRISE timer */
|
|
|
|
| SRP_DPW /* detect 167+ns SRP pulses */
|
|
|
|
| SRP_DATA | SRP_VBUS /* accept both kinds of SRP pulse */
|
|
|
|
;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(l, OTG_SYSCON_2);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
update_otg1(isp, isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE));
|
|
|
|
update_otg2(isp, isp1301_get_u8(isp, ISP1301_OTG_STATUS));
|
|
|
|
|
2008-04-22 22:16:47 +02:00
|
|
|
check_state(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
pr_debug("otg: %s, %s %06x\n",
|
2008-07-03 11:24:43 +02:00
|
|
|
state_name(isp), __func__, omap_readl(OTG_CTRL));
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(DRIVER_SWITCH | OPRT_CHG
|
2005-04-17 00:20:36 +02:00
|
|
|
| B_SRP_TMROUT | B_HNP_FAIL
|
2008-07-03 11:24:43 +02:00
|
|
|
| A_VBUS_ERR | A_SRP_DETECT | A_REQ_TMROUT, OTG_IRQ_EN);
|
|
|
|
|
|
|
|
l = omap_readl(OTG_SYSCON_2);
|
|
|
|
l |= OTG_EN;
|
|
|
|
omap_writel(l, OTG_SYSCON_2);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
static int otg_probe(struct platform_device *dev)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
// struct omap_usb_config *config = dev->platform_data;
|
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
otg_dev = dev;
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
static int otg_remove(struct platform_device *dev)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
otg_dev = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
struct platform_driver omap_otg_driver = {
|
2005-04-17 00:20:36 +02:00
|
|
|
.probe = otg_probe,
|
2005-11-09 23:32:44 +01:00
|
|
|
.remove = otg_remove,
|
|
|
|
.driver = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.name = "omap_otg",
|
|
|
|
},
|
2005-04-17 00:20:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
static int otg_bind(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (otg_dev)
|
|
|
|
return -EBUSY;
|
|
|
|
|
2005-11-09 23:32:44 +01:00
|
|
|
status = platform_driver_register(&omap_otg_driver);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
if (otg_dev)
|
|
|
|
status = request_irq(otg_dev->resource[1].start, omap_otg_irq,
|
2006-07-02 04:29:38 +02:00
|
|
|
IRQF_DISABLED, DRIVER_NAME, isp);
|
2005-04-17 00:20:36 +02:00
|
|
|
else
|
|
|
|
status = -ENODEV;
|
|
|
|
|
|
|
|
if (status < 0)
|
2005-11-09 23:32:44 +01:00
|
|
|
platform_driver_unregister(&omap_otg_driver);
|
2005-04-17 00:20:36 +02:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void otg_unbind(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
if (!otg_dev)
|
|
|
|
return;
|
|
|
|
free_irq(otg_dev->resource[1].start, isp);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
/* OTG controller isn't clocked */
|
|
|
|
|
|
|
|
#endif /* CONFIG_USB_OTG */
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static void b_peripheral(struct isp1301 *isp)
|
|
|
|
{
|
2008-07-03 11:24:43 +02:00
|
|
|
u32 l;
|
|
|
|
|
|
|
|
l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS;
|
|
|
|
omap_writel(l, OTG_CTRL);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
usb_gadget_vbus_connect(isp->otg.gadget);
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
enable_vbus_draw(isp, 8);
|
|
|
|
otg_update_isp(isp);
|
|
|
|
#else
|
|
|
|
enable_vbus_draw(isp, 100);
|
|
|
|
/* UDC driver just set OTG_BSESSVLD */
|
|
|
|
isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_DP_PULLUP);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_DP_PULLDOWN);
|
|
|
|
isp->otg.state = OTG_STATE_B_PERIPHERAL;
|
|
|
|
pr_debug(" --> b_peripheral\n");
|
|
|
|
dump_regs(isp, "2periph");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static void isp_update_otg(struct isp1301 *isp, u8 stat)
|
|
|
|
{
|
|
|
|
u8 isp_stat, isp_bstat;
|
|
|
|
enum usb_otg_state state = isp->otg.state;
|
|
|
|
|
|
|
|
if (stat & INTR_BDIS_ACON)
|
|
|
|
pr_debug("OTG: BDIS_ACON, %s\n", state_name(isp));
|
|
|
|
|
|
|
|
/* start certain state transitions right away */
|
|
|
|
isp_stat = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE);
|
|
|
|
if (isp_stat & INTR_ID_GND) {
|
|
|
|
if (isp->otg.default_a) {
|
|
|
|
switch (state) {
|
|
|
|
case OTG_STATE_B_IDLE:
|
|
|
|
a_idle(isp, "idle");
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case OTG_STATE_A_IDLE:
|
|
|
|
enable_vbus_source(isp);
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case OTG_STATE_A_WAIT_VRISE:
|
|
|
|
/* we skip over OTG_STATE_A_WAIT_BCON, since
|
|
|
|
* the HC will transition to A_HOST (or
|
|
|
|
* A_SUSPEND!) without our noticing except
|
|
|
|
* when HNP is used.
|
|
|
|
*/
|
|
|
|
if (isp_stat & INTR_VBUS_VLD)
|
|
|
|
isp->otg.state = OTG_STATE_A_HOST;
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_WAIT_VFALL:
|
|
|
|
if (!(isp_stat & INTR_SESS_VLD))
|
|
|
|
a_idle(isp, "vfell");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (!(isp_stat & INTR_VBUS_VLD))
|
|
|
|
isp->otg.state = OTG_STATE_A_VBUS_ERR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
isp_bstat = isp1301_get_u8(isp, ISP1301_OTG_STATUS);
|
|
|
|
} else {
|
|
|
|
switch (state) {
|
|
|
|
case OTG_STATE_B_PERIPHERAL:
|
|
|
|
case OTG_STATE_B_HOST:
|
|
|
|
case OTG_STATE_B_WAIT_ACON:
|
|
|
|
usb_gadget_vbus_disconnect(isp->otg.gadget);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (state != OTG_STATE_A_IDLE)
|
|
|
|
a_idle(isp, "id");
|
|
|
|
if (isp->otg.host && state == OTG_STATE_A_IDLE)
|
|
|
|
isp1301_defer_work(isp, WORK_HOST_RESUME);
|
|
|
|
isp_bstat = 0;
|
|
|
|
}
|
|
|
|
} else {
|
2008-07-03 11:24:43 +02:00
|
|
|
u32 l;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* if user unplugged mini-A end of cable,
|
|
|
|
* don't bypass A_WAIT_VFALL.
|
|
|
|
*/
|
|
|
|
if (isp->otg.default_a) {
|
|
|
|
switch (state) {
|
|
|
|
default:
|
|
|
|
isp->otg.state = OTG_STATE_A_WAIT_VFALL;
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_WAIT_VFALL:
|
|
|
|
state = OTG_STATE_A_IDLE;
|
|
|
|
/* khubd may take a while to notice and
|
|
|
|
* handle this disconnect, so don't go
|
|
|
|
* to B_IDLE quite yet.
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_IDLE:
|
|
|
|
host_suspend(isp);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1,
|
|
|
|
MC1_BDIS_ACON_EN);
|
|
|
|
isp->otg.state = OTG_STATE_B_IDLE;
|
2008-07-03 11:24:43 +02:00
|
|
|
l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK;
|
|
|
|
l &= ~OTG_CTRL_BITS;
|
|
|
|
omap_writel(l, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case OTG_STATE_B_IDLE:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
isp_bstat = isp1301_get_u8(isp, ISP1301_OTG_STATUS);
|
|
|
|
|
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_B_PERIPHERAL:
|
|
|
|
case OTG_STATE_B_WAIT_ACON:
|
|
|
|
case OTG_STATE_B_HOST:
|
|
|
|
if (likely(isp_bstat & OTG_B_SESS_VLD))
|
|
|
|
break;
|
|
|
|
enable_vbus_draw(isp, 0);
|
|
|
|
#ifndef CONFIG_USB_OTG
|
|
|
|
/* UDC driver will clear OTG_BSESSVLD */
|
|
|
|
isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1,
|
|
|
|
OTG1_DP_PULLDOWN);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1,
|
|
|
|
OTG1_DP_PULLUP);
|
2008-04-22 22:16:47 +02:00
|
|
|
dump_regs(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
#endif
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case OTG_STATE_B_SRP_INIT:
|
2008-04-22 22:16:47 +02:00
|
|
|
b_idle(isp, __func__);
|
2008-07-03 11:24:43 +02:00
|
|
|
l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS;
|
|
|
|
omap_writel(l, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
/* FALLTHROUGH */
|
|
|
|
case OTG_STATE_B_IDLE:
|
|
|
|
if (isp->otg.gadget && (isp_bstat & OTG_B_SESS_VLD)) {
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
update_otg1(isp, isp_stat);
|
|
|
|
update_otg2(isp, isp_bstat);
|
|
|
|
#endif
|
|
|
|
b_peripheral(isp);
|
|
|
|
} else if (!(isp_stat & (INTR_VBUS_VLD|INTR_SESS_VLD)))
|
|
|
|
isp_bstat |= OTG_B_SESS_END;
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_WAIT_VFALL:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pr_debug("otg: unsupported b-device %s\n",
|
|
|
|
state_name(isp));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state != isp->otg.state)
|
|
|
|
pr_debug(" isp, %s -> %s\n",
|
|
|
|
state_string(state), state_name(isp));
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
/* update the OTG controller state to match the isp1301; may
|
|
|
|
* trigger OPRT_CHG irqs for changes going to the isp1301.
|
|
|
|
*/
|
|
|
|
update_otg1(isp, isp_stat);
|
|
|
|
update_otg2(isp, isp_bstat);
|
2008-04-22 22:16:47 +02:00
|
|
|
check_state(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
dump_regs(isp, "isp1301->otg");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static u8 isp1301_clear_latch(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
u8 latch = isp1301_get_u8(isp, ISP1301_INTERRUPT_LATCH);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_INTERRUPT_LATCH, latch);
|
|
|
|
return latch;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2007-08-14 18:37:14 +02:00
|
|
|
isp1301_work(struct work_struct *work)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2007-08-14 18:37:14 +02:00
|
|
|
struct isp1301 *isp = container_of(work, struct isp1301, work);
|
2005-04-17 00:20:36 +02:00
|
|
|
int stop;
|
|
|
|
|
|
|
|
/* implicit lock: we're the only task using this device */
|
|
|
|
isp->working = 1;
|
|
|
|
do {
|
|
|
|
stop = test_bit(WORK_STOP, &isp->todo);
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
/* transfer state from otg engine to isp1301 */
|
|
|
|
if (test_and_clear_bit(WORK_UPDATE_ISP, &isp->todo)) {
|
|
|
|
otg_update_isp(isp);
|
|
|
|
put_device(&isp->client.dev);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/* transfer state from isp1301 to otg engine */
|
|
|
|
if (test_and_clear_bit(WORK_UPDATE_OTG, &isp->todo)) {
|
|
|
|
u8 stat = isp1301_clear_latch(isp);
|
|
|
|
|
|
|
|
isp_update_otg(isp, stat);
|
|
|
|
put_device(&isp->client.dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (test_and_clear_bit(WORK_HOST_RESUME, &isp->todo)) {
|
|
|
|
u32 otg_ctrl;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* skip A_WAIT_VRISE; hc transitions invisibly
|
|
|
|
* skip A_WAIT_BCON; same.
|
|
|
|
*/
|
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_A_WAIT_BCON:
|
|
|
|
case OTG_STATE_A_WAIT_VRISE:
|
|
|
|
isp->otg.state = OTG_STATE_A_HOST;
|
|
|
|
pr_debug(" --> a_host\n");
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
otg_ctrl |= OTG_A_BUSREQ;
|
|
|
|
otg_ctrl &= ~(OTG_BUSDROP|OTG_B_BUSREQ)
|
|
|
|
& OTG_CTRL_MASK;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case OTG_STATE_B_WAIT_ACON:
|
|
|
|
isp->otg.state = OTG_STATE_B_HOST;
|
|
|
|
pr_debug(" --> b_host (acon)\n");
|
|
|
|
break;
|
|
|
|
case OTG_STATE_B_HOST:
|
|
|
|
case OTG_STATE_B_IDLE:
|
|
|
|
case OTG_STATE_A_IDLE:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pr_debug(" host resume in %s\n",
|
|
|
|
state_name(isp));
|
|
|
|
}
|
|
|
|
host_resume(isp);
|
|
|
|
// mdelay(10);
|
|
|
|
put_device(&isp->client.dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (test_and_clear_bit(WORK_TIMER, &isp->todo)) {
|
|
|
|
#ifdef VERBOSE
|
|
|
|
dump_regs(isp, "timer");
|
|
|
|
if (!stop)
|
|
|
|
mod_timer(&isp->timer, jiffies + TIMER_JIFFIES);
|
|
|
|
#endif
|
|
|
|
put_device(&isp->client.dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isp->todo)
|
|
|
|
dev_vdbg(&isp->client.dev,
|
|
|
|
"work done, todo = 0x%lx\n",
|
|
|
|
isp->todo);
|
|
|
|
if (stop) {
|
|
|
|
dev_dbg(&isp->client.dev, "stop\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (isp->todo);
|
|
|
|
isp->working = 0;
|
|
|
|
}
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
static irqreturn_t isp1301_irq(int irq, void *isp)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
isp1301_defer_work(isp, WORK_UPDATE_OTG);
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void isp1301_timer(unsigned long _isp)
|
|
|
|
{
|
|
|
|
isp1301_defer_work((void *)_isp, WORK_TIMER);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static void isp1301_release(struct device *dev)
|
|
|
|
{
|
|
|
|
struct isp1301 *isp;
|
|
|
|
|
|
|
|
isp = container_of(dev, struct isp1301, client.dev);
|
|
|
|
|
|
|
|
/* ugly -- i2c hijacks our memory hook to wait_for_completion() */
|
|
|
|
if (isp->i2c_release)
|
|
|
|
isp->i2c_release(dev);
|
|
|
|
kfree (isp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct isp1301 *the_transceiver;
|
|
|
|
|
|
|
|
static int isp1301_detach_client(struct i2c_client *i2c)
|
|
|
|
{
|
|
|
|
struct isp1301 *isp;
|
|
|
|
|
|
|
|
isp = container_of(i2c, struct isp1301, client);
|
|
|
|
|
|
|
|
isp1301_clear_bits(isp, ISP1301_INTERRUPT_FALLING, ~0);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_INTERRUPT_RISING, ~0);
|
|
|
|
free_irq(isp->irq, isp);
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
otg_unbind(isp);
|
|
|
|
#endif
|
|
|
|
if (machine_is_omap_h2())
|
|
|
|
omap_free_gpio(2);
|
|
|
|
|
|
|
|
isp->timer.data = 0;
|
|
|
|
set_bit(WORK_STOP, &isp->todo);
|
|
|
|
del_timer_sync(&isp->timer);
|
|
|
|
flush_scheduled_work();
|
|
|
|
|
|
|
|
put_device(&i2c->dev);
|
|
|
|
the_transceiver = 0;
|
|
|
|
|
|
|
|
return i2c_detach_client(i2c);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* NOTE: three modes are possible here, only one of which
|
|
|
|
* will be standards-conformant on any given system:
|
|
|
|
*
|
|
|
|
* - OTG mode (dual-role), required if there's a Mini-AB connector
|
|
|
|
* - HOST mode, for when there's one or more A (host) connectors
|
|
|
|
* - DEVICE mode, for when there's a B/Mini-B (device) connector
|
|
|
|
*
|
|
|
|
* As a rule, you won't have an isp1301 chip unless it's there to
|
2007-08-14 18:37:14 +02:00
|
|
|
* support the OTG mode. Other modes help testing USB controllers
|
2005-04-17 00:20:36 +02:00
|
|
|
* in isolation from (full) OTG support, or maybe so later board
|
|
|
|
* revisions can help to support those feature.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
|
|
|
|
static int isp1301_otg_enable(struct isp1301 *isp)
|
|
|
|
{
|
|
|
|
power_up(isp);
|
|
|
|
otg_init(isp);
|
|
|
|
|
|
|
|
/* NOTE: since we don't change this, this provides
|
|
|
|
* a few more interrupts than are strictly needed.
|
|
|
|
*/
|
|
|
|
isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING,
|
2007-08-14 18:37:14 +02:00
|
|
|
INTR_VBUS_VLD | INTR_SESS_VLD | INTR_ID_GND);
|
2005-04-17 00:20:36 +02:00
|
|
|
isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING,
|
2007-08-14 18:37:14 +02:00
|
|
|
INTR_VBUS_VLD | INTR_SESS_VLD | INTR_ID_GND);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
dev_info(&isp->client.dev, "ready for dual-role USB ...\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* add or disable the host device+driver */
|
|
|
|
static int
|
|
|
|
isp1301_set_host(struct otg_transceiver *otg, struct usb_bus *host)
|
|
|
|
{
|
|
|
|
struct isp1301 *isp = container_of(otg, struct isp1301, otg);
|
|
|
|
|
|
|
|
if (!otg || isp != the_transceiver)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (!host) {
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(0, OTG_IRQ_EN);
|
2005-04-17 00:20:36 +02:00
|
|
|
power_down(isp);
|
|
|
|
isp->otg.host = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
isp->otg.host = host;
|
|
|
|
dev_dbg(&isp->client.dev, "registered host\n");
|
|
|
|
host_suspend(isp);
|
|
|
|
if (isp->otg.gadget)
|
|
|
|
return isp1301_otg_enable(isp);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#elif !defined(CONFIG_USB_GADGET_OMAP)
|
|
|
|
// FIXME update its refcount
|
|
|
|
isp->otg.host = host;
|
|
|
|
|
|
|
|
power_up(isp);
|
|
|
|
|
|
|
|
if (machine_is_omap_h2())
|
|
|
|
isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0);
|
|
|
|
|
|
|
|
dev_info(&isp->client.dev, "A-Host sessions ok\n");
|
|
|
|
isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING,
|
2007-08-14 18:37:14 +02:00
|
|
|
INTR_ID_GND);
|
2005-04-17 00:20:36 +02:00
|
|
|
isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING,
|
2007-08-14 18:37:14 +02:00
|
|
|
INTR_ID_GND);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* If this has a Mini-AB connector, this mode is highly
|
|
|
|
* nonstandard ... but can be handy for testing, especially with
|
|
|
|
* the Mini-A end of an OTG cable. (Or something nonstandard
|
|
|
|
* like MiniB-to-StandardB, maybe built with a gender mender.)
|
|
|
|
*/
|
|
|
|
isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_VBUS_DRV);
|
|
|
|
|
2008-04-22 22:16:47 +02:00
|
|
|
dump_regs(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#else
|
|
|
|
dev_dbg(&isp->client.dev, "host sessions not allowed\n");
|
|
|
|
return -EINVAL;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
isp1301_set_peripheral(struct otg_transceiver *otg, struct usb_gadget *gadget)
|
|
|
|
{
|
|
|
|
struct isp1301 *isp = container_of(otg, struct isp1301, otg);
|
2008-07-03 11:24:43 +02:00
|
|
|
u32 l;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (!otg || isp != the_transceiver)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (!gadget) {
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writew(0, OTG_IRQ_EN);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!isp->otg.default_a)
|
|
|
|
enable_vbus_draw(isp, 0);
|
|
|
|
usb_gadget_vbus_disconnect(isp->otg.gadget);
|
|
|
|
isp->otg.gadget = 0;
|
|
|
|
power_down(isp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
isp->otg.gadget = gadget;
|
|
|
|
dev_dbg(&isp->client.dev, "registered gadget\n");
|
|
|
|
/* gadget driver may be suspended until vbus_connect () */
|
|
|
|
if (isp->otg.host)
|
|
|
|
return isp1301_otg_enable(isp);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#elif !defined(CONFIG_USB_OHCI_HCD) && !defined(CONFIG_USB_OHCI_HCD_MODULE)
|
|
|
|
isp->otg.gadget = gadget;
|
|
|
|
// FIXME update its refcount
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK;
|
|
|
|
l &= ~(OTG_XCEIV_OUTPUTS|OTG_CTRL_BITS);
|
|
|
|
l |= OTG_ID;
|
|
|
|
omap_writel(l, OTG_CTRL);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
power_up(isp);
|
|
|
|
isp->otg.state = OTG_STATE_B_IDLE;
|
|
|
|
|
|
|
|
if (machine_is_omap_h2())
|
|
|
|
isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0);
|
|
|
|
|
|
|
|
isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING,
|
2007-08-14 18:37:14 +02:00
|
|
|
INTR_SESS_VLD);
|
2005-04-17 00:20:36 +02:00
|
|
|
isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING,
|
2007-08-14 18:37:14 +02:00
|
|
|
INTR_VBUS_VLD);
|
2005-04-17 00:20:36 +02:00
|
|
|
dev_info(&isp->client.dev, "B-Peripheral sessions ok\n");
|
2008-04-22 22:16:47 +02:00
|
|
|
dump_regs(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* If this has a Mini-AB connector, this mode is highly
|
|
|
|
* nonstandard ... but can be handy for testing, so long
|
|
|
|
* as you don't plug a Mini-A cable into the jack.
|
|
|
|
*/
|
|
|
|
if (isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE) & INTR_VBUS_VLD)
|
|
|
|
b_peripheral(isp);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#else
|
|
|
|
dev_dbg(&isp->client.dev, "peripheral sessions not allowed\n");
|
|
|
|
return -EINVAL;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
isp1301_set_power(struct otg_transceiver *dev, unsigned mA)
|
|
|
|
{
|
|
|
|
if (!the_transceiver)
|
|
|
|
return -ENODEV;
|
|
|
|
if (dev->state == OTG_STATE_B_PERIPHERAL)
|
|
|
|
enable_vbus_draw(the_transceiver, mA);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
isp1301_start_srp(struct otg_transceiver *dev)
|
|
|
|
{
|
|
|
|
struct isp1301 *isp = container_of(dev, struct isp1301, otg);
|
|
|
|
u32 otg_ctrl;
|
|
|
|
|
|
|
|
if (!dev || isp != the_transceiver
|
|
|
|
|| isp->otg.state != OTG_STATE_B_IDLE)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
otg_ctrl = omap_readl(OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!(otg_ctrl & OTG_BSESSEND))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
otg_ctrl |= OTG_B_BUSREQ;
|
|
|
|
otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK;
|
2008-07-03 11:24:43 +02:00
|
|
|
omap_writel(otg_ctrl, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
isp->otg.state = OTG_STATE_B_SRP_INIT;
|
|
|
|
|
2008-07-03 11:24:43 +02:00
|
|
|
pr_debug("otg: SRP, %s ... %06x\n", state_name(isp),
|
|
|
|
omap_readl(OTG_CTRL));
|
2005-04-17 00:20:36 +02:00
|
|
|
#ifdef CONFIG_USB_OTG
|
2008-04-22 22:16:47 +02:00
|
|
|
check_state(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
isp1301_start_hnp(struct otg_transceiver *dev)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
struct isp1301 *isp = container_of(dev, struct isp1301, otg);
|
2008-07-03 11:24:43 +02:00
|
|
|
u32 l;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (!dev || isp != the_transceiver)
|
|
|
|
return -ENODEV;
|
|
|
|
if (isp->otg.default_a && (isp->otg.host == NULL
|
|
|
|
|| !isp->otg.host->b_hnp_enable))
|
|
|
|
return -ENOTCONN;
|
|
|
|
if (!isp->otg.default_a && (isp->otg.gadget == NULL
|
|
|
|
|| !isp->otg.gadget->b_hnp_enable))
|
|
|
|
return -ENOTCONN;
|
|
|
|
|
|
|
|
/* We want hardware to manage most HNP protocol timings.
|
|
|
|
* So do this part as early as possible...
|
|
|
|
*/
|
|
|
|
switch (isp->otg.state) {
|
|
|
|
case OTG_STATE_B_HOST:
|
|
|
|
isp->otg.state = OTG_STATE_B_PERIPHERAL;
|
|
|
|
/* caller will suspend next */
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_HOST:
|
|
|
|
#if 0
|
|
|
|
/* autoconnect mode avoids irq latency bugs */
|
|
|
|
isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1,
|
|
|
|
MC1_BDIS_ACON_EN);
|
|
|
|
#endif
|
|
|
|
/* caller must suspend then clear A_BUSREQ */
|
|
|
|
usb_gadget_vbus_connect(isp->otg.gadget);
|
2008-07-03 11:24:43 +02:00
|
|
|
l = omap_readl(OTG_CTRL);
|
|
|
|
l |= OTG_A_SETB_HNPEN;
|
|
|
|
omap_writel(l, OTG_CTRL);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
break;
|
|
|
|
case OTG_STATE_A_PERIPHERAL:
|
|
|
|
/* initiated by B-Host suspend */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EILSEQ;
|
|
|
|
}
|
|
|
|
pr_debug("otg: HNP %s, %06x ...\n",
|
2008-07-03 11:24:43 +02:00
|
|
|
state_name(isp), omap_readl(OTG_CTRL));
|
2008-04-22 22:16:47 +02:00
|
|
|
check_state(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
/* srp-only */
|
|
|
|
return -EINVAL;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* no error returns, they'd just make bus scanning stop */
|
|
|
|
static int isp1301_probe(struct i2c_adapter *bus, int address, int kind)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
struct isp1301 *isp;
|
|
|
|
struct i2c_client *i2c;
|
|
|
|
|
|
|
|
if (the_transceiver)
|
|
|
|
return 0;
|
|
|
|
|
2005-09-07 00:18:35 +02:00
|
|
|
isp = kzalloc(sizeof *isp, GFP_KERNEL);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!isp)
|
|
|
|
return 0;
|
|
|
|
|
2007-08-14 18:37:14 +02:00
|
|
|
INIT_WORK(&isp->work, isp1301_work);
|
2005-04-17 00:20:36 +02:00
|
|
|
init_timer(&isp->timer);
|
|
|
|
isp->timer.function = isp1301_timer;
|
|
|
|
isp->timer.data = (unsigned long) isp;
|
|
|
|
|
|
|
|
isp->irq = -1;
|
|
|
|
isp->client.addr = address;
|
|
|
|
i2c_set_clientdata(&isp->client, isp);
|
|
|
|
isp->client.adapter = bus;
|
|
|
|
isp->client.driver = &isp1301_driver;
|
|
|
|
strlcpy(isp->client.name, DRIVER_NAME, I2C_NAME_SIZE);
|
|
|
|
i2c = &isp->client;
|
|
|
|
|
|
|
|
/* if this is a true probe, verify the chip ... */
|
|
|
|
if (kind < 0) {
|
|
|
|
status = isp1301_get_u16(isp, ISP1301_VENDOR_ID);
|
|
|
|
if (status != I2C_VENDOR_ID_PHILIPS) {
|
|
|
|
dev_dbg(&bus->dev, "addr %d not philips id: %d\n",
|
|
|
|
address, status);
|
|
|
|
goto fail1;
|
|
|
|
}
|
|
|
|
status = isp1301_get_u16(isp, ISP1301_PRODUCT_ID);
|
|
|
|
if (status != I2C_PRODUCT_ID_PHILIPS_1301) {
|
|
|
|
dev_dbg(&bus->dev, "%d not isp1301, %d\n",
|
|
|
|
address, status);
|
|
|
|
goto fail1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
status = i2c_attach_client(i2c);
|
|
|
|
if (status < 0) {
|
|
|
|
dev_dbg(&bus->dev, "can't attach %s to device %d, err %d\n",
|
|
|
|
DRIVER_NAME, address, status);
|
|
|
|
fail1:
|
|
|
|
kfree(isp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
isp->i2c_release = i2c->dev.release;
|
|
|
|
i2c->dev.release = isp1301_release;
|
|
|
|
|
|
|
|
/* initial development used chiprev 2.00 */
|
|
|
|
status = i2c_smbus_read_word_data(i2c, ISP1301_BCD_DEVICE);
|
|
|
|
dev_info(&i2c->dev, "chiprev %x.%02x, driver " DRIVER_VERSION "\n",
|
|
|
|
status >> 8, status & 0xff);
|
|
|
|
|
|
|
|
/* make like power-on reset */
|
|
|
|
isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_MASK);
|
|
|
|
|
|
|
|
isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_BI_DI);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_2, ~MC2_BI_DI);
|
|
|
|
|
|
|
|
isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1,
|
|
|
|
OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1,
|
|
|
|
~(OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN));
|
|
|
|
|
|
|
|
isp1301_clear_bits(isp, ISP1301_INTERRUPT_LATCH, ~0);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_INTERRUPT_FALLING, ~0);
|
|
|
|
isp1301_clear_bits(isp, ISP1301_INTERRUPT_RISING, ~0);
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
status = otg_bind(isp);
|
|
|
|
if (status < 0) {
|
|
|
|
dev_dbg(&i2c->dev, "can't bind OTG\n");
|
|
|
|
goto fail2;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (machine_is_omap_h2()) {
|
|
|
|
/* full speed signaling by default */
|
|
|
|
isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1,
|
|
|
|
MC1_SPEED_REG);
|
|
|
|
isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2,
|
|
|
|
MC2_SPD_SUSP_CTRL);
|
|
|
|
|
|
|
|
/* IRQ wired at M14 */
|
|
|
|
omap_cfg_reg(M14_1510_GPIO2);
|
|
|
|
isp->irq = OMAP_GPIO_IRQ(2);
|
2007-08-14 18:37:14 +02:00
|
|
|
if (gpio_request(2, "isp1301") == 0)
|
|
|
|
gpio_direction_input(2);
|
|
|
|
isp->irq_type = IRQF_TRIGGER_FALLING;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2007-08-14 18:37:14 +02:00
|
|
|
isp->irq_type |= IRQF_SAMPLE_RANDOM;
|
2005-04-17 00:20:36 +02:00
|
|
|
status = request_irq(isp->irq, isp1301_irq,
|
2007-08-14 18:37:14 +02:00
|
|
|
isp->irq_type, DRIVER_NAME, isp);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (status < 0) {
|
|
|
|
dev_dbg(&i2c->dev, "can't get IRQ %d, err %d\n",
|
|
|
|
isp->irq, status);
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
fail2:
|
|
|
|
#endif
|
|
|
|
i2c_detach_client(i2c);
|
|
|
|
goto fail1;
|
|
|
|
}
|
|
|
|
|
|
|
|
isp->otg.dev = &isp->client.dev;
|
|
|
|
isp->otg.label = DRIVER_NAME;
|
|
|
|
|
|
|
|
isp->otg.set_host = isp1301_set_host,
|
|
|
|
isp->otg.set_peripheral = isp1301_set_peripheral,
|
|
|
|
isp->otg.set_power = isp1301_set_power,
|
|
|
|
isp->otg.start_srp = isp1301_start_srp,
|
|
|
|
isp->otg.start_hnp = isp1301_start_hnp,
|
|
|
|
|
|
|
|
enable_vbus_draw(isp, 0);
|
|
|
|
power_down(isp);
|
|
|
|
the_transceiver = isp;
|
|
|
|
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
|
|
update_otg1(isp, isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE));
|
|
|
|
update_otg2(isp, isp1301_get_u8(isp, ISP1301_OTG_STATUS));
|
|
|
|
#endif
|
|
|
|
|
2008-04-22 22:16:47 +02:00
|
|
|
dump_regs(isp, __func__);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#ifdef VERBOSE
|
|
|
|
mod_timer(&isp->timer, jiffies + TIMER_JIFFIES);
|
|
|
|
dev_dbg(&i2c->dev, "scheduled timer, %d min\n", TIMER_MINUTES);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
status = otg_set_transceiver(&isp->otg);
|
|
|
|
if (status < 0)
|
|
|
|
dev_err(&i2c->dev, "can't register transceiver, %d\n",
|
|
|
|
status);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int isp1301_scan_bus(struct i2c_adapter *bus)
|
|
|
|
{
|
|
|
|
if (!i2c_check_functionality(bus, I2C_FUNC_SMBUS_BYTE_DATA
|
|
|
|
| I2C_FUNC_SMBUS_READ_WORD_DATA))
|
|
|
|
return -EINVAL;
|
|
|
|
return i2c_probe(bus, &addr_data, isp1301_probe);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct i2c_driver isp1301_driver = {
|
2005-11-26 20:36:00 +01:00
|
|
|
.driver = {
|
|
|
|
.name = "isp1301_omap",
|
|
|
|
},
|
2005-04-17 00:20:36 +02:00
|
|
|
.attach_adapter = isp1301_scan_bus,
|
|
|
|
.detach_client = isp1301_detach_client,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static int __init isp_init(void)
|
|
|
|
{
|
|
|
|
return i2c_add_driver(&isp1301_driver);
|
|
|
|
}
|
|
|
|
module_init(isp_init);
|
|
|
|
|
|
|
|
static void __exit isp_exit(void)
|
|
|
|
{
|
|
|
|
if (the_transceiver)
|
|
|
|
otg_set_transceiver(0);
|
|
|
|
i2c_del_driver(&isp1301_driver);
|
|
|
|
}
|
|
|
|
module_exit(isp_exit);
|
|
|
|
|