?? isp1301_omap.c
字號:
/* * 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. */#undef DEBUG#undef VERBOSE#include <linux/config.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/interrupt.h>#include <linux/platform_device.h>#include <linux/usb_ch9.h>#include <linux/usb_gadget.h>#include <linux/usb.h>#include <linux/usb_otg.h>#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"#define DRIVER_NAME (isp1301_driver.name)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; u32 last_otg_ctrl; unsigned working:1; struct timer_list timer; /* use keventd context to change the state for us */ struct work_struct work; 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 */};/* bits in OTG_CTRL_REG */#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 */#include <asm/arch/gpio.h>#include <asm/arch/mux.h>#include <asm/mach-types.h>#if defined(CONFIG_TPS65010) || defined(CONFIG_TPS65010_MODULE)#include <asm/arch/tps65010.h>#elsestatic inline int tps65010_set_vbus_draw(unsigned mA){ pr_debug("tps65010: draw %d mA (STUB)\n", mA); return 0;}#endifstatic 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 0x2cstatic 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 u8isp1301_get_u8(struct isp1301 *isp, u8 reg){ return i2c_smbus_read_byte_data(&isp->client, reg + 0);}static inline intisp1301_get_u16(struct isp1301 *isp, u8 reg){ return i2c_smbus_read_word_data(&isp->client, reg);}static inline intisp1301_set_bits(struct isp1301 *isp, u8 reg, u8 bits){ return i2c_smbus_write_byte_data(&isp->client, reg + 0, bits);}static inline intisp1301_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 */# define MC1_SPEED_REG (1 << 0)# define MC1_SUSPEND_REG (1 << 1)# 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);}#ifdef VERBOSE#define dev_vdbg dev_dbg#else#define dev_vdbg(dev, fmt, arg...) do{}while(0)#endif/*-------------------------------------------------------------------------*//* 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); isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND_REG); 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); isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND_REG); /* 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_SUSPENDstatic 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. */voidisp1301_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){ 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; isp->last_otg_ctrl = OTG_CTRL_REG = OTG_CTRL_REG & OTG_XCEIV_OUTPUTS; pr_debug(" --> %s/%s\n", state_name(isp), tag);}/* called from irq handlers */static void b_idle(struct isp1301 *isp, const char *tag){ 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; isp->last_otg_ctrl = OTG_CTRL_REG = OTG_CTRL_REG & OTG_XCEIV_OUTPUTS; pr_debug(" --> %s/%s\n", state_name(isp), tag);}static voiddump_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", OTG_CTRL_REG, label, state_name(isp), 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 VERBOSEstatic void check_state(struct isp1301 *isp, const char *tag){ enum usb_otg_state state = OTG_STATE_UNDEFINED; u8 fsm = OTG_TEST_REG & 0x0ff; 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, state_string(state), fsm, state_name(isp), OTG_CTRL_REG);}#elsestatic 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; otg_ctrl = OTG_CTRL_REG & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS & ~(OTG_ID|OTG_ASESSVLD|OTG_VBUSVLD); 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; } } OTG_CTRL_REG = otg_ctrl;}/* outputs from ISP1301_OTG_STATUS */static void update_otg2(struct isp1301 *isp, u8 otg_status){ u32 otg_ctrl; otg_ctrl = OTG_CTRL_REG & OTG_CTRL_MASK
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -