summaryrefslogtreecommitdiff
path: root/drivers/power/cpcap-battery.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/cpcap-battery.c')
-rw-r--r--drivers/power/cpcap-battery.c823
1 files changed, 823 insertions, 0 deletions
diff --git a/drivers/power/cpcap-battery.c b/drivers/power/cpcap-battery.c
new file mode 100644
index 00000000000..517d41bb919
--- /dev/null
+++ b/drivers/power/cpcap-battery.c
@@ -0,0 +1,823 @@
+/*
+ * Copyright (C) 2007-2009 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 <asm/div64.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/power_supply.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/spi/cpcap.h>
+#include <linux/spi/cpcap-regbits.h>
+#include <linux/spi/spi.h>
+#include <linux/time.h>
+#include <linux/miscdevice.h>
+#include <linux/debugfs.h>
+
+#define CPCAP_BATT_IRQ_BATTDET 0x01
+#define CPCAP_BATT_IRQ_OV 0x02
+#define CPCAP_BATT_IRQ_CC_CAL 0x04
+#define CPCAP_BATT_IRQ_ADCDONE 0x08
+#define CPCAP_BATT_IRQ_MACRO 0x10
+
+static long cpcap_batt_ioctl(
+ struct file *file,
+ unsigned int cmd,
+ unsigned long arg);
+static unsigned int cpcap_batt_poll(struct file *file, poll_table *wait);
+static int cpcap_batt_open(struct inode *inode, struct file *file);
+static ssize_t cpcap_batt_read(struct file *file, char *buf, size_t count,
+ loff_t *ppos);
+static int cpcap_batt_probe(struct platform_device *pdev);
+static int cpcap_batt_remove(struct platform_device *pdev);
+static int cpcap_batt_resume(struct platform_device *pdev);
+
+struct cpcap_batt_ps {
+ struct power_supply batt;
+ struct power_supply ac;
+ struct power_supply usb;
+ struct cpcap_device *cpcap;
+ struct cpcap_batt_data batt_state;
+ struct cpcap_batt_ac_data ac_state;
+ struct cpcap_batt_usb_data usb_state;
+ struct cpcap_adc_request req;
+ struct mutex lock;
+ char irq_status;
+ char data_pending;
+ wait_queue_head_t wait;
+ char async_req_pending;
+ unsigned long last_run_time;
+ bool no_update;
+};
+
+static const struct file_operations batt_fops = {
+ .owner = THIS_MODULE,
+ .open = cpcap_batt_open,
+ .unlocked_ioctl = cpcap_batt_ioctl,
+ .read = cpcap_batt_read,
+ .poll = cpcap_batt_poll,
+};
+
+static struct miscdevice batt_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "cpcap_batt",
+ .fops = &batt_fops,
+};
+
+static enum power_supply_property cpcap_batt_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER
+};
+
+static enum power_supply_property cpcap_batt_ac_props[] =
+{
+ POWER_SUPPLY_PROP_ONLINE
+};
+
+static enum power_supply_property cpcap_batt_usb_props[] =
+{
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_MODEL_NAME
+};
+
+static struct platform_driver cpcap_batt_driver = {
+ .probe = cpcap_batt_probe,
+ .remove = cpcap_batt_remove,
+ .resume = cpcap_batt_resume,
+ .driver = {
+ .name = "cpcap_battery",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct cpcap_batt_ps *cpcap_batt_sply;
+
+void cpcap_batt_irq_hdlr(enum cpcap_irqs irq, void *data)
+{
+ struct cpcap_batt_ps *sply = data;
+
+ mutex_lock(&sply->lock);
+ sply->data_pending = 1;
+
+ switch (irq) {
+ case CPCAP_IRQ_BATTDETB:
+ sply->irq_status |= CPCAP_BATT_IRQ_BATTDET;
+ cpcap_irq_unmask(sply->cpcap, irq);
+ break;
+
+ case CPCAP_IRQ_VBUSOV:
+ sply->irq_status |= CPCAP_BATT_IRQ_OV;
+ cpcap_irq_unmask(sply->cpcap, irq);
+ break;
+
+ case CPCAP_IRQ_CC_CAL:
+ sply->irq_status |= CPCAP_BATT_IRQ_CC_CAL;
+ cpcap_irq_unmask(sply->cpcap, irq);
+ break;
+
+ case CPCAP_IRQ_UC_PRIMACRO_7:
+ case CPCAP_IRQ_UC_PRIMACRO_8:
+ case CPCAP_IRQ_UC_PRIMACRO_9:
+ case CPCAP_IRQ_UC_PRIMACRO_10:
+ case CPCAP_IRQ_UC_PRIMACRO_11:
+ sply->irq_status |= CPCAP_BATT_IRQ_MACRO;
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&sply->lock);
+
+ wake_up_interruptible(&sply->wait);
+}
+
+void cpcap_batt_adc_hdlr(struct cpcap_device *cpcap, void *data)
+{
+ struct cpcap_batt_ps *sply = data;
+ mutex_lock(&sply->lock);
+
+ sply->async_req_pending = 0;
+
+ sply->data_pending = 1;
+
+ sply->irq_status |= CPCAP_BATT_IRQ_ADCDONE;
+
+ mutex_unlock(&sply->lock);
+
+ wake_up_interruptible(&sply->wait);
+}
+
+static int cpcap_batt_open(struct inode *inode, struct file *file)
+{
+ file->private_data = cpcap_batt_sply;
+ return 0;
+}
+
+static unsigned int cpcap_batt_poll(struct file *file, poll_table *wait)
+{
+ struct cpcap_batt_ps *sply = file->private_data;
+ unsigned int ret = 0;
+
+ poll_wait(file, &sply->wait, wait);
+
+ if (sply->data_pending)
+ ret = (POLLIN | POLLRDNORM);
+
+ return ret;
+}
+
+static ssize_t cpcap_batt_read(struct file *file,
+ char *buf, size_t count, loff_t *ppos)
+{
+ struct cpcap_batt_ps *sply = file->private_data;
+ int ret = -EFBIG;
+ unsigned long long temp;
+
+ if (count >= sizeof(char)) {
+ mutex_lock(&sply->lock);
+ if (!copy_to_user((void *)buf, (void *)&sply->irq_status,
+ sizeof(sply->irq_status)))
+ ret = sizeof(sply->irq_status);
+ else
+ ret = -EFAULT;
+ sply->data_pending = 0;
+ temp = sched_clock();
+ do_div(temp, NSEC_PER_SEC);
+ sply->last_run_time = (unsigned long)temp;
+
+ sply->irq_status = 0;
+ mutex_unlock(&sply->lock);
+ }
+
+ return ret;
+}
+
+static long cpcap_batt_ioctl(
+ struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ long ret = 0;
+ int i;
+ struct cpcap_batt_ps *sply = file->private_data;
+ struct cpcap_adc_request *req_async = &sply->req;
+ struct cpcap_adc_request req;
+ struct cpcap_adc_us_request req_us;
+ struct spi_device *spi = sply->cpcap->spi;
+ struct cpcap_platform_data *data = spi->controller_data;
+
+ switch (cmd) {
+ case CPCAP_IOCTL_BATT_DISPLAY_UPDATE:
+ if (sply->no_update)
+ return 0;
+ if (copy_from_user((void *)&sply->batt_state,
+ (void *)arg, sizeof(struct cpcap_batt_data)))
+ return -EFAULT;
+ power_supply_changed(&sply->batt);
+
+ if (data->batt_changed)
+ data->batt_changed(&sply->batt, &sply->batt_state);
+ break;
+
+ case CPCAP_IOCTL_BATT_ATOD_ASYNC:
+ mutex_lock(&sply->lock);
+ if (!sply->async_req_pending) {
+ if (copy_from_user((void *)&req_us, (void *)arg,
+ sizeof(struct cpcap_adc_us_request)
+ )) {
+ mutex_unlock(&sply->lock);
+ return -EFAULT;
+ }
+
+ req_async->format = req_us.format;
+ req_async->timing = req_us.timing;
+ req_async->type = req_us.type;
+ req_async->callback = cpcap_batt_adc_hdlr;
+ req_async->callback_param = sply;
+
+ ret = cpcap_adc_async_read(sply->cpcap, req_async);
+ if (!ret)
+ sply->async_req_pending = 1;
+ } else {
+ ret = -EAGAIN;
+ }
+ mutex_unlock(&sply->lock);
+
+ break;
+
+ case CPCAP_IOCTL_BATT_ATOD_SYNC:
+ if (copy_from_user((void *)&req_us, (void *)arg,
+ sizeof(struct cpcap_adc_us_request)))
+ return -EFAULT;
+
+ req.format = req_us.format;
+ req.timing = req_us.timing;
+ req.type = req_us.type;
+
+ ret = cpcap_adc_sync_read(sply->cpcap, &req);
+
+ if (ret)
+ return ret;
+
+ req_us.status = req.status;
+ for (i = 0; i < CPCAP_ADC_BANK0_NUM; i++)
+ req_us.result[i] = req.result[i];
+
+ if (copy_to_user((void *)arg, (void *)&req_us,
+ sizeof(struct cpcap_adc_us_request)))
+ return -EFAULT;
+ break;
+
+ case CPCAP_IOCTL_BATT_ATOD_READ:
+ req_us.format = req_async->format;
+ req_us.timing = req_async->timing;
+ req_us.type = req_async->type;
+ req_us.status = req_async->status;
+ for (i = 0; i < CPCAP_ADC_BANK0_NUM; i++)
+ req_us.result[i] = req_async->result[i];
+
+ if (copy_to_user((void *)arg, (void *)&req_us,
+ sizeof(struct cpcap_adc_us_request)))
+ return -EFAULT;
+ break;
+
+ default:
+ return -ENOTTY;
+ break;
+ }
+
+ return ret;
+}
+
+static int cpcap_batt_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct cpcap_batt_ps *sply = container_of(psy, struct cpcap_batt_ps,
+ ac);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = sply->ac_state.online;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static char *cpcap_batt_usb_models[] = {
+ "none", "usb", "factory"
+};
+
+static int cpcap_batt_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct cpcap_batt_ps *sply = container_of(psy, struct cpcap_batt_ps,
+ usb);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = sply->usb_state.online;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = sply->usb_state.current_now;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = cpcap_batt_usb_models[sply->usb_state.model];
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int cpcap_batt_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct cpcap_batt_ps *sply = container_of(psy, struct cpcap_batt_ps,
+ batt);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = sply->batt_state.status;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = sply->batt_state.health;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = sply->batt_state.present;
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = sply->batt_state.capacity;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = sply->batt_state.batt_volt;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = sply->batt_state.batt_temp;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = sply->batt_state.batt_full_capacity;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ val->intval = sply->batt_state.batt_capacity_one;
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int cpcap_batt_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct cpcap_batt_ps *sply;
+
+ if (pdev->dev.platform_data == NULL) {
+ dev_err(&pdev->dev, "no platform_data\n");
+ ret = -EINVAL;
+ goto prb_exit;
+ }
+
+ sply = kzalloc(sizeof(struct cpcap_batt_ps), GFP_KERNEL);
+ if (sply == NULL) {
+ ret = -ENOMEM;
+ goto prb_exit;
+ }
+
+ sply->cpcap = pdev->dev.platform_data;
+ mutex_init(&sply->lock);
+ init_waitqueue_head(&sply->wait);
+
+ sply->batt_state.status = POWER_SUPPLY_STATUS_UNKNOWN;
+ sply->batt_state.health = POWER_SUPPLY_HEALTH_GOOD;
+ sply->batt_state.present = 1;
+ sply->batt_state.capacity = 100; /* Percentage */
+ sply->batt_state.batt_volt = 4200000; /* uV */
+ sply->batt_state.batt_temp = 230; /* tenths of degrees Celsius */
+ sply->batt_state.batt_full_capacity = 0;
+ sply->batt_state.batt_capacity_one = 100;
+
+ sply->ac_state.online = 0;
+
+ sply->usb_state.online = 0;
+ sply->usb_state.current_now = 0;
+ sply->usb_state.model = CPCAP_BATT_USB_MODEL_NONE;
+
+ sply->batt.properties = cpcap_batt_props;
+ sply->batt.num_properties = ARRAY_SIZE(cpcap_batt_props);
+ sply->batt.get_property = cpcap_batt_get_property;
+ sply->batt.name = "battery";
+ sply->batt.type = POWER_SUPPLY_TYPE_BATTERY;
+
+ sply->ac.properties = cpcap_batt_ac_props;
+ sply->ac.num_properties = ARRAY_SIZE(cpcap_batt_ac_props);
+ sply->ac.get_property = cpcap_batt_ac_get_property;
+ sply->ac.name = "ac";
+ sply->ac.type = POWER_SUPPLY_TYPE_MAINS;
+
+ sply->usb.properties = cpcap_batt_usb_props;
+ sply->usb.num_properties = ARRAY_SIZE(cpcap_batt_usb_props);
+ sply->usb.get_property = cpcap_batt_usb_get_property;
+ sply->usb.name = "usb";
+ sply->usb.type = POWER_SUPPLY_TYPE_USB;
+
+ sply->no_update = false;
+
+ ret = power_supply_register(&pdev->dev, &sply->ac);
+ if (ret)
+ goto prb_exit;
+ ret = power_supply_register(&pdev->dev, &sply->batt);
+ if (ret)
+ goto unregac_exit;
+ ret = power_supply_register(&pdev->dev, &sply->usb);
+ if (ret)
+ goto unregbatt_exit;
+ platform_set_drvdata(pdev, sply);
+ sply->cpcap->battdata = sply;
+ cpcap_batt_sply = sply;
+
+ ret = misc_register(&batt_dev);
+ if (ret)
+ goto unregusb_exit;
+
+ ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_VBUSOV,
+ cpcap_batt_irq_hdlr, sply);
+ if (ret)
+ goto unregmisc_exit;
+ ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_BATTDETB,
+ cpcap_batt_irq_hdlr, sply);
+ if (ret)
+ goto unregirq_exit;
+ ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_CC_CAL,
+ cpcap_batt_irq_hdlr, sply);
+ if (ret)
+ goto unregirq_exit;
+
+ ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_7,
+ cpcap_batt_irq_hdlr, sply);
+ cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_7);
+
+ if (ret)
+ goto unregirq_exit;
+
+ ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_8,
+ cpcap_batt_irq_hdlr, sply);
+ cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_8);
+
+ if (ret)
+ goto unregirq_exit;
+
+ ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_9,
+ cpcap_batt_irq_hdlr, sply);
+ cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_9);
+
+ if (ret)
+ goto unregirq_exit;
+
+ ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_10,
+ cpcap_batt_irq_hdlr, sply);
+ cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_10);
+
+ if (ret)
+ goto unregirq_exit;
+
+ ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_11,
+ cpcap_batt_irq_hdlr, sply);
+ cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_11);
+
+ if (ret)
+ goto unregirq_exit;
+
+ goto prb_exit;
+
+unregirq_exit:
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_VBUSOV);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_BATTDETB);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_CC_CAL);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_7);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_8);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_9);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_10);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_11);
+unregmisc_exit:
+ misc_deregister(&batt_dev);
+unregusb_exit:
+ power_supply_unregister(&sply->usb);
+unregbatt_exit:
+ power_supply_unregister(&sply->batt);
+unregac_exit:
+ power_supply_unregister(&sply->ac);
+
+prb_exit:
+ return ret;
+}
+
+static int cpcap_batt_remove(struct platform_device *pdev)
+{
+ struct cpcap_batt_ps *sply = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&sply->batt);
+ power_supply_unregister(&sply->ac);
+ power_supply_unregister(&sply->usb);
+ misc_deregister(&batt_dev);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_VBUSOV);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_BATTDETB);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_CC_CAL);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_7);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_8);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_9);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_10);
+ cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_11);
+ sply->cpcap->battdata = NULL;
+ kfree(sply);
+
+ return 0;
+}
+
+static int cpcap_batt_resume(struct platform_device *pdev)
+{
+ struct cpcap_batt_ps *sply = platform_get_drvdata(pdev);
+ unsigned long cur_time;
+ unsigned long long temp;
+
+ temp = sched_clock();
+ do_div(temp, NSEC_PER_SEC);
+ cur_time = ((unsigned long)temp);
+ if ((cur_time - sply->last_run_time) < 0)
+ sply->last_run_time = 0;
+
+ if ((cur_time - sply->last_run_time) > 50) {
+ mutex_lock(&sply->lock);
+ sply->data_pending = 1;
+ sply->irq_status |= CPCAP_BATT_IRQ_MACRO;
+
+ mutex_unlock(&sply->lock);
+
+ wake_up_interruptible(&sply->wait);
+ }
+
+ return 0;
+}
+
+void cpcap_batt_set_ac_prop(struct cpcap_device *cpcap, int online)
+{
+ struct cpcap_batt_ps *sply = cpcap->battdata;
+ struct spi_device *spi = cpcap->spi;
+ struct cpcap_platform_data *data = spi->controller_data;
+
+ if (sply != NULL) {
+ sply->ac_state.online = online;
+ power_supply_changed(&sply->ac);
+
+ if (data->ac_changed)
+ data->ac_changed(&sply->ac, &sply->ac_state);
+ }
+}
+EXPORT_SYMBOL(cpcap_batt_set_ac_prop);
+
+void cpcap_batt_set_usb_prop_online(struct cpcap_device *cpcap, int online,
+ enum cpcap_batt_usb_model model)
+{
+ struct cpcap_batt_ps *sply = cpcap->battdata;
+ struct spi_device *spi = cpcap->spi;
+ struct cpcap_platform_data *data = spi->controller_data;
+
+ if (sply != NULL) {
+ sply->usb_state.online = online;
+ sply->usb_state.model = model;
+ power_supply_changed(&sply->usb);
+
+ if (data->usb_changed)
+ data->usb_changed(&sply->usb, &sply->usb_state);
+ }
+}
+EXPORT_SYMBOL(cpcap_batt_set_usb_prop_online);
+
+void cpcap_batt_set_usb_prop_curr(struct cpcap_device *cpcap, unsigned int curr)
+{
+ struct cpcap_batt_ps *sply = cpcap->battdata;
+ struct spi_device *spi = cpcap->spi;
+ struct cpcap_platform_data *data = spi->controller_data;
+
+ if (sply != NULL) {
+ sply->usb_state.current_now = curr;
+ power_supply_changed(&sply->usb);
+
+ if (data->usb_changed)
+ data->usb_changed(&sply->usb, &sply->usb_state);
+ }
+}
+EXPORT_SYMBOL(cpcap_batt_set_usb_prop_curr);
+
+/*
+ * Debugfs interface to test how system works with different values of
+ * the battery properties. Once the propety value is set through the
+ * debugfs, updtes from the drivers will be discarded.
+ */
+#ifdef CONFIG_DEBUG_FS
+
+static int cpcap_batt_debug_set(void *prop, u64 val)
+{
+ int data = (int)val;
+ enum power_supply_property psp = (enum power_supply_property)prop;
+ struct cpcap_batt_ps *sply = cpcap_batt_sply;
+ bool changed = true;
+ sply->no_update = true;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ sply->batt_state.status = data;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ sply->batt_state.health = data;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ sply->batt_state.present = data;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ sply->batt_state.capacity = data;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ sply->batt_state.batt_volt = data;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ sply->batt_state.batt_temp = data;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ sply->batt_state.batt_full_capacity = data;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ sply->batt_state.batt_capacity_one = data;
+ break;
+
+ default:
+ changed = false;
+ break;
+ }
+
+ if (changed)
+ power_supply_changed(&sply->batt);
+
+ return 0;
+}
+
+static int cpcap_batt_debug_get(void *prop, u64 *val)
+{
+ enum power_supply_property psp = (enum power_supply_property)prop;
+ struct cpcap_batt_ps *sply = cpcap_batt_sply;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ *val = sply->batt_state.status;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ *val = sply->batt_state.health;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ *val = sply->batt_state.present;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ *val = sply->batt_state.capacity;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ *val = sply->batt_state.batt_volt;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ *val = sply->batt_state.batt_temp;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ *val = sply->batt_state.batt_full_capacity;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ *val = sply->batt_state.batt_capacity_one;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(cpcap_battery_fops, cpcap_batt_debug_get,
+ cpcap_batt_debug_set, "%llu\n");
+
+static int __init cpcap_batt_debug_init(void)
+{
+ struct dentry *dent = debugfs_create_dir("battery", 0);
+ int ret = 0;
+
+ if (!IS_ERR(dent)) {
+ debugfs_create_file("status", 0666, dent,
+ (void *)POWER_SUPPLY_PROP_STATUS, &cpcap_battery_fops);
+ debugfs_create_file("health", 0666, dent,
+ (void *)POWER_SUPPLY_PROP_HEALTH, &cpcap_battery_fops);
+ debugfs_create_file("present", 0666, dent,
+ (void *)POWER_SUPPLY_PROP_PRESENT, &cpcap_battery_fops);
+ debugfs_create_file("voltage", 0666, dent,
+ (void *)POWER_SUPPLY_PROP_VOLTAGE_NOW, &cpcap_battery_fops);
+ debugfs_create_file("capacity", 0666, dent,
+ (void *)POWER_SUPPLY_PROP_CAPACITY, &cpcap_battery_fops);
+ debugfs_create_file("temp", 0666, dent,
+ (void *)POWER_SUPPLY_PROP_TEMP, &cpcap_battery_fops);
+ debugfs_create_file("charge_full_design", 0666, dent,
+ (void *)POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ &cpcap_battery_fops);
+ debugfs_create_file("charge_counter", 0666, dent,
+ (void *)POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ &cpcap_battery_fops);
+ } else {
+ ret = PTR_ERR(dent);
+ }
+
+ return ret;
+}
+
+late_initcall(cpcap_batt_debug_init);
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int __init cpcap_batt_init(void)
+{
+ return platform_driver_register(&cpcap_batt_driver);
+}
+subsys_initcall(cpcap_batt_init);
+
+static void __exit cpcap_batt_exit(void)
+{
+ platform_driver_unregister(&cpcap_batt_driver);
+}
+module_exit(cpcap_batt_exit);
+
+MODULE_ALIAS("platform:cpcap_batt");
+MODULE_DESCRIPTION("CPCAP BATTERY driver");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");