omap2430.c 13.3 KB
Newer Older
F
Felipe Balbi 已提交
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
/*
 * Copyright (C) 2005-2007 by Texas Instruments
 * Some code has been taken from tusb6010.c
 * Copyrights for that are attributable to:
 * Copyright (C) 2006 Nokia Corporation
 * Tony Lindgren <tony@atomide.com>
 *
 * 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
 *
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/clk.h>
#include <linux/io.h>
34 35
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
36 37
#include <linux/pm_runtime.h>
#include <linux/err.h>
F
Felipe Balbi 已提交
38 39 40 41

#include "musb_core.h"
#include "omap2430.h"

42 43 44 45
struct omap2430_glue {
	struct device		*dev;
	struct platform_device	*musb;
};
46
#define glue_to_musb(g)		platform_get_drvdata(g->musb)
47

F
Felipe Balbi 已提交
48 49 50 51 52 53
static struct timer_list musb_idle_timer;

static void musb_do_idle(unsigned long _musb)
{
	struct musb	*musb = (void *)_musb;
	unsigned long	flags;
54
#ifdef CONFIG_USB_MUSB_HDRC_HCD
F
Felipe Balbi 已提交
55
	u8	power;
56
#endif
F
Felipe Balbi 已提交
57 58 59 60
	u8	devctl;

	spin_lock_irqsave(&musb->lock, flags);

61
	switch (musb->xceiv->state) {
F
Felipe Balbi 已提交
62 63 64 65
	case OTG_STATE_A_WAIT_BCON:

		devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
		if (devctl & MUSB_DEVCTL_BDEVICE) {
66
			musb->xceiv->state = OTG_STATE_B_IDLE;
F
Felipe Balbi 已提交
67 68
			MUSB_DEV_MODE(musb);
		} else {
69
			musb->xceiv->state = OTG_STATE_A_IDLE;
F
Felipe Balbi 已提交
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
			MUSB_HST_MODE(musb);
		}
		break;
#ifdef CONFIG_USB_MUSB_HDRC_HCD
	case OTG_STATE_A_SUSPEND:
		/* finish RESUME signaling? */
		if (musb->port1_status & MUSB_PORT_STAT_RESUME) {
			power = musb_readb(musb->mregs, MUSB_POWER);
			power &= ~MUSB_POWER_RESUME;
			DBG(1, "root port resume stopped, power %02x\n", power);
			musb_writeb(musb->mregs, MUSB_POWER, power);
			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 ... */
87
			musb->xceiv->state = OTG_STATE_A_HOST;
F
Felipe Balbi 已提交
88 89 90 91 92 93 94
		}
		break;
#endif
#ifdef CONFIG_USB_MUSB_HDRC_HCD
	case OTG_STATE_A_HOST:
		devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
		if (devctl &  MUSB_DEVCTL_BDEVICE)
95
			musb->xceiv->state = OTG_STATE_B_IDLE;
F
Felipe Balbi 已提交
96
		else
97
			musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
F
Felipe Balbi 已提交
98 99 100 101 102 103 104 105
#endif
	default:
		break;
	}
	spin_unlock_irqrestore(&musb->lock, flags);
}


106
static void omap2430_musb_try_idle(struct musb *musb, unsigned long timeout)
F
Felipe Balbi 已提交
107 108 109 110 111 112 113 114 115
{
	unsigned long		default_timeout = jiffies + msecs_to_jiffies(3);
	static unsigned long	last_timer;

	if (timeout == 0)
		timeout = default_timeout;

	/* Never idle if active, or when VBUS timeout is not set as host */
	if (musb->is_active || ((musb->a_wait_bcon == 0)
116
			&& (musb->xceiv->state == OTG_STATE_A_WAIT_BCON))) {
117 118
		DBG(4, "%s active, deleting timer\n",
			otg_state_string(musb->xceiv->state));
F
Felipe Balbi 已提交
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
		del_timer(&musb_idle_timer);
		last_timer = jiffies;
		return;
	}

	if (time_after(last_timer, timeout)) {
		if (!timer_pending(&musb_idle_timer))
			last_timer = timeout;
		else {
			DBG(4, "Longer idle timer already pending, ignoring\n");
			return;
		}
	}
	last_timer = timeout;

	DBG(4, "%s inactive, for idle timer for %lu ms\n",
135
		otg_state_string(musb->xceiv->state),
F
Felipe Balbi 已提交
136 137 138 139
		(unsigned long)jiffies_to_msecs(timeout - jiffies));
	mod_timer(&musb_idle_timer, timeout);
}

140
static void omap2430_musb_set_vbus(struct musb *musb, int is_on)
F
Felipe Balbi 已提交
141 142
{
	u8		devctl;
143 144
	unsigned long timeout = jiffies + msecs_to_jiffies(1000);
	int ret = 1;
F
Felipe Balbi 已提交
145 146 147 148 149 150 151 152
	/* HDRC controls CPEN, but beware current surges during device
	 * connect.  They can trigger transient overcurrent conditions
	 * that must be ignored.
	 */

	devctl = musb_readb(musb->mregs, MUSB_DEVCTL);

	if (is_on) {
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
		if (musb->xceiv->state == OTG_STATE_A_IDLE) {
			/* start the session */
			devctl |= MUSB_DEVCTL_SESSION;
			musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
			/*
			 * Wait for the musb to set as A device to enable the
			 * VBUS
			 */
			while (musb_readb(musb->mregs, MUSB_DEVCTL) & 0x80) {

				cpu_relax();

				if (time_after(jiffies, timeout)) {
					dev_err(musb->controller,
					"configured as A device timeout");
					ret = -EINVAL;
					break;
				}
			}

			if (ret && musb->xceiv->set_vbus)
				otg_set_vbus(musb->xceiv, 1);
		} else {
			musb->is_active = 1;
			musb->xceiv->default_a = 1;
			musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
			devctl |= MUSB_DEVCTL_SESSION;
			MUSB_HST_MODE(musb);
		}
F
Felipe Balbi 已提交
182 183 184 185 186 187 188
	} else {
		musb->is_active = 0;

		/* NOTE:  we're skipping A_WAIT_VFALL -> A_IDLE and
		 * jumping right to B_IDLE...
		 */

189 190
		musb->xceiv->default_a = 0;
		musb->xceiv->state = OTG_STATE_B_IDLE;
F
Felipe Balbi 已提交
191 192 193 194 195 196 197 198
		devctl &= ~MUSB_DEVCTL_SESSION;

		MUSB_DEV_MODE(musb);
	}
	musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);

	DBG(1, "VBUS %s, devctl %02x "
		/* otg %3x conf %08x prcm %08x */ "\n",
199
		otg_state_string(musb->xceiv->state),
F
Felipe Balbi 已提交
200 201 202
		musb_readb(musb->mregs, MUSB_DEVCTL));
}

203
static int omap2430_musb_set_mode(struct musb *musb, u8 musb_mode)
F
Felipe Balbi 已提交
204 205 206 207 208 209
{
	u8	devctl = musb_readb(musb->mregs, MUSB_DEVCTL);

	devctl |= MUSB_DEVCTL_SESSION;
	musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);

D
David Brownell 已提交
210
	return 0;
F
Felipe Balbi 已提交
211 212
}

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
static inline void omap2430_low_level_exit(struct musb *musb)
{
	u32 l;

	/* in any role */
	l = musb_readl(musb->mregs, OTG_FORCESTDBY);
	l |= ENABLEFORCE;	/* enable MSTANDBY */
	musb_writel(musb->mregs, OTG_FORCESTDBY, l);
}

static inline void omap2430_low_level_init(struct musb *musb)
{
	u32 l;

	l = musb_readl(musb->mregs, OTG_FORCESTDBY);
	l &= ~ENABLEFORCE;	/* disable MSTANDBY */
	musb_writel(musb->mregs, OTG_FORCESTDBY, l);
}

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
/* blocking notifier support */
static int musb_otg_notifications(struct notifier_block *nb,
		unsigned long event, void *unused)
{
	struct musb	*musb = container_of(nb, struct musb, nb);
	struct device *dev = musb->controller;
	struct musb_hdrc_platform_data *pdata = dev->platform_data;
	struct omap_musb_board_data *data = pdata->board_data;

	switch (event) {
	case USB_EVENT_ID:
		DBG(4, "ID GND\n");

		if (is_otg_enabled(musb)) {
#ifdef CONFIG_USB_GADGET_MUSB_HDRC
			if (musb->gadget_driver) {
248
				pm_runtime_get_sync(musb->controller);
249
				otg_init(musb->xceiv);
250
				omap2430_musb_set_vbus(musb, 1);
251 252 253
			}
#endif
		} else {
254
			pm_runtime_get_sync(musb->controller);
255
			otg_init(musb->xceiv);
256
			omap2430_musb_set_vbus(musb, 1);
257 258 259 260 261 262
		}
		break;

	case USB_EVENT_VBUS:
		DBG(4, "VBUS Connect\n");

263
#ifdef CONFIG_USB_GADGET_MUSB_HDRC
264 265
		if (musb->gadget_driver)
			pm_runtime_get_sync(musb->controller);
266
#endif
267 268 269 270 271 272
		otg_init(musb->xceiv);
		break;

	case USB_EVENT_NONE:
		DBG(4, "VBUS Disconnect\n");

273
#ifdef CONFIG_USB_GADGET_MUSB_HDRC
274
		if (is_otg_enabled(musb) || is_peripheral_enabled(musb))
275 276 277 278 279 280 281
			if (musb->gadget_driver)
#endif
			{
				pm_runtime_mark_last_busy(musb->controller);
				pm_runtime_put_autosuspend(musb->controller);
			}

282 283 284 285 286 287 288 289 290 291 292 293 294 295
		if (data->interface_type == MUSB_INTERFACE_UTMI) {
			if (musb->xceiv->set_vbus)
				otg_set_vbus(musb->xceiv, 0);
		}
		otg_shutdown(musb->xceiv);
		break;
	default:
		DBG(4, "ID float\n");
		return NOTIFY_DONE;
	}

	return NOTIFY_OK;
}

296
static int omap2430_musb_init(struct musb *musb)
F
Felipe Balbi 已提交
297
{
298
	u32 l, status = 0;
299 300 301
	struct device *dev = musb->controller;
	struct musb_hdrc_platform_data *plat = dev->platform_data;
	struct omap_musb_board_data *data = plat->board_data;
F
Felipe Balbi 已提交
302

303 304 305 306 307 308 309 310 311 312
	/* We require some kind of external transceiver, hooked
	 * up through ULPI.  TWL4030-family PMICs include one,
	 * which needs a driver, drivers aren't always needed.
	 */
	musb->xceiv = otg_get_transceiver();
	if (!musb->xceiv) {
		pr_err("HS USB OTG: no transceiver configured\n");
		return -ENODEV;
	}

313 314 315 316 317
	status = pm_runtime_get_sync(dev);
	if (status < 0) {
		dev_err(dev, "pm_runtime_get_sync FAILED");
		goto err1;
	}
F
Felipe Balbi 已提交
318

319
	l = musb_readl(musb->mregs, OTG_INTERFSEL);
320 321 322 323 324 325 326 327 328

	if (data->interface_type == MUSB_INTERFACE_UTMI) {
		/* OMAP4 uses Internal PHY GS70 which uses UTMI interface */
		l &= ~ULPI_12PIN;       /* Disable ULPI */
		l |= UTMI_8BIT;         /* Enable UTMI  */
	} else {
		l |= ULPI_12PIN;
	}

329
	musb_writel(musb->mregs, OTG_INTERFSEL, l);
F
Felipe Balbi 已提交
330 331 332

	pr_debug("HS USB OTG: revision 0x%x, sysconfig 0x%02x, "
			"sysstatus 0x%x, intrfsel 0x%x, simenable  0x%x\n",
333 334 335 336 337
			musb_readl(musb->mregs, OTG_REVISION),
			musb_readl(musb->mregs, OTG_SYSCONFIG),
			musb_readl(musb->mregs, OTG_SYSSTATUS),
			musb_readl(musb->mregs, OTG_INTERFSEL),
			musb_readl(musb->mregs, OTG_SIMENABLE));
F
Felipe Balbi 已提交
338

339 340 341 342 343 344
	musb->nb.notifier_call = musb_otg_notifications;
	status = otg_register_notifier(musb->xceiv, &musb->nb);

	if (status)
		DBG(1, "notification register failed\n");

F
Felipe Balbi 已提交
345 346 347
	setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb);

	return 0;
348 349 350 351

err1:
	pm_runtime_disable(dev);
	return status;
F
Felipe Balbi 已提交
352 353
}

354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
static void omap2430_musb_enable(struct musb *musb)
{
	u8		devctl;
	unsigned long timeout = jiffies + msecs_to_jiffies(1000);
	struct device *dev = musb->controller;
	struct musb_hdrc_platform_data *pdata = dev->platform_data;
	struct omap_musb_board_data *data = pdata->board_data;

	switch (musb->xceiv->last_event) {

	case USB_EVENT_ID:
		otg_init(musb->xceiv);
		if (data->interface_type == MUSB_INTERFACE_UTMI) {
			devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
			/* start the session */
			devctl |= MUSB_DEVCTL_SESSION;
			musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
			while (musb_readb(musb->mregs, MUSB_DEVCTL) &
						MUSB_DEVCTL_BDEVICE) {
				cpu_relax();

				if (time_after(jiffies, timeout)) {
					dev_err(musb->controller,
					"configured as A device timeout");
					break;
				}
			}
		}
		break;

	case USB_EVENT_VBUS:
		otg_init(musb->xceiv);
		break;

	default:
		break;
	}
}

static void omap2430_musb_disable(struct musb *musb)
{
	if (musb->xceiv->last_event)
		otg_shutdown(musb->xceiv);
F
Felipe Balbi 已提交
397 398
}

399
static int omap2430_musb_exit(struct musb *musb)
F
Felipe Balbi 已提交
400
{
401
	del_timer_sync(&musb_idle_timer);
F
Felipe Balbi 已提交
402

403
	omap2430_low_level_exit(musb);
404
	otg_put_transceiver(musb->xceiv);
405

F
Felipe Balbi 已提交
406 407
	return 0;
}
408

409
static const struct musb_platform_ops omap2430_ops = {
410 411 412 413 414 415 416
	.init		= omap2430_musb_init,
	.exit		= omap2430_musb_exit,

	.set_mode	= omap2430_musb_set_mode,
	.try_idle	= omap2430_musb_try_idle,

	.set_vbus	= omap2430_musb_set_vbus,
417 418 419

	.enable		= omap2430_musb_enable,
	.disable	= omap2430_musb_disable,
420
};
421 422 423 424 425 426 427

static u64 omap2430_dmamask = DMA_BIT_MASK(32);

static int __init omap2430_probe(struct platform_device *pdev)
{
	struct musb_hdrc_platform_data	*pdata = pdev->dev.platform_data;
	struct platform_device		*musb;
428
	struct omap2430_glue		*glue;
429 430
	int				ret = -ENOMEM;

431 432 433 434 435 436
	glue = kzalloc(sizeof(*glue), GFP_KERNEL);
	if (!glue) {
		dev_err(&pdev->dev, "failed to allocate glue context\n");
		goto err0;
	}

437 438 439
	musb = platform_device_alloc("musb-hdrc", -1);
	if (!musb) {
		dev_err(&pdev->dev, "failed to allocate musb device\n");
440
		goto err1;
441 442 443 444 445 446
	}

	musb->dev.parent		= &pdev->dev;
	musb->dev.dma_mask		= &omap2430_dmamask;
	musb->dev.coherent_dma_mask	= omap2430_dmamask;

447 448 449
	glue->dev			= &pdev->dev;
	glue->musb			= musb;

450 451
	pdata->platform_ops		= &omap2430_ops;

452
	platform_set_drvdata(pdev, glue);
453 454 455 456 457

	ret = platform_device_add_resources(musb, pdev->resource,
			pdev->num_resources);
	if (ret) {
		dev_err(&pdev->dev, "failed to add resources\n");
458
		goto err2;
459 460 461 462 463
	}

	ret = platform_device_add_data(musb, pdata, sizeof(*pdata));
	if (ret) {
		dev_err(&pdev->dev, "failed to add platform_data\n");
464
		goto err2;
465 466 467 468 469
	}

	ret = platform_device_add(musb);
	if (ret) {
		dev_err(&pdev->dev, "failed to register musb device\n");
470
		goto err2;
471 472
	}

473
	pm_runtime_enable(&pdev->dev);
474

475
	return 0;
476

477
err2:
478 479
	platform_device_put(musb);

480 481 482
err1:
	kfree(glue);

483 484 485 486 487 488
err0:
	return ret;
}

static int __exit omap2430_remove(struct platform_device *pdev)
{
489
	struct omap2430_glue		*glue = platform_get_drvdata(pdev);
490

491 492
	platform_device_del(glue->musb);
	platform_device_put(glue->musb);
493 494
	pm_runtime_put(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
495
	kfree(glue);
496 497 498 499

	return 0;
}

500 501
#ifdef CONFIG_PM

502
static int omap2430_runtime_suspend(struct device *dev)
503 504 505 506 507 508 509 510 511 512
{
	struct omap2430_glue		*glue = dev_get_drvdata(dev);
	struct musb			*musb = glue_to_musb(glue);

	omap2430_low_level_exit(musb);
	otg_set_suspend(musb->xceiv, 1);

	return 0;
}

513
static int omap2430_runtime_resume(struct device *dev)
514 515 516 517 518 519 520 521 522 523 524
{
	struct omap2430_glue		*glue = dev_get_drvdata(dev);
	struct musb			*musb = glue_to_musb(glue);

	omap2430_low_level_init(musb);
	otg_set_suspend(musb->xceiv, 0);

	return 0;
}

static struct dev_pm_ops omap2430_pm_ops = {
525 526
	.runtime_suspend = omap2430_runtime_suspend,
	.runtime_resume = omap2430_runtime_resume,
527 528 529 530 531 532 533
};

#define DEV_PM_OPS	(&omap2430_pm_ops)
#else
#define DEV_PM_OPS	NULL
#endif

534 535 536 537
static struct platform_driver omap2430_driver = {
	.remove		= __exit_p(omap2430_remove),
	.driver		= {
		.name	= "musb-omap2430",
538
		.pm	= DEV_PM_OPS,
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
	},
};

MODULE_DESCRIPTION("OMAP2PLUS MUSB Glue Layer");
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
MODULE_LICENSE("GPL v2");

static int __init omap2430_init(void)
{
	return platform_driver_probe(&omap2430_driver, omap2430_probe);
}
subsys_initcall(omap2430_init);

static void __exit omap2430_exit(void)
{
	platform_driver_unregister(&omap2430_driver);
}
module_exit(omap2430_exit);