162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * SL811HS HCD (Host Controller Driver) for USB. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2004 Psion Teklogix (for NetBook PRO) 662306a36Sopenharmony_ci * Copyright (C) 2004-2005 David Brownell 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Periodic scheduling is based on Roman's OHCI code 962306a36Sopenharmony_ci * Copyright (C) 1999 Roman Weissgaerber 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * The SL811HS controller handles host side USB (like the SL11H, but with 1262306a36Sopenharmony_ci * another register set and SOF generation) as well as peripheral side USB 1362306a36Sopenharmony_ci * (like the SL811S). This driver version doesn't implement the Gadget API 1462306a36Sopenharmony_ci * for the peripheral role; or OTG (that'd need much external circuitry). 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * For documentation, see the SL811HS spec and the "SL811HS Embedded Host" 1762306a36Sopenharmony_ci * document (providing significant pieces missing from that spec); plus 1862306a36Sopenharmony_ci * the SL811S spec if you want peripheral side info. 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* 2262306a36Sopenharmony_ci * Status: Passed basic stress testing, works with hubs, mice, keyboards, 2362306a36Sopenharmony_ci * and usb-storage. 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * TODO: 2662306a36Sopenharmony_ci * - usb suspend/resume triggered by sl811 2762306a36Sopenharmony_ci * - various issues noted in the code 2862306a36Sopenharmony_ci * - performance work; use both register banks; ... 2962306a36Sopenharmony_ci * - use urb->iso_frame_desc[] with ISO transfers 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#undef VERBOSE 3362306a36Sopenharmony_ci#undef PACKET_TRACE 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include <linux/module.h> 3662306a36Sopenharmony_ci#include <linux/moduleparam.h> 3762306a36Sopenharmony_ci#include <linux/kernel.h> 3862306a36Sopenharmony_ci#include <linux/delay.h> 3962306a36Sopenharmony_ci#include <linux/ioport.h> 4062306a36Sopenharmony_ci#include <linux/sched.h> 4162306a36Sopenharmony_ci#include <linux/slab.h> 4262306a36Sopenharmony_ci#include <linux/errno.h> 4362306a36Sopenharmony_ci#include <linux/timer.h> 4462306a36Sopenharmony_ci#include <linux/list.h> 4562306a36Sopenharmony_ci#include <linux/interrupt.h> 4662306a36Sopenharmony_ci#include <linux/usb.h> 4762306a36Sopenharmony_ci#include <linux/usb/sl811.h> 4862306a36Sopenharmony_ci#include <linux/usb/hcd.h> 4962306a36Sopenharmony_ci#include <linux/platform_device.h> 5062306a36Sopenharmony_ci#include <linux/prefetch.h> 5162306a36Sopenharmony_ci#include <linux/debugfs.h> 5262306a36Sopenharmony_ci#include <linux/seq_file.h> 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci#include <asm/io.h> 5562306a36Sopenharmony_ci#include <asm/irq.h> 5662306a36Sopenharmony_ci#include <asm/byteorder.h> 5762306a36Sopenharmony_ci#include <asm/unaligned.h> 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci#include "sl811.h" 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ciMODULE_DESCRIPTION("SL811HS USB Host Controller Driver"); 6362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 6462306a36Sopenharmony_ciMODULE_ALIAS("platform:sl811-hcd"); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci#define DRIVER_VERSION "19 May 2005" 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* for now, use only one transfer register bank */ 6962306a36Sopenharmony_ci#undef USE_B 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci// #define QUIRK2 7262306a36Sopenharmony_ci#define QUIRK3 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic const char hcd_name[] = "sl811-hcd"; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic void port_power(struct sl811 *sl811, int is_on) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci struct usb_hcd *hcd = sl811_to_hcd(sl811); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci /* hub is inactive unless the port is powered */ 8362306a36Sopenharmony_ci if (is_on) { 8462306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_POWER) 8562306a36Sopenharmony_ci return; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci sl811->port1 = USB_PORT_STAT_POWER; 8862306a36Sopenharmony_ci sl811->irq_enable = SL11H_INTMASK_INSRMV; 8962306a36Sopenharmony_ci } else { 9062306a36Sopenharmony_ci sl811->port1 = 0; 9162306a36Sopenharmony_ci sl811->irq_enable = 0; 9262306a36Sopenharmony_ci hcd->state = HC_STATE_HALT; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci sl811->ctrl1 = 0; 9562306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, 0); 9662306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_STATUS, ~0); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci if (sl811->board && sl811->board->port_power) { 9962306a36Sopenharmony_ci /* switch VBUS, at 500mA unless hub power budget gets set */ 10062306a36Sopenharmony_ci dev_dbg(hcd->self.controller, "power %s\n", 10162306a36Sopenharmony_ci is_on ? "on" : "off"); 10262306a36Sopenharmony_ci sl811->board->port_power(hcd->self.controller, is_on); 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci /* reset as thoroughly as we can */ 10662306a36Sopenharmony_ci if (sl811->board && sl811->board->reset) 10762306a36Sopenharmony_ci sl811->board->reset(hcd->self.controller); 10862306a36Sopenharmony_ci else { 10962306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, SL11H_CTL1MASK_SE0); 11062306a36Sopenharmony_ci mdelay(20); 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, 0); 11462306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); 11562306a36Sopenharmony_ci sl811_write(sl811, SL811HS_CTLREG2, SL811HS_CTL2_INIT); 11662306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci // if !is_on, put into lowpower mode now 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/* This is a PIO-only HCD. Queueing appends URBs to the endpoint's queue, 12462306a36Sopenharmony_ci * and may start I/O. Endpoint queues are scanned during completion irq 12562306a36Sopenharmony_ci * handlers (one per packet: ACK, NAK, faults, etc) and urb cancellation. 12662306a36Sopenharmony_ci * 12762306a36Sopenharmony_ci * Using an external DMA engine to copy a packet at a time could work, 12862306a36Sopenharmony_ci * though setup/teardown costs may be too big to make it worthwhile. 12962306a36Sopenharmony_ci */ 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/* SETUP starts a new control request. Devices are not allowed to 13262306a36Sopenharmony_ci * STALL or NAK these; they must cancel any pending control requests. 13362306a36Sopenharmony_ci */ 13462306a36Sopenharmony_cistatic void setup_packet( 13562306a36Sopenharmony_ci struct sl811 *sl811, 13662306a36Sopenharmony_ci struct sl811h_ep *ep, 13762306a36Sopenharmony_ci struct urb *urb, 13862306a36Sopenharmony_ci u8 bank, 13962306a36Sopenharmony_ci u8 control 14062306a36Sopenharmony_ci) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci u8 addr; 14362306a36Sopenharmony_ci u8 len; 14462306a36Sopenharmony_ci void __iomem *data_reg; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci addr = SL811HS_PACKET_BUF(bank == 0); 14762306a36Sopenharmony_ci len = sizeof(struct usb_ctrlrequest); 14862306a36Sopenharmony_ci data_reg = sl811->data_reg; 14962306a36Sopenharmony_ci sl811_write_buf(sl811, addr, urb->setup_packet, len); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* autoincrementing */ 15262306a36Sopenharmony_ci sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); 15362306a36Sopenharmony_ci writeb(len, data_reg); 15462306a36Sopenharmony_ci writeb(SL_SETUP /* | ep->epnum */, data_reg); 15562306a36Sopenharmony_ci writeb(usb_pipedevice(urb->pipe), data_reg); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* always OUT/data0 */ 15862306a36Sopenharmony_ci sl811_write(sl811, bank + SL11H_HOSTCTLREG, 15962306a36Sopenharmony_ci control | SL11H_HCTLMASK_OUT); 16062306a36Sopenharmony_ci ep->length = 0; 16162306a36Sopenharmony_ci PACKET("SETUP qh%p\n", ep); 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci/* STATUS finishes control requests, often after IN or OUT data packets */ 16562306a36Sopenharmony_cistatic void status_packet( 16662306a36Sopenharmony_ci struct sl811 *sl811, 16762306a36Sopenharmony_ci struct sl811h_ep *ep, 16862306a36Sopenharmony_ci struct urb *urb, 16962306a36Sopenharmony_ci u8 bank, 17062306a36Sopenharmony_ci u8 control 17162306a36Sopenharmony_ci) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci int do_out; 17462306a36Sopenharmony_ci void __iomem *data_reg; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci do_out = urb->transfer_buffer_length && usb_pipein(urb->pipe); 17762306a36Sopenharmony_ci data_reg = sl811->data_reg; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* autoincrementing */ 18062306a36Sopenharmony_ci sl811_write(sl811, bank + SL11H_BUFADDRREG, 0); 18162306a36Sopenharmony_ci writeb(0, data_reg); 18262306a36Sopenharmony_ci writeb((do_out ? SL_OUT : SL_IN) /* | ep->epnum */, data_reg); 18362306a36Sopenharmony_ci writeb(usb_pipedevice(urb->pipe), data_reg); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* always data1; sometimes IN */ 18662306a36Sopenharmony_ci control |= SL11H_HCTLMASK_TOGGLE; 18762306a36Sopenharmony_ci if (do_out) 18862306a36Sopenharmony_ci control |= SL11H_HCTLMASK_OUT; 18962306a36Sopenharmony_ci sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); 19062306a36Sopenharmony_ci ep->length = 0; 19162306a36Sopenharmony_ci PACKET("STATUS%s/%s qh%p\n", ep->nak_count ? "/retry" : "", 19262306a36Sopenharmony_ci do_out ? "out" : "in", ep); 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci/* IN packets can be used with any type of endpoint. here we just 19662306a36Sopenharmony_ci * start the transfer, data from the peripheral may arrive later. 19762306a36Sopenharmony_ci * urb->iso_frame_desc is currently ignored here... 19862306a36Sopenharmony_ci */ 19962306a36Sopenharmony_cistatic void in_packet( 20062306a36Sopenharmony_ci struct sl811 *sl811, 20162306a36Sopenharmony_ci struct sl811h_ep *ep, 20262306a36Sopenharmony_ci struct urb *urb, 20362306a36Sopenharmony_ci u8 bank, 20462306a36Sopenharmony_ci u8 control 20562306a36Sopenharmony_ci) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci u8 addr; 20862306a36Sopenharmony_ci u8 len; 20962306a36Sopenharmony_ci void __iomem *data_reg; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci /* avoid losing data on overflow */ 21262306a36Sopenharmony_ci len = ep->maxpacket; 21362306a36Sopenharmony_ci addr = SL811HS_PACKET_BUF(bank == 0); 21462306a36Sopenharmony_ci if (!(control & SL11H_HCTLMASK_ISOCH) 21562306a36Sopenharmony_ci && usb_gettoggle(urb->dev, ep->epnum, 0)) 21662306a36Sopenharmony_ci control |= SL11H_HCTLMASK_TOGGLE; 21762306a36Sopenharmony_ci data_reg = sl811->data_reg; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* autoincrementing */ 22062306a36Sopenharmony_ci sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); 22162306a36Sopenharmony_ci writeb(len, data_reg); 22262306a36Sopenharmony_ci writeb(SL_IN | ep->epnum, data_reg); 22362306a36Sopenharmony_ci writeb(usb_pipedevice(urb->pipe), data_reg); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); 22662306a36Sopenharmony_ci ep->length = min_t(u32, len, 22762306a36Sopenharmony_ci urb->transfer_buffer_length - urb->actual_length); 22862306a36Sopenharmony_ci PACKET("IN%s/%d qh%p len%d\n", ep->nak_count ? "/retry" : "", 22962306a36Sopenharmony_ci !!usb_gettoggle(urb->dev, ep->epnum, 0), ep, len); 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci/* OUT packets can be used with any type of endpoint. 23362306a36Sopenharmony_ci * urb->iso_frame_desc is currently ignored here... 23462306a36Sopenharmony_ci */ 23562306a36Sopenharmony_cistatic void out_packet( 23662306a36Sopenharmony_ci struct sl811 *sl811, 23762306a36Sopenharmony_ci struct sl811h_ep *ep, 23862306a36Sopenharmony_ci struct urb *urb, 23962306a36Sopenharmony_ci u8 bank, 24062306a36Sopenharmony_ci u8 control 24162306a36Sopenharmony_ci) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci void *buf; 24462306a36Sopenharmony_ci u8 addr; 24562306a36Sopenharmony_ci u8 len; 24662306a36Sopenharmony_ci void __iomem *data_reg; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci buf = urb->transfer_buffer + urb->actual_length; 24962306a36Sopenharmony_ci prefetch(buf); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci len = min_t(u32, ep->maxpacket, 25262306a36Sopenharmony_ci urb->transfer_buffer_length - urb->actual_length); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (!(control & SL11H_HCTLMASK_ISOCH) 25562306a36Sopenharmony_ci && usb_gettoggle(urb->dev, ep->epnum, 1)) 25662306a36Sopenharmony_ci control |= SL11H_HCTLMASK_TOGGLE; 25762306a36Sopenharmony_ci addr = SL811HS_PACKET_BUF(bank == 0); 25862306a36Sopenharmony_ci data_reg = sl811->data_reg; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci sl811_write_buf(sl811, addr, buf, len); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci /* autoincrementing */ 26362306a36Sopenharmony_ci sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); 26462306a36Sopenharmony_ci writeb(len, data_reg); 26562306a36Sopenharmony_ci writeb(SL_OUT | ep->epnum, data_reg); 26662306a36Sopenharmony_ci writeb(usb_pipedevice(urb->pipe), data_reg); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci sl811_write(sl811, bank + SL11H_HOSTCTLREG, 26962306a36Sopenharmony_ci control | SL11H_HCTLMASK_OUT); 27062306a36Sopenharmony_ci ep->length = len; 27162306a36Sopenharmony_ci PACKET("OUT%s/%d qh%p len%d\n", ep->nak_count ? "/retry" : "", 27262306a36Sopenharmony_ci !!usb_gettoggle(urb->dev, ep->epnum, 1), ep, len); 27362306a36Sopenharmony_ci} 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci/* caller updates on-chip enables later */ 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic inline void sofirq_on(struct sl811 *sl811) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci if (sl811->irq_enable & SL11H_INTMASK_SOFINTR) 28262306a36Sopenharmony_ci return; 28362306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, "sof irq on\n"); 28462306a36Sopenharmony_ci sl811->irq_enable |= SL11H_INTMASK_SOFINTR; 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic inline void sofirq_off(struct sl811 *sl811) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci if (!(sl811->irq_enable & SL11H_INTMASK_SOFINTR)) 29062306a36Sopenharmony_ci return; 29162306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, "sof irq off\n"); 29262306a36Sopenharmony_ci sl811->irq_enable &= ~SL11H_INTMASK_SOFINTR; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci/* pick the next endpoint for a transaction, and issue it. 29862306a36Sopenharmony_ci * frames start with periodic transfers (after whatever is pending 29962306a36Sopenharmony_ci * from the previous frame), and the rest of the time is async 30062306a36Sopenharmony_ci * transfers, scheduled round-robin. 30162306a36Sopenharmony_ci */ 30262306a36Sopenharmony_cistatic struct sl811h_ep *start(struct sl811 *sl811, u8 bank) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci struct sl811h_ep *ep; 30562306a36Sopenharmony_ci struct urb *urb; 30662306a36Sopenharmony_ci int fclock; 30762306a36Sopenharmony_ci u8 control; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci /* use endpoint at schedule head */ 31062306a36Sopenharmony_ci if (sl811->next_periodic) { 31162306a36Sopenharmony_ci ep = sl811->next_periodic; 31262306a36Sopenharmony_ci sl811->next_periodic = ep->next; 31362306a36Sopenharmony_ci } else { 31462306a36Sopenharmony_ci if (sl811->next_async) 31562306a36Sopenharmony_ci ep = sl811->next_async; 31662306a36Sopenharmony_ci else if (!list_empty(&sl811->async)) 31762306a36Sopenharmony_ci ep = container_of(sl811->async.next, 31862306a36Sopenharmony_ci struct sl811h_ep, schedule); 31962306a36Sopenharmony_ci else { 32062306a36Sopenharmony_ci /* could set up the first fullspeed periodic 32162306a36Sopenharmony_ci * transfer for the next frame ... 32262306a36Sopenharmony_ci */ 32362306a36Sopenharmony_ci return NULL; 32462306a36Sopenharmony_ci } 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci#ifdef USE_B 32762306a36Sopenharmony_ci if ((bank && sl811->active_b == ep) || sl811->active_a == ep) 32862306a36Sopenharmony_ci return NULL; 32962306a36Sopenharmony_ci#endif 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci if (ep->schedule.next == &sl811->async) 33262306a36Sopenharmony_ci sl811->next_async = NULL; 33362306a36Sopenharmony_ci else 33462306a36Sopenharmony_ci sl811->next_async = container_of(ep->schedule.next, 33562306a36Sopenharmony_ci struct sl811h_ep, schedule); 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci if (unlikely(list_empty(&ep->hep->urb_list))) { 33962306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, 34062306a36Sopenharmony_ci "empty %p queue?\n", ep); 34162306a36Sopenharmony_ci return NULL; 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci urb = container_of(ep->hep->urb_list.next, struct urb, urb_list); 34562306a36Sopenharmony_ci control = ep->defctrl; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci /* if this frame doesn't have enough time left to transfer this 34862306a36Sopenharmony_ci * packet, wait till the next frame. too-simple algorithm... 34962306a36Sopenharmony_ci */ 35062306a36Sopenharmony_ci fclock = sl811_read(sl811, SL11H_SOFTMRREG) << 6; 35162306a36Sopenharmony_ci fclock -= 100; /* setup takes not much time */ 35262306a36Sopenharmony_ci if (urb->dev->speed == USB_SPEED_LOW) { 35362306a36Sopenharmony_ci if (control & SL11H_HCTLMASK_PREAMBLE) { 35462306a36Sopenharmony_ci /* also note erratum 1: some hubs won't work */ 35562306a36Sopenharmony_ci fclock -= 800; 35662306a36Sopenharmony_ci } 35762306a36Sopenharmony_ci fclock -= ep->maxpacket << 8; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci /* erratum 2: AFTERSOF only works for fullspeed */ 36062306a36Sopenharmony_ci if (fclock < 0) { 36162306a36Sopenharmony_ci if (ep->period) 36262306a36Sopenharmony_ci sl811->stat_overrun++; 36362306a36Sopenharmony_ci sofirq_on(sl811); 36462306a36Sopenharmony_ci return NULL; 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci } else { 36762306a36Sopenharmony_ci fclock -= 12000 / 19; /* 19 64byte packets/msec */ 36862306a36Sopenharmony_ci if (fclock < 0) { 36962306a36Sopenharmony_ci if (ep->period) 37062306a36Sopenharmony_ci sl811->stat_overrun++; 37162306a36Sopenharmony_ci control |= SL11H_HCTLMASK_AFTERSOF; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci /* throttle bulk/control irq noise */ 37462306a36Sopenharmony_ci } else if (ep->nak_count) 37562306a36Sopenharmony_ci control |= SL11H_HCTLMASK_AFTERSOF; 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci switch (ep->nextpid) { 38062306a36Sopenharmony_ci case USB_PID_IN: 38162306a36Sopenharmony_ci in_packet(sl811, ep, urb, bank, control); 38262306a36Sopenharmony_ci break; 38362306a36Sopenharmony_ci case USB_PID_OUT: 38462306a36Sopenharmony_ci out_packet(sl811, ep, urb, bank, control); 38562306a36Sopenharmony_ci break; 38662306a36Sopenharmony_ci case USB_PID_SETUP: 38762306a36Sopenharmony_ci setup_packet(sl811, ep, urb, bank, control); 38862306a36Sopenharmony_ci break; 38962306a36Sopenharmony_ci case USB_PID_ACK: /* for control status */ 39062306a36Sopenharmony_ci status_packet(sl811, ep, urb, bank, control); 39162306a36Sopenharmony_ci break; 39262306a36Sopenharmony_ci default: 39362306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, 39462306a36Sopenharmony_ci "bad ep%p pid %02x\n", ep, ep->nextpid); 39562306a36Sopenharmony_ci ep = NULL; 39662306a36Sopenharmony_ci } 39762306a36Sopenharmony_ci return ep; 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci#define MIN_JIFFIES ((msecs_to_jiffies(2) > 1) ? msecs_to_jiffies(2) : 2) 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_cistatic inline void start_transfer(struct sl811 *sl811) 40362306a36Sopenharmony_ci{ 40462306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_SUSPEND) 40562306a36Sopenharmony_ci return; 40662306a36Sopenharmony_ci if (sl811->active_a == NULL) { 40762306a36Sopenharmony_ci sl811->active_a = start(sl811, SL811_EP_A(SL811_HOST_BUF)); 40862306a36Sopenharmony_ci if (sl811->active_a != NULL) 40962306a36Sopenharmony_ci sl811->jiffies_a = jiffies + MIN_JIFFIES; 41062306a36Sopenharmony_ci } 41162306a36Sopenharmony_ci#ifdef USE_B 41262306a36Sopenharmony_ci if (sl811->active_b == NULL) { 41362306a36Sopenharmony_ci sl811->active_b = start(sl811, SL811_EP_B(SL811_HOST_BUF)); 41462306a36Sopenharmony_ci if (sl811->active_b != NULL) 41562306a36Sopenharmony_ci sl811->jiffies_b = jiffies + MIN_JIFFIES; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci#endif 41862306a36Sopenharmony_ci} 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_cistatic void finish_request( 42162306a36Sopenharmony_ci struct sl811 *sl811, 42262306a36Sopenharmony_ci struct sl811h_ep *ep, 42362306a36Sopenharmony_ci struct urb *urb, 42462306a36Sopenharmony_ci int status 42562306a36Sopenharmony_ci) __releases(sl811->lock) __acquires(sl811->lock) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci unsigned i; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci if (usb_pipecontrol(urb->pipe)) 43062306a36Sopenharmony_ci ep->nextpid = USB_PID_SETUP; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci usb_hcd_unlink_urb_from_ep(sl811_to_hcd(sl811), urb); 43362306a36Sopenharmony_ci spin_unlock(&sl811->lock); 43462306a36Sopenharmony_ci usb_hcd_giveback_urb(sl811_to_hcd(sl811), urb, status); 43562306a36Sopenharmony_ci spin_lock(&sl811->lock); 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci /* leave active endpoints in the schedule */ 43862306a36Sopenharmony_ci if (!list_empty(&ep->hep->urb_list)) 43962306a36Sopenharmony_ci return; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci /* async deschedule? */ 44262306a36Sopenharmony_ci if (!list_empty(&ep->schedule)) { 44362306a36Sopenharmony_ci list_del_init(&ep->schedule); 44462306a36Sopenharmony_ci if (ep == sl811->next_async) 44562306a36Sopenharmony_ci sl811->next_async = NULL; 44662306a36Sopenharmony_ci return; 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci /* periodic deschedule */ 45062306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, 45162306a36Sopenharmony_ci "deschedule qh%d/%p branch %d\n", ep->period, ep, ep->branch); 45262306a36Sopenharmony_ci for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) { 45362306a36Sopenharmony_ci struct sl811h_ep *temp; 45462306a36Sopenharmony_ci struct sl811h_ep **prev = &sl811->periodic[i]; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci while (*prev && ((temp = *prev) != ep)) 45762306a36Sopenharmony_ci prev = &temp->next; 45862306a36Sopenharmony_ci if (*prev) 45962306a36Sopenharmony_ci *prev = ep->next; 46062306a36Sopenharmony_ci sl811->load[i] -= ep->load; 46162306a36Sopenharmony_ci } 46262306a36Sopenharmony_ci ep->branch = PERIODIC_SIZE; 46362306a36Sopenharmony_ci sl811->periodic_count--; 46462306a36Sopenharmony_ci sl811_to_hcd(sl811)->self.bandwidth_allocated 46562306a36Sopenharmony_ci -= ep->load / ep->period; 46662306a36Sopenharmony_ci if (ep == sl811->next_periodic) 46762306a36Sopenharmony_ci sl811->next_periodic = ep->next; 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci /* we might turn SOFs back on again for the async schedule */ 47062306a36Sopenharmony_ci if (sl811->periodic_count == 0) 47162306a36Sopenharmony_ci sofirq_off(sl811); 47262306a36Sopenharmony_ci} 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_cistatic void 47562306a36Sopenharmony_cidone(struct sl811 *sl811, struct sl811h_ep *ep, u8 bank) 47662306a36Sopenharmony_ci{ 47762306a36Sopenharmony_ci u8 status; 47862306a36Sopenharmony_ci struct urb *urb; 47962306a36Sopenharmony_ci int urbstat = -EINPROGRESS; 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci if (unlikely(!ep)) 48262306a36Sopenharmony_ci return; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci status = sl811_read(sl811, bank + SL11H_PKTSTATREG); 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci urb = container_of(ep->hep->urb_list.next, struct urb, urb_list); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci /* we can safely ignore NAKs */ 48962306a36Sopenharmony_ci if (status & SL11H_STATMASK_NAK) { 49062306a36Sopenharmony_ci // PACKET("...NAK_%02x qh%p\n", bank, ep); 49162306a36Sopenharmony_ci if (!ep->period) 49262306a36Sopenharmony_ci ep->nak_count++; 49362306a36Sopenharmony_ci ep->error_count = 0; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* ACK advances transfer, toggle, and maybe queue */ 49662306a36Sopenharmony_ci } else if (status & SL11H_STATMASK_ACK) { 49762306a36Sopenharmony_ci struct usb_device *udev = urb->dev; 49862306a36Sopenharmony_ci int len; 49962306a36Sopenharmony_ci unsigned char *buf; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci /* urb->iso_frame_desc is currently ignored here... */ 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci ep->nak_count = ep->error_count = 0; 50462306a36Sopenharmony_ci switch (ep->nextpid) { 50562306a36Sopenharmony_ci case USB_PID_OUT: 50662306a36Sopenharmony_ci // PACKET("...ACK/out_%02x qh%p\n", bank, ep); 50762306a36Sopenharmony_ci urb->actual_length += ep->length; 50862306a36Sopenharmony_ci usb_dotoggle(udev, ep->epnum, 1); 50962306a36Sopenharmony_ci if (urb->actual_length 51062306a36Sopenharmony_ci == urb->transfer_buffer_length) { 51162306a36Sopenharmony_ci if (usb_pipecontrol(urb->pipe)) 51262306a36Sopenharmony_ci ep->nextpid = USB_PID_ACK; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci /* some bulk protocols terminate OUT transfers 51562306a36Sopenharmony_ci * by a short packet, using ZLPs not padding. 51662306a36Sopenharmony_ci */ 51762306a36Sopenharmony_ci else if (ep->length < ep->maxpacket 51862306a36Sopenharmony_ci || !(urb->transfer_flags 51962306a36Sopenharmony_ci & URB_ZERO_PACKET)) 52062306a36Sopenharmony_ci urbstat = 0; 52162306a36Sopenharmony_ci } 52262306a36Sopenharmony_ci break; 52362306a36Sopenharmony_ci case USB_PID_IN: 52462306a36Sopenharmony_ci // PACKET("...ACK/in_%02x qh%p\n", bank, ep); 52562306a36Sopenharmony_ci buf = urb->transfer_buffer + urb->actual_length; 52662306a36Sopenharmony_ci prefetchw(buf); 52762306a36Sopenharmony_ci len = ep->maxpacket - sl811_read(sl811, 52862306a36Sopenharmony_ci bank + SL11H_XFERCNTREG); 52962306a36Sopenharmony_ci if (len > ep->length) { 53062306a36Sopenharmony_ci len = ep->length; 53162306a36Sopenharmony_ci urbstat = -EOVERFLOW; 53262306a36Sopenharmony_ci } 53362306a36Sopenharmony_ci urb->actual_length += len; 53462306a36Sopenharmony_ci sl811_read_buf(sl811, SL811HS_PACKET_BUF(bank == 0), 53562306a36Sopenharmony_ci buf, len); 53662306a36Sopenharmony_ci usb_dotoggle(udev, ep->epnum, 0); 53762306a36Sopenharmony_ci if (urbstat == -EINPROGRESS && 53862306a36Sopenharmony_ci (len < ep->maxpacket || 53962306a36Sopenharmony_ci urb->actual_length == 54062306a36Sopenharmony_ci urb->transfer_buffer_length)) { 54162306a36Sopenharmony_ci if (usb_pipecontrol(urb->pipe)) 54262306a36Sopenharmony_ci ep->nextpid = USB_PID_ACK; 54362306a36Sopenharmony_ci else 54462306a36Sopenharmony_ci urbstat = 0; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci break; 54762306a36Sopenharmony_ci case USB_PID_SETUP: 54862306a36Sopenharmony_ci // PACKET("...ACK/setup_%02x qh%p\n", bank, ep); 54962306a36Sopenharmony_ci if (urb->transfer_buffer_length == urb->actual_length) 55062306a36Sopenharmony_ci ep->nextpid = USB_PID_ACK; 55162306a36Sopenharmony_ci else if (usb_pipeout(urb->pipe)) { 55262306a36Sopenharmony_ci usb_settoggle(udev, 0, 1, 1); 55362306a36Sopenharmony_ci ep->nextpid = USB_PID_OUT; 55462306a36Sopenharmony_ci } else { 55562306a36Sopenharmony_ci usb_settoggle(udev, 0, 0, 1); 55662306a36Sopenharmony_ci ep->nextpid = USB_PID_IN; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci break; 55962306a36Sopenharmony_ci case USB_PID_ACK: 56062306a36Sopenharmony_ci // PACKET("...ACK/status_%02x qh%p\n", bank, ep); 56162306a36Sopenharmony_ci urbstat = 0; 56262306a36Sopenharmony_ci break; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci /* STALL stops all transfers */ 56662306a36Sopenharmony_ci } else if (status & SL11H_STATMASK_STALL) { 56762306a36Sopenharmony_ci PACKET("...STALL_%02x qh%p\n", bank, ep); 56862306a36Sopenharmony_ci ep->nak_count = ep->error_count = 0; 56962306a36Sopenharmony_ci urbstat = -EPIPE; 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci /* error? retry, until "3 strikes" */ 57262306a36Sopenharmony_ci } else if (++ep->error_count >= 3) { 57362306a36Sopenharmony_ci if (status & SL11H_STATMASK_TMOUT) 57462306a36Sopenharmony_ci urbstat = -ETIME; 57562306a36Sopenharmony_ci else if (status & SL11H_STATMASK_OVF) 57662306a36Sopenharmony_ci urbstat = -EOVERFLOW; 57762306a36Sopenharmony_ci else 57862306a36Sopenharmony_ci urbstat = -EPROTO; 57962306a36Sopenharmony_ci ep->error_count = 0; 58062306a36Sopenharmony_ci PACKET("...3STRIKES_%02x %02x qh%p stat %d\n", 58162306a36Sopenharmony_ci bank, status, ep, urbstat); 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci if (urbstat != -EINPROGRESS || urb->unlinked) 58562306a36Sopenharmony_ci finish_request(sl811, ep, urb, urbstat); 58662306a36Sopenharmony_ci} 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_cistatic inline u8 checkdone(struct sl811 *sl811) 58962306a36Sopenharmony_ci{ 59062306a36Sopenharmony_ci u8 ctl; 59162306a36Sopenharmony_ci u8 irqstat = 0; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci if (sl811->active_a && time_before_eq(sl811->jiffies_a, jiffies)) { 59462306a36Sopenharmony_ci ctl = sl811_read(sl811, SL811_EP_A(SL11H_HOSTCTLREG)); 59562306a36Sopenharmony_ci if (ctl & SL11H_HCTLMASK_ARM) 59662306a36Sopenharmony_ci sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 0); 59762306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, 59862306a36Sopenharmony_ci "%s DONE_A: ctrl %02x sts %02x\n", 59962306a36Sopenharmony_ci (ctl & SL11H_HCTLMASK_ARM) ? "timeout" : "lost", 60062306a36Sopenharmony_ci ctl, 60162306a36Sopenharmony_ci sl811_read(sl811, SL811_EP_A(SL11H_PKTSTATREG))); 60262306a36Sopenharmony_ci irqstat |= SL11H_INTMASK_DONE_A; 60362306a36Sopenharmony_ci } 60462306a36Sopenharmony_ci#ifdef USE_B 60562306a36Sopenharmony_ci if (sl811->active_b && time_before_eq(sl811->jiffies_b, jiffies)) { 60662306a36Sopenharmony_ci ctl = sl811_read(sl811, SL811_EP_B(SL11H_HOSTCTLREG)); 60762306a36Sopenharmony_ci if (ctl & SL11H_HCTLMASK_ARM) 60862306a36Sopenharmony_ci sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), 0); 60962306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, 61062306a36Sopenharmony_ci "%s DONE_B: ctrl %02x sts %02x\n", 61162306a36Sopenharmony_ci (ctl & SL11H_HCTLMASK_ARM) ? "timeout" : "lost", 61262306a36Sopenharmony_ci ctl, 61362306a36Sopenharmony_ci sl811_read(sl811, SL811_EP_B(SL11H_PKTSTATREG))); 61462306a36Sopenharmony_ci irqstat |= SL11H_INTMASK_DONE_A; 61562306a36Sopenharmony_ci } 61662306a36Sopenharmony_ci#endif 61762306a36Sopenharmony_ci return irqstat; 61862306a36Sopenharmony_ci} 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_cistatic irqreturn_t sl811h_irq(struct usb_hcd *hcd) 62162306a36Sopenharmony_ci{ 62262306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 62362306a36Sopenharmony_ci u8 irqstat; 62462306a36Sopenharmony_ci irqreturn_t ret = IRQ_NONE; 62562306a36Sopenharmony_ci unsigned retries = 5; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci spin_lock(&sl811->lock); 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ciretry: 63062306a36Sopenharmony_ci irqstat = sl811_read(sl811, SL11H_IRQ_STATUS) & ~SL11H_INTMASK_DP; 63162306a36Sopenharmony_ci if (irqstat) { 63262306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_STATUS, irqstat); 63362306a36Sopenharmony_ci irqstat &= sl811->irq_enable; 63462306a36Sopenharmony_ci } 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci#ifdef QUIRK2 63762306a36Sopenharmony_ci /* this may no longer be necessary ... */ 63862306a36Sopenharmony_ci if (irqstat == 0) { 63962306a36Sopenharmony_ci irqstat = checkdone(sl811); 64062306a36Sopenharmony_ci if (irqstat) 64162306a36Sopenharmony_ci sl811->stat_lost++; 64262306a36Sopenharmony_ci } 64362306a36Sopenharmony_ci#endif 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci /* USB packets, not necessarily handled in the order they're 64662306a36Sopenharmony_ci * issued ... that's fine if they're different endpoints. 64762306a36Sopenharmony_ci */ 64862306a36Sopenharmony_ci if (irqstat & SL11H_INTMASK_DONE_A) { 64962306a36Sopenharmony_ci done(sl811, sl811->active_a, SL811_EP_A(SL811_HOST_BUF)); 65062306a36Sopenharmony_ci sl811->active_a = NULL; 65162306a36Sopenharmony_ci sl811->stat_a++; 65262306a36Sopenharmony_ci } 65362306a36Sopenharmony_ci#ifdef USE_B 65462306a36Sopenharmony_ci if (irqstat & SL11H_INTMASK_DONE_B) { 65562306a36Sopenharmony_ci done(sl811, sl811->active_b, SL811_EP_B(SL811_HOST_BUF)); 65662306a36Sopenharmony_ci sl811->active_b = NULL; 65762306a36Sopenharmony_ci sl811->stat_b++; 65862306a36Sopenharmony_ci } 65962306a36Sopenharmony_ci#endif 66062306a36Sopenharmony_ci if (irqstat & SL11H_INTMASK_SOFINTR) { 66162306a36Sopenharmony_ci unsigned index; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci index = sl811->frame++ % (PERIODIC_SIZE - 1); 66462306a36Sopenharmony_ci sl811->stat_sof++; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci /* be graceful about almost-inevitable periodic schedule 66762306a36Sopenharmony_ci * overruns: continue the previous frame's transfers iff 66862306a36Sopenharmony_ci * this one has nothing scheduled. 66962306a36Sopenharmony_ci */ 67062306a36Sopenharmony_ci if (sl811->next_periodic) { 67162306a36Sopenharmony_ci // dev_err(hcd->self.controller, "overrun to slot %d\n", index); 67262306a36Sopenharmony_ci sl811->stat_overrun++; 67362306a36Sopenharmony_ci } 67462306a36Sopenharmony_ci if (sl811->periodic[index]) 67562306a36Sopenharmony_ci sl811->next_periodic = sl811->periodic[index]; 67662306a36Sopenharmony_ci } 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci /* hub_wq manages debouncing and wakeup */ 67962306a36Sopenharmony_ci if (irqstat & SL11H_INTMASK_INSRMV) { 68062306a36Sopenharmony_ci sl811->stat_insrmv++; 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci /* most stats are reset for each VBUS session */ 68362306a36Sopenharmony_ci sl811->stat_wake = 0; 68462306a36Sopenharmony_ci sl811->stat_sof = 0; 68562306a36Sopenharmony_ci sl811->stat_a = 0; 68662306a36Sopenharmony_ci sl811->stat_b = 0; 68762306a36Sopenharmony_ci sl811->stat_lost = 0; 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci sl811->ctrl1 = 0; 69062306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci sl811->irq_enable = SL11H_INTMASK_INSRMV; 69362306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci /* usbcore nukes other pending transactions on disconnect */ 69662306a36Sopenharmony_ci if (sl811->active_a) { 69762306a36Sopenharmony_ci sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 0); 69862306a36Sopenharmony_ci finish_request(sl811, sl811->active_a, 69962306a36Sopenharmony_ci container_of(sl811->active_a 70062306a36Sopenharmony_ci ->hep->urb_list.next, 70162306a36Sopenharmony_ci struct urb, urb_list), 70262306a36Sopenharmony_ci -ESHUTDOWN); 70362306a36Sopenharmony_ci sl811->active_a = NULL; 70462306a36Sopenharmony_ci } 70562306a36Sopenharmony_ci#ifdef USE_B 70662306a36Sopenharmony_ci if (sl811->active_b) { 70762306a36Sopenharmony_ci sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), 0); 70862306a36Sopenharmony_ci finish_request(sl811, sl811->active_b, 70962306a36Sopenharmony_ci container_of(sl811->active_b 71062306a36Sopenharmony_ci ->hep->urb_list.next, 71162306a36Sopenharmony_ci struct urb, urb_list), 71262306a36Sopenharmony_ci NULL, -ESHUTDOWN); 71362306a36Sopenharmony_ci sl811->active_b = NULL; 71462306a36Sopenharmony_ci } 71562306a36Sopenharmony_ci#endif 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci /* port status seems weird until after reset, so 71862306a36Sopenharmony_ci * force the reset and make hub_wq clean up later. 71962306a36Sopenharmony_ci */ 72062306a36Sopenharmony_ci if (irqstat & SL11H_INTMASK_RD) 72162306a36Sopenharmony_ci sl811->port1 &= ~USB_PORT_STAT_CONNECTION; 72262306a36Sopenharmony_ci else 72362306a36Sopenharmony_ci sl811->port1 |= USB_PORT_STAT_CONNECTION; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci sl811->port1 |= USB_PORT_STAT_C_CONNECTION << 16; 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci } else if (irqstat & SL11H_INTMASK_RD) { 72862306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_SUSPEND) { 72962306a36Sopenharmony_ci dev_dbg(hcd->self.controller, "wakeup\n"); 73062306a36Sopenharmony_ci sl811->port1 |= USB_PORT_STAT_C_SUSPEND << 16; 73162306a36Sopenharmony_ci sl811->stat_wake++; 73262306a36Sopenharmony_ci } else 73362306a36Sopenharmony_ci irqstat &= ~SL11H_INTMASK_RD; 73462306a36Sopenharmony_ci } 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci if (irqstat) { 73762306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_ENABLE) 73862306a36Sopenharmony_ci start_transfer(sl811); 73962306a36Sopenharmony_ci ret = IRQ_HANDLED; 74062306a36Sopenharmony_ci if (retries--) 74162306a36Sopenharmony_ci goto retry; 74262306a36Sopenharmony_ci } 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci if (sl811->periodic_count == 0 && list_empty(&sl811->async)) 74562306a36Sopenharmony_ci sofirq_off(sl811); 74662306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ci spin_unlock(&sl811->lock); 74962306a36Sopenharmony_ci 75062306a36Sopenharmony_ci return ret; 75162306a36Sopenharmony_ci} 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_ci/* usb 1.1 says max 90% of a frame is available for periodic transfers. 75662306a36Sopenharmony_ci * this driver doesn't promise that much since it's got to handle an 75762306a36Sopenharmony_ci * IRQ per packet; irq handling latencies also use up that time. 75862306a36Sopenharmony_ci * 75962306a36Sopenharmony_ci * NOTE: the periodic schedule is a sparse tree, with the load for 76062306a36Sopenharmony_ci * each branch minimized. see fig 3.5 in the OHCI spec for example. 76162306a36Sopenharmony_ci */ 76262306a36Sopenharmony_ci#define MAX_PERIODIC_LOAD 500 /* out of 1000 usec */ 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_cistatic int balance(struct sl811 *sl811, u16 period, u16 load) 76562306a36Sopenharmony_ci{ 76662306a36Sopenharmony_ci int i, branch = -ENOSPC; 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci /* search for the least loaded schedule branch of that period 76962306a36Sopenharmony_ci * which has enough bandwidth left unreserved. 77062306a36Sopenharmony_ci */ 77162306a36Sopenharmony_ci for (i = 0; i < period ; i++) { 77262306a36Sopenharmony_ci if (branch < 0 || sl811->load[branch] > sl811->load[i]) { 77362306a36Sopenharmony_ci int j; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci for (j = i; j < PERIODIC_SIZE; j += period) { 77662306a36Sopenharmony_ci if ((sl811->load[j] + load) 77762306a36Sopenharmony_ci > MAX_PERIODIC_LOAD) 77862306a36Sopenharmony_ci break; 77962306a36Sopenharmony_ci } 78062306a36Sopenharmony_ci if (j < PERIODIC_SIZE) 78162306a36Sopenharmony_ci continue; 78262306a36Sopenharmony_ci branch = i; 78362306a36Sopenharmony_ci } 78462306a36Sopenharmony_ci } 78562306a36Sopenharmony_ci return branch; 78662306a36Sopenharmony_ci} 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 78962306a36Sopenharmony_ci 79062306a36Sopenharmony_cistatic int sl811h_urb_enqueue( 79162306a36Sopenharmony_ci struct usb_hcd *hcd, 79262306a36Sopenharmony_ci struct urb *urb, 79362306a36Sopenharmony_ci gfp_t mem_flags 79462306a36Sopenharmony_ci) { 79562306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 79662306a36Sopenharmony_ci struct usb_device *udev = urb->dev; 79762306a36Sopenharmony_ci unsigned int pipe = urb->pipe; 79862306a36Sopenharmony_ci int is_out = !usb_pipein(pipe); 79962306a36Sopenharmony_ci int type = usb_pipetype(pipe); 80062306a36Sopenharmony_ci int epnum = usb_pipeendpoint(pipe); 80162306a36Sopenharmony_ci struct sl811h_ep *ep = NULL; 80262306a36Sopenharmony_ci unsigned long flags; 80362306a36Sopenharmony_ci int i; 80462306a36Sopenharmony_ci int retval; 80562306a36Sopenharmony_ci struct usb_host_endpoint *hep = urb->ep; 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci#ifndef CONFIG_USB_SL811_HCD_ISO 80862306a36Sopenharmony_ci if (type == PIPE_ISOCHRONOUS) 80962306a36Sopenharmony_ci return -ENOSPC; 81062306a36Sopenharmony_ci#endif 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci /* avoid all allocations within spinlocks */ 81362306a36Sopenharmony_ci if (!hep->hcpriv) { 81462306a36Sopenharmony_ci ep = kzalloc(sizeof *ep, mem_flags); 81562306a36Sopenharmony_ci if (ep == NULL) 81662306a36Sopenharmony_ci return -ENOMEM; 81762306a36Sopenharmony_ci } 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci spin_lock_irqsave(&sl811->lock, flags); 82062306a36Sopenharmony_ci 82162306a36Sopenharmony_ci /* don't submit to a dead or disabled port */ 82262306a36Sopenharmony_ci if (!(sl811->port1 & USB_PORT_STAT_ENABLE) 82362306a36Sopenharmony_ci || !HC_IS_RUNNING(hcd->state)) { 82462306a36Sopenharmony_ci retval = -ENODEV; 82562306a36Sopenharmony_ci kfree(ep); 82662306a36Sopenharmony_ci goto fail_not_linked; 82762306a36Sopenharmony_ci } 82862306a36Sopenharmony_ci retval = usb_hcd_link_urb_to_ep(hcd, urb); 82962306a36Sopenharmony_ci if (retval) { 83062306a36Sopenharmony_ci kfree(ep); 83162306a36Sopenharmony_ci goto fail_not_linked; 83262306a36Sopenharmony_ci } 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci if (hep->hcpriv) { 83562306a36Sopenharmony_ci kfree(ep); 83662306a36Sopenharmony_ci ep = hep->hcpriv; 83762306a36Sopenharmony_ci } else if (!ep) { 83862306a36Sopenharmony_ci retval = -ENOMEM; 83962306a36Sopenharmony_ci goto fail; 84062306a36Sopenharmony_ci 84162306a36Sopenharmony_ci } else { 84262306a36Sopenharmony_ci INIT_LIST_HEAD(&ep->schedule); 84362306a36Sopenharmony_ci ep->udev = udev; 84462306a36Sopenharmony_ci ep->epnum = epnum; 84562306a36Sopenharmony_ci ep->maxpacket = usb_maxpacket(udev, urb->pipe); 84662306a36Sopenharmony_ci ep->defctrl = SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENABLE; 84762306a36Sopenharmony_ci usb_settoggle(udev, epnum, is_out, 0); 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci if (type == PIPE_CONTROL) 85062306a36Sopenharmony_ci ep->nextpid = USB_PID_SETUP; 85162306a36Sopenharmony_ci else if (is_out) 85262306a36Sopenharmony_ci ep->nextpid = USB_PID_OUT; 85362306a36Sopenharmony_ci else 85462306a36Sopenharmony_ci ep->nextpid = USB_PID_IN; 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ci if (ep->maxpacket > H_MAXPACKET) { 85762306a36Sopenharmony_ci /* iso packets up to 240 bytes could work... */ 85862306a36Sopenharmony_ci dev_dbg(hcd->self.controller, 85962306a36Sopenharmony_ci "dev %d ep%d maxpacket %d\n", udev->devnum, 86062306a36Sopenharmony_ci epnum, ep->maxpacket); 86162306a36Sopenharmony_ci retval = -EINVAL; 86262306a36Sopenharmony_ci kfree(ep); 86362306a36Sopenharmony_ci goto fail; 86462306a36Sopenharmony_ci } 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci if (udev->speed == USB_SPEED_LOW) { 86762306a36Sopenharmony_ci /* send preamble for external hub? */ 86862306a36Sopenharmony_ci if (!(sl811->ctrl1 & SL11H_CTL1MASK_LSPD)) 86962306a36Sopenharmony_ci ep->defctrl |= SL11H_HCTLMASK_PREAMBLE; 87062306a36Sopenharmony_ci } 87162306a36Sopenharmony_ci switch (type) { 87262306a36Sopenharmony_ci case PIPE_ISOCHRONOUS: 87362306a36Sopenharmony_ci case PIPE_INTERRUPT: 87462306a36Sopenharmony_ci if (urb->interval > PERIODIC_SIZE) 87562306a36Sopenharmony_ci urb->interval = PERIODIC_SIZE; 87662306a36Sopenharmony_ci ep->period = urb->interval; 87762306a36Sopenharmony_ci ep->branch = PERIODIC_SIZE; 87862306a36Sopenharmony_ci if (type == PIPE_ISOCHRONOUS) 87962306a36Sopenharmony_ci ep->defctrl |= SL11H_HCTLMASK_ISOCH; 88062306a36Sopenharmony_ci ep->load = usb_calc_bus_time(udev->speed, !is_out, 88162306a36Sopenharmony_ci type == PIPE_ISOCHRONOUS, 88262306a36Sopenharmony_ci usb_maxpacket(udev, pipe)) 88362306a36Sopenharmony_ci / 1000; 88462306a36Sopenharmony_ci break; 88562306a36Sopenharmony_ci } 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci ep->hep = hep; 88862306a36Sopenharmony_ci hep->hcpriv = ep; 88962306a36Sopenharmony_ci } 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci /* maybe put endpoint into schedule */ 89262306a36Sopenharmony_ci switch (type) { 89362306a36Sopenharmony_ci case PIPE_CONTROL: 89462306a36Sopenharmony_ci case PIPE_BULK: 89562306a36Sopenharmony_ci if (list_empty(&ep->schedule)) 89662306a36Sopenharmony_ci list_add_tail(&ep->schedule, &sl811->async); 89762306a36Sopenharmony_ci break; 89862306a36Sopenharmony_ci case PIPE_ISOCHRONOUS: 89962306a36Sopenharmony_ci case PIPE_INTERRUPT: 90062306a36Sopenharmony_ci urb->interval = ep->period; 90162306a36Sopenharmony_ci if (ep->branch < PERIODIC_SIZE) { 90262306a36Sopenharmony_ci /* NOTE: the phase is correct here, but the value 90362306a36Sopenharmony_ci * needs offsetting by the transfer queue depth. 90462306a36Sopenharmony_ci * All current drivers ignore start_frame, so this 90562306a36Sopenharmony_ci * is unlikely to ever matter... 90662306a36Sopenharmony_ci */ 90762306a36Sopenharmony_ci urb->start_frame = (sl811->frame & (PERIODIC_SIZE - 1)) 90862306a36Sopenharmony_ci + ep->branch; 90962306a36Sopenharmony_ci break; 91062306a36Sopenharmony_ci } 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_ci retval = balance(sl811, ep->period, ep->load); 91362306a36Sopenharmony_ci if (retval < 0) 91462306a36Sopenharmony_ci goto fail; 91562306a36Sopenharmony_ci ep->branch = retval; 91662306a36Sopenharmony_ci retval = 0; 91762306a36Sopenharmony_ci urb->start_frame = (sl811->frame & (PERIODIC_SIZE - 1)) 91862306a36Sopenharmony_ci + ep->branch; 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci /* sort each schedule branch by period (slow before fast) 92162306a36Sopenharmony_ci * to share the faster parts of the tree without needing 92262306a36Sopenharmony_ci * dummy/placeholder nodes 92362306a36Sopenharmony_ci */ 92462306a36Sopenharmony_ci dev_dbg(hcd->self.controller, "schedule qh%d/%p branch %d\n", 92562306a36Sopenharmony_ci ep->period, ep, ep->branch); 92662306a36Sopenharmony_ci for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) { 92762306a36Sopenharmony_ci struct sl811h_ep **prev = &sl811->periodic[i]; 92862306a36Sopenharmony_ci struct sl811h_ep *here = *prev; 92962306a36Sopenharmony_ci 93062306a36Sopenharmony_ci while (here && ep != here) { 93162306a36Sopenharmony_ci if (ep->period > here->period) 93262306a36Sopenharmony_ci break; 93362306a36Sopenharmony_ci prev = &here->next; 93462306a36Sopenharmony_ci here = *prev; 93562306a36Sopenharmony_ci } 93662306a36Sopenharmony_ci if (ep != here) { 93762306a36Sopenharmony_ci ep->next = here; 93862306a36Sopenharmony_ci *prev = ep; 93962306a36Sopenharmony_ci } 94062306a36Sopenharmony_ci sl811->load[i] += ep->load; 94162306a36Sopenharmony_ci } 94262306a36Sopenharmony_ci sl811->periodic_count++; 94362306a36Sopenharmony_ci hcd->self.bandwidth_allocated += ep->load / ep->period; 94462306a36Sopenharmony_ci sofirq_on(sl811); 94562306a36Sopenharmony_ci } 94662306a36Sopenharmony_ci 94762306a36Sopenharmony_ci urb->hcpriv = hep; 94862306a36Sopenharmony_ci start_transfer(sl811); 94962306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); 95062306a36Sopenharmony_cifail: 95162306a36Sopenharmony_ci if (retval) 95262306a36Sopenharmony_ci usb_hcd_unlink_urb_from_ep(hcd, urb); 95362306a36Sopenharmony_cifail_not_linked: 95462306a36Sopenharmony_ci spin_unlock_irqrestore(&sl811->lock, flags); 95562306a36Sopenharmony_ci return retval; 95662306a36Sopenharmony_ci} 95762306a36Sopenharmony_ci 95862306a36Sopenharmony_cistatic int sl811h_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) 95962306a36Sopenharmony_ci{ 96062306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 96162306a36Sopenharmony_ci struct usb_host_endpoint *hep; 96262306a36Sopenharmony_ci unsigned long flags; 96362306a36Sopenharmony_ci struct sl811h_ep *ep; 96462306a36Sopenharmony_ci int retval; 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_ci spin_lock_irqsave(&sl811->lock, flags); 96762306a36Sopenharmony_ci retval = usb_hcd_check_unlink_urb(hcd, urb, status); 96862306a36Sopenharmony_ci if (retval) 96962306a36Sopenharmony_ci goto fail; 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci hep = urb->hcpriv; 97262306a36Sopenharmony_ci ep = hep->hcpriv; 97362306a36Sopenharmony_ci if (ep) { 97462306a36Sopenharmony_ci /* finish right away if this urb can't be active ... 97562306a36Sopenharmony_ci * note that some drivers wrongly expect delays 97662306a36Sopenharmony_ci */ 97762306a36Sopenharmony_ci if (ep->hep->urb_list.next != &urb->urb_list) { 97862306a36Sopenharmony_ci /* not front of queue? never active */ 97962306a36Sopenharmony_ci 98062306a36Sopenharmony_ci /* for active transfers, we expect an IRQ */ 98162306a36Sopenharmony_ci } else if (sl811->active_a == ep) { 98262306a36Sopenharmony_ci if (time_before_eq(sl811->jiffies_a, jiffies)) { 98362306a36Sopenharmony_ci /* happens a lot with lowspeed?? */ 98462306a36Sopenharmony_ci dev_dbg(hcd->self.controller, 98562306a36Sopenharmony_ci "giveup on DONE_A: ctrl %02x sts %02x\n", 98662306a36Sopenharmony_ci sl811_read(sl811, 98762306a36Sopenharmony_ci SL811_EP_A(SL11H_HOSTCTLREG)), 98862306a36Sopenharmony_ci sl811_read(sl811, 98962306a36Sopenharmony_ci SL811_EP_A(SL11H_PKTSTATREG))); 99062306a36Sopenharmony_ci sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 99162306a36Sopenharmony_ci 0); 99262306a36Sopenharmony_ci sl811->active_a = NULL; 99362306a36Sopenharmony_ci } else 99462306a36Sopenharmony_ci urb = NULL; 99562306a36Sopenharmony_ci#ifdef USE_B 99662306a36Sopenharmony_ci } else if (sl811->active_b == ep) { 99762306a36Sopenharmony_ci if (time_before_eq(sl811->jiffies_a, jiffies)) { 99862306a36Sopenharmony_ci /* happens a lot with lowspeed?? */ 99962306a36Sopenharmony_ci dev_dbg(hcd->self.controller, 100062306a36Sopenharmony_ci "giveup on DONE_B: ctrl %02x sts %02x\n", 100162306a36Sopenharmony_ci sl811_read(sl811, 100262306a36Sopenharmony_ci SL811_EP_B(SL11H_HOSTCTLREG)), 100362306a36Sopenharmony_ci sl811_read(sl811, 100462306a36Sopenharmony_ci SL811_EP_B(SL11H_PKTSTATREG))); 100562306a36Sopenharmony_ci sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), 100662306a36Sopenharmony_ci 0); 100762306a36Sopenharmony_ci sl811->active_b = NULL; 100862306a36Sopenharmony_ci } else 100962306a36Sopenharmony_ci urb = NULL; 101062306a36Sopenharmony_ci#endif 101162306a36Sopenharmony_ci } else { 101262306a36Sopenharmony_ci /* front of queue for inactive endpoint */ 101362306a36Sopenharmony_ci } 101462306a36Sopenharmony_ci 101562306a36Sopenharmony_ci if (urb) 101662306a36Sopenharmony_ci finish_request(sl811, ep, urb, 0); 101762306a36Sopenharmony_ci else 101862306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, 101962306a36Sopenharmony_ci "dequeue, urb %p active %s; wait4irq\n", urb, 102062306a36Sopenharmony_ci (sl811->active_a == ep) ? "A" : "B"); 102162306a36Sopenharmony_ci } else 102262306a36Sopenharmony_ci retval = -EINVAL; 102362306a36Sopenharmony_ci fail: 102462306a36Sopenharmony_ci spin_unlock_irqrestore(&sl811->lock, flags); 102562306a36Sopenharmony_ci return retval; 102662306a36Sopenharmony_ci} 102762306a36Sopenharmony_ci 102862306a36Sopenharmony_cistatic void 102962306a36Sopenharmony_cisl811h_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) 103062306a36Sopenharmony_ci{ 103162306a36Sopenharmony_ci struct sl811h_ep *ep = hep->hcpriv; 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci if (!ep) 103462306a36Sopenharmony_ci return; 103562306a36Sopenharmony_ci 103662306a36Sopenharmony_ci /* assume we'd just wait for the irq */ 103762306a36Sopenharmony_ci if (!list_empty(&hep->urb_list)) 103862306a36Sopenharmony_ci msleep(3); 103962306a36Sopenharmony_ci if (!list_empty(&hep->urb_list)) 104062306a36Sopenharmony_ci dev_warn(hcd->self.controller, "ep %p not empty?\n", ep); 104162306a36Sopenharmony_ci 104262306a36Sopenharmony_ci kfree(ep); 104362306a36Sopenharmony_ci hep->hcpriv = NULL; 104462306a36Sopenharmony_ci} 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_cistatic int 104762306a36Sopenharmony_cisl811h_get_frame(struct usb_hcd *hcd) 104862306a36Sopenharmony_ci{ 104962306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 105062306a36Sopenharmony_ci 105162306a36Sopenharmony_ci /* wrong except while periodic transfers are scheduled; 105262306a36Sopenharmony_ci * never matches the on-the-wire frame; 105362306a36Sopenharmony_ci * subject to overruns. 105462306a36Sopenharmony_ci */ 105562306a36Sopenharmony_ci return sl811->frame; 105662306a36Sopenharmony_ci} 105762306a36Sopenharmony_ci 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_ci/* the virtual root hub timer IRQ checks for hub status */ 106262306a36Sopenharmony_cistatic int 106362306a36Sopenharmony_cisl811h_hub_status_data(struct usb_hcd *hcd, char *buf) 106462306a36Sopenharmony_ci{ 106562306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 106662306a36Sopenharmony_ci#ifdef QUIRK3 106762306a36Sopenharmony_ci unsigned long flags; 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_ci /* non-SMP HACK: use root hub timer as i/o watchdog 107062306a36Sopenharmony_ci * this seems essential when SOF IRQs aren't in use... 107162306a36Sopenharmony_ci */ 107262306a36Sopenharmony_ci local_irq_save(flags); 107362306a36Sopenharmony_ci if (!timer_pending(&sl811->timer)) { 107462306a36Sopenharmony_ci if (sl811h_irq( /* ~0, */ hcd) != IRQ_NONE) 107562306a36Sopenharmony_ci sl811->stat_lost++; 107662306a36Sopenharmony_ci } 107762306a36Sopenharmony_ci local_irq_restore(flags); 107862306a36Sopenharmony_ci#endif 107962306a36Sopenharmony_ci 108062306a36Sopenharmony_ci if (!(sl811->port1 & (0xffff << 16))) 108162306a36Sopenharmony_ci return 0; 108262306a36Sopenharmony_ci 108362306a36Sopenharmony_ci /* tell hub_wq port 1 changed */ 108462306a36Sopenharmony_ci *buf = (1 << 1); 108562306a36Sopenharmony_ci return 1; 108662306a36Sopenharmony_ci} 108762306a36Sopenharmony_ci 108862306a36Sopenharmony_cistatic void 108962306a36Sopenharmony_cisl811h_hub_descriptor ( 109062306a36Sopenharmony_ci struct sl811 *sl811, 109162306a36Sopenharmony_ci struct usb_hub_descriptor *desc 109262306a36Sopenharmony_ci) { 109362306a36Sopenharmony_ci u16 temp = 0; 109462306a36Sopenharmony_ci 109562306a36Sopenharmony_ci desc->bDescriptorType = USB_DT_HUB; 109662306a36Sopenharmony_ci desc->bHubContrCurrent = 0; 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_ci desc->bNbrPorts = 1; 109962306a36Sopenharmony_ci desc->bDescLength = 9; 110062306a36Sopenharmony_ci 110162306a36Sopenharmony_ci /* per-port power switching (gang of one!), or none */ 110262306a36Sopenharmony_ci desc->bPwrOn2PwrGood = 0; 110362306a36Sopenharmony_ci if (sl811->board && sl811->board->port_power) { 110462306a36Sopenharmony_ci desc->bPwrOn2PwrGood = sl811->board->potpg; 110562306a36Sopenharmony_ci if (!desc->bPwrOn2PwrGood) 110662306a36Sopenharmony_ci desc->bPwrOn2PwrGood = 10; 110762306a36Sopenharmony_ci temp = HUB_CHAR_INDV_PORT_LPSM; 110862306a36Sopenharmony_ci } else 110962306a36Sopenharmony_ci temp = HUB_CHAR_NO_LPSM; 111062306a36Sopenharmony_ci 111162306a36Sopenharmony_ci /* no overcurrent errors detection/handling */ 111262306a36Sopenharmony_ci temp |= HUB_CHAR_NO_OCPM; 111362306a36Sopenharmony_ci 111462306a36Sopenharmony_ci desc->wHubCharacteristics = cpu_to_le16(temp); 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci /* ports removable, and legacy PortPwrCtrlMask */ 111762306a36Sopenharmony_ci desc->u.hs.DeviceRemovable[0] = 0 << 1; 111862306a36Sopenharmony_ci desc->u.hs.DeviceRemovable[1] = ~0; 111962306a36Sopenharmony_ci} 112062306a36Sopenharmony_ci 112162306a36Sopenharmony_cistatic void 112262306a36Sopenharmony_cisl811h_timer(struct timer_list *t) 112362306a36Sopenharmony_ci{ 112462306a36Sopenharmony_ci struct sl811 *sl811 = from_timer(sl811, t, timer); 112562306a36Sopenharmony_ci unsigned long flags; 112662306a36Sopenharmony_ci u8 irqstat; 112762306a36Sopenharmony_ci u8 signaling = sl811->ctrl1 & SL11H_CTL1MASK_FORCE; 112862306a36Sopenharmony_ci const u32 mask = USB_PORT_STAT_CONNECTION 112962306a36Sopenharmony_ci | USB_PORT_STAT_ENABLE 113062306a36Sopenharmony_ci | USB_PORT_STAT_LOW_SPEED; 113162306a36Sopenharmony_ci 113262306a36Sopenharmony_ci spin_lock_irqsave(&sl811->lock, flags); 113362306a36Sopenharmony_ci 113462306a36Sopenharmony_ci /* stop special signaling */ 113562306a36Sopenharmony_ci sl811->ctrl1 &= ~SL11H_CTL1MASK_FORCE; 113662306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); 113762306a36Sopenharmony_ci udelay(3); 113862306a36Sopenharmony_ci 113962306a36Sopenharmony_ci irqstat = sl811_read(sl811, SL11H_IRQ_STATUS); 114062306a36Sopenharmony_ci 114162306a36Sopenharmony_ci switch (signaling) { 114262306a36Sopenharmony_ci case SL11H_CTL1MASK_SE0: 114362306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, "end reset\n"); 114462306a36Sopenharmony_ci sl811->port1 = (USB_PORT_STAT_C_RESET << 16) 114562306a36Sopenharmony_ci | USB_PORT_STAT_POWER; 114662306a36Sopenharmony_ci sl811->ctrl1 = 0; 114762306a36Sopenharmony_ci /* don't wrongly ack RD */ 114862306a36Sopenharmony_ci if (irqstat & SL11H_INTMASK_INSRMV) 114962306a36Sopenharmony_ci irqstat &= ~SL11H_INTMASK_RD; 115062306a36Sopenharmony_ci break; 115162306a36Sopenharmony_ci case SL11H_CTL1MASK_K: 115262306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, "end resume\n"); 115362306a36Sopenharmony_ci sl811->port1 &= ~USB_PORT_STAT_SUSPEND; 115462306a36Sopenharmony_ci break; 115562306a36Sopenharmony_ci default: 115662306a36Sopenharmony_ci dev_dbg(sl811_to_hcd(sl811)->self.controller, 115762306a36Sopenharmony_ci "odd timer signaling: %02x\n", signaling); 115862306a36Sopenharmony_ci break; 115962306a36Sopenharmony_ci } 116062306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_STATUS, irqstat); 116162306a36Sopenharmony_ci 116262306a36Sopenharmony_ci if (irqstat & SL11H_INTMASK_RD) { 116362306a36Sopenharmony_ci /* usbcore nukes all pending transactions on disconnect */ 116462306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_CONNECTION) 116562306a36Sopenharmony_ci sl811->port1 |= (USB_PORT_STAT_C_CONNECTION << 16) 116662306a36Sopenharmony_ci | (USB_PORT_STAT_C_ENABLE << 16); 116762306a36Sopenharmony_ci sl811->port1 &= ~mask; 116862306a36Sopenharmony_ci sl811->irq_enable = SL11H_INTMASK_INSRMV; 116962306a36Sopenharmony_ci } else { 117062306a36Sopenharmony_ci sl811->port1 |= mask; 117162306a36Sopenharmony_ci if (irqstat & SL11H_INTMASK_DP) 117262306a36Sopenharmony_ci sl811->port1 &= ~USB_PORT_STAT_LOW_SPEED; 117362306a36Sopenharmony_ci sl811->irq_enable = SL11H_INTMASK_INSRMV | SL11H_INTMASK_RD; 117462306a36Sopenharmony_ci } 117562306a36Sopenharmony_ci 117662306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_CONNECTION) { 117762306a36Sopenharmony_ci u8 ctrl2 = SL811HS_CTL2_INIT; 117862306a36Sopenharmony_ci 117962306a36Sopenharmony_ci sl811->irq_enable |= SL11H_INTMASK_DONE_A; 118062306a36Sopenharmony_ci#ifdef USE_B 118162306a36Sopenharmony_ci sl811->irq_enable |= SL11H_INTMASK_DONE_B; 118262306a36Sopenharmony_ci#endif 118362306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_LOW_SPEED) { 118462306a36Sopenharmony_ci sl811->ctrl1 |= SL11H_CTL1MASK_LSPD; 118562306a36Sopenharmony_ci ctrl2 |= SL811HS_CTL2MASK_DSWAP; 118662306a36Sopenharmony_ci } 118762306a36Sopenharmony_ci 118862306a36Sopenharmony_ci /* start SOFs flowing, kickstarting with A registers */ 118962306a36Sopenharmony_ci sl811->ctrl1 |= SL11H_CTL1MASK_SOF_ENA; 119062306a36Sopenharmony_ci sl811_write(sl811, SL11H_SOFLOWREG, 0xe0); 119162306a36Sopenharmony_ci sl811_write(sl811, SL811HS_CTLREG2, ctrl2); 119262306a36Sopenharmony_ci 119362306a36Sopenharmony_ci /* autoincrementing */ 119462306a36Sopenharmony_ci sl811_write(sl811, SL811_EP_A(SL11H_BUFLNTHREG), 0); 119562306a36Sopenharmony_ci writeb(SL_SOF, sl811->data_reg); 119662306a36Sopenharmony_ci writeb(0, sl811->data_reg); 119762306a36Sopenharmony_ci sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 119862306a36Sopenharmony_ci SL11H_HCTLMASK_ARM); 119962306a36Sopenharmony_ci 120062306a36Sopenharmony_ci /* hub_wq provides debounce delay */ 120162306a36Sopenharmony_ci } else { 120262306a36Sopenharmony_ci sl811->ctrl1 = 0; 120362306a36Sopenharmony_ci } 120462306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); 120562306a36Sopenharmony_ci 120662306a36Sopenharmony_ci /* reenable irqs */ 120762306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); 120862306a36Sopenharmony_ci spin_unlock_irqrestore(&sl811->lock, flags); 120962306a36Sopenharmony_ci} 121062306a36Sopenharmony_ci 121162306a36Sopenharmony_cistatic int 121262306a36Sopenharmony_cisl811h_hub_control( 121362306a36Sopenharmony_ci struct usb_hcd *hcd, 121462306a36Sopenharmony_ci u16 typeReq, 121562306a36Sopenharmony_ci u16 wValue, 121662306a36Sopenharmony_ci u16 wIndex, 121762306a36Sopenharmony_ci char *buf, 121862306a36Sopenharmony_ci u16 wLength 121962306a36Sopenharmony_ci) { 122062306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 122162306a36Sopenharmony_ci int retval = 0; 122262306a36Sopenharmony_ci unsigned long flags; 122362306a36Sopenharmony_ci 122462306a36Sopenharmony_ci spin_lock_irqsave(&sl811->lock, flags); 122562306a36Sopenharmony_ci 122662306a36Sopenharmony_ci switch (typeReq) { 122762306a36Sopenharmony_ci case ClearHubFeature: 122862306a36Sopenharmony_ci case SetHubFeature: 122962306a36Sopenharmony_ci switch (wValue) { 123062306a36Sopenharmony_ci case C_HUB_OVER_CURRENT: 123162306a36Sopenharmony_ci case C_HUB_LOCAL_POWER: 123262306a36Sopenharmony_ci break; 123362306a36Sopenharmony_ci default: 123462306a36Sopenharmony_ci goto error; 123562306a36Sopenharmony_ci } 123662306a36Sopenharmony_ci break; 123762306a36Sopenharmony_ci case ClearPortFeature: 123862306a36Sopenharmony_ci if (wIndex != 1 || wLength != 0) 123962306a36Sopenharmony_ci goto error; 124062306a36Sopenharmony_ci 124162306a36Sopenharmony_ci switch (wValue) { 124262306a36Sopenharmony_ci case USB_PORT_FEAT_ENABLE: 124362306a36Sopenharmony_ci sl811->port1 &= USB_PORT_STAT_POWER; 124462306a36Sopenharmony_ci sl811->ctrl1 = 0; 124562306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); 124662306a36Sopenharmony_ci sl811->irq_enable = SL11H_INTMASK_INSRMV; 124762306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, 124862306a36Sopenharmony_ci sl811->irq_enable); 124962306a36Sopenharmony_ci break; 125062306a36Sopenharmony_ci case USB_PORT_FEAT_SUSPEND: 125162306a36Sopenharmony_ci if (!(sl811->port1 & USB_PORT_STAT_SUSPEND)) 125262306a36Sopenharmony_ci break; 125362306a36Sopenharmony_ci 125462306a36Sopenharmony_ci /* 20 msec of resume/K signaling, other irqs blocked */ 125562306a36Sopenharmony_ci dev_dbg(hcd->self.controller, "start resume...\n"); 125662306a36Sopenharmony_ci sl811->irq_enable = 0; 125762306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, 125862306a36Sopenharmony_ci sl811->irq_enable); 125962306a36Sopenharmony_ci sl811->ctrl1 |= SL11H_CTL1MASK_K; 126062306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); 126162306a36Sopenharmony_ci 126262306a36Sopenharmony_ci mod_timer(&sl811->timer, jiffies 126362306a36Sopenharmony_ci + msecs_to_jiffies(USB_RESUME_TIMEOUT)); 126462306a36Sopenharmony_ci break; 126562306a36Sopenharmony_ci case USB_PORT_FEAT_POWER: 126662306a36Sopenharmony_ci port_power(sl811, 0); 126762306a36Sopenharmony_ci break; 126862306a36Sopenharmony_ci case USB_PORT_FEAT_C_ENABLE: 126962306a36Sopenharmony_ci case USB_PORT_FEAT_C_SUSPEND: 127062306a36Sopenharmony_ci case USB_PORT_FEAT_C_CONNECTION: 127162306a36Sopenharmony_ci case USB_PORT_FEAT_C_OVER_CURRENT: 127262306a36Sopenharmony_ci case USB_PORT_FEAT_C_RESET: 127362306a36Sopenharmony_ci break; 127462306a36Sopenharmony_ci default: 127562306a36Sopenharmony_ci goto error; 127662306a36Sopenharmony_ci } 127762306a36Sopenharmony_ci sl811->port1 &= ~(1 << wValue); 127862306a36Sopenharmony_ci break; 127962306a36Sopenharmony_ci case GetHubDescriptor: 128062306a36Sopenharmony_ci sl811h_hub_descriptor(sl811, (struct usb_hub_descriptor *) buf); 128162306a36Sopenharmony_ci break; 128262306a36Sopenharmony_ci case GetHubStatus: 128362306a36Sopenharmony_ci put_unaligned_le32(0, buf); 128462306a36Sopenharmony_ci break; 128562306a36Sopenharmony_ci case GetPortStatus: 128662306a36Sopenharmony_ci if (wIndex != 1) 128762306a36Sopenharmony_ci goto error; 128862306a36Sopenharmony_ci put_unaligned_le32(sl811->port1, buf); 128962306a36Sopenharmony_ci 129062306a36Sopenharmony_ci if (__is_defined(VERBOSE) || 129162306a36Sopenharmony_ci *(u16*)(buf+2)) /* only if wPortChange is interesting */ 129262306a36Sopenharmony_ci dev_dbg(hcd->self.controller, "GetPortStatus %08x\n", 129362306a36Sopenharmony_ci sl811->port1); 129462306a36Sopenharmony_ci break; 129562306a36Sopenharmony_ci case SetPortFeature: 129662306a36Sopenharmony_ci if (wIndex != 1 || wLength != 0) 129762306a36Sopenharmony_ci goto error; 129862306a36Sopenharmony_ci switch (wValue) { 129962306a36Sopenharmony_ci case USB_PORT_FEAT_SUSPEND: 130062306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_RESET) 130162306a36Sopenharmony_ci goto error; 130262306a36Sopenharmony_ci if (!(sl811->port1 & USB_PORT_STAT_ENABLE)) 130362306a36Sopenharmony_ci goto error; 130462306a36Sopenharmony_ci 130562306a36Sopenharmony_ci dev_dbg(hcd->self.controller,"suspend...\n"); 130662306a36Sopenharmony_ci sl811->ctrl1 &= ~SL11H_CTL1MASK_SOF_ENA; 130762306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); 130862306a36Sopenharmony_ci break; 130962306a36Sopenharmony_ci case USB_PORT_FEAT_POWER: 131062306a36Sopenharmony_ci port_power(sl811, 1); 131162306a36Sopenharmony_ci break; 131262306a36Sopenharmony_ci case USB_PORT_FEAT_RESET: 131362306a36Sopenharmony_ci if (sl811->port1 & USB_PORT_STAT_SUSPEND) 131462306a36Sopenharmony_ci goto error; 131562306a36Sopenharmony_ci if (!(sl811->port1 & USB_PORT_STAT_POWER)) 131662306a36Sopenharmony_ci break; 131762306a36Sopenharmony_ci 131862306a36Sopenharmony_ci /* 50 msec of reset/SE0 signaling, irqs blocked */ 131962306a36Sopenharmony_ci sl811->irq_enable = 0; 132062306a36Sopenharmony_ci sl811_write(sl811, SL11H_IRQ_ENABLE, 132162306a36Sopenharmony_ci sl811->irq_enable); 132262306a36Sopenharmony_ci sl811->ctrl1 = SL11H_CTL1MASK_SE0; 132362306a36Sopenharmony_ci sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); 132462306a36Sopenharmony_ci sl811->port1 |= USB_PORT_STAT_RESET; 132562306a36Sopenharmony_ci mod_timer(&sl811->timer, jiffies 132662306a36Sopenharmony_ci + msecs_to_jiffies(50)); 132762306a36Sopenharmony_ci break; 132862306a36Sopenharmony_ci default: 132962306a36Sopenharmony_ci goto error; 133062306a36Sopenharmony_ci } 133162306a36Sopenharmony_ci sl811->port1 |= 1 << wValue; 133262306a36Sopenharmony_ci break; 133362306a36Sopenharmony_ci 133462306a36Sopenharmony_ci default: 133562306a36Sopenharmony_cierror: 133662306a36Sopenharmony_ci /* "protocol stall" on error */ 133762306a36Sopenharmony_ci retval = -EPIPE; 133862306a36Sopenharmony_ci } 133962306a36Sopenharmony_ci 134062306a36Sopenharmony_ci spin_unlock_irqrestore(&sl811->lock, flags); 134162306a36Sopenharmony_ci return retval; 134262306a36Sopenharmony_ci} 134362306a36Sopenharmony_ci 134462306a36Sopenharmony_ci#ifdef CONFIG_PM 134562306a36Sopenharmony_ci 134662306a36Sopenharmony_cistatic int 134762306a36Sopenharmony_cisl811h_bus_suspend(struct usb_hcd *hcd) 134862306a36Sopenharmony_ci{ 134962306a36Sopenharmony_ci // SOFs off 135062306a36Sopenharmony_ci dev_dbg(hcd->self.controller, "%s\n", __func__); 135162306a36Sopenharmony_ci return 0; 135262306a36Sopenharmony_ci} 135362306a36Sopenharmony_ci 135462306a36Sopenharmony_cistatic int 135562306a36Sopenharmony_cisl811h_bus_resume(struct usb_hcd *hcd) 135662306a36Sopenharmony_ci{ 135762306a36Sopenharmony_ci // SOFs on 135862306a36Sopenharmony_ci dev_dbg(hcd->self.controller, "%s\n", __func__); 135962306a36Sopenharmony_ci return 0; 136062306a36Sopenharmony_ci} 136162306a36Sopenharmony_ci 136262306a36Sopenharmony_ci#else 136362306a36Sopenharmony_ci 136462306a36Sopenharmony_ci#define sl811h_bus_suspend NULL 136562306a36Sopenharmony_ci#define sl811h_bus_resume NULL 136662306a36Sopenharmony_ci 136762306a36Sopenharmony_ci#endif 136862306a36Sopenharmony_ci 136962306a36Sopenharmony_ci 137062306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 137162306a36Sopenharmony_ci 137262306a36Sopenharmony_cistatic void dump_irq(struct seq_file *s, char *label, u8 mask) 137362306a36Sopenharmony_ci{ 137462306a36Sopenharmony_ci seq_printf(s, "%s %02x%s%s%s%s%s%s\n", label, mask, 137562306a36Sopenharmony_ci (mask & SL11H_INTMASK_DONE_A) ? " done_a" : "", 137662306a36Sopenharmony_ci (mask & SL11H_INTMASK_DONE_B) ? " done_b" : "", 137762306a36Sopenharmony_ci (mask & SL11H_INTMASK_SOFINTR) ? " sof" : "", 137862306a36Sopenharmony_ci (mask & SL11H_INTMASK_INSRMV) ? " ins/rmv" : "", 137962306a36Sopenharmony_ci (mask & SL11H_INTMASK_RD) ? " rd" : "", 138062306a36Sopenharmony_ci (mask & SL11H_INTMASK_DP) ? " dp" : ""); 138162306a36Sopenharmony_ci} 138262306a36Sopenharmony_ci 138362306a36Sopenharmony_cistatic int sl811h_debug_show(struct seq_file *s, void *unused) 138462306a36Sopenharmony_ci{ 138562306a36Sopenharmony_ci struct sl811 *sl811 = s->private; 138662306a36Sopenharmony_ci struct sl811h_ep *ep; 138762306a36Sopenharmony_ci unsigned i; 138862306a36Sopenharmony_ci 138962306a36Sopenharmony_ci seq_printf(s, "%s\n%s version %s\nportstatus[1] = %08x\n", 139062306a36Sopenharmony_ci sl811_to_hcd(sl811)->product_desc, 139162306a36Sopenharmony_ci hcd_name, DRIVER_VERSION, 139262306a36Sopenharmony_ci sl811->port1); 139362306a36Sopenharmony_ci 139462306a36Sopenharmony_ci seq_printf(s, "insert/remove: %ld\n", sl811->stat_insrmv); 139562306a36Sopenharmony_ci seq_printf(s, "current session: done_a %ld done_b %ld " 139662306a36Sopenharmony_ci "wake %ld sof %ld overrun %ld lost %ld\n\n", 139762306a36Sopenharmony_ci sl811->stat_a, sl811->stat_b, 139862306a36Sopenharmony_ci sl811->stat_wake, sl811->stat_sof, 139962306a36Sopenharmony_ci sl811->stat_overrun, sl811->stat_lost); 140062306a36Sopenharmony_ci 140162306a36Sopenharmony_ci spin_lock_irq(&sl811->lock); 140262306a36Sopenharmony_ci 140362306a36Sopenharmony_ci if (sl811->ctrl1 & SL11H_CTL1MASK_SUSPEND) 140462306a36Sopenharmony_ci seq_printf(s, "(suspended)\n\n"); 140562306a36Sopenharmony_ci else { 140662306a36Sopenharmony_ci u8 t = sl811_read(sl811, SL11H_CTLREG1); 140762306a36Sopenharmony_ci 140862306a36Sopenharmony_ci seq_printf(s, "ctrl1 %02x%s%s%s%s\n", t, 140962306a36Sopenharmony_ci (t & SL11H_CTL1MASK_SOF_ENA) ? " sofgen" : "", 141062306a36Sopenharmony_ci ({char *s; switch (t & SL11H_CTL1MASK_FORCE) { 141162306a36Sopenharmony_ci case SL11H_CTL1MASK_NORMAL: s = ""; break; 141262306a36Sopenharmony_ci case SL11H_CTL1MASK_SE0: s = " se0/reset"; break; 141362306a36Sopenharmony_ci case SL11H_CTL1MASK_K: s = " k/resume"; break; 141462306a36Sopenharmony_ci default: s = "j"; break; 141562306a36Sopenharmony_ci } s; }), 141662306a36Sopenharmony_ci (t & SL11H_CTL1MASK_LSPD) ? " lowspeed" : "", 141762306a36Sopenharmony_ci (t & SL11H_CTL1MASK_SUSPEND) ? " suspend" : ""); 141862306a36Sopenharmony_ci 141962306a36Sopenharmony_ci dump_irq(s, "irq_enable", 142062306a36Sopenharmony_ci sl811_read(sl811, SL11H_IRQ_ENABLE)); 142162306a36Sopenharmony_ci dump_irq(s, "irq_status", 142262306a36Sopenharmony_ci sl811_read(sl811, SL11H_IRQ_STATUS)); 142362306a36Sopenharmony_ci seq_printf(s, "frame clocks remaining: %d\n", 142462306a36Sopenharmony_ci sl811_read(sl811, SL11H_SOFTMRREG) << 6); 142562306a36Sopenharmony_ci } 142662306a36Sopenharmony_ci 142762306a36Sopenharmony_ci seq_printf(s, "A: qh%p ctl %02x sts %02x\n", sl811->active_a, 142862306a36Sopenharmony_ci sl811_read(sl811, SL811_EP_A(SL11H_HOSTCTLREG)), 142962306a36Sopenharmony_ci sl811_read(sl811, SL811_EP_A(SL11H_PKTSTATREG))); 143062306a36Sopenharmony_ci seq_printf(s, "B: qh%p ctl %02x sts %02x\n", sl811->active_b, 143162306a36Sopenharmony_ci sl811_read(sl811, SL811_EP_B(SL11H_HOSTCTLREG)), 143262306a36Sopenharmony_ci sl811_read(sl811, SL811_EP_B(SL11H_PKTSTATREG))); 143362306a36Sopenharmony_ci seq_printf(s, "\n"); 143462306a36Sopenharmony_ci list_for_each_entry (ep, &sl811->async, schedule) { 143562306a36Sopenharmony_ci struct urb *urb; 143662306a36Sopenharmony_ci 143762306a36Sopenharmony_ci seq_printf(s, "%s%sqh%p, ep%d%s, maxpacket %d" 143862306a36Sopenharmony_ci " nak %d err %d\n", 143962306a36Sopenharmony_ci (ep == sl811->active_a) ? "(A) " : "", 144062306a36Sopenharmony_ci (ep == sl811->active_b) ? "(B) " : "", 144162306a36Sopenharmony_ci ep, ep->epnum, 144262306a36Sopenharmony_ci ({ char *s; switch (ep->nextpid) { 144362306a36Sopenharmony_ci case USB_PID_IN: s = "in"; break; 144462306a36Sopenharmony_ci case USB_PID_OUT: s = "out"; break; 144562306a36Sopenharmony_ci case USB_PID_SETUP: s = "setup"; break; 144662306a36Sopenharmony_ci case USB_PID_ACK: s = "status"; break; 144762306a36Sopenharmony_ci default: s = "?"; break; 144862306a36Sopenharmony_ci } s;}), 144962306a36Sopenharmony_ci ep->maxpacket, 145062306a36Sopenharmony_ci ep->nak_count, ep->error_count); 145162306a36Sopenharmony_ci list_for_each_entry (urb, &ep->hep->urb_list, urb_list) { 145262306a36Sopenharmony_ci seq_printf(s, " urb%p, %d/%d\n", urb, 145362306a36Sopenharmony_ci urb->actual_length, 145462306a36Sopenharmony_ci urb->transfer_buffer_length); 145562306a36Sopenharmony_ci } 145662306a36Sopenharmony_ci } 145762306a36Sopenharmony_ci if (!list_empty(&sl811->async)) 145862306a36Sopenharmony_ci seq_printf(s, "\n"); 145962306a36Sopenharmony_ci 146062306a36Sopenharmony_ci seq_printf(s, "periodic size= %d\n", PERIODIC_SIZE); 146162306a36Sopenharmony_ci 146262306a36Sopenharmony_ci for (i = 0; i < PERIODIC_SIZE; i++) { 146362306a36Sopenharmony_ci ep = sl811->periodic[i]; 146462306a36Sopenharmony_ci if (!ep) 146562306a36Sopenharmony_ci continue; 146662306a36Sopenharmony_ci seq_printf(s, "%2d [%3d]:\n", i, sl811->load[i]); 146762306a36Sopenharmony_ci 146862306a36Sopenharmony_ci /* DUMB: prints shared entries multiple times */ 146962306a36Sopenharmony_ci do { 147062306a36Sopenharmony_ci seq_printf(s, 147162306a36Sopenharmony_ci " %s%sqh%d/%p (%sdev%d ep%d%s max %d) " 147262306a36Sopenharmony_ci "err %d\n", 147362306a36Sopenharmony_ci (ep == sl811->active_a) ? "(A) " : "", 147462306a36Sopenharmony_ci (ep == sl811->active_b) ? "(B) " : "", 147562306a36Sopenharmony_ci ep->period, ep, 147662306a36Sopenharmony_ci (ep->udev->speed == USB_SPEED_FULL) 147762306a36Sopenharmony_ci ? "" : "ls ", 147862306a36Sopenharmony_ci ep->udev->devnum, ep->epnum, 147962306a36Sopenharmony_ci (ep->epnum == 0) ? "" 148062306a36Sopenharmony_ci : ((ep->nextpid == USB_PID_IN) 148162306a36Sopenharmony_ci ? "in" 148262306a36Sopenharmony_ci : "out"), 148362306a36Sopenharmony_ci ep->maxpacket, ep->error_count); 148462306a36Sopenharmony_ci ep = ep->next; 148562306a36Sopenharmony_ci } while (ep); 148662306a36Sopenharmony_ci } 148762306a36Sopenharmony_ci 148862306a36Sopenharmony_ci spin_unlock_irq(&sl811->lock); 148962306a36Sopenharmony_ci seq_printf(s, "\n"); 149062306a36Sopenharmony_ci 149162306a36Sopenharmony_ci return 0; 149262306a36Sopenharmony_ci} 149362306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(sl811h_debug); 149462306a36Sopenharmony_ci 149562306a36Sopenharmony_ci/* expect just one sl811 per system */ 149662306a36Sopenharmony_cistatic void create_debug_file(struct sl811 *sl811) 149762306a36Sopenharmony_ci{ 149862306a36Sopenharmony_ci debugfs_create_file("sl811h", S_IRUGO, usb_debug_root, sl811, 149962306a36Sopenharmony_ci &sl811h_debug_fops); 150062306a36Sopenharmony_ci} 150162306a36Sopenharmony_ci 150262306a36Sopenharmony_cistatic void remove_debug_file(struct sl811 *sl811) 150362306a36Sopenharmony_ci{ 150462306a36Sopenharmony_ci debugfs_lookup_and_remove("sl811h", usb_debug_root); 150562306a36Sopenharmony_ci} 150662306a36Sopenharmony_ci 150762306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 150862306a36Sopenharmony_ci 150962306a36Sopenharmony_cistatic void 151062306a36Sopenharmony_cisl811h_stop(struct usb_hcd *hcd) 151162306a36Sopenharmony_ci{ 151262306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 151362306a36Sopenharmony_ci unsigned long flags; 151462306a36Sopenharmony_ci 151562306a36Sopenharmony_ci del_timer_sync(&hcd->rh_timer); 151662306a36Sopenharmony_ci 151762306a36Sopenharmony_ci spin_lock_irqsave(&sl811->lock, flags); 151862306a36Sopenharmony_ci port_power(sl811, 0); 151962306a36Sopenharmony_ci spin_unlock_irqrestore(&sl811->lock, flags); 152062306a36Sopenharmony_ci} 152162306a36Sopenharmony_ci 152262306a36Sopenharmony_cistatic int 152362306a36Sopenharmony_cisl811h_start(struct usb_hcd *hcd) 152462306a36Sopenharmony_ci{ 152562306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 152662306a36Sopenharmony_ci 152762306a36Sopenharmony_ci /* chip has been reset, VBUS power is off */ 152862306a36Sopenharmony_ci hcd->state = HC_STATE_RUNNING; 152962306a36Sopenharmony_ci 153062306a36Sopenharmony_ci if (sl811->board) { 153162306a36Sopenharmony_ci if (!device_can_wakeup(hcd->self.controller)) 153262306a36Sopenharmony_ci device_init_wakeup(hcd->self.controller, 153362306a36Sopenharmony_ci sl811->board->can_wakeup); 153462306a36Sopenharmony_ci hcd->power_budget = sl811->board->power * 2; 153562306a36Sopenharmony_ci } 153662306a36Sopenharmony_ci 153762306a36Sopenharmony_ci /* enable power and interrupts */ 153862306a36Sopenharmony_ci port_power(sl811, 1); 153962306a36Sopenharmony_ci 154062306a36Sopenharmony_ci return 0; 154162306a36Sopenharmony_ci} 154262306a36Sopenharmony_ci 154362306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 154462306a36Sopenharmony_ci 154562306a36Sopenharmony_cistatic const struct hc_driver sl811h_hc_driver = { 154662306a36Sopenharmony_ci .description = hcd_name, 154762306a36Sopenharmony_ci .hcd_priv_size = sizeof(struct sl811), 154862306a36Sopenharmony_ci 154962306a36Sopenharmony_ci /* 155062306a36Sopenharmony_ci * generic hardware linkage 155162306a36Sopenharmony_ci */ 155262306a36Sopenharmony_ci .irq = sl811h_irq, 155362306a36Sopenharmony_ci .flags = HCD_USB11 | HCD_MEMORY, 155462306a36Sopenharmony_ci 155562306a36Sopenharmony_ci /* Basic lifecycle operations */ 155662306a36Sopenharmony_ci .start = sl811h_start, 155762306a36Sopenharmony_ci .stop = sl811h_stop, 155862306a36Sopenharmony_ci 155962306a36Sopenharmony_ci /* 156062306a36Sopenharmony_ci * managing i/o requests and associated device resources 156162306a36Sopenharmony_ci */ 156262306a36Sopenharmony_ci .urb_enqueue = sl811h_urb_enqueue, 156362306a36Sopenharmony_ci .urb_dequeue = sl811h_urb_dequeue, 156462306a36Sopenharmony_ci .endpoint_disable = sl811h_endpoint_disable, 156562306a36Sopenharmony_ci 156662306a36Sopenharmony_ci /* 156762306a36Sopenharmony_ci * periodic schedule support 156862306a36Sopenharmony_ci */ 156962306a36Sopenharmony_ci .get_frame_number = sl811h_get_frame, 157062306a36Sopenharmony_ci 157162306a36Sopenharmony_ci /* 157262306a36Sopenharmony_ci * root hub support 157362306a36Sopenharmony_ci */ 157462306a36Sopenharmony_ci .hub_status_data = sl811h_hub_status_data, 157562306a36Sopenharmony_ci .hub_control = sl811h_hub_control, 157662306a36Sopenharmony_ci .bus_suspend = sl811h_bus_suspend, 157762306a36Sopenharmony_ci .bus_resume = sl811h_bus_resume, 157862306a36Sopenharmony_ci}; 157962306a36Sopenharmony_ci 158062306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/ 158162306a36Sopenharmony_ci 158262306a36Sopenharmony_cistatic void 158362306a36Sopenharmony_cisl811h_remove(struct platform_device *dev) 158462306a36Sopenharmony_ci{ 158562306a36Sopenharmony_ci struct usb_hcd *hcd = platform_get_drvdata(dev); 158662306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 158762306a36Sopenharmony_ci struct resource *res; 158862306a36Sopenharmony_ci 158962306a36Sopenharmony_ci remove_debug_file(sl811); 159062306a36Sopenharmony_ci usb_remove_hcd(hcd); 159162306a36Sopenharmony_ci 159262306a36Sopenharmony_ci /* some platforms may use IORESOURCE_IO */ 159362306a36Sopenharmony_ci res = platform_get_resource(dev, IORESOURCE_MEM, 1); 159462306a36Sopenharmony_ci if (res) 159562306a36Sopenharmony_ci iounmap(sl811->data_reg); 159662306a36Sopenharmony_ci 159762306a36Sopenharmony_ci res = platform_get_resource(dev, IORESOURCE_MEM, 0); 159862306a36Sopenharmony_ci if (res) 159962306a36Sopenharmony_ci iounmap(sl811->addr_reg); 160062306a36Sopenharmony_ci 160162306a36Sopenharmony_ci usb_put_hcd(hcd); 160262306a36Sopenharmony_ci} 160362306a36Sopenharmony_ci 160462306a36Sopenharmony_cistatic int 160562306a36Sopenharmony_cisl811h_probe(struct platform_device *dev) 160662306a36Sopenharmony_ci{ 160762306a36Sopenharmony_ci struct usb_hcd *hcd; 160862306a36Sopenharmony_ci struct sl811 *sl811; 160962306a36Sopenharmony_ci struct resource *addr, *data, *ires; 161062306a36Sopenharmony_ci int irq; 161162306a36Sopenharmony_ci void __iomem *addr_reg; 161262306a36Sopenharmony_ci void __iomem *data_reg; 161362306a36Sopenharmony_ci int retval; 161462306a36Sopenharmony_ci u8 tmp, ioaddr; 161562306a36Sopenharmony_ci unsigned long irqflags; 161662306a36Sopenharmony_ci 161762306a36Sopenharmony_ci if (usb_disabled()) 161862306a36Sopenharmony_ci return -ENODEV; 161962306a36Sopenharmony_ci 162062306a36Sopenharmony_ci /* the chip may be wired for either kind of addressing */ 162162306a36Sopenharmony_ci addr = platform_get_mem_or_io(dev, 0); 162262306a36Sopenharmony_ci data = platform_get_mem_or_io(dev, 1); 162362306a36Sopenharmony_ci if (!addr || !data || resource_type(addr) != resource_type(data)) 162462306a36Sopenharmony_ci return -ENODEV; 162562306a36Sopenharmony_ci 162662306a36Sopenharmony_ci /* basic sanity checks first. board-specific init logic should 162762306a36Sopenharmony_ci * have initialized these three resources and probably board 162862306a36Sopenharmony_ci * specific platform_data. we don't probe for IRQs, and do only 162962306a36Sopenharmony_ci * minimal sanity checking. 163062306a36Sopenharmony_ci */ 163162306a36Sopenharmony_ci ires = platform_get_resource(dev, IORESOURCE_IRQ, 0); 163262306a36Sopenharmony_ci if (dev->num_resources < 3 || !ires) 163362306a36Sopenharmony_ci return -ENODEV; 163462306a36Sopenharmony_ci 163562306a36Sopenharmony_ci irq = ires->start; 163662306a36Sopenharmony_ci irqflags = ires->flags & IRQF_TRIGGER_MASK; 163762306a36Sopenharmony_ci 163862306a36Sopenharmony_ci ioaddr = resource_type(addr) == IORESOURCE_IO; 163962306a36Sopenharmony_ci if (ioaddr) { 164062306a36Sopenharmony_ci /* 164162306a36Sopenharmony_ci * NOTE: 64-bit resource->start is getting truncated 164262306a36Sopenharmony_ci * to avoid compiler warning, assuming that ->start 164362306a36Sopenharmony_ci * is always 32-bit for this case 164462306a36Sopenharmony_ci */ 164562306a36Sopenharmony_ci addr_reg = (void __iomem *) (unsigned long) addr->start; 164662306a36Sopenharmony_ci data_reg = (void __iomem *) (unsigned long) data->start; 164762306a36Sopenharmony_ci } else { 164862306a36Sopenharmony_ci addr_reg = ioremap(addr->start, 1); 164962306a36Sopenharmony_ci if (addr_reg == NULL) { 165062306a36Sopenharmony_ci retval = -ENOMEM; 165162306a36Sopenharmony_ci goto err2; 165262306a36Sopenharmony_ci } 165362306a36Sopenharmony_ci 165462306a36Sopenharmony_ci data_reg = ioremap(data->start, 1); 165562306a36Sopenharmony_ci if (data_reg == NULL) { 165662306a36Sopenharmony_ci retval = -ENOMEM; 165762306a36Sopenharmony_ci goto err4; 165862306a36Sopenharmony_ci } 165962306a36Sopenharmony_ci } 166062306a36Sopenharmony_ci 166162306a36Sopenharmony_ci /* allocate and initialize hcd */ 166262306a36Sopenharmony_ci hcd = usb_create_hcd(&sl811h_hc_driver, &dev->dev, dev_name(&dev->dev)); 166362306a36Sopenharmony_ci if (!hcd) { 166462306a36Sopenharmony_ci retval = -ENOMEM; 166562306a36Sopenharmony_ci goto err5; 166662306a36Sopenharmony_ci } 166762306a36Sopenharmony_ci hcd->rsrc_start = addr->start; 166862306a36Sopenharmony_ci sl811 = hcd_to_sl811(hcd); 166962306a36Sopenharmony_ci 167062306a36Sopenharmony_ci spin_lock_init(&sl811->lock); 167162306a36Sopenharmony_ci INIT_LIST_HEAD(&sl811->async); 167262306a36Sopenharmony_ci sl811->board = dev_get_platdata(&dev->dev); 167362306a36Sopenharmony_ci timer_setup(&sl811->timer, sl811h_timer, 0); 167462306a36Sopenharmony_ci sl811->addr_reg = addr_reg; 167562306a36Sopenharmony_ci sl811->data_reg = data_reg; 167662306a36Sopenharmony_ci 167762306a36Sopenharmony_ci spin_lock_irq(&sl811->lock); 167862306a36Sopenharmony_ci port_power(sl811, 0); 167962306a36Sopenharmony_ci spin_unlock_irq(&sl811->lock); 168062306a36Sopenharmony_ci msleep(200); 168162306a36Sopenharmony_ci 168262306a36Sopenharmony_ci tmp = sl811_read(sl811, SL11H_HWREVREG); 168362306a36Sopenharmony_ci switch (tmp >> 4) { 168462306a36Sopenharmony_ci case 1: 168562306a36Sopenharmony_ci hcd->product_desc = "SL811HS v1.2"; 168662306a36Sopenharmony_ci break; 168762306a36Sopenharmony_ci case 2: 168862306a36Sopenharmony_ci hcd->product_desc = "SL811HS v1.5"; 168962306a36Sopenharmony_ci break; 169062306a36Sopenharmony_ci default: 169162306a36Sopenharmony_ci /* reject case 0, SL11S is less functional */ 169262306a36Sopenharmony_ci dev_dbg(&dev->dev, "chiprev %02x\n", tmp); 169362306a36Sopenharmony_ci retval = -ENXIO; 169462306a36Sopenharmony_ci goto err6; 169562306a36Sopenharmony_ci } 169662306a36Sopenharmony_ci 169762306a36Sopenharmony_ci /* The chip's IRQ is level triggered, active high. A requirement 169862306a36Sopenharmony_ci * for platform device setup is to cope with things like signal 169962306a36Sopenharmony_ci * inverters (e.g. CF is active low) or working only with edge 170062306a36Sopenharmony_ci * triggers (e.g. most ARM CPUs). Initial driver stress testing 170162306a36Sopenharmony_ci * was on a system with single edge triggering, so most sorts of 170262306a36Sopenharmony_ci * triggering arrangement should work. 170362306a36Sopenharmony_ci * 170462306a36Sopenharmony_ci * Use resource IRQ flags if set by platform device setup. 170562306a36Sopenharmony_ci */ 170662306a36Sopenharmony_ci irqflags |= IRQF_SHARED; 170762306a36Sopenharmony_ci retval = usb_add_hcd(hcd, irq, irqflags); 170862306a36Sopenharmony_ci if (retval != 0) 170962306a36Sopenharmony_ci goto err6; 171062306a36Sopenharmony_ci 171162306a36Sopenharmony_ci device_wakeup_enable(hcd->self.controller); 171262306a36Sopenharmony_ci 171362306a36Sopenharmony_ci create_debug_file(sl811); 171462306a36Sopenharmony_ci return retval; 171562306a36Sopenharmony_ci 171662306a36Sopenharmony_ci err6: 171762306a36Sopenharmony_ci usb_put_hcd(hcd); 171862306a36Sopenharmony_ci err5: 171962306a36Sopenharmony_ci if (!ioaddr) 172062306a36Sopenharmony_ci iounmap(data_reg); 172162306a36Sopenharmony_ci err4: 172262306a36Sopenharmony_ci if (!ioaddr) 172362306a36Sopenharmony_ci iounmap(addr_reg); 172462306a36Sopenharmony_ci err2: 172562306a36Sopenharmony_ci dev_dbg(&dev->dev, "init error, %d\n", retval); 172662306a36Sopenharmony_ci return retval; 172762306a36Sopenharmony_ci} 172862306a36Sopenharmony_ci 172962306a36Sopenharmony_ci#ifdef CONFIG_PM 173062306a36Sopenharmony_ci 173162306a36Sopenharmony_ci/* for this device there's no useful distinction between the controller 173262306a36Sopenharmony_ci * and its root hub. 173362306a36Sopenharmony_ci */ 173462306a36Sopenharmony_ci 173562306a36Sopenharmony_cistatic int 173662306a36Sopenharmony_cisl811h_suspend(struct platform_device *dev, pm_message_t state) 173762306a36Sopenharmony_ci{ 173862306a36Sopenharmony_ci struct usb_hcd *hcd = platform_get_drvdata(dev); 173962306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 174062306a36Sopenharmony_ci int retval = 0; 174162306a36Sopenharmony_ci 174262306a36Sopenharmony_ci switch (state.event) { 174362306a36Sopenharmony_ci case PM_EVENT_FREEZE: 174462306a36Sopenharmony_ci retval = sl811h_bus_suspend(hcd); 174562306a36Sopenharmony_ci break; 174662306a36Sopenharmony_ci case PM_EVENT_SUSPEND: 174762306a36Sopenharmony_ci case PM_EVENT_HIBERNATE: 174862306a36Sopenharmony_ci case PM_EVENT_PRETHAW: /* explicitly discard hw state */ 174962306a36Sopenharmony_ci port_power(sl811, 0); 175062306a36Sopenharmony_ci break; 175162306a36Sopenharmony_ci } 175262306a36Sopenharmony_ci return retval; 175362306a36Sopenharmony_ci} 175462306a36Sopenharmony_ci 175562306a36Sopenharmony_cistatic int 175662306a36Sopenharmony_cisl811h_resume(struct platform_device *dev) 175762306a36Sopenharmony_ci{ 175862306a36Sopenharmony_ci struct usb_hcd *hcd = platform_get_drvdata(dev); 175962306a36Sopenharmony_ci struct sl811 *sl811 = hcd_to_sl811(hcd); 176062306a36Sopenharmony_ci 176162306a36Sopenharmony_ci /* with no "check to see if VBUS is still powered" board hook, 176262306a36Sopenharmony_ci * let's assume it'd only be powered to enable remote wakeup. 176362306a36Sopenharmony_ci */ 176462306a36Sopenharmony_ci if (!sl811->port1 || !device_can_wakeup(&hcd->self.root_hub->dev)) { 176562306a36Sopenharmony_ci sl811->port1 = 0; 176662306a36Sopenharmony_ci port_power(sl811, 1); 176762306a36Sopenharmony_ci usb_root_hub_lost_power(hcd->self.root_hub); 176862306a36Sopenharmony_ci return 0; 176962306a36Sopenharmony_ci } 177062306a36Sopenharmony_ci 177162306a36Sopenharmony_ci return sl811h_bus_resume(hcd); 177262306a36Sopenharmony_ci} 177362306a36Sopenharmony_ci 177462306a36Sopenharmony_ci#else 177562306a36Sopenharmony_ci 177662306a36Sopenharmony_ci#define sl811h_suspend NULL 177762306a36Sopenharmony_ci#define sl811h_resume NULL 177862306a36Sopenharmony_ci 177962306a36Sopenharmony_ci#endif 178062306a36Sopenharmony_ci 178162306a36Sopenharmony_ci 178262306a36Sopenharmony_ci/* this driver is exported so sl811_cs can depend on it */ 178362306a36Sopenharmony_cistruct platform_driver sl811h_driver = { 178462306a36Sopenharmony_ci .probe = sl811h_probe, 178562306a36Sopenharmony_ci .remove_new = sl811h_remove, 178662306a36Sopenharmony_ci 178762306a36Sopenharmony_ci .suspend = sl811h_suspend, 178862306a36Sopenharmony_ci .resume = sl811h_resume, 178962306a36Sopenharmony_ci .driver = { 179062306a36Sopenharmony_ci .name = hcd_name, 179162306a36Sopenharmony_ci }, 179262306a36Sopenharmony_ci}; 179362306a36Sopenharmony_ciEXPORT_SYMBOL(sl811h_driver); 179462306a36Sopenharmony_ci 179562306a36Sopenharmony_cimodule_platform_driver(sl811h_driver); 1796