mirror of
https://github.com/torvalds/linux.git
synced 2026-06-10 15:42:19 +02:00
misc: mdm6600_ctrl: Add query and control of BP status via sysfs.
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>
This commit is contained in:
parent
c3b9e22802
commit
6d02b486b3
|
|
@ -20,6 +20,12 @@
|
|||
#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
|
||||
|
|
@ -39,25 +45,114 @@
|
|||
|
||||
#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];
|
||||
unsigned int status = BP_STATUS_UNDEFINED;
|
||||
unsigned int bp_status[3] = {0};
|
||||
|
||||
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]);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user