📄 ohci-sl811-emu.c
字号:
/* * SL811HS OHCI HCD (Host Controller Driver) for USB. * * linux/drivers/usb/host/ohci-sl811-emu.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 OHCI emulation * */#include <asm/arch/dma.h>#define get_hw_fld(p,n) le32_to_cpu((p)->hw##n)#define set_hw_fld(p,n,v) (p)->hw##n = cpu_to_le32(v)#define get_hwinfo(p) get_hw_fld(p, INFO)#define set_hwinfo(p,v) set_hw_fld(p, INFO, v)#define ED_HEADP(ed) get_hw_fld(ed, HeadP)#define ED_TAILP(ed) get_hw_fld(ed, TailP)#define ED_HALTED(ed) (ED_HEADP(ed) & ED_H)#define EP_SKIP(info) ((info) & ED_SKIP)#define EP_ISO(info) ((info) & ED_ISO)#define TD_CC_SET_HW(td, cc) set_hwinfo(td, (get_hwinfo(td) & ~TD_CC) | (((cc) << 28) & TD_CC))#define TD_FC_GET(info) (((info) >> 24) & 7)#define TD_SF_GET(info) (((info) >> 0) & 0xffff)static u32 TD_BUF_LEN(struct td *td){ u32 len = 0; u32 cbp = get_hw_fld(td, CBP); if (cbp != 0) { u32 be = get_hw_fld(td, BE); if (unlikely((cbp ^ be) & PAGE_MASK)) { // CBP and BE in different pages // length till end of first page + length in second page len = (PAGE_SIZE - (cbp & ~PAGE_MASK)) + (be & ~PAGE_MASK) + 1; } else { len = be - cbp + 1; } } return len;}#define ED_MPS (((1 << 10) - 1) << 16)#define ed_pid(info) (((info) & (ED_IN | ED_OUT)) >> 11)#define td_pid(info) (((info) & TD_DP) >> 19)#define ed_mps(i) (((i) & ED_MPS) >> 16)#define ed_fa(info) ((info) & ((1 << 7) - 1))#define ed_en(info) (((info) >> 7) & ((1 << 4) - 1))static const u8 ed_td_to_pid[4][4] = { { PID_SETUP, PID_OUT, PID_IN, 0 }, // PID from TD { PID_OUT, PID_OUT, PID_OUT, PID_OUT }, // PID from ED { PID_IN, PID_IN, PID_IN, PID_IN }, // PID from ED { PID_SETUP, PID_OUT, PID_IN, 0 }, // PID from TD};static inline u32 DMA_XFER_SIZE(u32 addr, u32 len){ u32 count = len; if (unlikely(((addr & ~PAGE_MASK) + len) > PAGE_SIZE)) { count = PAGE_ALIGN(addr + len) - addr; } return count;}#define OHCI_FR ((1 << 14) - 1)#define OHCI_FRT (1 << 31)static u16 update_fm_remaining(struct ohci_hcd *ohci){ struct hc_sl811_dev *dev = ohci_to_sl811_dev(ohci); struct ohci_regs *regs = ohci->regs; u8 rem = __hc_sl811_read_reg(dev, SL11H_CTLREG2); u8 int_stat = __hc_sl811_read_reg(dev, SL11H_INTSTATREG); u16 fmremaining = (int_stat & SL11H_INTMASK_SOFINTR) ? 0 : rem << 6; //struct usb_hcd *hcd = ohci_to_hcd(ohci); HcFmRemaining = (HcFmRemaining & ~OHCI_FR) | fmremaining; return fmremaining;}static int check_bustime(struct ohci_hcd *ohci, struct xfer_buf *buf, u32 ed_flags){ unsigned int time; unsigned int available = update_fm_remaining(ohci); struct ohci_regs *regs = ohci->regs; struct usb_hcd *hcd = ohci_to_hcd(ohci); struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); if (ed_flags & ED_LOWSPEED) { if (available < HcLsThreshold) { return 0; } time = 8 * 8 * buf->len + 1024; } else { if (buf->hc & SL11H_HCTLMASK_PREAMBLE) { time = 8 * 8 * buf->len + 2048; } else { time = 8 * buf->len + 256; } } time += 2 * 10 * buf->len; return time < available;}static void kill_xfer_bufs(struct usb_hcd *hcd, struct hc_sl811_dev *dev, struct td *td){ int flip; for (flip = 0; flip < FLIP_BUFFERS; flip++) { if ((dev->buf_map & (1 << flip))) { struct xfer_buf *buf = &dev->xfer_buf[flip]; if (buf->td == td) { buf->td = NULL; } dev->buf_map &= ~(1 << flip); } }}static void sl811_dma_recv_data(struct hc_sl811_dev *dev, struct xfer_buf *buf, u8 offset){ struct usb_hcd *hcd = hc_sl811_dev_to_hcd(dev); if (buf->count == 0) { // nothing to to, if no transfer buffer given return; } BUG_ON(buf->count > buf->buf_len || buf->count > (SL811_BUF_SIZE / FLIP_BUFFERS)); BUG_ON(buf->buf_addr == NULL); __hc_sl811_read_regs(dev, offset, buf->buf_addr, buf->count); dma_sync_single_for_cpu(hcd->self.controller, get_hw_fld(buf->td, CBP), buf->count, DMA_TO_DEVICE);}static void sl811_dma_send_data(struct hc_sl811_dev *dev, struct xfer_buf *buf, u8 offset){ struct usb_hcd *hcd = hc_sl811_dev_to_hcd(dev); if (buf->len == 0) { // nothing to to, if no transfer buffer given return; } BUG_ON(buf->len > buf->buf_len || buf->len > (SL811_BUF_SIZE / FLIP_BUFFERS)); dma_sync_single_for_cpu(hcd->self.controller, get_hw_fld(buf->td, CBP), buf->len, DMA_FROM_DEVICE); __hc_sl811_write_regs(dev, offset, buf->buf_addr, buf->len);}static int start_xfer(struct usb_hcd *hcd, struct hc_sl811_dev *dev, int flip){ struct xfer_buf *buf = &dev->xfer_buf[flip]; struct td *td = dev->current_td; struct ohci_hcd *ohci = hcd_to_ohci(hcd); int reg_offs = flip * 8; int buf_offs = flip * (SL811_BUF_SIZE / FLIP_BUFFERS); BUG_ON(flip >= FLIP_BUFFERS); BUG_ON(dev->num_bufs >= FLIP_BUFFERS); BUG_ON(dev->current_td == NULL); BUG_ON((dev->buf_map & (1 << flip))); BUG_ON(td->ed == NULL); if (!check_bustime(ohci, buf, get_hwinfo(td->ed))) { dev->current_td = NULL; return 0; } BUG_ON(td == NULL); BUG_ON(buf_offs + buf->len > SL811_BUF_SIZE); BUG_ON(buf->len > (SL811_BUF_SIZE / FLIP_BUFFERS)); if (buf->hc & SL11H_HCTLMASK_WRITE && buf->len > 0) { // transfer data from TD buffer to SL811 buffer memory BUG_ON(buf->buf_addr == NULL); ohci_vdbg(hcd_to_ohci(hcd), "%s: Transferring %d of %d byte from %08x to %02x\n", __FUNCTION__, buf->len, buf->buf_len, (u32)buf->buf_addr, SL11H_DATA_START + buf_offs); sl811_dma_send_data(dev, buf, SL11H_DATA_START + buf_offs); } __hc_sl811_write_reg(dev, SL11H_BUFADDRREG + reg_offs, SL11H_DATA_START + buf_offs); __hc_sl811_write_regs(dev, SL11H_BUFLNTHREG + reg_offs, &buf->send_data, sizeof(buf->send_data)); __hc_sl811_write_reg(dev, SL11H_HOSTCTLREG + reg_offs, buf->hc); dev->buf_map |= (1 << flip); dev->num_bufs++; BUG_ON(dev->num_bufs > FLIP_BUFFERS); dev->flip = (dev->flip << 1) | flip; return 1;}static int process_td(struct ohci_hcd *ohci, struct td *td, int flip){ struct usb_hcd *hcd = ohci_to_hcd(ohci); struct ohci_regs *regs = ohci->regs; struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); struct ed *ed = td->ed; u8 pid; struct xfer_buf *buf; u32 ed_flags = get_hwinfo(ed); u32 td_flags = get_hwinfo(td); u8 hc_flags = SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENBLEP; u8 dev_addr; u32 max_packet_size = ed_mps(ed_flags); int len; if (!(dev->dev_state & DEV_SOF)) { ohci_info(ohci, "%s: Retiring TD because device disconnected\n", __FUNCTION__); retire_td(hcd, td, ed, TD_DEVNOTRESP); } if (dev->num_bufs == FLIP_BUFFERS) { return -EBUSY; } if (dev->current_td == NULL) { dev->current_td = td; if (td->hwCBP != 0) { dev->buf_addr = (void*)dma_to_virt(hcd->self.controller, get_hw_fld(td, CBP)); dev->buf_len = TD_BUF_LEN(td); } else { dev->buf_addr = NULL; dev->buf_len = 0; } } pid = ed_td_to_pid[ed_pid(ed_flags)][td_pid(td_flags)]; len = max_packet_size; if (pid != PID_IN) { hc_flags |= SL11H_HCTLMASK_WRITE; } if (pid != PID_SETUP) { if ((td_flags & TD_T) == TD_T_DATA1) { hc_flags |= SL11H_HCTLMASK_SEQ; } else if ((td_flags & TD_T) != TD_T_DATA0) { hc_flags |= (ED_HEADP(ed) & ED_C) ? SL11H_HCTLMASK_SEQ : 0; } } if (ed_flags & ED_ISO) { hc_flags |= SL11H_HCTLMASK_ISOCH; } if (ed_flags & ED_LOWSPEED) { if (!(HcRhPortStatus[0] & RH_PS_LSDA)) { hc_flags |= SL11H_HCTLMASK_PREAMBLE; } } pid = (pid << 4) | ed_en(ed_flags); dev_addr = ed_fa(ed_flags); BUG_ON(dev->buf_map & (1 << flip)); buf = &dev->xfer_buf[flip]; buf->td = td; if (len > dev->buf_len) { len = dev->buf_len; } buf->buf_addr = dev->buf_addr; buf->buf_len = dev->buf_len; buf->hc = hc_flags; buf->pid_ep = pid; buf->dev_addr = dev_addr; buf->len = len; BUG_ON(len > (SL811_BUF_SIZE / FLIP_BUFFERS)); if (start_xfer(hcd, dev, flip)) { if (dev->buf_len > len) { dev->buf_addr += len; dev->buf_len -= len; } else { dev->buf_len = 0; dev->buf_addr = NULL; } } return 1;}static struct td *find_td(struct ohci_hcd *ohci, struct hc_sl811_dev *dev, struct ed *ed){ struct td *td; td = dma_to_td(ohci, ED_HEADP(ed) & TD_MASK); if ((td == NULL) || (ED_TAILP(ed) == td->td_dma)) { return NULL; } return td;}/* * process_ed_list * * generic ED list processing routine * * Return codes: 1 TD found * 0 no TD needed processing * <0 Error */static struct ed *next_ed(struct usb_hcd *hcd, u32 *head_reg){ u32 ed_head = le32_to_cpup(head_reg); struct ed *ed; if (ed_head == 0) { return NULL; } ed = (struct ed*)dma_to_virt(hcd->self-controller, ed_head); dma_sync_single_for_cpu(hcd->self.controller, ed_head, sizeof(struct ed), DMA_FROM_DEVICE); *head_reg = ed->hwNextED; return ed;}static int process_ed_list(struct ohci_hcd *ohci, u32 *head_reg, int flip){ int ret = 0; int done = 0; struct usb_hcd *hcd = ohci_to_hcd(ohci); struct ohci_regs *regs = ohci->regs; struct hc_sl811_dev *dev = ohci_to_sl811_dev(ohci); struct ed *ed; // walk ED list and find TD that needs processing while (!done && ((ed = next_ed(hcd, head_reg)) != NULL)) { struct td *td = NULL; u32 ed_flags = get_hwinfo(ed); u16 rem = update_fm_remaining(ohci); if (!EP_SKIP(ed_flags) && !ED_HALTED(ed) && !(EP_ISO(ed_flags) && !(HcControl & OHCI_CTRL_IE))) { td = find_td(ohci, dev, ed); } if ((td && (ed_flags & ED_LOWSPEED) && (rem < HcLsThreshold)) || ((td == dev->current_td) && (dev->buf_len == 0))) { td = NULL; ret = 1; } if (td != NULL) { done = process_td(ohci, td, flip); ret = done; } } return ret;}/* * process_ctrl_bulk_lists * * try to find a TD that needs processing in the ctrl or bulk ED list * according to cbsr and cbc * * Return codes: 1 TD found * 0 no TD needed processing * <0 Error * */static int process_ctrl_bulk_lists(struct ohci_hcd *ohci, int flip){ int ret = 0; struct hc_sl811_dev *dev = ohci_to_sl811_dev(ohci); //struct usb_hcd *hcd = ohci_to_hcd(ohci); struct ohci_regs *regs = ohci->regs; int cbsr = HcControl & OHCI_CTRL_CBSR; // ctrl/bulk ratio int bulk_ena = (HcControl & OHCI_CTRL_BLE) && (HcBulkCurrentED != 0); // reset ctrl/bulk ratio counter, if bulk processing disabled or // no bulk descriptors to process if (!bulk_ena) { dev->cbc = 0; } // process ctrl EDs until cbsr limit reached if ((HcControl & OHCI_CTRL_CLE) && (dev->cbc <= cbsr)) { if ((HcControlCurrentED == 0) && (HcCmdStatus & OHCI_CLF)) { HcControlCurrentED = HcControlHeadED; HcCmdStatus &= ~OHCI_CLF; } if (HcControlCurrentED != 0) { ret = process_ed_list(ohci, &HcControlCurrentED, flip); if (ret > 0) { dev->cbc++; HcCmdStatus |= OHCI_CLF; return ret; } else if (ret != 0) { return ret; } } } if (HcControl & OHCI_CTRL_BLE) { if ((HcBulkCurrentED == 0) && (HcCmdStatus & OHCI_BLF)) { HcBulkCurrentED = HcBulkHeadED; HcCmdStatus &= ~OHCI_BLF; } if (HcBulkCurrentED != 0) { ret = process_ed_list(ohci, &HcBulkCurrentED, flip); if (ret > 0) { HcCmdStatus |= OHCI_BLF; dev->cbc = 0; } else if (ret != 0) { return ret; } } } return ret;}/* * process_ed_lists * * try to find a TD that needs processing in the appropriate ED list * depending on frame number and HcFmRemaining... * * Return codes: 1 TD found and processed * 0 no TD needed processing * <0 Error * */#define OHCI_PS ((1 << 14) - 1)static int process_ed_lists(struct ohci_hcd *ohci, int flip){ int ret = 0; struct ohci_regs *regs = ohci->regs; u16 periodicstart = HcPeriodicStart & OHCI_PS; // limit for ctrl/bulk processing u16 fmremaining = update_fm_remaining(ohci); /* * process cbsr control desc. * process bulk desc. */ // do control/bulk processing until periodicstart reached if (fmremaining > periodicstart) { ret = process_ctrl_bulk_lists(ohci, flip); if (ret != 0) { return ret; } fmremaining = update_fm_remaining(ohci); } // do interrupt/iso processing if ((HcControl & OHCI_CTRL_PLE) && (HcPeriodCurrentED != 0)) { ret = process_ed_list(ohci, &HcPeriodCurrentED, flip); if (ret != 0) { return ret; } fmremaining = update_fm_remaining(ohci); } if (fmremaining > 0) { // do control/bulk processing again until end of frame ret = process_ctrl_bulk_lists(ohci, flip); if (ret != 0) { return ret; } } return ret;}static u32 retire_td(struct usb_hcd *hcd, struct td *td, struct ed *ed, u32 cc){ u32 ohci_int_status = 0; struct hc_sl811_dev *dev = hcd_to_sl811_dev(hcd); struct ohci_hcd *ohci = hcd_to_ohci(hcd); struct ohci_regs *regs = ohci->regs; u32 ed_flags = get_hwinfo(ed); u32 td_flags = get_hwinfo(td); u32 td_dma = td->td_dma; u32 int_delay = (cc == TD_CC_NOERROR) ? (td_flags & TD_DI) >> 21 : 0; u32 ed_halt = 0; BUG_ON(TD_CC_GET(td_flags) != TD_NOTACCESSED); if (dev->current_td == td) { dev->current_td = NULL; } ohci_dbg(ohci, "%s: Retiring TD %08x of ED %08x\n", __FUNCTION__, (u32)td, (u32)ed); if (dev->num_bufs > 1) { WARN_ON(1); kill_xfer_bufs(hcd, dev, td); } TD_CC_SET_HW(td, cc); if (!(ed_flags & ED_ISO)) { ed_halt = (cc != TD_CC_NOERROR) ? cpu_to_le32(ED_H) : 0; if (ed_halt) { ohci_dbg(ohci, "%s: ED %08x (TD %08x) Halted due to error: %d\n", __FUNCTION__, (u32)ed, (u32)td, cc); } ed_halt |= ((td_flags & TD_T) == TD_T_DATA1) ? cpu_to_le32(ED_C) : 0; } ed->hwHeadP = td->hwNextTD | ed_halt; td->hwNextTD = HcDoneHead; HcDoneHead = td_dma; if ((int_delay != 7) && (int_delay < dev->intrdelay)) { dev->intrdelay = int_delay; } return ohci_int_status;}static void update_data_toggle(struct usb_hcd *hcd, struct td *td){ u32 td_flags = get_hwinfo(td); u32 ed_carry = ED_HEADP(td->ed) & ED_C; if ((td_flags & TD_T) == TD_T_TOGGLE) { td_flags |= (ed_carry ? TD_T_DATA0 : TD_T_DATA1);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -