aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/misc/tegra-baseband/bb-m7400.c
blob: 5808a6e321cdb11fdd2147583f6509f332131da5 (plain) (tree)



















































































































































































































































































































































                                                                                 
/*
 * drivers/misc/tegra-baseband/bb-m7400.c
 *
 * Copyright (c) 2011, NVIDIA Corporation.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <linux/resource.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/usb.h>
#include <linux/wakelock.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <mach/tegra-bb-power.h>
#include <mach/usb_phy.h>
#include "bb-power.h"

static struct tegra_bb_gpio_data m7400_gpios[] = {
	{ { GPIO_INVALID, GPIOF_OUT_INIT_LOW, "MDM_PWR_ON" }, true },
	{ { GPIO_INVALID, GPIOF_IN, "MDM_PWRSTATUS" }, true },
	{ { GPIO_INVALID, GPIOF_OUT_INIT_HIGH, "MDM_SERVICE" }, true },
	{ { GPIO_INVALID, GPIOF_OUT_INIT_LOW, "MDM_USB_AWR" }, false },
	{ { GPIO_INVALID, GPIOF_IN, "MDM_USB_CWR" }, false },
	{ { GPIO_INVALID, GPIOF_IN, "MDM_RESOUT2" }, true },
	{ { GPIO_INVALID, GPIOF_OUT_INIT_LOW, "MDM_USB_ARR" }, false },
	{ { GPIO_INVALID, 0, NULL }, false },	/* End of table */
};
static bool ehci_registered;
static int modem_status;
static int gpio_awr;
static int gpio_cwr;
static int gpio_arr;
static struct usb_device *m7400_usb_device;

static int gpio_wait_timeout(int gpio, int value, int timeout_msec)
{
	int count;
	for (count = 0; count < timeout_msec; ++count) {
		if (gpio_get_value(gpio) == value)
			return 0;
		mdelay(1);
	}
	return -1;
}

static int m7400_enum_handshake(void)
{
	int retval = 0;

	/* Wait for CP to indicate ready - by driving CWR high. */
	if (gpio_wait_timeout(gpio_cwr, 1, 10) != 0) {
			pr_info("%s: Error: timeout waiting for modem resume.\n",
							__func__);
			retval = -1;
	}

	/* Signal AP ready - Drive AWR and ARR high. */
	gpio_set_value(gpio_awr, 1);
	gpio_set_value(gpio_arr, 1);

	return retval;
}

static int m7400_apup_handshake(bool checkresponse)
{
	int retval = 0;

	/* Signal AP ready - Drive AWR and ARR high. */
	gpio_set_value(gpio_awr, 1);
	gpio_set_value(gpio_arr, 1);

	if (checkresponse) {
		/* Wait for CP ack - by driving CWR high. */
		if (gpio_wait_timeout(gpio_cwr, 1, 10) != 0) {
			pr_info("%s: Error: timeout waiting for modem ack.\n",
							__func__);
			retval = -1;
		}
	}
	return retval;
}

static void m7400_apdown_handshake(void)
{
	/* Signal AP going down to modem - Drive AWR low. */
	/* No need to wait for a CP response */
	gpio_set_value(gpio_awr, 0);
}

static int m7400_l2_suspend(void)
{
	/* Gets called for two cases :
		a) Port suspend.
		b) Bus suspend. */
	if (modem_status == BBSTATE_L2)
		return 0;

	/* Post bus suspend: Drive ARR low. */
	gpio_set_value(gpio_arr, 0);
	modem_status = BBSTATE_L2;
	return 0;
}

static int m7400_l2_resume(void)
{
	/* Gets called for two cases :
		a) L2 resume.
		b) bus resume phase of L3 resume. */
	if (modem_status == BBSTATE_L0)
		return 0;

	/* Pre bus resume: Drive ARR high. */
	gpio_set_value(gpio_arr, 1);

	/* If host initiated resume - Wait for CP ack (CWR goes high). */
	/* If device initiated resume - CWR will be already high. */
	if (gpio_wait_timeout(gpio_cwr, 1, 10) != 0) {
		pr_info("%s: Error: timeout waiting for modem ack.\n",
						__func__);
		return -1;
	}
	modem_status = BBSTATE_L0;
	return 0;
}

static void m7400_l3_suspend(void)
{
	m7400_apdown_handshake();
	modem_status = BBSTATE_L3;
}

static void m7400_l3_resume(void)
{
	m7400_apup_handshake(true);
	modem_status = BBSTATE_L0;
}

static irqreturn_t m7400_wake_irq(int irq, void *dev_id)
{
	struct usb_interface *intf;

	switch (modem_status) {
	case BBSTATE_L2:
		/* Resume usb host activity. */
		if (m7400_usb_device) {
			usb_lock_device(m7400_usb_device);
			intf = usb_ifnum_to_if(m7400_usb_device, 0);
			usb_autopm_get_interface(intf);
			usb_autopm_put_interface(intf);
			usb_unlock_device(m7400_usb_device);
		}
		break;
	default:
		break;
	}

	return IRQ_HANDLED;
}

static int m7400_power(int code)
{
	switch (code) {
	case PWRSTATE_L2L3:
	m7400_l3_suspend();
	break;
	case PWRSTATE_L3L0:
	m7400_l3_resume();
	break;
	default:
	break;
	}
	return 0;
}

static void m7400_ehci_customize(struct platform_device *pdev)
{
	struct tegra_ehci_platform_data *ehci_pdata;
	struct tegra_uhsic_config *hsic_config;

	ehci_pdata = (struct tegra_ehci_platform_data *)
			pdev->dev.platform_data;
	hsic_config = (struct tegra_uhsic_config *)
			ehci_pdata->phy_config;

	/* Register PHY callbacks */
	hsic_config->postsuspend = m7400_l2_suspend;
	hsic_config->preresume = m7400_l2_resume;

	/* Override required settings */
	ehci_pdata->power_down_on_bus_suspend = 0;
}

static int m7400_attrib_write(struct device *dev, int value)
{
	struct tegra_bb_pdata *pdata;
	static struct platform_device *ehci_device;
	static bool first_enum = true;

	if (value > 1 || (!ehci_registered && !value)) {
		/* Supported values are 0/1. */
		return -1;
	}

	pdata = (struct tegra_bb_pdata *) dev->platform_data;
	if (value) {

		/* Check readiness for enumeration */
		if (first_enum)
			first_enum = false;
		else
			m7400_enum_handshake();

		/* Register ehci controller */
		ehci_device = pdata->ehci_register();
		if (ehci_device == NULL) {
			pr_info("%s - Error: ehci register failed.\n",
							 __func__);
			return -1;
		}

		/* Customize PHY setup/callbacks */
		m7400_ehci_customize(ehci_device);

		ehci_registered = true;
	} else {
		/* Unregister ehci controller */
		if (ehci_device != NULL)
			pdata->ehci_unregister(ehci_device);

		/* Signal AP going down */
		m7400_apdown_handshake();
		ehci_registered = false;
	}

	return 0;
}

static int m7400_registered(struct usb_device *udev)
{
	m7400_usb_device = udev;
	modem_status = BBSTATE_L0;
	return 0;
}

static struct tegra_bb_gpio_irqdata m7400_gpioirqs[] = {
	{ GPIO_INVALID, "tegra_bb_wake", m7400_wake_irq,
				IRQF_TRIGGER_RISING, true, NULL },
	{ GPIO_INVALID, NULL, NULL, 0, NULL },	/* End of table */
};

static struct tegra_bb_power_gdata m7400_gdata = {
	.gpio = m7400_gpios,
	.gpioirq = m7400_gpioirqs,
};

static struct tegra_bb_power_mdata m7400_mdata = {
	.vid = 0x04cc,
	.pid = 0x230f,
	.wake_capable = true,
	.autosuspend_ready = true,
	.reg_cb = m7400_registered,
};

static struct tegra_bb_power_data m7400_data = {
	.gpio_data = &m7400_gdata,
	.modem_data = &m7400_mdata,
};

static void *m7400_init(void *pdata)
{
	struct tegra_bb_pdata *platdata = (struct tegra_bb_pdata *) pdata;
	union tegra_bb_gpio_id *id = platdata->id;

	/* Fill the gpio ids allocated by hardware */
	m7400_gpios[0].data.gpio = id->m7400.pwr_on;
	m7400_gpios[1].data.gpio = id->m7400.pwr_status;
	m7400_gpios[2].data.gpio = id->m7400.service;
	m7400_gpios[3].data.gpio = id->m7400.usb_awr;
	m7400_gpios[4].data.gpio = id->m7400.usb_cwr;
	m7400_gpios[5].data.gpio = id->m7400.resout2;
	m7400_gpios[6].data.gpio = id->m7400.uart_awr;
	m7400_gpioirqs[0].id = id->m7400.usb_cwr;

	if (!platdata->ehci_register || !platdata->ehci_unregister) {
		pr_info("%s - Error: ehci reg/unreg functions missing.\n"
							, __func__);
		return 0;
	}

	gpio_awr = m7400_gpios[3].data.gpio;
	gpio_cwr = m7400_gpios[4].data.gpio;
	gpio_arr = m7400_gpios[6].data.gpio;
	if (gpio_awr == GPIO_INVALID || gpio_cwr == GPIO_INVALID
			|| gpio_arr == GPIO_INVALID) {
		pr_info("%s - Error: Invalid gpio data.\n", __func__);
		return 0;
	}

	ehci_registered = false;
	modem_status = BBSTATE_UNKNOWN;
	return (void *) &m7400_data;
}

static void *m7400_deinit(void)
{
	return (void *) &m7400_data;
}

static struct tegra_bb_callback m7400_callbacks = {
	.init = m7400_init,
	.deinit = m7400_deinit,
	.attrib = m7400_attrib_write,
#ifdef CONFIG_PM
	.power = m7400_power,
#endif
};

void *m7400_get_cblist(void)
{
	return (void *) &m7400_callbacks;
}