From 246ce81338b27092c9ba678d10929771641f5316 Mon Sep 17 00:00:00 2001 From: James Wylder Date: Thu, 10 Feb 2011 11:16:05 -0600 Subject: [PATCH] misc: radio_ctrl: Add Wrigley Driver to support LTE Add driver to support control operations power-up, power-down, incoming wakeup and reset for the Motorola Wrigley Modem. Change-Id: I6955759b7bbfd48a9756c07886aa8b3f34dd2762 Signed-off-by: James Wylder --- drivers/misc/radio_ctrl/Kconfig | 12 + drivers/misc/radio_ctrl/Makefile | 1 + drivers/misc/radio_ctrl/wrigley_ctrl.c | 411 ++++++++++++++++++++++++ include/linux/radio_ctrl/wrigley_ctrl.h | 27 ++ 4 files changed, 451 insertions(+) create mode 100644 drivers/misc/radio_ctrl/wrigley_ctrl.c create mode 100644 include/linux/radio_ctrl/wrigley_ctrl.h diff --git a/drivers/misc/radio_ctrl/Kconfig b/drivers/misc/radio_ctrl/Kconfig index 4a24f50d0864..c2f64b70745f 100644 --- a/drivers/misc/radio_ctrl/Kconfig +++ b/drivers/misc/radio_ctrl/Kconfig @@ -13,3 +13,15 @@ config MDM6600_CTRL and allow modem power up/down support. If unsure, say N. + +config WRIGLEY_CTRL + bool "Motorola Wrigley Modem Controller" + default n + select RADIO_CTRL_CLASS + ---help--- + Enables the device driver to control and interface with + the Wrigley modem co-processor. This module is needed to monitor + modem panics, interact with the modem during factory resets, + and allow modem power up/down support. + + If unsure, say N. diff --git a/drivers/misc/radio_ctrl/Makefile b/drivers/misc/radio_ctrl/Makefile index 68936c356a87..b8f863f3559d 100644 --- a/drivers/misc/radio_ctrl/Makefile +++ b/drivers/misc/radio_ctrl/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_RADIO_CTRL_CLASS) += radio_class.o obj-$(CONFIG_MDM6600_CTRL) += mdm6600_ctrl.o +obj-$(CONFIG_WRIGLEY_CTRL) += wrigley_ctrl.o diff --git a/drivers/misc/radio_ctrl/wrigley_ctrl.c b/drivers/misc/radio_ctrl/wrigley_ctrl.c new file mode 100644 index 000000000000..b1e6a136617f --- /dev/null +++ b/drivers/misc/radio_ctrl/wrigley_ctrl.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2011 Motorola, Inc. + * + * This program 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_MAX_NAME 30 + +enum wrigley_status { + WRIGLEY_STATUS_NORMAL, + WRIGLEY_STATUS_FLASH, + WRIGLEY_STATUS_RESETTING, + WRIGLEY_STATUS_OFF, + WRIGLEY_STATUS_UNDEFINED, +}; + +static const char *wrigley_status_str[] = { + [WRIGLEY_STATUS_NORMAL] = "normal", + [WRIGLEY_STATUS_FLASH] = "flash", + [WRIGLEY_STATUS_RESETTING] = "resetting", + [WRIGLEY_STATUS_OFF] = "off", + [WRIGLEY_STATUS_UNDEFINED] = "undefined", +}; + +struct wrigley_info { + unsigned int disable_gpio; + char disable_name[GPIO_MAX_NAME]; + + unsigned int flash_gpio; + char flash_name[GPIO_MAX_NAME]; + + unsigned int reset_gpio; + char reset_name[GPIO_MAX_NAME]; + + unsigned int host_wake_gpio; + char host_wake_name[GPIO_MAX_NAME]; + struct wake_lock wake_lock; + + bool boot_flash; + enum wrigley_status status; + + struct radio_dev rdev; +}; + +static ssize_t wrigley_status_show(struct radio_dev *rdev, char *buff) +{ + struct wrigley_info *info = + container_of(rdev, struct wrigley_info, rdev); + + pr_debug("%s: wrigley_status = %d\n", __func__, info->status); + if (info->status > WRIGLEY_STATUS_UNDEFINED) + info->status = WRIGLEY_STATUS_UNDEFINED; + + return snprintf(buff, RADIO_STATUS_MAX_LENGTH, "%s\n", + wrigley_status_str[info->status]); +} + +static ssize_t wrigley_do_powerdown(struct wrigley_info *info) +{ + int i, value, err = -1; + + pr_info("%s: powering down\n", __func__); + gpio_direction_output(info->disable_gpio, 0); + + for (i = 0; i < 10; i++) { + value = gpio_get_value(info->reset_gpio); + pr_debug("%s: reset value = %d\n", __func__, value); + if (!value) { + err = 0; + info->status = WRIGLEY_STATUS_OFF; + break; + } + msleep(100); + } + + return err; +} + +static ssize_t wrigley_do_powerup(struct wrigley_info *info) +{ + int i, value, err = -1; + + pr_debug("%s: enter\n", __func__); + + /* power on in normal or flash mode */ + if (info->boot_flash) + gpio_direction_output(info->flash_gpio, 1); + else + gpio_direction_output(info->flash_gpio, 0); + + /* set disable high to actually power on the card */ + pr_debug("%s: set disable high\n", __func__); + gpio_direction_output(info->disable_gpio, 1); + info->status = WRIGLEY_STATUS_RESETTING; + + /* verify power up by sampling reset */ + for (i = 0; i < 10; i++) { + value = gpio_get_value(info->reset_gpio); + pr_debug("%s: reset value = %d\n", __func__, value); + if (value) { + err = 0; + break; + } + msleep(100); + } + + if (!err) { + if (info->boot_flash) { + pr_debug("%s: started wrigley in flash mode\n", + __func__); + info->status = WRIGLEY_STATUS_FLASH; + } else { + pr_debug("%s: started wrigley in normal mode\n", + __func__); + info->status = WRIGLEY_STATUS_NORMAL; + } + } else { + pr_err("%s: failed to start wrigley\n", __func__); + info->status = WRIGLEY_STATUS_UNDEFINED; + } + + return err; +} + +static ssize_t wrigley_set_flash_mode(struct wrigley_info *info, bool enable) +{ + pr_debug("%s: set boot state to %d\n", __func__, enable); + info->boot_flash = enable; + return 0; +} + +static ssize_t wrigley_command(struct radio_dev *rdev, char *cmd) +{ + struct wrigley_info *info = + container_of(rdev, struct wrigley_info, rdev); + + pr_info("%s: user command = %s\n", __func__, cmd); + + if (strcmp(cmd, "shutdown") == 0) + return wrigley_do_powerdown(info); + else if (strcmp(cmd, "powerup") == 0) + return wrigley_do_powerup(info); + else if (strcmp(cmd, "bootmode_normal") == 0) + return wrigley_set_flash_mode(info, 0); + else if (strcmp(cmd, "bootmode_flash") == 0) + return wrigley_set_flash_mode(info, 1); + + pr_err("%s: command %s not supported\n", __func__, cmd); + return -EINVAL; +} + +static irqreturn_t wrigley_host_wake_isr(int irq, void *data) +{ + struct wrigley_info *info = (struct wrigley_info *) data; + pr_debug("%s: take wakelock\n", __func__); + wake_lock_timeout(&info->wake_lock, HZ / 2); + + return IRQ_HANDLED; +} + +static irqreturn_t wrigley_reset_fn(int irq, void *data) +{ + struct wrigley_info *info = (struct wrigley_info *) data; + pr_debug("%s: reset irq (%d) fired\n", __func__, irq); + if (info->rdev.dev) + kobject_uevent(&info->rdev.dev->kobj, KOBJ_CHANGE); + return IRQ_HANDLED; +} + +static irqreturn_t wrigley_reset_isr(int irq, void *data) +{ + struct wrigley_info *info = (struct wrigley_info *) data; + pr_debug("%s: reset irq (%d) fired\n", __func__, irq); + info->status = WRIGLEY_STATUS_RESETTING; + return IRQ_WAKE_THREAD; +} + +static int __devinit wrigley_probe(struct platform_device *pdev) +{ + struct wrigley_ctrl_platform_data *pdata = pdev->dev.platform_data; + struct wrigley_info *info; + int reset_irq, host_wake_irq, err = 0; + + pr_info("%s: %s\n", __func__, dev_name(&pdev->dev)); + + info = kzalloc(sizeof(struct wrigley_info), GFP_KERNEL); + if (!info) { + err = -ENOMEM; + goto err_exit; + } + + platform_set_drvdata(pdev, info); + + /* setup radio_class device */ + info->rdev.name = dev_name(&pdev->dev); + info->rdev.status = wrigley_status_show; + info->rdev.command = wrigley_command; + + wake_lock_init(&info->wake_lock, WAKE_LOCK_SUSPEND, + dev_name(&pdev->dev)); + + /* host wake */ + pr_debug("%s: setup host_wake\n", __func__); + info->host_wake_gpio = pdata->gpio_host_wake; + snprintf(info->host_wake_name, GPIO_MAX_NAME, "%s-%s", + dev_name(&pdev->dev), "host-wake"); + err = gpio_request(info->host_wake_gpio, info->host_wake_name); + if (err) { + pr_err("%s: error requesting host wake gpio\n", __func__); + goto err_host_wake; + } + gpio_direction_input(info->host_wake_gpio); + + host_wake_irq = gpio_to_irq(info->host_wake_gpio); + err = request_irq(host_wake_irq, wrigley_host_wake_isr, + IRQ_TYPE_EDGE_FALLING, info->host_wake_name, info); + if (err) { + pr_err("%s: error requesting host wake irq\n", __func__); + gpio_free(info->host_wake_gpio); + goto err_host_wake; + } + + err = enable_irq_wake(host_wake_irq); + if (err) { + pr_err("%s: request host_wake irq (%d) %s failed\n", + __func__, host_wake_irq, info->host_wake_name); + free_irq(host_wake_irq, info); + gpio_free(info->host_wake_gpio); + goto err_host_wake; + } + gpio_export(info->host_wake_gpio, false); + + /* disable */ + pr_debug("%s: setup wrigley_disable\n", __func__); + info->disable_gpio = pdata->gpio_disable; + snprintf(info->disable_name, GPIO_MAX_NAME, "%s-%s", + dev_name(&pdev->dev), "disable"); + err = gpio_request(info->disable_gpio, info->disable_name); + if (err) { + pr_err("%s: err_disable\n", __func__); + goto err_disable; + } + gpio_export(info->disable_gpio, false); + + /* reset */ + pr_debug("%s: setup wrigley_reset\n", __func__); + info->reset_gpio = pdata->gpio_reset; + snprintf(info->reset_name, GPIO_MAX_NAME, "%s-%s", + dev_name(&pdev->dev), "reset"); + err = gpio_request(info->reset_gpio, info->reset_name); + if (err) { + pr_err("%s: err requesting reset gpio\n", __func__); + goto err_reset; + } + gpio_direction_input(info->reset_gpio); + reset_irq = gpio_to_irq(info->reset_gpio); + err = request_threaded_irq(reset_irq, wrigley_reset_isr, + wrigley_reset_fn, IRQ_TYPE_EDGE_FALLING, info->reset_name, + info); + if (err) { + pr_err("%s: request irq (%d) %s failed\n", + __func__, reset_irq, info->reset_name); + gpio_free(info->reset_gpio); + goto err_reset; + } + gpio_export(info->reset_gpio, false); + + /* force_flash */ + pr_debug("%s: setup wrigley_force_flash\n", __func__); + info->flash_gpio = pdata->gpio_force_flash; + snprintf(info->flash_name, GPIO_MAX_NAME, "%s-%s", + dev_name(&pdev->dev), "flash"); + err = gpio_request(info->flash_gpio, info->flash_name); + if (err) { + pr_err("%s: error requesting flash gpio\n", __func__); + goto err_flash; + } + gpio_export(info->flash_gpio, false); + + /* try to determine the boot up mode of the device */ + info->boot_flash = !!gpio_get_value(info->flash_gpio); + if (gpio_get_value(info->reset_gpio)) { + if (info->boot_flash) + info->status = WRIGLEY_STATUS_FLASH; + else + info->status = WRIGLEY_STATUS_NORMAL; + } else + info->status = WRIGLEY_STATUS_OFF; + + pr_debug("%s: initial status = %s\n", __func__, + wrigley_status_str[info->status]); + + err = radio_dev_register(&info->rdev); + if (err) { + pr_err("%s: failed to register radio device\n", __func__); + goto err_dev_register; + } + + return 0; + +err_dev_register: + gpio_free(info->flash_gpio); +err_flash: + free_irq(reset_irq, info); + gpio_free(info->reset_gpio); +err_reset: + gpio_free(info->disable_gpio); +err_disable: + disable_irq_wake(host_wake_irq); + free_irq(host_wake_irq, info); + gpio_free(info->host_wake_gpio); +err_host_wake: + wake_lock_destroy(&info->wake_lock); + platform_set_drvdata(pdev, NULL); + kfree(info); +err_exit: + return err; +} + +static void __devexit wrigley_shutdown(struct platform_device *pdev) +{ + struct wrigley_info *info = platform_get_drvdata(pdev); + pr_info("%s: %s\n", __func__, dev_name(&pdev->dev)); + (void) wrigley_do_powerdown(info); +} + +static int __devexit wrigley_remove(struct platform_device *pdev) +{ + struct wrigley_info *info = platform_get_drvdata(pdev); + + pr_info("%s: %s\n", __func__, dev_name(&pdev->dev)); + + radio_dev_unregister(&info->rdev); + + /* flash */ + gpio_free(info->flash_gpio); + + /* reset */ + free_irq(gpio_to_irq(info->reset_gpio), info); + gpio_free(info->reset_gpio); + + /* disable */ + gpio_free(info->disable_gpio); + + /* host_wake */ + disable_irq_wake(gpio_to_irq(info->host_wake_gpio)); + free_irq(gpio_to_irq(info->host_wake_gpio), info); + gpio_free(info->host_wake_gpio); + wake_lock_destroy(&info->wake_lock); + + platform_set_drvdata(pdev, NULL); + kfree(info); + + return 0; +} + +static struct platform_driver wrigley_driver = { + .probe = wrigley_probe, + .remove = __devexit_p(wrigley_remove), + .shutdown = __devexit_p(wrigley_shutdown), + .driver = { + .name = "wrigley", + .owner = THIS_MODULE, + }, +}; + +static int __init wrigley_init(void) +{ + + pr_info("%s: initializing %s\n", __func__, wrigley_driver.driver.name); + + return platform_driver_register(&wrigley_driver); +} + +static void __exit wrigley_exit(void) +{ + pr_info("%s: exiting %s\n", __func__, wrigley_driver.driver.name); + return platform_driver_unregister(&wrigley_driver); +} + +module_init(wrigley_init); +module_exit(wrigley_exit); + +MODULE_AUTHOR("Jim Wylder "); +MODULE_DESCRIPTION("Wrigley Modem Control"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/radio_ctrl/wrigley_ctrl.h b/include/linux/radio_ctrl/wrigley_ctrl.h new file mode 100644 index 000000000000..770f055d8efb --- /dev/null +++ b/include/linux/radio_ctrl/wrigley_ctrl.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2011 Motorola, Inc. + * + * This program 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ +#ifndef __LINUX_WRIGLEY_CTRL_H__ +#define __LINUX_WRIGLEY_CTRL_H__ + +struct wrigley_ctrl_platform_data { + unsigned int gpio_host_wake; + unsigned int gpio_disable; + unsigned int gpio_reset; + unsigned int gpio_force_flash; +}; +#endif /* __LINUX_WRIGLEY_CTRL_H__ */ -- 2.34.1