virthub.c 10.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
/*****************************************************************
 * Copyright 2005 Mentor Graphics Corporation
 * Copyright (C) 2005-2006 by Texas Instruments
 * Copyright (C) 2006 by Nokia Corporation
 *
 * This file is part of the Inventra Controller Driver for Linux.
 *
 * The Inventra Controller Driver for Linux is free software; you
 * can redistribute it and/or modify it under the terms of the GNU
 * General Public License version 2 as published by the Free Software
 * Foundation.
 *
 * The Inventra Controller Driver for Linux is distributed in
 * the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with The Inventra Controller Driver for Linux ; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston, MA  02111-1307  USA
 *
 * ANY DOWNLOAD, USE, REPRODUCTION, MODIFICATION OR DISTRIBUTION
 * OF THIS DRIVER INDICATES YOUR COMPLETE AND UNCONDITIONAL ACCEPTANCE
 * OF THOSE TERMS.THIS DRIVER IS PROVIDED "AS IS" AND MENTOR GRAPHICS
 * MAKES NO WARRANTIES, EXPRESS OR IMPLIED, RELATED TO THIS DRIVER.
 * MENTOR GRAPHICS SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY; FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT.  MENTOR GRAPHICS DOES NOT PROVIDE SUPPORT
 * SERVICES OR UPDATES FOR THIS DRIVER, EVEN IF YOU ARE A MENTOR
 * GRAPHICS SUPPORT CUSTOMER.
 ******************************************************************/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/timer.h>

#include "musbdefs.h"


static void musb_port_suspend(struct musb *musb, u8 bSuspend)
{
	u8		power;
	void __iomem	*pBase = musb->pRegs;

David Brownell's avatar
David Brownell committed
52 53 54
	if (!is_host_active(musb))
		return;

55 56 57 58 59
	/* NOTE:  this doesn't necessarily put PHY into low power mode,
	 * turning off its clock; that's a function of PHY integration and
	 * MGC_M_POWER_ENSUSPEND.  PHY may need a clock (sigh) to detect
	 * SE0 changing to connect (J) or wakeup (K) states.
	 */
60 61
	power = musb_readb(pBase, MGC_O_HDRC_POWER);
	if (bSuspend) {
62 63 64 65 66 67
		power &= ~MGC_M_POWER_RESUME;
		power |= MGC_M_POWER_SUSPENDM;
		musb_writeb(pBase, MGC_O_HDRC_POWER, power);

		DBG(3, "Root port suspended, power %02x\n", power);

68
		musb->port1_status |= USB_PORT_STAT_SUSPEND;
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
		switch (musb->xceiv.state) {
		case OTG_STATE_A_HOST:
			musb->xceiv.state = OTG_STATE_A_SUSPEND;
			musb->is_active = is_otg_enabled(musb)
					&& musb->xceiv.host->b_hnp_enable;
			musb_platform_try_idle(musb);
			break;
		case OTG_STATE_B_HOST:
			musb->xceiv.state = OTG_STATE_B_PERIPHERAL;
			MUSB_DEV_MODE(musb);
			/* REVISIT restore setting of MGC_M_DEVCTL_HR */
			break;
		default:
			DBG(1, "bogus rh suspend? %s\n",
				otg_state_string(musb));
		}
85
	} else if (power & MGC_M_POWER_SUSPENDM) {
86 87
		power &= ~MGC_M_POWER_SUSPENDM;
		power |= MGC_M_POWER_RESUME;
88
		musb_writeb(pBase, MGC_O_HDRC_POWER, power);
89 90 91 92 93 94

		DBG(3, "Root port resuming, power %02x\n", power);

		/* later, GetPortStatus will stop RESUME signaling */
		musb->port1_status |= MUSB_PORT_STAT_RESUME;
		musb->rh_timer = jiffies + msecs_to_jiffies(20);
95 96 97 98 99 100 101 102 103 104 105 106 107
	}
}

static void musb_port_reset(struct musb *musb, u8 bReset)
{
	u8		power;
	void __iomem	*pBase = musb->pRegs;

#ifdef CONFIG_USB_MUSB_OTG
	/* REVISIT this looks wrong for HNP */
	u8 devctl = musb_readb(pBase, MGC_O_HDRC_DEVCTL);

	if (musb->bDelayPortPowerOff || !(devctl & MGC_M_DEVCTL_HM)) {
108
		return;
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
	}
#endif

	if (!is_host_active(musb))
		return;

	/* NOTE:  caller guarantees it will turn off the reset when
	 * the appropriate amount of time has passed
	 */
	power = musb_readb(pBase, MGC_O_HDRC_POWER);
	if (bReset) {
		musb->bIgnoreDisconnect = TRUE;
		power &= 0xf0;
		musb_writeb(pBase, MGC_O_HDRC_POWER,
				power | MGC_M_POWER_RESET);

		musb->port1_status |= USB_PORT_STAT_RESET;
		musb->port1_status &= ~USB_PORT_STAT_ENABLE;
		musb->rh_timer = jiffies + msecs_to_jiffies(50);
	} else {
		DBG(4, "root port reset stopped\n");
		musb_writeb(pBase, MGC_O_HDRC_POWER,
				power & ~MGC_M_POWER_RESET);

		musb->bIgnoreDisconnect = FALSE;

		power = musb_readb(pBase, MGC_O_HDRC_POWER);
		if (power & MGC_M_POWER_HSMODE) {
			DBG(4, "high-speed device connected\n");
			musb->port1_status |= USB_PORT_STAT_HIGH_SPEED;
		}

		musb->port1_status &= ~USB_PORT_STAT_RESET;
		musb->port1_status |= USB_PORT_STAT_ENABLE
					| (USB_PORT_STAT_C_RESET << 16)
					| (USB_PORT_STAT_C_ENABLE << 16);
		usb_hcd_poll_rh_status(musb_to_hcd(musb));

147
		musb->vbuserr_retry = VBUSERR_RETRY_COUNT;
148 149 150 151 152
	}
}

void musb_root_disconnect(struct musb *musb)
{
153 154 155
	musb->port1_status = (1 << USB_PORT_FEAT_POWER)
			| (1 << USB_PORT_FEAT_C_CONNECTION);

156
	usb_hcd_poll_rh_status(musb_to_hcd(musb));
David Brownell's avatar
David Brownell committed
157
	musb->is_active = 0;
158 159 160

	switch (musb->xceiv.state) {
	case OTG_STATE_A_HOST:
161
	case OTG_STATE_A_SUSPEND:
162 163 164 165 166 167
		musb->xceiv.state = OTG_STATE_A_WAIT_BCON;
		break;
	case OTG_STATE_A_WAIT_VFALL:
		musb->xceiv.state = OTG_STATE_B_IDLE;
		break;
	default:
168
		DBG(1, "host disconnect (%s)\n", otg_state_string(musb));
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
	}
}


/*---------------------------------------------------------------------*/

int musb_hub_status_data(struct usb_hcd *hcd, char *buf)
{
	struct musb	*musb = hcd_to_musb(hcd);
	int		retval = 0;

	/* called in_irq() via usb_hcd_poll_rh_status() */
	if (musb->port1_status & 0xffff0000) {
		*buf = 0x02;
		retval = 1;
	}
	return retval;
}

int musb_hub_control(
	struct usb_hcd	*hcd,
	u16		typeReq,
	u16		wValue,
	u16		wIndex,
	char		*buf,
	u16		wLength)
{
	struct musb	*musb = hcd_to_musb(hcd);
	u32		temp;
	int		retval = 0;
	unsigned long	flags;

David Brownell's avatar
David Brownell committed
201
	if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
202 203 204
		return -ESHUTDOWN;

	/* hub features:  always zero, setting is a NOP
David Brownell's avatar
David Brownell committed
205
	 * port features: reported, sometimes updated when host is active
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
	 * no indicators
	 */
	spin_lock_irqsave(&musb->Lock, flags);
	switch (typeReq) {
	case ClearHubFeature:
	case SetHubFeature:
		switch (wValue) {
		case C_HUB_OVER_CURRENT:
		case C_HUB_LOCAL_POWER:
			break;
		default:
			goto error;
		}
		break;
	case ClearPortFeature:
		if (wIndex != 1)
			goto error;

		switch (wValue) {
		case USB_PORT_FEAT_ENABLE:
			break;
		case USB_PORT_FEAT_SUSPEND:
			musb_port_suspend(musb, FALSE);
			break;
		case USB_PORT_FEAT_POWER:
231 232 233
			if (!(is_otg_enabled(musb) && hcd->self.is_b_host))
				musb_set_vbus(musb, 0);
			break;
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
		case USB_PORT_FEAT_C_CONNECTION:
		case USB_PORT_FEAT_C_ENABLE:
		case USB_PORT_FEAT_C_OVER_CURRENT:
		case USB_PORT_FEAT_C_RESET:
		case USB_PORT_FEAT_C_SUSPEND:
			break;
		default:
			goto error;
		}
		DBG(5, "clear feature %d\n", wValue);
		musb->port1_status &= ~(1 << wValue);
		break;
	case GetHubDescriptor:
		{
		struct usb_hub_descriptor *desc = (void *)buf;

		desc->bDescLength = 9;
		desc->bDescriptorType = 0x29;
		desc->bNbrPorts = 1;
		desc->wHubCharacteristics = __constant_cpu_to_le16(
				  0x0001	/* per-port power switching */
				| 0x0010	/* no overcurrent reporting */
				);
		desc->bPwrOn2PwrGood = 5;	/* msec/2 */
		desc->bHubContrCurrent = 0;

		/* workaround bogus struct definition */
		desc->DeviceRemovable[0] = 0x02;	/* port 1 */
		desc->DeviceRemovable[1] = 0xff;
		}
		break;
	case GetHubStatus:
		temp = 0;
		*(__le32 *) buf = cpu_to_le32 (temp);
		break;
	case GetPortStatus:
		if (wIndex != 1)
			goto error;

273
		/* finish RESET signaling? */
274 275 276 277
		if ((musb->port1_status & USB_PORT_STAT_RESET)
				&& time_after(jiffies, musb->rh_timer))
			musb_port_reset(musb, FALSE);

278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
		/* finish RESUME signaling? */
		if ((musb->port1_status & MUSB_PORT_STAT_RESUME)
				&& time_after(jiffies, musb->rh_timer)) {
			u8		power;

			power = musb_readb(musb->pRegs, MGC_O_HDRC_POWER);
			power &= ~MGC_M_POWER_RESUME;
			DBG(4, "root port resume stopped, power %02x\n",
					power);
			musb_writeb(musb->pRegs, MGC_O_HDRC_POWER, power);

			/* ISSUE:  DaVinci (RTL 1.300) disconnects after
			 * resume of high speed peripherals (but not full
			 * speed ones).
			 */

			musb->is_active = 1;
			musb->port1_status &= ~(USB_PORT_STAT_SUSPEND
					| MUSB_PORT_STAT_RESUME);
			musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16;
			usb_hcd_poll_rh_status(musb_to_hcd(musb));
			/* NOTE: it might really be A_WAIT_BCON ... */
			musb->xceiv.state = OTG_STATE_A_HOST;
		}

		*(__le32 *) buf = cpu_to_le32(musb->port1_status
				& ~MUSB_PORT_STAT_RESUME);

306 307 308 309 310
		/* port change status is more interesting */
		DBG((*(u16*)(buf+2)) ? 2 : 5, "port status %08x\n",
				musb->port1_status);
		break;
	case SetPortFeature:
311
		if ((wIndex & 0xff) != 1)
312 313 314 315 316 317 318 319 320 321 322 323 324 325
			goto error;

		switch (wValue) {
		case USB_PORT_FEAT_POWER:
			/* NOTE: this controller has a strange state machine
			 * that involves "requesting sessions" according to
			 * magic side effects from incompletely-described
			 * rules about startup...
			 *
			 * This call is what really starts the host mode; be
			 * very careful about side effects if you reorder any
			 * initialization logic, e.g. for OTG, or change any
			 * logic relating to VBUS power-up.
			 */
David Brownell's avatar
David Brownell committed
326 327
			if (!(is_otg_enabled(musb) && hcd->self.is_b_host))
				musb_start(musb);
328 329 330 331 332 333 334 335
			break;
		case USB_PORT_FEAT_RESET:
			musb_port_reset(musb, TRUE);
			break;
		case USB_PORT_FEAT_SUSPEND:
			musb_port_suspend(musb, TRUE);
			break;
		case USB_PORT_FEAT_TEST:
David Brownell's avatar
David Brownell committed
336 337 338
			if (unlikely(is_host_active(musb)))
				goto error;

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
			wIndex >>= 8;
			switch (wIndex) {
			case 1:
				pr_debug("TEST_J\n");
				temp = MGC_M_TEST_J;
				break;
			case 2:
				pr_debug("TEST_K\n");
				temp = MGC_M_TEST_K;
				break;
			case 3:
				pr_debug("TEST_SE0_NAK\n");
				temp = MGC_M_TEST_SE0_NAK;
				break;
			case 4:
				pr_debug("TEST_PACKET\n");
				temp = MGC_M_TEST_PACKET;
				musb_load_testpacket(musb);
				break;
			case 5:
				pr_debug("TEST_FORCE_ENABLE\n");
				temp = MGC_M_TEST_FORCE_HOST
					| MGC_M_TEST_FORCE_HS;

				/* FIXME and enable a session too */
				break;
			default:
				goto error;
			}
			musb_writeb(musb->pRegs, MGC_O_HDRC_TESTMODE, temp);
369 370 371 372 373
			break;
		default:
			goto error;
		}
		DBG(5, "set feature %d\n", wValue);
David Brownell's avatar
David Brownell committed
374
		musb->port1_status |= 1 << wValue;
375 376 377 378 379 380 381 382 383 384
		break;

	default:
error:
		/* "protocol stall" on error */
		retval = -EPIPE;
	}
	spin_unlock_irqrestore(&musb->Lock, flags);
	return retval;
}