misc: Add mdm6000 modem shutdown logic
authorJames Wylder <james.wylder@motorola.com>
Wed, 7 Jul 2010 23:15:25 +0000 (16:15 -0700)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:33:16 +0000 (16:33 -0700)
Add driver to communicate shutdown request to mdm6600
modem, through gpio triplets.

Change-Id: I00ab47e64e8f398caebac83f7c734f2f2c1a2aa3
Signed-off-by: James Wylder <james.wylder@motorola.com>
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/mdm6600_ctrl.c [new file with mode: 0644]
include/linux/mdm6600_ctrl.h [new file with mode: 0644]

index 6bde1ef6586334a1e59893453e4c6ad2df144538..231b3fb882eed0e70671babede1fca426a9cc124 100644 (file)
@@ -210,6 +210,17 @@ config KERNEL_DEBUGGER_CORE
          Generic kernel debugging command processor used by low level
          (interrupt context) platform-specific debuggers.
 
+config MDM6600_CTRL
+       bool "Motorola Modem Controller"
+       default n
+       ---help---
+         Enables the device driver to control and interface with
+         the 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.
+
 config SGI_XP
        tristate "Support communication between SGI SSIs"
        depends on NET
index 5695c1b35ef41d5167e3cbb420dd3f82e042c08e..dc1002259afd304ce45f6867d0a04627b414f946 100644 (file)
@@ -45,3 +45,4 @@ obj-$(CONFIG_SENSORS_KXTF9)   += kxtf9.o
 obj-$(CONFIG_SENSORS_MAX9635)  += max9635.o
 obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o
 obj-$(CONFIG_GPS_GPIO_BRCM4750)        += gps-gpio-brcm4750.o
+obj-$(CONFIG_MDM6600_CTRL)     += mdm6600_ctrl.o
diff --git a/drivers/misc/mdm6600_ctrl.c b/drivers/misc/mdm6600_ctrl.c
new file mode 100644 (file)
index 0000000..e8f0cad
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+     Copyright (C) 2010 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/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/mdm6600_ctrl.h>
+#include <linux/platform_device.h>
+
+#define AP_STATUS_BP_PANIC_ACK      0x00
+#define AP_STATUS_DATA_ONLY_BYPASS  0x01
+#define AP_STATUS_FULL_BYPASS       0x02
+#define AP_STATUS_NO_BYPASS         0x03
+#define AP_STATUS_BP_SHUTDOWN_REQ   0x04
+#define AP_STATUS_UNDEFINED         0x07
+
+#define BP_STATUS_PANIC             0x00
+#define BP_STATUS_PANIC_BUSY_WAIT   0x01
+#define BP_STATUS_QC_DLOAD          0x02
+#define BP_STATUS_RAM_DOWNLOADER    0x03
+#define BP_STATUS_PHONE_CODE_AWAKE  0x04
+#define BP_STATUS_PHONE_CODE_ASLEEP 0x05
+#define BP_STATUS_SHUTDOWN_ACK      0x06
+#define BP_STATUS_UNDEFINED         0x07
+
+static unsigned int mdm_gpio_get_value(struct mdm_ctrl_gpio gpio)
+{
+       return gpio_get_value(gpio.number);
+}
+
+static void mdm_gpio_set_value(struct mdm_ctrl_gpio gpio,
+       unsigned int value)
+{
+       gpio_set_value(gpio.number, value);
+}
+
+static void mdm_gpio_free(struct mdm_ctrl_gpio *gpio)
+{
+       if (gpio->allocated)
+               gpio_free(gpio->number);
+       gpio->allocated = 0;
+}
+
+static int mdm_gpio_setup(struct mdm_ctrl_gpio *gpio)
+{
+       snprintf(gpio->name, MAX_GPIO_NAME, "mdm_gpio_%05d", gpio->number);
+
+       if (gpio_request(gpio->number, gpio->name))  {
+               printk(KERN_ERR "failed to aquire gpio %s", gpio->name);
+               return -1;
+       }
+       gpio->allocated = 1;
+       if (gpio->direction == MDM_GPIO_DIRECTION_IN)
+               gpio_direction_input(gpio->number);
+       else if (gpio->direction == MDM_GPIO_DIRECTION_OUT)
+               gpio_direction_output(gpio->number, gpio->default_value);
+       return 0;
+}
+
+static unsigned int get_bp_status(struct mdm_ctrl_platform_data *pdata)
+{
+       unsigned int status = 0;
+       unsigned int bp_status[3];
+
+       bp_status[0] = mdm_gpio_get_value(
+               pdata->gpios[MDM_CTRL_GPIO_BP_STATUS_0]);
+       bp_status[1] = mdm_gpio_get_value(
+               pdata->gpios[MDM_CTRL_GPIO_BP_STATUS_1]);
+       bp_status[2] = mdm_gpio_get_value(
+               pdata->gpios[MDM_CTRL_GPIO_BP_STATUS_2]);
+
+       status = ((bp_status[2] & 0x1) << 2) |
+                ((bp_status[1] & 0x1) << 1) |
+                 (bp_status[0] & 0x1);
+
+       return status;
+}
+
+static unsigned int get_ap_status(struct mdm_ctrl_platform_data *pdata)
+{
+       unsigned int status = 0;
+       unsigned int ap_status[3];
+
+       ap_status[0] = mdm_gpio_get_value(
+               pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_0]);
+       ap_status[1] = mdm_gpio_get_value(
+               pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_1]);
+       ap_status[2] = mdm_gpio_get_value(
+               pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_2]);
+
+       status = ((ap_status[2] & 0x1) << 2) |
+                ((ap_status[1] & 0x1) << 1) |
+                 (ap_status[0] & 0x1);
+
+       return status;
+}
+
+static void set_ap_status(struct mdm_ctrl_platform_data *pdata,
+                         unsigned int status)
+{
+       mdm_gpio_set_value(
+               pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_0],
+               (status & 0x1));
+       mdm_gpio_set_value(
+               pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_1],
+               (status >> 1) & 0x1);
+       mdm_gpio_set_value(
+               pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_2],
+               (status >> 2) & 0x1);
+}
+
+static int __devinit mdm_ctrl_probe(struct platform_device *pdev)
+{
+       int i;
+       struct mdm_ctrl_platform_data *pdata = pdev->dev.platform_data;
+
+       dev_info(&pdev->dev, "mdm_ctrl_probe");
+
+       for (i = 0; i < MDM_CTRL_NUM_GPIOS; i++) {
+               if (mdm_gpio_setup(&pdata->gpios[i])) {
+                       dev_err(&pdev->dev, "failed to aquire gpio %d\n",
+                               pdata->gpios[i].number);
+                       goto probe_cleanup;
+               }
+       }
+
+       return 0;
+
+probe_cleanup:
+       for (i = 0; i < MDM_CTRL_NUM_GPIOS; i++)
+               mdm_gpio_free(&pdata->gpios[i]);
+
+       return -1;
+}
+
+static int __devexit mdm_ctrl_remove(struct platform_device *pdev)
+{
+       int i;
+       struct mdm_ctrl_platform_data *pdata = pdev->dev.platform_data;
+
+       dev_info(&pdev->dev, "cleanup\n");
+       for (i = 0; i < MDM_CTRL_NUM_GPIOS; i++)
+               mdm_gpio_free(&pdata->gpios[i]);
+
+       return 0;
+}
+
+static unsigned int __devexit bp_shutdown_wait(struct platform_device *pdev,
+                    struct mdm_ctrl_platform_data *pdata)
+{
+       unsigned int delay;
+       unsigned int bp_status;
+       unsigned int gpio_value;
+       unsigned int pd_failure = 1;
+
+       for (delay = 0; delay < 10; delay++) {
+               bp_status = get_bp_status(pdata);
+               if (bp_status == BP_STATUS_SHUTDOWN_ACK) {
+                       dev_info(&pdev->dev, "Modem power down success.\n");
+                       pd_failure = 0;
+                       break;
+               }
+               gpio_value = mdm_gpio_get_value(
+                       pdata->gpios[MDM_CTRL_GPIO_BP_RESOUT]);
+
+               dev_info(&pdev->dev, "gpio_resout_gpio = %d\n", gpio_value);
+               if (!gpio_value) {
+                       dev_info(&pdev->dev, "Modem reporting Panic.\n");
+                       pd_failure = 0;
+                       break;
+               } else {
+                       dev_info(&pdev->dev, "Modem status 0x%x\n", bp_status);
+                       msleep(500);
+               }
+       }
+       return pd_failure;
+}
+
+static void __devexit mdm_ctrl_shutdown(struct platform_device *pdev)
+{
+       unsigned int pd_failure;
+
+       struct mdm_ctrl_platform_data *pdata = pdev->dev.platform_data;
+
+       dev_info(&pdev->dev, "Shutting down modem.\n");
+
+       dev_info(&pdev->dev, "Initial modem status 0x%x\n",
+                get_bp_status(pdata));
+
+       dev_info(&pdev->dev, "Initial ap status 0x%x\n",
+                get_ap_status(pdata));
+
+       set_ap_status(pdata, AP_STATUS_BP_SHUTDOWN_REQ);
+
+       /* Allow modem to process status */
+       msleep(100);
+       dev_info(&pdev->dev, "ap_status set to %d\n", get_ap_status(pdata));
+
+       /* Toggle the power, delaying to allow modem to respond */
+       mdm_gpio_set_value(pdata->gpios[MDM_CTRL_GPIO_BP_PWRON], 1);
+       msleep(100);
+       mdm_gpio_set_value(pdata->gpios[MDM_CTRL_GPIO_BP_PWRON], 0);
+       msleep(100);
+
+       /* This should be enough to power down the modem */
+       /* if this doesn't work, reset the modem and try */
+       /* one more time, ultimately the modem will be   */
+       /* hard powered off */
+       pd_failure = bp_shutdown_wait(pdev, pdata);
+       if (pd_failure) {
+               mdm_gpio_set_value(pdata->gpios[MDM_CTRL_GPIO_BP_PWRON], 1);
+               pd_failure = bp_shutdown_wait(pdev, pdata);
+       }
+
+       if (pd_failure)
+               dev_err(&pdev->dev, "Modem failed to power down.\n");
+       else
+               dev_info(&pdev->dev, "Modem successfully powered down.\n");
+}
+
+static struct platform_driver mdm6x00_ctrl_driver = {
+       .probe = mdm_ctrl_probe,
+       .remove = __devexit_p(mdm_ctrl_remove),
+       .shutdown = __devexit_p(mdm_ctrl_shutdown),
+       .driver = {
+               .name = MDM_CTRL_MODULE_NAME,
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init mdm6600_ctrl_init(void)
+{
+       printk(KERN_DEBUG "mdm6600_ctrl_init\n");
+       return platform_driver_register(&mdm6x00_ctrl_driver);
+}
+
+static void __exit mdm6600_ctrl_exit(void)
+{
+       printk(KERN_DEBUG "mdm6600_ctrl_exit\n");
+       platform_driver_unregister(&mdm6x00_ctrl_driver);
+}
+
+module_init(mdm6600_ctrl_init);
+module_exit(mdm6600_ctrl_exit);
+
+MODULE_AUTHOR("Motorola");
+MODULE_DESCRIPTION("Modem Control Driver");
+MODULE_VERSION("1.1.3");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mdm6600_ctrl.h b/include/linux/mdm6600_ctrl.h
new file mode 100644 (file)
index 0000000..597e5cb
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+     Copyright (C) 2010 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_MDM_CTRL_H__
+#define __LINUX_MDM_CTRL_H__
+
+#define MDM_CTRL_MODULE_NAME "mdm6600_ctrl"
+#define MAX_GPIO_NAME 20
+
+enum {
+       MDM_CTRL_GPIO_AP_STATUS_0,
+       MDM_CTRL_GPIO_AP_STATUS_1,
+       MDM_CTRL_GPIO_AP_STATUS_2,
+       MDM_CTRL_GPIO_BP_STATUS_0,
+       MDM_CTRL_GPIO_BP_STATUS_1,
+       MDM_CTRL_GPIO_BP_STATUS_2,
+       MDM_CTRL_GPIO_BP_RESOUT,
+       MDM_CTRL_GPIO_BP_RESIN,
+       MDM_CTRL_GPIO_BP_PWRON,
+
+       MDM_CTRL_NUM_GPIOS,
+};
+
+enum {
+       MDM_GPIO_DIRECTION_IN,
+       MDM_GPIO_DIRECTION_OUT,
+       MDM_GPIO_DIRECTION_OUT_NO_DEFAULT,
+};
+
+struct mdm_ctrl_gpio {
+       unsigned int number;
+       unsigned int direction;
+       unsigned int default_value;
+       unsigned int allocated;
+       char name[MAX_GPIO_NAME];
+};
+
+struct mdm_ctrl_platform_data {
+       struct mdm_ctrl_gpio gpios[MDM_CTRL_NUM_GPIOS];
+};
+#endif /* __LINUX_MDM_CTRL_H__ */