misc: radio_ctrl: Add Wrigley Driver to support LTE
authorJames Wylder <james.wylder@motorola.com>
Thu, 10 Feb 2011 17:16:05 +0000 (11:16 -0600)
committerBenoit Goby <benoit@android.com>
Sat, 19 Feb 2011 02:46:27 +0000 (18:46 -0800)
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 <james.wylder@motorola.com>
drivers/misc/radio_ctrl/Kconfig
drivers/misc/radio_ctrl/Makefile
drivers/misc/radio_ctrl/wrigley_ctrl.c [new file with mode: 0644]
include/linux/radio_ctrl/wrigley_ctrl.h [new file with mode: 0644]

index 4a24f50d0864644ba8042ab29e2355fd8bcda568..c2f64b70745f99030ae10ebaced6c252ed2b6ef2 100644 (file)
@@ -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.
index 68936c356a8779b566ae4b40265b8334114eed2d..b8f863f3559dc954321cb998d350b8b83881dec8 100644 (file)
@@ -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 (file)
index 0000000..b1e6a13
--- /dev/null
@@ -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 <linux/cdev.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/wakelock.h>
+#include <linux/radio_ctrl/radio_class.h>
+#include <linux/radio_ctrl/wrigley_ctrl.h>
+
+#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 <james.wylder@motorola.com>");
+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 (file)
index 0000000..770f055
--- /dev/null
@@ -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__ */