diff options
Diffstat (limited to 'drivers/misc/tegra-baseband')
| -rw-r--r-- | drivers/misc/tegra-baseband/Kconfig | 32 | ||||
| -rw-r--r-- | drivers/misc/tegra-baseband/Makefile | 6 | ||||
| -rw-r--r-- | drivers/misc/tegra-baseband/bb-m7400.c | 340 | ||||
| -rw-r--r-- | drivers/misc/tegra-baseband/bb-power.c | 337 | ||||
| -rw-r--r-- | drivers/misc/tegra-baseband/bb-power.h | 99 |
5 files changed, 814 insertions, 0 deletions
diff --git a/drivers/misc/tegra-baseband/Kconfig b/drivers/misc/tegra-baseband/Kconfig new file mode 100644 index 00000000000..1f116918296 --- /dev/null +++ b/drivers/misc/tegra-baseband/Kconfig | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | menuconfig TEGRA_BB_SUPPORT | ||
| 2 | bool "Tegra baseband support" | ||
| 3 | depends on ARCH_TEGRA | ||
| 4 | ---help--- | ||
| 5 | Say Y here to get to see options for tegra baseband support. | ||
| 6 | This option alone does not add any kernel code. | ||
| 7 | |||
| 8 | If you say N, all options in this submenu will be skipped and disabled. | ||
| 9 | |||
| 10 | if TEGRA_BB_SUPPORT | ||
| 11 | |||
| 12 | config TEGRA_BB_POWER | ||
| 13 | bool "Enable tegra baseband power driver" | ||
| 14 | ---help--- | ||
| 15 | Adds power management driver for managing different baseband | ||
| 16 | modems with tegra processor. | ||
| 17 | |||
| 18 | This driver should work with at least the following devices: | ||
| 19 | |||
| 20 | * STE M7400 | ||
| 21 | * ... | ||
| 22 | |||
| 23 | Disabled by default. Choose Y here if you want to build the driver. | ||
| 24 | |||
| 25 | config TEGRA_BB_M7400 | ||
| 26 | bool "Enable driver for M7400 modem" | ||
| 27 | ---help--- | ||
| 28 | Enables driver for M7400 modem. | ||
| 29 | |||
| 30 | Disabled by default. Choose Y here if you want to build the driver. | ||
| 31 | |||
| 32 | endif # TEGRA_BB_SUPPORT | ||
diff --git a/drivers/misc/tegra-baseband/Makefile b/drivers/misc/tegra-baseband/Makefile new file mode 100644 index 00000000000..a95d84dbf11 --- /dev/null +++ b/drivers/misc/tegra-baseband/Makefile | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | # | ||
| 2 | # Makefile for tegra baseband support. | ||
| 3 | # | ||
| 4 | |||
| 5 | obj-$(CONFIG_TEGRA_BB_POWER) += bb-power.o | ||
| 6 | obj-$(CONFIG_TEGRA_BB_M7400) += bb-m7400.o | ||
diff --git a/drivers/misc/tegra-baseband/bb-m7400.c b/drivers/misc/tegra-baseband/bb-m7400.c new file mode 100644 index 00000000000..5808a6e321c --- /dev/null +++ b/drivers/misc/tegra-baseband/bb-m7400.c | |||
| @@ -0,0 +1,340 @@ | |||
| 1 | /* | ||
| 2 | * drivers/misc/tegra-baseband/bb-m7400.c | ||
| 3 | * | ||
| 4 | * Copyright (c) 2011, NVIDIA Corporation. | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or modify | ||
| 7 | * it under the terms of the GNU General Public License as published by | ||
| 8 | * the Free Software Foundation; either version 2 of the License, or | ||
| 9 | * (at your option) any later version. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 14 | * more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License along | ||
| 17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
| 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| 19 | */ | ||
| 20 | |||
| 21 | #include <linux/resource.h> | ||
| 22 | #include <linux/platform_device.h> | ||
| 23 | #include <linux/delay.h> | ||
| 24 | #include <linux/gpio.h> | ||
| 25 | #include <linux/interrupt.h> | ||
| 26 | #include <linux/irq.h> | ||
| 27 | #include <linux/err.h> | ||
| 28 | #include <linux/device.h> | ||
| 29 | #include <linux/usb.h> | ||
| 30 | #include <linux/wakelock.h> | ||
| 31 | #include <asm/mach-types.h> | ||
| 32 | #include <asm/mach/arch.h> | ||
| 33 | #include <mach/tegra-bb-power.h> | ||
| 34 | #include <mach/usb_phy.h> | ||
| 35 | #include "bb-power.h" | ||
| 36 | |||
| 37 | static struct tegra_bb_gpio_data m7400_gpios[] = { | ||
| 38 | { { GPIO_INVALID, GPIOF_OUT_INIT_LOW, "MDM_PWR_ON" }, true }, | ||
| 39 | { { GPIO_INVALID, GPIOF_IN, "MDM_PWRSTATUS" }, true }, | ||
| 40 | { { GPIO_INVALID, GPIOF_OUT_INIT_HIGH, "MDM_SERVICE" }, true }, | ||
| 41 | { { GPIO_INVALID, GPIOF_OUT_INIT_LOW, "MDM_USB_AWR" }, false }, | ||
| 42 | { { GPIO_INVALID, GPIOF_IN, "MDM_USB_CWR" }, false }, | ||
| 43 | { { GPIO_INVALID, GPIOF_IN, "MDM_RESOUT2" }, true }, | ||
| 44 | { { GPIO_INVALID, GPIOF_OUT_INIT_LOW, "MDM_USB_ARR" }, false }, | ||
| 45 | { { GPIO_INVALID, 0, NULL }, false }, /* End of table */ | ||
| 46 | }; | ||
| 47 | static bool ehci_registered; | ||
| 48 | static int modem_status; | ||
| 49 | static int gpio_awr; | ||
| 50 | static int gpio_cwr; | ||
| 51 | static int gpio_arr; | ||
| 52 | static struct usb_device *m7400_usb_device; | ||
| 53 | |||
| 54 | static int gpio_wait_timeout(int gpio, int value, int timeout_msec) | ||
| 55 | { | ||
| 56 | int count; | ||
| 57 | for (count = 0; count < timeout_msec; ++count) { | ||
| 58 | if (gpio_get_value(gpio) == value) | ||
| 59 | return 0; | ||
| 60 | mdelay(1); | ||
| 61 | } | ||
| 62 | return -1; | ||
| 63 | } | ||
| 64 | |||
| 65 | static int m7400_enum_handshake(void) | ||
| 66 | { | ||
| 67 | int retval = 0; | ||
| 68 | |||
| 69 | /* Wait for CP to indicate ready - by driving CWR high. */ | ||
| 70 | if (gpio_wait_timeout(gpio_cwr, 1, 10) != 0) { | ||
| 71 | pr_info("%s: Error: timeout waiting for modem resume.\n", | ||
| 72 | __func__); | ||
| 73 | retval = -1; | ||
| 74 | } | ||
| 75 | |||
| 76 | /* Signal AP ready - Drive AWR and ARR high. */ | ||
| 77 | gpio_set_value(gpio_awr, 1); | ||
| 78 | gpio_set_value(gpio_arr, 1); | ||
| 79 | |||
| 80 | return retval; | ||
| 81 | } | ||
| 82 | |||
| 83 | static int m7400_apup_handshake(bool checkresponse) | ||
| 84 | { | ||
| 85 | int retval = 0; | ||
| 86 | |||
| 87 | /* Signal AP ready - Drive AWR and ARR high. */ | ||
| 88 | gpio_set_value(gpio_awr, 1); | ||
| 89 | gpio_set_value(gpio_arr, 1); | ||
| 90 | |||
| 91 | if (checkresponse) { | ||
| 92 | /* Wait for CP ack - by driving CWR high. */ | ||
| 93 | if (gpio_wait_timeout(gpio_cwr, 1, 10) != 0) { | ||
| 94 | pr_info("%s: Error: timeout waiting for modem ack.\n", | ||
| 95 | __func__); | ||
| 96 | retval = -1; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | return retval; | ||
| 100 | } | ||
| 101 | |||
| 102 | static void m7400_apdown_handshake(void) | ||
| 103 | { | ||
| 104 | /* Signal AP going down to modem - Drive AWR low. */ | ||
| 105 | /* No need to wait for a CP response */ | ||
| 106 | gpio_set_value(gpio_awr, 0); | ||
| 107 | } | ||
| 108 | |||
| 109 | static int m7400_l2_suspend(void) | ||
| 110 | { | ||
| 111 | /* Gets called for two cases : | ||
| 112 | a) Port suspend. | ||
| 113 | b) Bus suspend. */ | ||
| 114 | if (modem_status == BBSTATE_L2) | ||
| 115 | return 0; | ||
| 116 | |||
| 117 | /* Post bus suspend: Drive ARR low. */ | ||
| 118 | gpio_set_value(gpio_arr, 0); | ||
| 119 | modem_status = BBSTATE_L2; | ||
| 120 | return 0; | ||
| 121 | } | ||
| 122 | |||
| 123 | static int m7400_l2_resume(void) | ||
| 124 | { | ||
| 125 | /* Gets called for two cases : | ||
| 126 | a) L2 resume. | ||
| 127 | b) bus resume phase of L3 resume. */ | ||
| 128 | if (modem_status == BBSTATE_L0) | ||
| 129 | return 0; | ||
| 130 | |||
| 131 | /* Pre bus resume: Drive ARR high. */ | ||
| 132 | gpio_set_value(gpio_arr, 1); | ||
| 133 | |||
| 134 | /* If host initiated resume - Wait for CP ack (CWR goes high). */ | ||
| 135 | /* If device initiated resume - CWR will be already high. */ | ||
| 136 | if (gpio_wait_timeout(gpio_cwr, 1, 10) != 0) { | ||
| 137 | pr_info("%s: Error: timeout waiting for modem ack.\n", | ||
| 138 | __func__); | ||
| 139 | return -1; | ||
| 140 | } | ||
| 141 | modem_status = BBSTATE_L0; | ||
| 142 | return 0; | ||
| 143 | } | ||
| 144 | |||
| 145 | static void m7400_l3_suspend(void) | ||
| 146 | { | ||
| 147 | m7400_apdown_handshake(); | ||
| 148 | modem_status = BBSTATE_L3; | ||
| 149 | } | ||
| 150 | |||
| 151 | static void m7400_l3_resume(void) | ||
| 152 | { | ||
| 153 | m7400_apup_handshake(true); | ||
| 154 | modem_status = BBSTATE_L0; | ||
| 155 | } | ||
| 156 | |||
| 157 | static irqreturn_t m7400_wake_irq(int irq, void *dev_id) | ||
| 158 | { | ||
| 159 | struct usb_interface *intf; | ||
| 160 | |||
| 161 | switch (modem_status) { | ||
| 162 | case BBSTATE_L2: | ||
| 163 | /* Resume usb host activity. */ | ||
| 164 | if (m7400_usb_device) { | ||
| 165 | usb_lock_device(m7400_usb_device); | ||
| 166 | intf = usb_ifnum_to_if(m7400_usb_device, 0); | ||
| 167 | usb_autopm_get_interface(intf); | ||
| 168 | usb_autopm_put_interface(intf); | ||
| 169 | usb_unlock_device(m7400_usb_device); | ||
| 170 | } | ||
| 171 | break; | ||
| 172 | default: | ||
| 173 | break; | ||
| 174 | } | ||
| 175 | |||
| 176 | return IRQ_HANDLED; | ||
| 177 | } | ||
| 178 | |||
| 179 | static int m7400_power(int code) | ||
| 180 | { | ||
| 181 | switch (code) { | ||
| 182 | case PWRSTATE_L2L3: | ||
| 183 | m7400_l3_suspend(); | ||
| 184 | break; | ||
| 185 | case PWRSTATE_L3L0: | ||
| 186 | m7400_l3_resume(); | ||
| 187 | break; | ||
| 188 | default: | ||
| 189 | break; | ||
| 190 | } | ||
| 191 | return 0; | ||
| 192 | } | ||
| 193 | |||
| 194 | static void m7400_ehci_customize(struct platform_device *pdev) | ||
| 195 | { | ||
| 196 | struct tegra_ehci_platform_data *ehci_pdata; | ||
| 197 | struct tegra_uhsic_config *hsic_config; | ||
| 198 | |||
| 199 | ehci_pdata = (struct tegra_ehci_platform_data *) | ||
| 200 | pdev->dev.platform_data; | ||
| 201 | hsic_config = (struct tegra_uhsic_config *) | ||
| 202 | ehci_pdata->phy_config; | ||
| 203 | |||
| 204 | /* Register PHY callbacks */ | ||
| 205 | hsic_config->postsuspend = m7400_l2_suspend; | ||
| 206 | hsic_config->preresume = m7400_l2_resume; | ||
| 207 | |||
| 208 | /* Override required settings */ | ||
| 209 | ehci_pdata->power_down_on_bus_suspend = 0; | ||
| 210 | } | ||
| 211 | |||
| 212 | static int m7400_attrib_write(struct device *dev, int value) | ||
| 213 | { | ||
| 214 | struct tegra_bb_pdata *pdata; | ||
| 215 | static struct platform_device *ehci_device; | ||
| 216 | static bool first_enum = true; | ||
| 217 | |||
| 218 | if (value > 1 || (!ehci_registered && !value)) { | ||
| 219 | /* Supported values are 0/1. */ | ||
| 220 | return -1; | ||
| 221 | } | ||
| 222 | |||
| 223 | pdata = (struct tegra_bb_pdata *) dev->platform_data; | ||
| 224 | if (value) { | ||
| 225 | |||
| 226 | /* Check readiness for enumeration */ | ||
| 227 | if (first_enum) | ||
| 228 | first_enum = false; | ||
| 229 | else | ||
| 230 | m7400_enum_handshake(); | ||
| 231 | |||
| 232 | /* Register ehci controller */ | ||
| 233 | ehci_device = pdata->ehci_register(); | ||
| 234 | if (ehci_device == NULL) { | ||
| 235 | pr_info("%s - Error: ehci register failed.\n", | ||
| 236 | __func__); | ||
| 237 | return -1; | ||
| 238 | } | ||
| 239 | |||
| 240 | /* Customize PHY setup/callbacks */ | ||
| 241 | m7400_ehci_customize(ehci_device); | ||
| 242 | |||
| 243 | ehci_registered = true; | ||
| 244 | } else { | ||
| 245 | /* Unregister ehci controller */ | ||
| 246 | if (ehci_device != NULL) | ||
| 247 | pdata->ehci_unregister(ehci_device); | ||
| 248 | |||
| 249 | /* Signal AP going down */ | ||
| 250 | m7400_apdown_handshake(); | ||
| 251 | ehci_registered = false; | ||
| 252 | } | ||
| 253 | |||
| 254 | return 0; | ||
| 255 | } | ||
| 256 | |||
| 257 | static int m7400_registered(struct usb_device *udev) | ||
| 258 | { | ||
| 259 | m7400_usb_device = udev; | ||
| 260 | modem_status = BBSTATE_L0; | ||
| 261 | return 0; | ||
| 262 | } | ||
| 263 | |||
| 264 | static struct tegra_bb_gpio_irqdata m7400_gpioirqs[] = { | ||
| 265 | { GPIO_INVALID, "tegra_bb_wake", m7400_wake_irq, | ||
| 266 | IRQF_TRIGGER_RISING, true, NULL }, | ||
| 267 | { GPIO_INVALID, NULL, NULL, 0, NULL }, /* End of table */ | ||
| 268 | }; | ||
| 269 | |||
| 270 | static struct tegra_bb_power_gdata m7400_gdata = { | ||
| 271 | .gpio = m7400_gpios, | ||
| 272 | .gpioirq = m7400_gpioirqs, | ||
| 273 | }; | ||
| 274 | |||
| 275 | static struct tegra_bb_power_mdata m7400_mdata = { | ||
| 276 | .vid = 0x04cc, | ||
| 277 | .pid = 0x230f, | ||
| 278 | .wake_capable = true, | ||
| 279 | .autosuspend_ready = true, | ||
| 280 | .reg_cb = m7400_registered, | ||
| 281 | }; | ||
| 282 | |||
| 283 | static struct tegra_bb_power_data m7400_data = { | ||
| 284 | .gpio_data = &m7400_gdata, | ||
| 285 | .modem_data = &m7400_mdata, | ||
| 286 | }; | ||
| 287 | |||
| 288 | static void *m7400_init(void *pdata) | ||
| 289 | { | ||
| 290 | struct tegra_bb_pdata *platdata = (struct tegra_bb_pdata *) pdata; | ||
| 291 | union tegra_bb_gpio_id *id = platdata->id; | ||
| 292 | |||
| 293 | /* Fill the gpio ids allocated by hardware */ | ||
| 294 | m7400_gpios[0].data.gpio = id->m7400.pwr_on; | ||
| 295 | m7400_gpios[1].data.gpio = id->m7400.pwr_status; | ||
| 296 | m7400_gpios[2].data.gpio = id->m7400.service; | ||
| 297 | m7400_gpios[3].data.gpio = id->m7400.usb_awr; | ||
| 298 | m7400_gpios[4].data.gpio = id->m7400.usb_cwr; | ||
| 299 | m7400_gpios[5].data.gpio = id->m7400.resout2; | ||
| 300 | m7400_gpios[6].data.gpio = id->m7400.uart_awr; | ||
| 301 | m7400_gpioirqs[0].id = id->m7400.usb_cwr; | ||
| 302 | |||
| 303 | if (!platdata->ehci_register || !platdata->ehci_unregister) { | ||
| 304 | pr_info("%s - Error: ehci reg/unreg functions missing.\n" | ||
| 305 | , __func__); | ||
| 306 | return 0; | ||
| 307 | } | ||
| 308 | |||
| 309 | gpio_awr = m7400_gpios[3].data.gpio; | ||
| 310 | gpio_cwr = m7400_gpios[4].data.gpio; | ||
| 311 | gpio_arr = m7400_gpios[6].data.gpio; | ||
| 312 | if (gpio_awr == GPIO_INVALID || gpio_cwr == GPIO_INVALID | ||
| 313 | || gpio_arr == GPIO_INVALID) { | ||
| 314 | pr_info("%s - Error: Invalid gpio data.\n", __func__); | ||
| 315 | return 0; | ||
| 316 | } | ||
| 317 | |||
| 318 | ehci_registered = false; | ||
| 319 | modem_status = BBSTATE_UNKNOWN; | ||
| 320 | return (void *) &m7400_data; | ||
| 321 | } | ||
| 322 | |||
| 323 | static void *m7400_deinit(void) | ||
| 324 | { | ||
| 325 | return (void *) &m7400_data; | ||
| 326 | } | ||
| 327 | |||
| 328 | static struct tegra_bb_callback m7400_callbacks = { | ||
| 329 | .init = m7400_init, | ||
| 330 | .deinit = m7400_deinit, | ||
| 331 | .attrib = m7400_attrib_write, | ||
| 332 | #ifdef CONFIG_PM | ||
| 333 | .power = m7400_power, | ||
| 334 | #endif | ||
| 335 | }; | ||
| 336 | |||
| 337 | void *m7400_get_cblist(void) | ||
| 338 | { | ||
| 339 | return (void *) &m7400_callbacks; | ||
| 340 | } | ||
diff --git a/drivers/misc/tegra-baseband/bb-power.c b/drivers/misc/tegra-baseband/bb-power.c new file mode 100644 index 00000000000..9210a8f3e84 --- /dev/null +++ b/drivers/misc/tegra-baseband/bb-power.c | |||
| @@ -0,0 +1,337 @@ | |||
| 1 | /* | ||
| 2 | * drivers/misc/tegra-baseband/bb-power.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 NVIDIA Corporation | ||
| 5 | * | ||
| 6 | * This software is licensed under the terms of the GNU General Public | ||
| 7 | * License version 2, as published by the Free Software Foundation, and | ||
| 8 | * may be copied, distributed, and modified under those terms. | ||
| 9 | * | ||
| 10 | * This program is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | */ | ||
| 16 | |||
| 17 | #include <linux/kernel.h> | ||
| 18 | #include <linux/init.h> | ||
| 19 | #include <linux/module.h> | ||
| 20 | #include <linux/moduleparam.h> | ||
| 21 | #include <linux/platform_device.h> | ||
| 22 | #include <linux/gpio.h> | ||
| 23 | #include <linux/interrupt.h> | ||
| 24 | #include <linux/workqueue.h> | ||
| 25 | #include <linux/delay.h> | ||
| 26 | #include <linux/fs.h> | ||
| 27 | #include <linux/usb.h> | ||
| 28 | #include <linux/uaccess.h> | ||
| 29 | #include <linux/platform_data/tegra_usb.h> | ||
| 30 | #include <mach/usb_phy.h> | ||
| 31 | #include <mach/tegra-bb-power.h> | ||
| 32 | #include "bb-power.h" | ||
| 33 | |||
| 34 | static struct tegra_bb_callback *callback; | ||
| 35 | static int attr_load_val; | ||
| 36 | static struct tegra_bb_power_mdata *mdata; | ||
| 37 | static bb_get_cblist get_cblist[] = { | ||
| 38 | NULL, | ||
| 39 | NULL, | ||
| 40 | NULL, | ||
| 41 | M7400_CB, | ||
| 42 | }; | ||
| 43 | |||
| 44 | static int tegra_bb_power_gpio_init(struct tegra_bb_power_gdata *gdata) | ||
| 45 | { | ||
| 46 | int ret; | ||
| 47 | int irq; | ||
| 48 | unsigned gpio_id; | ||
| 49 | const char *gpio_label; | ||
| 50 | unsigned long gpio_flags; | ||
| 51 | struct tegra_bb_gpio_data *gpiolist; | ||
| 52 | struct tegra_bb_gpio_irqdata *gpioirq; | ||
| 53 | |||
| 54 | gpiolist = gdata->gpio; | ||
| 55 | for (; gpiolist->data.gpio != GPIO_INVALID; ++gpiolist) { | ||
| 56 | gpio_id = (gpiolist->data.gpio); | ||
| 57 | gpio_label = (gpiolist->data.label); | ||
| 58 | gpio_flags = (gpiolist->data.flags); | ||
| 59 | |||
| 60 | /* Request the gpio */ | ||
| 61 | ret = gpio_request(gpio_id, gpio_label); | ||
| 62 | if (ret) { | ||
| 63 | pr_err("%s: Error: gpio_request for gpio %d failed.\n", | ||
| 64 | __func__, gpio_id); | ||
| 65 | return ret; | ||
| 66 | } | ||
| 67 | |||
| 68 | /* Set gpio direction, as requested */ | ||
| 69 | if (gpio_flags == GPIOF_IN) | ||
| 70 | gpio_direction_input(gpio_id); | ||
| 71 | else | ||
| 72 | gpio_direction_output(gpio_id, (!gpio_flags ? 0 : 1)); | ||
| 73 | |||
| 74 | /* Enable the gpio */ | ||
| 75 | tegra_gpio_enable(gpio_id); | ||
| 76 | |||
| 77 | /* Create a sysfs node, if requested */ | ||
| 78 | if (gpiolist->doexport) | ||
| 79 | gpio_export(gpio_id, false); | ||
| 80 | } | ||
| 81 | |||
| 82 | gpioirq = gdata->gpioirq; | ||
| 83 | for (; gpioirq->id != GPIO_INVALID; ++gpioirq) { | ||
| 84 | |||
| 85 | /* Create interrupt handler, if requested */ | ||
| 86 | if (gpioirq->handler != NULL) { | ||
| 87 | irq = gpio_to_irq(gpioirq->id); | ||
| 88 | ret = request_threaded_irq(irq, NULL, gpioirq->handler, | ||
| 89 | gpioirq->flags, gpioirq->name, gpioirq->cookie); | ||
| 90 | if (ret < 0) { | ||
| 91 | pr_err("%s: Error: threaded_irq req fail.\n" | ||
| 92 | , __func__); | ||
| 93 | return ret; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (gpioirq->wake_capable) { | ||
| 97 | ret = enable_irq_wake(irq); | ||
| 98 | if (ret) { | ||
| 99 | pr_err("%s: Error: irqwake req fail.\n", | ||
| 100 | __func__); | ||
| 101 | return ret; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | |||
| 109 | static int tegra_bb_power_gpio_deinit(struct tegra_bb_power_gdata *gdata) | ||
| 110 | { | ||
| 111 | struct tegra_bb_gpio_data *gpiolist; | ||
| 112 | struct tegra_bb_gpio_irqdata *gpioirq; | ||
| 113 | |||
| 114 | gpiolist = gdata->gpio; | ||
| 115 | for (; gpiolist->data.gpio != GPIO_INVALID; ++gpiolist) { | ||
| 116 | |||
| 117 | /* Free the gpio */ | ||
| 118 | gpio_free(gpiolist->data.gpio); | ||
| 119 | } | ||
| 120 | |||
| 121 | gpioirq = gdata->gpioirq; | ||
| 122 | for (; gpioirq->id != GPIO_INVALID; ++gpioirq) { | ||
| 123 | |||
| 124 | /* Free the irq */ | ||
| 125 | free_irq(gpio_to_irq(gpioirq->id), gpioirq->cookie); | ||
| 126 | } | ||
| 127 | return 0; | ||
| 128 | } | ||
| 129 | |||
| 130 | static ssize_t tegra_bb_attr_write(struct device *dev, | ||
| 131 | struct device_attribute *attr, | ||
| 132 | const char *buf, size_t count) | ||
| 133 | { | ||
| 134 | int val; | ||
| 135 | |||
| 136 | if (sscanf(buf, "%d", &val) != 1) | ||
| 137 | return -EINVAL; | ||
| 138 | |||
| 139 | if (callback && callback->attrib) { | ||
| 140 | if (!callback->attrib(dev, val)) | ||
| 141 | attr_load_val = val; | ||
| 142 | } | ||
| 143 | return count; | ||
| 144 | } | ||
| 145 | |||
| 146 | static ssize_t tegra_bb_attr_read(struct device *dev, | ||
| 147 | struct device_attribute *attr, char *buf) | ||
| 148 | { | ||
| 149 | return sprintf(buf, "%d", attr_load_val); | ||
| 150 | } | ||
| 151 | |||
| 152 | static DEVICE_ATTR(load, S_IRUSR | S_IWUSR | S_IRGRP, | ||
| 153 | tegra_bb_attr_read, tegra_bb_attr_write); | ||
| 154 | |||
| 155 | static void tegra_usbdevice_added(struct usb_device *udev) | ||
| 156 | { | ||
| 157 | const struct usb_device_descriptor *desc = &udev->descriptor; | ||
| 158 | |||
| 159 | if (desc->idVendor == mdata->vid && | ||
| 160 | desc->idProduct == mdata->pid) { | ||
| 161 | pr_debug("%s: Device %s added.\n", udev->product, __func__); | ||
| 162 | |||
| 163 | if (mdata->wake_capable) | ||
| 164 | device_set_wakeup_enable(&udev->dev, true); | ||
| 165 | if (mdata->autosuspend_ready) | ||
| 166 | usb_enable_autosuspend(udev); | ||
| 167 | if (mdata->reg_cb) | ||
| 168 | mdata->reg_cb(udev); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | static void tegra_usbdevice_removed(struct usb_device *udev) | ||
| 173 | { | ||
| 174 | const struct usb_device_descriptor *desc = &udev->descriptor; | ||
| 175 | |||
| 176 | if (desc->idVendor == mdata->vid && | ||
| 177 | desc->idProduct == mdata->pid) { | ||
| 178 | pr_debug("%s: Device %s removed.\n", udev->product, __func__); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | static int tegra_usb_notify(struct notifier_block *self, unsigned long action, | ||
| 183 | void *dev) | ||
| 184 | { | ||
| 185 | switch (action) { | ||
| 186 | case USB_DEVICE_ADD: | ||
| 187 | tegra_usbdevice_added((struct usb_device *)dev); | ||
| 188 | break; | ||
| 189 | case USB_DEVICE_REMOVE: | ||
| 190 | tegra_usbdevice_removed((struct usb_device *)dev); | ||
| 191 | break; | ||
| 192 | } | ||
| 193 | return NOTIFY_OK; | ||
| 194 | } | ||
| 195 | |||
| 196 | static struct notifier_block tegra_usb_nb = { | ||
| 197 | .notifier_call = tegra_usb_notify, | ||
| 198 | }; | ||
| 199 | |||
| 200 | static int tegra_bb_power_probe(struct platform_device *device) | ||
| 201 | { | ||
| 202 | struct device *dev = &device->dev; | ||
| 203 | struct tegra_bb_pdata *pdata; | ||
| 204 | struct tegra_bb_power_data *data; | ||
| 205 | struct tegra_bb_power_gdata *gdata; | ||
| 206 | int err; | ||
| 207 | unsigned int bb_id; | ||
| 208 | |||
| 209 | pdata = (struct tegra_bb_pdata *) dev->platform_data; | ||
| 210 | if (!pdata) { | ||
| 211 | pr_err("%s - Error: platform data is empty.\n", __func__); | ||
| 212 | return -ENODEV; | ||
| 213 | } | ||
| 214 | |||
| 215 | /* Obtain BB specific callback list */ | ||
| 216 | bb_id = pdata->bb_id; | ||
| 217 | if (get_cblist[bb_id] != NULL) { | ||
| 218 | callback = (struct tegra_bb_callback *) get_cblist[bb_id](); | ||
| 219 | if (callback && callback->init) { | ||
| 220 | data = (struct tegra_bb_power_data *) | ||
| 221 | callback->init((void *)pdata); | ||
| 222 | |||
| 223 | gdata = data->gpio_data; | ||
| 224 | if (!gdata) { | ||
| 225 | pr_err("%s - Error: Gpio data is empty.\n", | ||
| 226 | __func__); | ||
| 227 | return -ENODEV; | ||
| 228 | } | ||
| 229 | |||
| 230 | /* Initialize gpio as required */ | ||
| 231 | tegra_bb_power_gpio_init(gdata); | ||
| 232 | |||
| 233 | mdata = data->modem_data; | ||
| 234 | if (mdata && mdata->vid && mdata->pid) | ||
| 235 | /* Register to notifications from usb core */ | ||
| 236 | usb_register_notify(&tegra_usb_nb); | ||
| 237 | } else { | ||
| 238 | pr_err("%s - Error: init callback is empty.\n", | ||
| 239 | __func__); | ||
| 240 | return -ENODEV; | ||
| 241 | } | ||
| 242 | } else { | ||
| 243 | pr_err("%s - Error: callback data is empty.\n", __func__); | ||
| 244 | return -ENODEV; | ||
| 245 | } | ||
| 246 | |||
| 247 | /* Create the control sysfs node */ | ||
| 248 | err = device_create_file(dev, &dev_attr_load); | ||
| 249 | if (err < 0) { | ||
| 250 | pr_err("%s - Error: device_create_file failed.\n", __func__); | ||
| 251 | return -ENODEV; | ||
| 252 | } | ||
| 253 | attr_load_val = 0; | ||
| 254 | |||
| 255 | return 0; | ||
| 256 | } | ||
| 257 | |||
| 258 | static int tegra_bb_power_remove(struct platform_device *device) | ||
| 259 | { | ||
| 260 | struct device *dev = &device->dev; | ||
| 261 | struct tegra_bb_power_data *data; | ||
| 262 | struct tegra_bb_power_gdata *gdata; | ||
| 263 | |||
| 264 | /* BB specific callback */ | ||
| 265 | if (callback && callback->deinit) { | ||
| 266 | data = (struct tegra_bb_power_data *) | ||
| 267 | callback->deinit(); | ||
| 268 | |||
| 269 | /* Deinitialize gpios */ | ||
| 270 | gdata = data->gpio_data; | ||
| 271 | if (gdata) | ||
| 272 | tegra_bb_power_gpio_deinit(gdata); | ||
| 273 | else { | ||
| 274 | pr_err("%s - Error: Gpio data is empty.\n", __func__); | ||
| 275 | return -ENODEV; | ||
| 276 | } | ||
| 277 | |||
| 278 | mdata = data->modem_data; | ||
| 279 | if (mdata && mdata->vid && mdata->pid) | ||
| 280 | /* Register to notifications from usb core */ | ||
| 281 | usb_unregister_notify(&tegra_usb_nb); | ||
| 282 | } | ||
| 283 | |||
| 284 | /* Remove the control sysfs node */ | ||
| 285 | device_remove_file(dev, &dev_attr_load); | ||
| 286 | |||
| 287 | return 0; | ||
| 288 | } | ||
| 289 | |||
| 290 | #ifdef CONFIG_PM | ||
| 291 | static int tegra_bb_power_suspend(struct platform_device *device, | ||
| 292 | pm_message_t state) | ||
| 293 | { | ||
| 294 | /* BB specific callback */ | ||
| 295 | if (callback && callback->power) | ||
| 296 | callback->power(PWRSTATE_L2L3); | ||
| 297 | return 0; | ||
| 298 | } | ||
| 299 | |||
| 300 | static int tegra_bb_power_resume(struct platform_device *device) | ||
| 301 | { | ||
| 302 | /* BB specific callback */ | ||
| 303 | if (callback && callback->power) | ||
| 304 | callback->power(PWRSTATE_L3L0); | ||
| 305 | return 0; | ||
| 306 | } | ||
| 307 | #endif | ||
| 308 | |||
| 309 | static struct platform_driver tegra_bb_power_driver = { | ||
| 310 | .probe = tegra_bb_power_probe, | ||
| 311 | .remove = tegra_bb_power_remove, | ||
| 312 | #ifdef CONFIG_PM | ||
| 313 | .suspend = tegra_bb_power_suspend, | ||
| 314 | .resume = tegra_bb_power_resume, | ||
| 315 | #endif | ||
| 316 | .driver = { | ||
| 317 | .name = "tegra_baseband_power", | ||
| 318 | }, | ||
| 319 | }; | ||
| 320 | |||
| 321 | static int __init tegra_baseband_power_init(void) | ||
| 322 | { | ||
| 323 | pr_debug("%s\n", __func__); | ||
| 324 | return platform_driver_register(&tegra_bb_power_driver); | ||
| 325 | } | ||
| 326 | |||
| 327 | static void __exit tegra_baseband_power_exit(void) | ||
| 328 | { | ||
| 329 | pr_debug("%s\n", __func__); | ||
| 330 | platform_driver_unregister(&tegra_bb_power_driver); | ||
| 331 | } | ||
| 332 | |||
| 333 | module_init(tegra_baseband_power_init) | ||
| 334 | module_exit(tegra_baseband_power_exit) | ||
| 335 | MODULE_AUTHOR("NVIDIA Corporation"); | ||
| 336 | MODULE_DESCRIPTION("Tegra modem power management driver"); | ||
| 337 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/misc/tegra-baseband/bb-power.h b/drivers/misc/tegra-baseband/bb-power.h new file mode 100644 index 00000000000..cdd69380203 --- /dev/null +++ b/drivers/misc/tegra-baseband/bb-power.h | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | /* | ||
| 2 | * drivers/misc/tegra-baseband/bb-power.h | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 NVIDIA Corporation | ||
| 5 | * | ||
| 6 | * This software is licensed under the terms of the GNU General Public | ||
| 7 | * License version 2, as published by the Free Software Foundation, and | ||
| 8 | * may be copied, distributed, and modified under those terms. | ||
| 9 | * | ||
| 10 | * This program is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | */ | ||
| 16 | |||
| 17 | enum tegra_bb_state { | ||
| 18 | BBSTATE_UNKNOWN, | ||
| 19 | /* Baseband state L0 - Running */ | ||
| 20 | BBSTATE_L0, | ||
| 21 | /* Baseband state L2 - Suspended */ | ||
| 22 | BBSTATE_L2, | ||
| 23 | /* Baseband state L3 - Suspended and detached */ | ||
| 24 | BBSTATE_L3, | ||
| 25 | }; | ||
| 26 | |||
| 27 | enum tegra_bb_pwrstate { | ||
| 28 | /* System power state - Entering suspend */ | ||
| 29 | PWRSTATE_L2L3, | ||
| 30 | /* System power state - Resuming from suspend */ | ||
| 31 | PWRSTATE_L3L0, | ||
| 32 | PWRSTATE_INVALID, | ||
| 33 | }; | ||
| 34 | |||
| 35 | struct tegra_bb_gpio_data { | ||
| 36 | /* Baseband gpio data */ | ||
| 37 | struct gpio data; | ||
| 38 | /* Baseband gpio - Should it be exported to sysfs ? */ | ||
| 39 | bool doexport; | ||
| 40 | }; | ||
| 41 | |||
| 42 | struct tegra_bb_gpio_irqdata { | ||
| 43 | /* Baseband gpio IRQ - Id */ | ||
| 44 | int id; | ||
| 45 | /* Baseband gpio IRQ - Friendly name */ | ||
| 46 | const char *name; | ||
| 47 | /* Baseband gpio IRQ - IRQ handler */ | ||
| 48 | irq_handler_t handler; | ||
| 49 | /* Baseband gpio IRQ - IRQ trigger flags */ | ||
| 50 | int flags; | ||
| 51 | /* Baseband gpio IRQ - Can the gpio wake system from sleep ? */ | ||
| 52 | bool wake_capable; | ||
| 53 | void *cookie; | ||
| 54 | }; | ||
| 55 | |||
| 56 | typedef void* (*bb_get_cblist)(void); | ||
| 57 | typedef void* (*bb_init_cb)(void *pdata); | ||
| 58 | typedef void* (*bb_deinit_cb)(void); | ||
| 59 | typedef int (*bb_power_cb)(int code); | ||
| 60 | typedef int (*bb_attrib_cb)(struct device *dev, int value); | ||
| 61 | typedef int (*modem_register_cb)(struct usb_device *udev); | ||
| 62 | |||
| 63 | struct tegra_bb_power_gdata { | ||
| 64 | struct tegra_bb_gpio_data *gpio; | ||
| 65 | struct tegra_bb_gpio_irqdata *gpioirq; | ||
| 66 | }; | ||
| 67 | |||
| 68 | struct tegra_bb_power_mdata { | ||
| 69 | /* Baseband USB vendor ID */ | ||
| 70 | int vid; | ||
| 71 | /* Baseband USB product ID */ | ||
| 72 | int pid; | ||
| 73 | /* Baseband capability - Can it generate a wakeup ? */ | ||
| 74 | bool wake_capable; | ||
| 75 | /* Baseband capability - Can it be auto/runtime suspended ? */ | ||
| 76 | bool autosuspend_ready; | ||
| 77 | /* Baseband callback after a successful registration */ | ||
| 78 | modem_register_cb reg_cb; | ||
| 79 | }; | ||
| 80 | |||
| 81 | struct tegra_bb_power_data { | ||
| 82 | struct tegra_bb_power_gdata *gpio_data; | ||
| 83 | struct tegra_bb_power_mdata *modem_data; | ||
| 84 | }; | ||
| 85 | |||
| 86 | struct tegra_bb_callback { | ||
| 87 | bb_init_cb init; | ||
| 88 | bb_deinit_cb deinit; | ||
| 89 | bb_power_cb power; | ||
| 90 | bb_attrib_cb attrib; | ||
| 91 | bool valid; | ||
| 92 | }; | ||
| 93 | |||
| 94 | #ifdef CONFIG_TEGRA_BB_M7400 | ||
| 95 | extern void *m7400_get_cblist(void); | ||
| 96 | #define M7400_CB m7400_get_cblist | ||
| 97 | #else | ||
| 98 | #define M7400_CB NULL | ||
| 99 | #endif | ||
