ohci-omap.c 13.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6
/*
 * OHCI HCD (Host Controller Driver) for USB.
 *
 * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
 * (C) Copyright 2000-2005 David Brownell
 * (C) Copyright 2002 Hewlett-Packard Company
D
David Brownell 已提交
7
 *
L
Linus Torvalds 已提交
8 9 10 11 12 13 14 15 16
 * OMAP Bus Glue
 *
 * Modified for OMAP by Tony Lindgren <tony@atomide.com>
 * Based on the 2.4 OMAP OHCI driver originally done by MontaVista Software Inc.
 * and on ohci-sa1111.c by Christopher Hoover <ch@hpl.hp.com>
 *
 * This file is licenced under the GPL.
 */

17
#include <linux/signal.h>	/* IRQF_DISABLED */
T
Tim Schmielau 已提交
18
#include <linux/jiffies.h>
19
#include <linux/platform_device.h>
20
#include <linux/clk.h>
T
Tim Schmielau 已提交
21

L
Linus Torvalds 已提交
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/mach-types.h>

#include <asm/arch/mux.h>
#include <asm/arch/irqs.h>
#include <asm/arch/gpio.h>
#include <asm/arch/fpga.h>
#include <asm/arch/usb.h>


/* OMAP-1510 OHCI has its own MMU for DMA */
#define OMAP1510_LB_MEMSIZE	32	/* Should be same as SDRAM size */
#define OMAP1510_LB_CLOCK_DIV	0xfffec10c
#define OMAP1510_LB_MMU_CTL	0xfffec208
#define OMAP1510_LB_MMU_LCK	0xfffec224
#define OMAP1510_LB_MMU_LD_TLB	0xfffec228
#define OMAP1510_LB_MMU_CAM_H	0xfffec22c
#define OMAP1510_LB_MMU_CAM_L	0xfffec230
#define OMAP1510_LB_MMU_RAM_H	0xfffec234
#define OMAP1510_LB_MMU_RAM_L	0xfffec238


#ifndef CONFIG_ARCH_OMAP
#error "This file is OMAP bus glue.  CONFIG_OMAP must be defined."
#endif

#ifdef CONFIG_TPS65010
#include <asm/arch/tps65010.h>
#else

#define LOW	0
#define HIGH	1

#define GPIO1	1

static inline int tps65010_set_gpio_out_value(unsigned gpio, unsigned value)
{
	return 0;
}

#endif

extern int usb_disabled(void);
extern int ocpi_enable(void);

static struct clk *usb_host_ck;
D
David Brownell 已提交
69 70 71
static struct clk *usb_dc_ck;
static int host_enabled;
static int host_initialized;
L
Linus Torvalds 已提交
72 73 74 75

static void omap_ohci_clock_power(int on)
{
	if (on) {
D
David Brownell 已提交
76
		clk_enable(usb_dc_ck);
L
Linus Torvalds 已提交
77 78 79 80 81
		clk_enable(usb_host_ck);
		/* guesstimate for T5 == 1x 32K clock + APLL lock time */
		udelay(100);
	} else {
		clk_disable(usb_host_ck);
D
David Brownell 已提交
82
		clk_disable(usb_dc_ck);
L
Linus Torvalds 已提交
83 84 85 86 87 88 89 90 91 92 93 94
	}
}

/*
 * Board specific gang-switched transceiver power on/off.
 * NOTE:  OSK supplies power from DC, not battery.
 */
static int omap_ohci_transceiver_power(int on)
{
	if (on) {
		if (machine_is_omap_innovator() && cpu_is_omap1510())
			fpga_write(fpga_read(INNOVATOR_FPGA_CAM_USB_CONTROL)
D
David Brownell 已提交
95
				| ((1 << 5/*usb1*/) | (1 << 3/*usb2*/)),
L
Linus Torvalds 已提交
96 97 98 99 100 101
			       INNOVATOR_FPGA_CAM_USB_CONTROL);
		else if (machine_is_omap_osk())
			tps65010_set_gpio_out_value(GPIO1, LOW);
	} else {
		if (machine_is_omap_innovator() && cpu_is_omap1510())
			fpga_write(fpga_read(INNOVATOR_FPGA_CAM_USB_CONTROL)
D
David Brownell 已提交
102
				& ~((1 << 5/*usb1*/) | (1 << 3/*usb2*/)),
L
Linus Torvalds 已提交
103 104 105 106 107 108 109 110
			       INNOVATOR_FPGA_CAM_USB_CONTROL);
		else if (machine_is_omap_osk())
			tps65010_set_gpio_out_value(GPIO1, HIGH);
	}

	return 0;
}

D
David Brownell 已提交
111
#ifdef CONFIG_ARCH_OMAP15XX
L
Linus Torvalds 已提交
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
/*
 * OMAP-1510 specific Local Bus clock on/off
 */
static int omap_1510_local_bus_power(int on)
{
	if (on) {
		omap_writel((1 << 1) | (1 << 0), OMAP1510_LB_MMU_CTL);
		udelay(200);
	} else {
		omap_writel(0, OMAP1510_LB_MMU_CTL);
	}

	return 0;
}

/*
 * OMAP-1510 specific Local Bus initialization
 * NOTE: This assumes 32MB memory size in OMAP1510LB_MEMSIZE.
D
David Brownell 已提交
130 131
 *       See also arch/mach-omap/memory.h for __virt_to_dma() and
 *       __dma_to_virt() which need to match with the physical
L
Linus Torvalds 已提交
132 133 134 135 136 137 138
 *       Local Bus address below.
 */
static int omap_1510_local_bus_init(void)
{
	unsigned int tlb;
	unsigned long lbaddr, physaddr;

D
David Brownell 已提交
139
	omap_writel((omap_readl(OMAP1510_LB_CLOCK_DIV) & 0xfffffff8) | 0x4,
L
Linus Torvalds 已提交
140 141 142 143 144 145 146
	       OMAP1510_LB_CLOCK_DIV);

	/* Configure the Local Bus MMU table */
	for (tlb = 0; tlb < OMAP1510_LB_MEMSIZE; tlb++) {
		lbaddr = tlb * 0x00100000 + OMAP1510_LB_OFFSET;
		physaddr = tlb * 0x00100000 + PHYS_OFFSET;
		omap_writel((lbaddr & 0x0fffffff) >> 22, OMAP1510_LB_MMU_CAM_H);
D
David Brownell 已提交
147
		omap_writel(((lbaddr & 0x003ffc00) >> 6) | 0xc,
L
Linus Torvalds 已提交
148 149 150 151 152 153 154 155 156 157 158 159 160
		       OMAP1510_LB_MMU_CAM_L);
		omap_writel(physaddr >> 16, OMAP1510_LB_MMU_RAM_H);
		omap_writel((physaddr & 0x0000fc00) | 0x300, OMAP1510_LB_MMU_RAM_L);
		omap_writel(tlb << 4, OMAP1510_LB_MMU_LCK);
		omap_writel(0x1, OMAP1510_LB_MMU_LD_TLB);
	}

	/* Enable the walking table */
	omap_writel(omap_readl(OMAP1510_LB_MMU_CTL) | (1 << 3), OMAP1510_LB_MMU_CTL);
	udelay(200);

	return 0;
}
D
David Brownell 已提交
161 162 163 164
#else
#define omap_1510_local_bus_power(x)	{}
#define omap_1510_local_bus_init()	{}
#endif
L
Linus Torvalds 已提交
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185

#ifdef	CONFIG_USB_OTG

static void start_hnp(struct ohci_hcd *ohci)
{
	const unsigned	port = ohci_to_hcd(ohci)->self.otg_port - 1;
	unsigned long	flags;

	otg_start_hnp(ohci->transceiver);

	local_irq_save(flags);
	ohci->transceiver->state = OTG_STATE_A_SUSPEND;
	writel (RH_PS_PSS, &ohci->regs->roothub.portstatus [port]);
	OTG_CTRL_REG &= ~OTG_A_BUSREQ;
	local_irq_restore(flags);
}

#endif

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

D
David Brownell 已提交
186
static int ohci_omap_init(struct usb_hcd *hcd)
L
Linus Torvalds 已提交
187
{
D
David Brownell 已提交
188 189
	struct ohci_hcd		*ohci = hcd_to_ohci(hcd);
	struct omap_usb_config	*config = hcd->self.controller->platform_data;
L
Linus Torvalds 已提交
190 191 192
	int			need_transceiver = (config->otg != 0);
	int			ret;

D
David Brownell 已提交
193
	dev_dbg(hcd->self.controller, "starting USB Controller\n");
L
Linus Torvalds 已提交
194 195 196 197

	if (config->otg) {
		ohci_to_hcd(ohci)->self.otg_port = config->otg;
		/* default/minimum OTG power budget:  8 mA */
198
		ohci_to_hcd(ohci)->power_budget = 8;
L
Linus Torvalds 已提交
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
	}

	/* boards can use OTG transceivers in non-OTG modes */
	need_transceiver = need_transceiver
			|| machine_is_omap_h2() || machine_is_omap_h3();

	if (cpu_is_omap16xx())
		ocpi_enable();

#ifdef	CONFIG_ARCH_OMAP_OTG
	if (need_transceiver) {
		ohci->transceiver = otg_get_transceiver();
		if (ohci->transceiver) {
			int	status = otg_set_host(ohci->transceiver,
						&ohci_to_hcd(ohci)->self);
D
David Brownell 已提交
214
			dev_dbg(hcd->self.controller, "init %s transceiver, status %d\n",
L
Linus Torvalds 已提交
215 216 217 218 219 220 221
					ohci->transceiver->label, status);
			if (status) {
				if (ohci->transceiver)
					put_device(ohci->transceiver->dev);
				return status;
			}
		} else {
D
David Brownell 已提交
222
			dev_err(hcd->self.controller, "can't find transceiver\n");
L
Linus Torvalds 已提交
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
			return -ENODEV;
		}
	}
#endif

	omap_ohci_clock_power(1);

	if (cpu_is_omap1510()) {
		omap_1510_local_bus_power(1);
		omap_1510_local_bus_init();
	}

	if ((ret = ohci_init(ohci)) < 0)
		return ret;

	/* board-specific power switching and overcurrent support */
	if (machine_is_omap_osk() || machine_is_omap_innovator()) {
		u32	rh = roothub_a (ohci);

		/* power switching (ganged by default) */
		rh &= ~RH_A_NPS;

		/* TPS2045 switch for internal transceiver (port 1) */
		if (machine_is_omap_osk()) {
247
			ohci_to_hcd(ohci)->power_budget = 250;
L
Linus Torvalds 已提交
248 249 250 251 252 253 254 255 256 257 258 259 260

			rh &= ~RH_A_NOCP;

			/* gpio9 for overcurrent detction */
			omap_cfg_reg(W8_1610_GPIO9);
			omap_request_gpio(9);
			omap_set_gpio_direction(9, 1 /* IN */);

			/* for paranoia's sake:  disable USB.PUEN */
			omap_cfg_reg(W4_USB_HIGHZ);
		}
		ohci_writel(ohci, rh, &ohci->regs->roothub.a);
		distrust_firmware = 0;
D
David Brownell 已提交
261 262 263 264
	} else if (machine_is_nokia770()) {
		/* We require a self-powered hub, which should have
		 * plenty of power. */
		ohci_to_hcd(ohci)->power_budget = 0;
L
Linus Torvalds 已提交
265 266 267 268 269 270 271 272 273 274 275 276 277
	}

	/* FIXME khubd hub requests should manage power switching */
	omap_ohci_transceiver_power(1);

	/* board init will have already handled HMC and mux setup.
	 * any external transceiver should already be initialized
	 * too, so all configured ports use the right signaling now.
	 */

	return 0;
}

D
David Brownell 已提交
278
static void ohci_omap_stop(struct usb_hcd *hcd)
L
Linus Torvalds 已提交
279
{
D
David Brownell 已提交
280
	dev_dbg(hcd->self.controller, "stopping USB Controller\n");
L
Linus Torvalds 已提交
281 282 283 284 285 286 287 288 289 290 291 292 293 294
	omap_ohci_clock_power(0);
}


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

/**
 * usb_hcd_omap_probe - initialize OMAP-based HCDs
 * Context: !in_interrupt()
 *
 * Allocates basic resources for this USB host controller, and
 * then invokes the start() method for the HCD associated with it
 * through the hotplug entry's driver_data.
 */
D
David Brownell 已提交
295
static int usb_hcd_omap_probe (const struct hc_driver *driver,
L
Linus Torvalds 已提交
296 297
			  struct platform_device *pdev)
{
298
	int retval, irq;
L
Linus Torvalds 已提交
299 300 301 302
	struct usb_hcd *hcd = 0;
	struct ohci_hcd *ohci;

	if (pdev->num_resources != 2) {
D
David Brownell 已提交
303
		printk(KERN_ERR "hcd probe: invalid num_resources: %i\n",
L
Linus Torvalds 已提交
304 305 306 307
		       pdev->num_resources);
		return -ENODEV;
	}

D
David Brownell 已提交
308
	if (pdev->resource[0].flags != IORESOURCE_MEM
L
Linus Torvalds 已提交
309 310 311 312 313 314 315 316 317
			|| pdev->resource[1].flags != IORESOURCE_IRQ) {
		printk(KERN_ERR "hcd probe: invalid resource type\n");
		return -ENODEV;
	}

	usb_host_ck = clk_get(0, "usb_hhc_ck");
	if (IS_ERR(usb_host_ck))
		return PTR_ERR(usb_host_ck);

D
David Brownell 已提交
318 319 320 321 322 323 324 325 326 327 328
	if (!cpu_is_omap1510())
		usb_dc_ck = clk_get(0, "usb_dc_ck");
	else
		usb_dc_ck = clk_get(0, "lb_ck");

	if (IS_ERR(usb_dc_ck)) {
		clk_put(usb_host_ck);
		return PTR_ERR(usb_dc_ck);
	}


L
Linus Torvalds 已提交
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
	hcd = usb_create_hcd (driver, &pdev->dev, pdev->dev.bus_id);
	if (!hcd) {
		retval = -ENOMEM;
		goto err0;
	}
	hcd->rsrc_start = pdev->resource[0].start;
	hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1;

	if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
		dev_dbg(&pdev->dev, "request_mem_region failed\n");
		retval = -EBUSY;
		goto err1;
	}

	hcd->regs = (void __iomem *) (int) IO_ADDRESS(hcd->rsrc_start);

	ohci = hcd_to_ohci(hcd);
	ohci_hcd_init(ohci);

D
David Brownell 已提交
348 349
	host_initialized = 0;
	host_enabled = 1;
L
Linus Torvalds 已提交
350

351 352 353 354 355
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		retval = -ENXIO;
		goto err2;
	}
356
	retval = usb_add_hcd(hcd, irq, IRQF_DISABLED);
D
David Brownell 已提交
357 358 359 360 361 362 363
	if (retval)
		goto err2;

	host_initialized = 1;

	if (!host_enabled)
		omap_ohci_clock_power(0);
L
Linus Torvalds 已提交
364

D
David Brownell 已提交
365
	return 0;
L
Linus Torvalds 已提交
366 367 368 369 370
err2:
	release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
err1:
	usb_put_hcd(hcd);
err0:
D
David Brownell 已提交
371
	clk_put(usb_dc_ck);
L
Linus Torvalds 已提交
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
	clk_put(usb_host_ck);
	return retval;
}


/* may be called with controller, bus, and devices active */

/**
 * usb_hcd_omap_remove - shutdown processing for OMAP-based HCDs
 * @dev: USB Host Controller being removed
 * Context: !in_interrupt()
 *
 * Reverses the effect of usb_hcd_omap_probe(), first invoking
 * the HCD's stop() method.  It is always called from a thread
 * context, normally "rmmod", "apmd", or something similar.
 */
D
David Brownell 已提交
388 389
static inline void
usb_hcd_omap_remove (struct usb_hcd *hcd, struct platform_device *pdev)
L
Linus Torvalds 已提交
390
{
D
David Brownell 已提交
391 392
	struct ohci_hcd		*ohci = hcd_to_ohci (hcd);

L
Linus Torvalds 已提交
393
	usb_remove_hcd(hcd);
D
David Brownell 已提交
394 395 396 397
	if (ohci->transceiver) {
		(void) otg_set_host(ohci->transceiver, 0);
		put_device(ohci->transceiver->dev);
	}
L
Linus Torvalds 已提交
398 399 400 401
	if (machine_is_omap_osk())
		omap_free_gpio(9);
	release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
	usb_put_hcd(hcd);
D
David Brownell 已提交
402
	clk_put(usb_dc_ck);
L
Linus Torvalds 已提交
403 404 405 406 407
	clk_put(usb_host_ck);
}

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

D
David Brownell 已提交
408
static int
L
Linus Torvalds 已提交
409 410 411 412 413 414
ohci_omap_start (struct usb_hcd *hcd)
{
	struct omap_usb_config *config;
	struct ohci_hcd	*ohci = hcd_to_ohci (hcd);
	int		ret;

D
David Brownell 已提交
415 416
	if (!host_enabled)
		return 0;
L
Linus Torvalds 已提交
417
	config = hcd->self.controller->platform_data;
418 419
	if (config->otg || config->rwc) {
		ohci->hc_control = OHCI_CTRL_RWC;
L
Linus Torvalds 已提交
420
		writel(OHCI_CTRL_RWC, &ohci->regs->control);
421
	}
L
Linus Torvalds 已提交
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446

	if ((ret = ohci_run (ohci)) < 0) {
		dev_err(hcd->self.controller, "can't start\n");
		ohci_stop (hcd);
		return ret;
	}
	return 0;
}

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

static const struct hc_driver ohci_omap_hc_driver = {
	.description =		hcd_name,
	.product_desc =		"OMAP OHCI",
	.hcd_priv_size =	sizeof(struct ohci_hcd),

	/*
	 * generic hardware linkage
	 */
	.irq =			ohci_irq,
	.flags =		HCD_USB11 | HCD_MEMORY,

	/*
	 * basic lifecycle operations
	 */
D
David Brownell 已提交
447
	.reset =		ohci_omap_init,
L
Linus Torvalds 已提交
448
	.start =		ohci_omap_start,
D
David Brownell 已提交
449
	.stop =			ohci_omap_stop,
450
	.shutdown = 		ohci_shutdown,
L
Linus Torvalds 已提交
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468

	/*
	 * managing i/o requests and associated device resources
	 */
	.urb_enqueue =		ohci_urb_enqueue,
	.urb_dequeue =		ohci_urb_dequeue,
	.endpoint_disable =	ohci_endpoint_disable,

	/*
	 * scheduling support
	 */
	.get_frame_number =	ohci_get_frame,

	/*
	 * root hub support
	 */
	.hub_status_data =	ohci_hub_status_data,
	.hub_control =		ohci_hub_control,
469
	.hub_irq_enable =	ohci_rhsc_enable,
470
#ifdef	CONFIG_PM
471 472
	.bus_suspend =		ohci_bus_suspend,
	.bus_resume =		ohci_bus_resume,
L
Linus Torvalds 已提交
473 474 475 476 477 478
#endif
	.start_port_reset =	ohci_start_port_reset,
};

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

479
static int ohci_hcd_omap_drv_probe(struct platform_device *dev)
L
Linus Torvalds 已提交
480
{
481
	return usb_hcd_omap_probe(&ohci_omap_hc_driver, dev);
L
Linus Torvalds 已提交
482 483
}

484
static int ohci_hcd_omap_drv_remove(struct platform_device *dev)
L
Linus Torvalds 已提交
485
{
486
	struct usb_hcd		*hcd = platform_get_drvdata(dev);
L
Linus Torvalds 已提交
487

488 489
	usb_hcd_omap_remove(hcd, dev);
	platform_set_drvdata(dev, NULL);
L
Linus Torvalds 已提交
490 491 492 493 494 495 496 497

	return 0;
}

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

#ifdef	CONFIG_PM

498
static int ohci_omap_suspend(struct platform_device *dev, pm_message_t message)
L
Linus Torvalds 已提交
499
{
500
	struct ohci_hcd	*ohci = hcd_to_ohci(platform_get_drvdata(dev));
D
David Brownell 已提交
501 502 503 504 505 506 507

	if (time_before(jiffies, ohci->next_statechange))
		msleep(5);
	ohci->next_statechange = jiffies;

	omap_ohci_clock_power(0);
	ohci_to_hcd(ohci)->state = HC_STATE_SUSPENDED;
D
David Brownell 已提交
508
	dev->dev.power.power_state = PMSG_SUSPEND;
D
David Brownell 已提交
509
	return 0;
L
Linus Torvalds 已提交
510 511
}

512
static int ohci_omap_resume(struct platform_device *dev)
L
Linus Torvalds 已提交
513
{
514
	struct ohci_hcd	*ohci = hcd_to_ohci(platform_get_drvdata(dev));
L
Linus Torvalds 已提交
515

516 517 518
	if (time_before(jiffies, ohci->next_statechange))
		msleep(5);
	ohci->next_statechange = jiffies;
D
David Brownell 已提交
519

520
	omap_ohci_clock_power(1);
D
David Brownell 已提交
521 522
	dev->dev.power.power_state = PMSG_ON;
	usb_hcd_resume_root_hub(platform_get_drvdata(dev));
D
David Brownell 已提交
523
	return 0;
L
Linus Torvalds 已提交
524 525 526 527 528 529 530 531 532
}

#endif

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

/*
 * Driver definition to register with the OMAP bus
 */
533
static struct platform_driver ohci_hcd_omap_driver = {
L
Linus Torvalds 已提交
534 535
	.probe		= ohci_hcd_omap_drv_probe,
	.remove		= ohci_hcd_omap_drv_remove,
536
	.shutdown 	= usb_hcd_platform_shutdown,
L
Linus Torvalds 已提交
537 538 539 540
#ifdef	CONFIG_PM
	.suspend	= ohci_omap_suspend,
	.resume		= ohci_omap_resume,
#endif
541 542 543 544
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "ohci",
	},
L
Linus Torvalds 已提交
545 546 547 548 549 550 551 552 553 554 555
};

static int __init ohci_hcd_omap_init (void)
{
	printk (KERN_DEBUG "%s: " DRIVER_INFO " (OMAP)\n", hcd_name);
	if (usb_disabled())
		return -ENODEV;

	pr_debug("%s: block sizes: ed %Zd td %Zd\n", hcd_name,
		sizeof (struct ed), sizeof (struct td));

556
	return platform_driver_register(&ohci_hcd_omap_driver);
L
Linus Torvalds 已提交
557 558 559 560
}

static void __exit ohci_hcd_omap_cleanup (void)
{
561
	platform_driver_unregister(&ohci_hcd_omap_driver);
L
Linus Torvalds 已提交
562 563 564 565
}

module_init (ohci_hcd_omap_init);
module_exit (ohci_hcd_omap_cleanup);