📄 ohci-sl811.c
字号:
/* * SL811HS OHCI HCD (Host Controller Driver) for USB. * * linux/drivers/usb/host/ohci-sl811.c * * Copyright (C) 2004 by Lothar Wassmann <LW at KARO-electronics.de> * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * * SL811HS Bus Glue for PXA2xx * */#include <linux/device.h>#include <linux/timer.h>#include <asm/hardware.h>#include <asm/mach-types.h>#include <asm/arch/dma.h>#include <linux/kallsyms.h>#define CONFIG_ARCH_KARO //add by hzh#ifndef CONFIG_ARCH_KARO#error "This file is HC_SL811 bus glue. CONFIG_ARCH_KARO must be defined."#endif#include "ohci-sl811-emu.c"extern int usb_disabled(void);static int usb_hcd_sl811_remove(struct usb_hcd *hcd, struct hc_sl811_dev *dev);static inline void hc_sl811_set_drvdata(struct hc_sl811_dev *dev, struct usb_hcd *data){ dev_set_drvdata(&dev->dev, data);}static inline struct usb_hcd *hc_sl811_get_drvdata(struct hc_sl811_dev *dev){ struct usb_hcd *hcd; hcd = dev_get_drvdata(&dev->dev); return hcd;}static int __devinit sl811_irq_probe(struct hc_sl811_dev *dev){ int irq = NO_IRQ; unsigned long mask; hc_sl811_write_reg(dev, SL11H_INTENBLREG, 0); mask = probe_irq_on(); hc_sl811_write_reg(dev, SL11H_CTLREG2, SL11H_CTL2MASK_HOSTMODE); hc_sl811_write_reg(dev, SL11H_CTLREG1, SL11H_CTL1VAL_RESET); hc_sl811_write_reg(dev, SL11H_INTENBLREG, SL11H_INTMASK_RESUME); hc_sl811_read_reg(dev, SL11H_INTSTATREG); hc_sl811_write_reg(dev, SL11H_CTLREG1, 0); hc_sl811_write_reg(dev, SL11H_INTENBLREG, 0); irq = probe_irq_off(mask); return irq;}static int hc_sl811_chip_probe(struct hc_sl811_dev *dev){ int ret = 0; int i; u8 *tst_buf; unsigned long flags; sl811_set_hw_reset(); tst_buf = kmalloc(SL811_MAP_SIZE, GFP_KERNEL); if (tst_buf == NULL) { return -ENOMEM; } sl811_clr_hw_reset(); for (i = 0; i < SL811_BUF_SIZE; i++) { tst_buf[i] = ~i; } local_irq_save(flags); __hc_sl811_write_regs(dev, SL11H_DATA_START, tst_buf, SL811_BUF_SIZE); for (i = 0; i < SL811_BUF_SIZE; i++) { u8 val = __hc_sl811_read_reg(dev, SL11H_DATA_START + i); u8 ref = ~i; if (val != ref) { printk(KERN_ERR "%s: SL811 register test failed @ %02x: wr: %02x, rd %02x\n", __FUNCTION__, i, ref, val); ret = -ENODEV; goto out; } } memset(tst_buf, 0, SL811_MAP_SIZE); //__hc_sl811_write_regs(dev, SL11H_DATA_START, tst_buf, SL811_BUF_SIZE); __hc_sl811_write_regs(dev, 0, tst_buf, SL811_MAP_SIZE); dev->hw_rev = __hc_sl811_read_reg(dev, SL11H_HWREVREG) >> 4; if (dev->hw_rev == 0) { printk("%s: Bad HW rev.\n", __FUNCTION__); ret = -ENODEV; } out: local_irq_restore(flags); kfree(tst_buf); if (dev->irq == NO_IRQ) { dev->irq = sl811_irq_probe(dev); if (dev->irq == NO_IRQ) { ret = -ENODEV; } } sl811_set_hw_reset(); return ret;}static void sl811_start_hc(struct usb_hcd *hcd){ /* * Now, carefully enable the USB clock, and take * the USB host controller out of reset. */ struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); sl811_clr_hw_reset(); __hc_sl811_write_reg(dev, SL11H_INTENBLREG, 0); __hc_sl811_write_reg(dev, SL11H_INTSTATREG, 0xff); __hc_sl811_write_reg(dev, SL11H_CTLREG1, 0); __hc_sl811_write_reg(dev, SL11H_CTLREG2, SL11H_CTL2MASK_HOSTMODE);//add by hzh //__hc_sl811_write_reg(dev, SL11H_INTENBLREG, SL11H_INTMASK_INSRMV); hc_sl811_detect_device(hcd);}static void sl811_stop_hc(struct hc_sl811_dev *dev){ dev->dev_state = DEV_IDLE; del_timer(&dev->int_timer); /* * Put the USB host controller into reset. */ hc_sl811_write_reg(dev, SL11H_INTENBLREG, 0); sl811_set_hw_reset(); /* * Stop the USB clock. */ // No way to do it on KARO boards}/* * initialize the emulated OHCI registers */static void sl811_reset_hc(struct usb_hcd *hcd, int cold){ struct ohci_regs *regs = hcd->regs; struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); const u8 nr_ports = 1; const u8 pwr_on_delay = 100; ohci_dbg(hcd_to_ohci(hcd), "%s: Performing %s reset of HC SL811\n", __FUNCTION__, cold ? "cold" : "warm"); if (cold) { memset(regs, 0, sizeof(struct ohci_regs)); regs->revision = 0x10; // OHCI Rev. regs->roothub.a = RH_A_PSM | RH_A_NOCP | nr_ports | (pwr_on_delay << 24); regs->roothub.status = 0; regs->roothub.portstatus[0] = RH_PS_PPS | RH_PS_PES; regs->fminterval = DEFAULT_FMINTERVAL; regs->periodicstart = ((9 * FI) / 10) & 0x3fff; regs->lsthresh = LSTHRESH; } else { regs->ed_controlcurrent = 0; regs->ed_controlhead = 0; regs->ed_bulkcurrent = 0; regs->ed_bulkhead = 0; } HcControl = OHCI_USB_RESET; dev->intrdelay = 7;}static void hc_sl811_start_sof(struct usb_hcd *hcd, int connected){ struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); struct ohci_hcd *ohci = hcd_to_ohci(hcd); struct ohci_regs *regs = ohci->regs; u16 sof_ctr = (HcFmInterval & OHCI_FR) + 1; u8 ctrl2 = SL11H_CTL2MASK_HOSTMODE | (sof_ctr >> 8); u8 ctrl1 = SL11H_CTL1MASK_SOFENA; u8 int_mask = SL11H_INTMASK_INSRMV | SL11H_INTMASK_SOFINTR | SL11H_INTMASK_XFERDONE | SL11H_INTMASK_XFERDONE_B; unsigned long flags; BUG_ON(!ohci); local_irq_save(flags); switch (connected) { case 0: ohci_dbg(ohci, "%s: Disabling SOF\n", __FUNCTION__); __hc_sl811_write_reg(dev, SL11H_CTLREG1, 0); dev->dev_state &= ~(DEV_ACTIVE | DEV_SOF);#if 0 __hc_sl811_write_reg(dev, SL11H_CTLREG1, SL11H_CTL1MASK_SUSPEND); dev->dev_state |= DEV_SLEEP;#endif break; case 1: ctrl1 |= SL11H_CTL1MASK_NSPD; ctrl2 |= SL11H_CTL2MASK_DSWAP; // fall through case 2: ohci_dbg(ohci, "%s: Enabling SOF %d: %04x\n", __FUNCTION__, connected, sof_ctr);#if FLIP_BUFFERS > 1 int_mask |= SL11H_INTMASK_SOFINTR | SL11H_INTMASK_XFERDONE | SL11H_INTMASK_XFERDONE_B;#else int_mask |= SL11H_INTMASK_SOFINTR | SL11H_INTMASK_XFERDONE;#endif __hc_sl811_write_reg(dev, SL11H_BUFLNTHREG, 0); //zero lenth __hc_sl811_write_reg(dev, SL11H_PIDEPREG, 0x50); //send SOF to EP0 __hc_sl811_write_reg(dev, SL11H_DEVADDRREG, 0x01); //address0 __hc_sl811_write_reg(dev, SL11H_SOFLOWREG, (u8)sof_ctr); __hc_sl811_write_reg(dev, SL11H_CTLREG2, ctrl2); __hc_sl811_write_reg(dev, SL11H_CTLREG1, ctrl1); // Enable SOF __hc_sl811_write_reg(dev, SL11H_HOSTCTLREG, SL11H_HCTLMASK_ARM); } __hc_sl811_write_reg(dev, SL11H_INTENBLREG, int_mask); local_irq_restore(flags); ohci_sl811_update_portstatus(ohci, 0, 0, RH_PS_PRS);}static int hc_sl811_detect_device(struct usb_hcd *hcd){ int ret = 0; struct ohci_hcd *ohci = hcd_to_ohci(hcd); struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); u8 int_stat; u8 int_mask = SL11H_INTMASK_INSRMV; unsigned long flags; BUG_ON(!hcd); BUG_ON(!ohci); WARN_ON(irqs_disabled()); del_timer(&dev->int_timer); if (dev->dev_state & DEV_SLEEP) { ohci_info(ohci, "%s: WAKING up SL811 ctl1=%02x\n", __FUNCTION__, hc_sl811_read_reg(dev, SL11H_CTLREG1)); local_irq_save(flags); HC_SL811_WRITE_ADDR(dev, SL11H_CTLREG1); HC_SL811_WRITE_DATA(dev, SL11H_CTLREG1); __hc_sl811_write_reg(dev, SL11H_CTLREG1, 0); // get chip out of suspend mode local_irq_restore(flags); ohci_info(ohci, "%s: WOKE up SL811 ctl1=%02x\n", __FUNCTION__, hc_sl811_read_reg(dev, SL11H_CTLREG1)); } ohci_dbg(ohci, "%s: Enabling device detection; dev_state=%02x\n", __FUNCTION__, dev->dev_state); local_irq_save(flags); dev->dev_state &= ~(DEV_ACTIVE | DEV_CONNECTED | DEV_SOF); __hc_sl811_write_reg(dev, SL11H_INTENBLREG, SL11H_INTMASK_RESUME); local_irq_restore(flags); mdelay(10); local_irq_save(flags); __hc_sl811_write_reg(dev, SL11H_INTSTATREG, SL11H_INTMASK_RESUME | SL11H_INTMASK_INSRMV); int_stat = __hc_sl811_read_reg(dev, SL11H_INTSTATREG); __hc_sl811_write_reg(dev, SL11H_INTENBLREG, 0); ohci_dbg(ohci, "%s: Disabling device detection; dev_state=%02x, int_stat=%02x\n", __FUNCTION__, dev->dev_state, int_stat); local_irq_restore(flags); if (int_stat & SL11H_INTMASK_RESUME) { ohci_dbg(ohci, "%s: Device removed: %02x\n", __FUNCTION__, int_stat); ohci_sl811_update_portstatus(ohci, 0, 0, RH_PS_CCS | RH_PS_PES); } else { if (int_stat & SL11H_INTMASK_DSTATE) { ohci_dbg(ohci, "%s: Full speed device attached: %02x\n", __FUNCTION__, int_stat); ret = DEV_FULL_SPEED; ohci_sl811_update_portstatus(ohci, 0, RH_PS_CCS | RH_PS_PES, RH_PS_LSDA); } else { ohci_dbg(ohci, "%s: Low speed device attached: %02x\n", __FUNCTION__, int_stat); ret = DEV_LOW_SPEED; ohci_sl811_update_portstatus(ohci, 0, RH_PS_CCS | RH_PS_PES | RH_PS_LSDA, 0); } } local_irq_save(flags); __hc_sl811_write_reg(dev, SL11H_INTENBLREG, int_mask); tasklet_disable(&dev->usb_reset_bh); dev->dev_state = (dev->dev_state & ~DEV_CONNECTED) | DEV_ACTIVE | ret; tasklet_enable(&dev->usb_reset_bh); local_irq_restore(flags); return ret;}static void hc_sl811_usb_reset(struct usb_hcd *hcd, int assert){ struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); WARN_ON(!irqs_disabled()); del_timer(&dev->int_timer); __hc_sl811_write_reg(dev, SL11H_INTENBLREG, 0); if (assert) { __hc_sl811_write_reg(dev, SL11H_CTLREG1, SL11H_CTL1VAL_RESET); //set_current_state(TASK_UNINTERRUPTIBLE); //schedule_timeout(10); //__hc_sl811_write_reg(dev, SL11H_CTLREG1, 0); __hc_sl811_write_reg(dev, SL11H_INTSTATREG, 0xff); } if (assert < 0) { if (in_interrupt()) { mdelay(USB_RESET_WIDTH); } else { set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(msecs_to_jiffies(USB_RESET_WIDTH)); } } if (assert <= 0) { __hc_sl811_write_reg(dev, SL11H_INTSTATREG, 0xff); __hc_sl811_write_reg(dev, SL11H_CTLREG1, 0); __hc_sl811_write_reg(dev, SL11H_CTLREG2, SL11H_CTL2MASK_HOSTMODE); __hc_sl811_write_reg(dev, SL11H_INTENBLREG, SL11H_INTMASK_INSRMV); }}static void ohci_sl811_bh(unsigned long data){ struct usb_hcd *hcd = (struct usb_hcd *)data; struct ohci_hcd *ohci = hcd_to_ohci(hcd); struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); int handled; unsigned long flags; local_irq_save(flags); handled = dev->dev_state & DEV_TRIGGERED; dev->dev_state &= ~handled; local_irq_restore(flags); if (!handled) { ohci_warn(ohci, "%s: Spurious tasklet schedule: %02x:%02x!\n", __FUNCTION__, dev->dev_state, hc_sl811_read_reg(dev, SL11H_INTSTATREG)); } if (handled & DEV_CONN_CHK) { int connected; local_irq_save(flags); dev->dev_state |= DEV_CHECK; local_irq_restore(flags); ohci_dbg(ohci, "%s: Checking device connect status: %02x\n", __FUNCTION__, dev->dev_state); dev->dev_state &= ~(DEV_ACTIVE | DEV_CONNECTED | DEV_SOF); connected = hc_sl811_detect_device(hcd); hc_sl811_start_sof(hcd, connected); local_irq_save(flags); dev->dev_state &= ~DEV_CHECK; local_irq_restore(flags); ohci_dbg(ohci, "%s: Device detection completed: dev_state=%02x\n", __FUNCTION__, dev->dev_state); } if (handled & DEV_OHCI_IRQ) { struct pt_regs pt_regs; struct ohci_regs *regs = ohci->regs; if (HcIntrStatus & HcIntrEnable) { ohci_vdbg(ohci, "%s: Calling OHCI Interrupt: %08x:%08x #%04x\n", __FUNCTION__, HcIntrStatus, HcIntrEnable, HcFmNumber); if (usb_hcd_irq(hcd->irq, hcd, &pt_regs) != IRQ_HANDLED) { ohci_dbg(ohci, "%s: Spurious OHCI Interrupt: %08x:%08x #%04x\n", __FUNCTION__, HcIntrStatus, HcIntrEnable, HcFmNumber); } } }}static void int_timer(unsigned long data){ unsigned int oscr = OSCR; struct usb_hcd *hcd = (struct usb_hcd*)data; struct ohci_regs *regs = hcd->regs; struct ohci_hcd *ohci = hcd_to_ohci(hcd); struct ohci_hcca *hcca = ohci->hcca; struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); unsigned long flags; local_irq_save(flags); // windup the timer again in case we got here too early// if ((int)((oscr - dev->last_sof) - USEC_TO_OSCR(1100)) < 0) { if ((int)((oscr - dev->last_sof) - 4055) < 0) { //1100*3.6864 = 4055 mod_timer(&dev->int_timer, jiffies + msecs_to_jiffies(1)); goto out; } // Nothing to do here, if a bottom half run is still pending if (dev->dev_state & DEV_TRIGGERED) { goto out; } WARN_ON(dev->current_td); if (dev->current_td) { ohci_warn(ohci, "%s: Killing current_td %08x\n", __FUNCTION__, (u32)dev->current_td); kill_current_td(hcd, dev); } if ((HcIntrEnable & OHCI_INTR_SF) && !(HcIntrStatus & OHCI_INTR_SF)) { ohci_dbg(ohci, "%s: Faking OHCI_INTR_SF: %08x->%08x #%04x\n", __FUNCTION__, HcIntrStatus, HcIntrStatus | OHCI_INTR_SF, (u16)(HcFmNumber + 1)); HcIntrStatus |= OHCI_INTR_SF; HcFmNumber = (u16)(HcFmNumber + 1); if (hcca) { HCCAFrameNumber = HcFmNumber; if (HcDoneHead && !(HcIntrStatus & OHCI_INTR_WDH)) { int ohci_int = HcIntrStatus & HcIntrEnable; HCCADoneHead = HcDoneHead | (ohci_int ? cpu_to_le32(1) : 0); HcDoneHead = 0; HcIntrStatus |= OHCI_INTR_WDH; dev->intrdelay = 7; } } sl811_trigger_bh(dev, DEV_OHCI_IRQ); } else if (HcControl & (OHCI_CTRL_BLE | OHCI_CTRL_CLE | OHCI_CTRL_PLE)) { mod_timer(&dev->int_timer, jiffies + msecs_to_jiffies(1)); } out: local_irq_restore(flags);}/** * usb_hcd_sl811_probe - initialize HC-SL811-based HCDs * Context: !in_interrupt() * * Allocates basic resources for this USB host controller, and * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. * * Store this function in the HCD's struct pci_driver as probe(). */static int usb_hcd_sl811_probe(struct hc_driver *driver, struct usb_hcd **hcd_out, struct hc_sl811_dev *dev){ int retval; struct usb_hcd *hcd = NULL; struct device *parent = dev->dev.parent; const char *chip_names[] = { "SL11H", "SL811HS Rev 1.2", "SL811HS Rev 1.5", "Unknown"}; hcd = driver->hcd_alloc(); if (hcd == NULL) { dev_err(hcd->self.controller, "hcd_alloc failed\n"); retval = -ENOMEM; goto out; } hc_sl811_set_drvdata(dev, hcd); hcd->regs = kmalloc(sizeof(struct ohci_regs), GFP_KERNEL); if (hcd->regs == NULL) { retval = -ENOMEM; goto err1; } hcd->driver = driver; hcd->description = driver->description; hcd->self.bus_name = "sl811"; if (hcd->product_desc == NULL) { hcd->product_desc = "HC-SL811 OHCI"; } hcd->self.controller = parent; init_timer(&dev->int_timer); dev->int_timer.function = int_timer; dev->int_timer.data = (unsigned long)hcd; tasklet_init(&dev->usb_reset_bh, ohci_sl811_bh, (unsigned long)hcd); retval = hcd_buffer_create(hcd); if (retval != 0) { dev_err(hcd->self.controller, "pool alloc failed\n"); goto err2; } if (driver->reset && (retval = driver->reset(hcd)) < 0) { dev_err(hcd->self.controller, "can't reset\n"); goto err3; } hcd->state = USB_STATE_HALT; retval = request_irq(dev->irq, ohci_sl811_interrupt, SA_INTERRUPT, hcd->description, hcd); if (retval != 0) { dev_err(hcd->self.controller, "requesting irq %d failed\n", dev->irq); retval = -EBUSY; goto err3; } hcd->irq = dev->irq; set_irq_type(hcd->irq, IRQT_RISING); usb_bus_init(&hcd->self); hcd->self.op = &usb_hcd_operations; hcd->self.hcpriv = hcd; INIT_LIST_HEAD(&hcd->dev_list); usb_register_bus(&hcd->self); if ((retval = driver->start(hcd)) < 0) { dev_err(hcd->self.controller, "%s: Start failed: %d; Removing driver\n", __FUNCTION__, retval); usb_hcd_sl811_remove(hcd, dev); return retval; } info("%s (HC-%s) at 0x%p, irq %d\n", hcd->description, chip_names[dev->hw_rev & 0x0f], hcd->regs, hcd->irq); *hcd_out = hcd; return 0; err3: hcd_buffer_destroy(hcd); err2: kfree(hcd->regs);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -