misc: mdm6600_ctrl: Add query and control of BP status via sysfs.
authorKazuhiro Ondo <kazuhiro.ondo@motorola.com>
Wed, 8 Sep 2010 15:21:54 +0000 (10:21 -0500)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:51:28 +0000 (16:51 -0700)
The following sysfs nodes will be exposed.
/sys/class/radio/mdm6600/status (query BP status)
/sys/class/radio/mdm6600/power_status (query BP power status)
/sys/class/radio/mdm6600/command (To control BP status)

Change-Id: I4ed4b6d0d9df010713732e79f3a0598e09ad5dec
Signed-off-by: Rebecca Schultz Zavin <rebecca@android.com>
drivers/misc/mdm6600_ctrl.c

index 13f8ff53cf5cde62d4e5599dc6d7dbf2bcd27831..315bdbef4aa50606d832168113df37ac8d8b816e 100644 (file)
 #include <linux/gpio.h>
 #include <linux/mdm6600_ctrl.h>
 #include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kobject.h>
 
 #define AP_STATUS_BP_PANIC_ACK      0x00
 #define AP_STATUS_DATA_ONLY_BYPASS  0x01
 
 #define LOOP_DELAY_TIME_MS          500
 
-char *bp_status[8] = {
-       "panic",
-       "panic busy wait",
-       "qc dload",
-       "ram downloader",
-       "awake",
-       "asleep",
-       "shutdown ack",
-       "undefined",
+static const char *mdmctrl = "mdm6600_ctrl";
+
+static const char *bp_status[8] = {
+       [BP_STATUS_PANIC] = "panic",
+       [BP_STATUS_PANIC_BUSY_WAIT] = "panic busy wait",
+       [BP_STATUS_QC_DLOAD] = "qc dload",
+       [BP_STATUS_RAM_DOWNLOADER] = "ram downloader",
+       [BP_STATUS_PHONE_CODE_AWAKE] = "awake",
+       [BP_STATUS_PHONE_CODE_ASLEEP] = "asleep",
+       [BP_STATUS_SHUTDOWN_ACK] = "shutdown ack",
+       [BP_STATUS_UNDEFINED] = "undefined",
 };
 
-static char *bp_status_string(unsigned int stat)
+static const char *bp_power_state[2] = {
+       "off",
+       "on",
+};
+
+#define BP_STATUS_MAX_LENGTH        32
+#define BP_COMMAND_MAX_LENGTH       32
+
+/* structure to keep track of gpio, irq, and irq enabled info */
+struct gpio_info {
+       int irq;
+       struct work_struct work;
+};
+
+struct mdm_ctrl_info {
+       struct mdm_ctrl_platform_data *pdata;
+       struct gpio_info gpios[MDM_CTRL_NUM_GPIOS];
+};
+
+static struct mdm_ctrl_info mdm_ctrl;
+
+static DEFINE_MUTEX(mdm_ctrl_info_lock);
+
+struct workqueue_struct *working_queue = NULL;
+
+static dev_t dev_number;
+struct class *radio_cls = NULL;
+struct device *mdm_dev = NULL;
+
+static unsigned int bp_status_idx = BP_STATUS_UNDEFINED;
+static unsigned int bp_power_idx = 0;
+
+static const char *bp_status_string(unsigned int stat)
 {
-       if (stat < 8)
+       if (stat < ARRAY_SIZE(bp_status))
                return bp_status[stat];
        else
                return "status out of range";
 }
 
+static const char *bp_power_state_string(unsigned int stat)
+{
+       if (stat < ARRAY_SIZE(bp_power_state))
+               return bp_power_state[stat];
+       else
+               return "status out of range";
+}
+
+static ssize_t mdm_status_show(struct device *dev,
+                              struct device_attribute *attr, char *buff)
+{
+       ssize_t status = 0;
+       status = snprintf(buff, BP_STATUS_MAX_LENGTH, "%s\n",
+                         bp_status_string(bp_status_idx));
+
+       return status;
+}
+
+static ssize_t mdm_power_show(struct device *dev,
+                             struct device_attribute *attr, char *buff)
+{
+       ssize_t status = 0;
+       status = snprintf(buff, BP_STATUS_MAX_LENGTH, "%s\n",
+                         bp_power_state_string(bp_power_idx));
+
+       return status;
+}
+
+static ssize_t mdm_user_command(struct device *dev,
+                               struct device_attribute *attr, const char *buff,
+                               size_t size)
+{
+       char tmp[BP_COMMAND_MAX_LENGTH];
+       char *post_strip = NULL;
+
+       if (size > BP_COMMAND_MAX_LENGTH - 1) {
+               return size;
+       }
+
+       /* strip whitespaces if any */
+       memcpy(tmp, buff, size);
+       tmp[size] = '\0';
+       post_strip = strim(tmp);
+
+       pr_info("%s: user command = %s\n", mdmctrl, post_strip);
+
+       /* TODO : real handlers of user commands will be added later */
+
+       return size;
+}
+
+static DEVICE_ATTR(status, 0444, mdm_status_show, NULL);
+static DEVICE_ATTR(power_status, 0444, mdm_power_show, NULL);
+static DEVICE_ATTR(command, 0200, NULL, mdm_user_command);
+
 static unsigned int mdm_gpio_get_value(struct mdm_ctrl_gpio gpio)
 {
        return gpio_get_value(gpio.number);
@@ -91,17 +186,21 @@ static int mdm_gpio_setup(struct mdm_ctrl_gpio *gpio)
        return 0;
 }
 
-static unsigned int get_bp_status(struct mdm_ctrl_platform_data *pdata)
+static unsigned int get_bp_status(void)
 {
-       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]);
+       unsigned int status = BP_STATUS_UNDEFINED;
+       unsigned int bp_status[3] = {0};
+
+       mutex_lock(&mdm_ctrl_info_lock);
+       if (mdm_ctrl.pdata) {
+               bp_status[0] = mdm_gpio_get_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_BP_STATUS_0]);
+               bp_status[1] = mdm_gpio_get_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_BP_STATUS_1]);
+               bp_status[2] = mdm_gpio_get_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_BP_STATUS_2]);
+       }
+       mutex_unlock(&mdm_ctrl_info_lock);
 
        status = ((bp_status[2] & 0x1) << 2) |
                 ((bp_status[1] & 0x1) << 1) |
@@ -110,17 +209,36 @@ static unsigned int get_bp_status(struct mdm_ctrl_platform_data *pdata)
        return status;
 }
 
-static unsigned int get_ap_status(struct mdm_ctrl_platform_data *pdata)
+static unsigned int get_bp_power_status(void)
 {
        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]);
+       mutex_lock(&mdm_ctrl_info_lock);
+       if (mdm_ctrl.pdata) {
+               status = mdm_gpio_get_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_BP_RESOUT]);
+       }
+
+       mutex_unlock(&mdm_ctrl_info_lock);
+
+       return status & 0x1;
+}
+
+static unsigned int get_ap_status(void)
+{
+       unsigned int status = AP_STATUS_UNDEFINED;
+       unsigned int ap_status[3] =  {0};
+
+       mutex_lock(&mdm_ctrl_info_lock);
+       if (mdm_ctrl.pdata) {
+               ap_status[0] = mdm_gpio_get_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_0]);
+               ap_status[1] = mdm_gpio_get_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_1]);
+               ap_status[2] = mdm_gpio_get_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_2]);
+       }
+       mutex_unlock(&mdm_ctrl_info_lock);
 
        status = ((ap_status[2] & 0x1) << 2) |
                 ((ap_status[1] & 0x1) << 1) |
@@ -129,18 +247,105 @@ static unsigned int get_ap_status(struct mdm_ctrl_platform_data *pdata)
        return status;
 }
 
-static void set_ap_status(struct mdm_ctrl_platform_data *pdata,
-                         unsigned int status)
+static void set_ap_status(unsigned int status)
+{
+       mutex_lock(&mdm_ctrl_info_lock);
+       if (mdm_ctrl.pdata) {
+               mdm_gpio_set_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_0],
+                       (status & 0x1));
+               mdm_gpio_set_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_1],
+                       (status >> 1) & 0x1);
+               mdm_gpio_set_value(
+                       mdm_ctrl.pdata->gpios[MDM_CTRL_GPIO_AP_STATUS_2],
+                       (status >> 2) & 0x1);
+       }
+       mutex_unlock(&mdm_ctrl_info_lock);
+}
+
+static void update_bp_status(void) {
+
+       static int bp_status_prev_idx = BP_STATUS_UNDEFINED;
+
+       bp_status_prev_idx = bp_status_idx;
+       bp_status_idx = get_bp_status();
+       bp_power_idx = get_bp_power_status();
+
+       pr_info("%s: modem status: %s -> %s [power %s]", mdmctrl,
+               bp_status_string(bp_status_prev_idx),
+               bp_status_string(bp_status_idx),
+               bp_power_state_string(bp_power_idx));
+
+       kobject_uevent(&mdm_dev->kobj, KOBJ_CHANGE);
+}
+
+static void irq_worker(struct work_struct *work)
 {
-       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);
+       struct gpio_info *gpio = container_of(work, struct gpio_info, work);
+       update_bp_status();
+       enable_irq(gpio->irq);
+}
+
+static irqreturn_t irq_handler(int irq, void *data)
+{
+       struct gpio_info *gpio = (struct gpio_info *) data;
+
+       disable_irq_nosync(irq);
+       queue_work(working_queue, &gpio->work);
+
+       return IRQ_HANDLED;
+}
+
+static int mdm_gpio_setup_internal(struct mdm_ctrl_platform_data *pdata)
+{
+       int i;
+       int rv = 0;
+       struct gpio_info *gpio_data = NULL;
+
+       mutex_lock(&mdm_ctrl_info_lock);
+       memset(&mdm_ctrl, 0, sizeof (mdm_ctrl));
+
+       mdm_ctrl.pdata = pdata;
+
+       for (i = 0; i < MDM_CTRL_NUM_GPIOS; i++) {
+               gpio_data = &mdm_ctrl.gpios[i];
+               if (pdata->gpios[i].direction == MDM_GPIO_DIRECTION_IN) {
+                       INIT_WORK(&gpio_data->work, irq_worker);
+                       gpio_data->irq = gpio_to_irq(pdata->gpios[i].number);
+                       rv = request_irq(gpio_data->irq, irq_handler,
+                                        IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING,
+                                        pdata->gpios[i].name, gpio_data);
+                        if (rv < 0) {
+                               pr_err("%s: Cannot request IRQ (%d) from kernel!",
+                                      mdmctrl, gpio_data->irq);
+                       } else {
+                               enable_irq_wake(gpio_data->irq);
+                       }
+               }
+        }
+
+       mutex_unlock(&mdm_ctrl_info_lock);
+       return rv;
+}
+
+static void mdm_gpio_cleanup_internal(void)
+{
+       int i;
+       struct gpio_info *gpio_data = NULL;
+
+       mutex_lock(&mdm_ctrl_info_lock);
+
+       for (i = 0; i < MDM_CTRL_NUM_GPIOS; i++) {
+               gpio_data = &mdm_ctrl.gpios[i];
+
+               if (gpio_data->irq) {
+                       disable_irq_wake(gpio_data->irq);
+                       free_irq(gpio_data->irq, gpio_data);
+               }
+       }
+       memset(&mdm_ctrl, 0, sizeof (mdm_ctrl));
+       mutex_unlock(&mdm_ctrl_info_lock);
 }
 
 static int __devinit mdm_ctrl_probe(struct platform_device *pdev)
@@ -150,6 +355,43 @@ static int __devinit mdm_ctrl_probe(struct platform_device *pdev)
 
        dev_info(&pdev->dev, "mdm_ctrl_probe");
 
+       if (alloc_chrdev_region(&dev_number, 0, 1, "mdm_ctrl") < 0) {
+               dev_err(&pdev->dev, "Can't register new device.");
+               return -1;
+       }
+
+       /* /sys/class/radio */
+       radio_cls = class_create(THIS_MODULE, "radio");
+       if (IS_ERR(radio_cls)) {
+               dev_err(&pdev->dev, "Failed to create radio class.");
+               goto err_cls;
+       }
+
+       /* /sys/class/radio/mdm6600 */
+       mdm_dev = device_create(radio_cls, NULL, dev_number, NULL, "mdm6600");
+       if (IS_ERR(mdm_dev)) {
+               dev_err(&pdev->dev, "Failed to create mdm_dev.");
+               goto err_mdm;
+       }
+
+       /* /sys/class/radio/mdm6600/status */
+       if (device_create_file(mdm_dev, &dev_attr_status) > 0) {
+               dev_err(&pdev->dev, "Failed to create status sysfile.");
+               goto err_status;
+       }
+
+       /* /sys/class/radio/mdm6600/power_status */
+       if (device_create_file(mdm_dev, &dev_attr_power_status) > 0) {
+               dev_err(&pdev->dev, "Failed to create power sysfile .");
+               goto err_power;
+       }
+
+       /* /sys/class/radio/mdm6600/command */
+       if (device_create_file(mdm_dev, &dev_attr_command) > 0) {
+               dev_err(&pdev->dev, "Failed to create command sysfile.");
+               goto err_command;
+       }
+
        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",
@@ -158,12 +400,52 @@ static int __devinit mdm_ctrl_probe(struct platform_device *pdev)
                }
        }
 
+       working_queue = create_singlethread_workqueue("mdm_ctrl_wq");
+       if (!working_queue) {
+               dev_err(&pdev->dev, "Cannot create work queue.");
+               goto probe_err;
+       }
+
+       if (mdm_gpio_setup_internal(pdata) < 0) {
+               dev_err(&pdev->dev, "Failed to setup bp  status irq");
+               goto err_setup;
+       }
+
+       update_bp_status();
+
        return 0;
 
+err_setup:
+       mdm_gpio_cleanup_internal();
+
+probe_err:
+       destroy_workqueue(working_queue);
+
 probe_cleanup:
        for (i = 0; i < MDM_CTRL_NUM_GPIOS; i++)
                mdm_gpio_free(&pdata->gpios[i]);
 
+err_command:
+       device_remove_file(mdm_dev, &dev_attr_command);
+
+err_power:
+       device_remove_file(mdm_dev, &dev_attr_power_status);
+
+err_status:
+       device_remove_file(mdm_dev, &dev_attr_status);
+
+err_mdm:
+       if (!IS_ERR_OR_NULL(mdm_dev)) {
+               device_destroy(radio_cls, dev_number);
+               mdm_dev = NULL;
+       }
+
+err_cls:
+       if (!IS_ERR_OR_NULL(radio_cls)) {
+               class_destroy(radio_cls);
+               radio_cls = NULL;
+       }
+
        return -1;
 }
 
@@ -173,9 +455,29 @@ static int __devexit mdm_ctrl_remove(struct platform_device *pdev)
        struct mdm_ctrl_platform_data *pdata = pdev->dev.platform_data;
 
        dev_info(&pdev->dev, "cleanup\n");
+
+       mdm_gpio_cleanup_internal();
+
+        if (working_queue)
+               destroy_workqueue(working_queue);
+
        for (i = 0; i < MDM_CTRL_NUM_GPIOS; i++)
                mdm_gpio_free(&pdata->gpios[i]);
 
+       device_remove_file(mdm_dev, &dev_attr_command);
+       device_remove_file(mdm_dev, &dev_attr_power_status);
+       device_remove_file(mdm_dev, &dev_attr_status);
+
+       if (!IS_ERR_OR_NULL(mdm_dev)) {
+               device_destroy(radio_cls, dev_number);
+               mdm_dev = NULL;
+       }
+
+       if (!IS_ERR_OR_NULL(radio_cls)) {
+               class_destroy(radio_cls);
+               radio_cls = NULL;
+       }
+
        return 0;
 }
 
@@ -192,7 +494,7 @@ static unsigned int __devexit bp_shutdown_wait(struct platform_device *pdev,
 
        for (i = 0; i < loop_count; i++) {
                msleep(LOOP_DELAY_TIME_MS);
-               bp_status = get_bp_status(pdata);
+               bp_status = get_bp_status();
                if (bp_status == BP_STATUS_SHUTDOWN_ACK) {
                        dev_info(&pdev->dev, "Modem powered off (with ack).\n");
                        pd_failure = 0;
@@ -222,15 +524,15 @@ static void __devexit mdm_ctrl_shutdown(struct platform_device *pdev)
 
        dev_info(&pdev->dev, "Shutting down modem.\n");
 
-       bp_status = get_bp_status(pdata);
+       bp_status = get_bp_status();
        dev_info(&pdev->dev, "Initial Modem status %s [0x%x]\n",
                 bp_status_string(bp_status), bp_status);
 
-       set_ap_status(pdata, AP_STATUS_BP_SHUTDOWN_REQ);
+       set_ap_status(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));
+       dev_info(&pdev->dev, "ap_status set to %d\n", get_ap_status());
 
        /* Toggle the power, delaying to allow modem to respond */
        mdm_gpio_set_value(pdata->gpios[MDM_CTRL_GPIO_BP_PWRON], 1);