/* * drivers/misc/tegra-baseband/bb-power.c * * Copyright (C) 2011 NVIDIA Corporation * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bb-power.h" static struct tegra_bb_callback *callback; static int attr_load_val; static struct tegra_bb_power_mdata *mdata; static bb_get_cblist get_cblist[] = { NULL, NULL, NULL, M7400_CB, }; static int tegra_bb_power_gpio_init(struct tegra_bb_power_gdata *gdata) { int ret; int irq; unsigned gpio_id; const char *gpio_label; unsigned long gpio_flags; struct tegra_bb_gpio_data *gpiolist; struct tegra_bb_gpio_irqdata *gpioirq; gpiolist = gdata->gpio; for (; gpiolist->data.gpio != GPIO_INVALID; ++gpiolist) { gpio_id = (gpiolist->data.gpio); gpio_label = (gpiolist->data.label); gpio_flags = (gpiolist->data.flags); /* Request the gpio */ ret = gpio_request(gpio_id, gpio_label); if (ret) { pr_err("%s: Error: gpio_request for gpio %d failed.\n", __func__, gpio_id); return ret; } /* Set gpio direction, as requested */ if (gpio_flags == GPIOF_IN) gpio_direction_input(gpio_id); else gpio_direction_output(gpio_id, (!gpio_flags ? 0 : 1)); /* Create a sysfs node, if requested */ if (gpiolist->doexport) gpio_export(gpio_id, false); } gpioirq = gdata->gpioirq; for (; gpioirq->id != GPIO_INVALID; ++gpioirq) { /* Create interrupt handler, if requested */ if (gpioirq->handler != NULL) { irq = gpio_to_irq(gpioirq->id); ret = request_threaded_irq(irq, NULL, gpioirq->handler, gpioirq->flags, gpioirq->name, gpioirq->cookie); if (ret < 0) { pr_err("%s: Error: threaded_irq req fail.\n" , __func__); return ret; } if (gpioirq->wake_capable) { ret = enable_irq_wake(irq); if (ret) { pr_err("%s: Error: irqwake req fail.\n", __func__); return ret; } } } } return 0; } static int tegra_bb_power_gpio_deinit(struct tegra_bb_power_gdata *gdata) { struct tegra_bb_gpio_data *gpiolist; struct tegra_bb_gpio_irqdata *gpioirq; gpiolist = gdata->gpio; for (; gpiolist->data.gpio != GPIO_INVALID; ++gpiolist) { /* Free the gpio */ gpio_free(gpiolist->data.gpio); } gpioirq = gdata->gpioirq; for (; gpioirq->id != GPIO_INVALID; ++gpioirq) { /* Free the irq */ free_irq(gpio_to_irq(gpioirq->id), gpioirq->cookie); } return 0; } static ssize_t tegra_bb_attr_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val; if (sscanf(buf, "%d", &val) != 1) return -EINVAL; if (callback && callback->attrib) { if (!callback->attrib(dev, val)) attr_load_val = val; } return count; } static ssize_t tegra_bb_attr_read(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d", attr_load_val); } static DEVICE_ATTR(load, S_IRUSR | S_IWUSR | S_IRGRP, tegra_bb_attr_read, tegra_bb_attr_write); static void tegra_usbdevice_added(struct usb_device *udev) { const struct usb_device_descriptor *desc = &udev->descriptor; if (desc->idVendor == mdata->vid && desc->idProduct == mdata->pid) { pr_debug("%s: Device %s added.\n", udev->product, __func__); if (mdata->wake_capable) device_set_wakeup_enable(&udev->dev, true); if (mdata->autosuspend_ready) usb_enable_autosuspend(udev); if (mdata->reg_cb) mdata->reg_cb(udev); } } static void tegra_usbdevice_removed(struct usb_device *udev) { const struct usb_device_descriptor *desc = &udev->descriptor; if (desc->idVendor == mdata->vid && desc->idProduct == mdata->pid) { pr_debug("%s: Device %s removed.\n", udev->product, __func__); } } static int tegra_usb_notify(struct notifier_block *self, unsigned long action, void *dev) { switch (action) { case USB_DEVICE_ADD: tegra_usbdevice_added((struct usb_device *)dev); break; case USB_DEVICE_REMOVE: tegra_usbdevice_removed((struct usb_device *)dev); break; } return NOTIFY_OK; } static struct notifier_block tegra_usb_nb = { .notifier_call = tegra_usb_notify, }; static int tegra_bb_power_probe(struct platform_device *device) { struct device *dev = &device->dev; struct tegra_bb_pdata *pdata; struct tegra_bb_power_data *data; struct tegra_bb_power_gdata *gdata; int err; unsigned int bb_id; pdata = (struct tegra_bb_pdata *) dev->platform_data; if (!pdata) { pr_err("%s - Error: platform data is empty.\n", __func__); return -ENODEV; } /* Obtain BB specific callback list */ bb_id = pdata->bb_id; if (get_cblist[bb_id] != NULL) { callback = (struct tegra_bb_callback *) get_cblist[bb_id](); if (callback && callback->init) { data = (struct tegra_bb_power_data *) callback->init((void *)pdata); gdata = data->gpio_data; if (!gdata) { pr_err("%s - Error: Gpio data is empty.\n", __func__); return -ENODEV; } /* Initialize gpio as required */ tegra_bb_power_gpio_init(gdata); mdata = data->modem_data; if (mdata && mdata->vid && mdata->pid) /* Register to notifications from usb core */ usb_register_notify(&tegra_usb_nb); } else { pr_err("%s - Error: init callback is empty.\n", __func__); return -ENODEV; } } else { pr_err("%s - Error: callback data is empty.\n", __func__); return -ENODEV; } /* Create the control sysfs node */ err = device_create_file(dev, &dev_attr_load); if (err < 0) { pr_err("%s - Error: device_create_file failed.\n", __func__); return -ENODEV; } attr_load_val = 0; return 0; } static int tegra_bb_power_remove(struct platform_device *device) { struct device *dev = &device->dev; struct tegra_bb_power_data *data; struct tegra_bb_power_gdata *gdata; /* BB specific callback */ if (callback && callback->deinit) { data = (struct tegra_bb_power_data *) callback->deinit(); /* Deinitialize gpios */ gdata = data->gpio_data; if (gdata) tegra_bb_power_gpio_deinit(gdata); else { pr_err("%s - Error: Gpio data is empty.\n", __func__); return -ENODEV; } mdata = data->modem_data; if (mdata && mdata->vid && mdata->pid) /* Register to notifications from usb core */ usb_unregister_notify(&tegra_usb_nb); } /* Remove the control sysfs node */ device_remove_file(dev, &dev_attr_load); return 0; } #ifdef CONFIG_PM static int tegra_bb_power_suspend(struct platform_device *device, pm_message_t state) { /* BB specific callback */ if (callback && callback->power) callback->power(PWRSTATE_L2L3); return 0; } static int tegra_bb_power_resume(struct platform_device *device) { /* BB specific callback */ if (callback && callback->power) callback->power(PWRSTATE_L3L0); return 0; } #endif static struct platform_driver tegra_bb_power_driver = { .probe = tegra_bb_power_probe, .remove = tegra_bb_power_remove, #ifdef CONFIG_PM .suspend = tegra_bb_power_suspend, .resume = tegra_bb_power_resume, #endif .driver = { .name = "tegra_baseband_power", }, }; static int __init tegra_baseband_power_init(void) { pr_debug("%s\n", __func__); return platform_driver_register(&tegra_bb_power_driver); } static void __exit tegra_baseband_power_exit(void) { pr_debug("%s\n", __func__); platform_driver_unregister(&tegra_bb_power_driver); } module_init(tegra_baseband_power_init) module_exit(tegra_baseband_power_exit) MODULE_AUTHOR("NVIDIA Corporation"); MODULE_DESCRIPTION("Tegra modem power management driver"); MODULE_LICENSE("GPL");