Logo Search packages:      
Sourcecode: linux-qcm-msm version File versions  Download package

htc_battery.c

/* arch/arm/mach-msm/htc_battery.c
 *
 * Copyright (C) 2008 HTC Corporation.
 * Copyright (C) 2008 Google, Inc.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/power_supply.h>
#include <linux/platform_device.h>
#include <linux/debugfs.h>
#include <linux/wakelock.h>
#include <asm/gpio.h>
#include <mach/msm_rpcrouter.h>
#include <mach/board.h>

static struct wake_lock vbus_wake_lock;

#define TRACE_BATT 0

#if TRACE_BATT
#define BATT(x...) printk(KERN_INFO "[BATT] " x)
#else
#define BATT(x...) do {} while (0)
#endif

/* rpc related */
#define APP_BATT_PDEV_NAME          "rs30100001:00000000"
#define APP_BATT_PROG               0x30100001
#define APP_BATT_VER                0
#define HTC_PROCEDURE_BATTERY_NULL  0
#define HTC_PROCEDURE_GET_BATT_LEVEL      1
#define HTC_PROCEDURE_GET_BATT_INFO 2
#define HTC_PROCEDURE_GET_CABLE_STATUS    3
#define HTC_PROCEDURE_SET_BATT_DELTA      4

/* module debugger */
#define HTC_BATTERY_DEBUG           1
#define BATTERY_PREVENTION          1

/* Enable this will shut down if no battery */
#define ENABLE_BATTERY_DETECTION    0

#define GPIO_BATTERY_DETECTION            21
#define GPIO_BATTERY_CHARGER_EN           128

/* Charge current selection */
#define GPIO_BATTERY_CHARGER_CURRENT      129

typedef enum {
      DISABLE = 0,
      ENABLE_SLOW_CHG,
      ENABLE_FAST_CHG
} batt_ctl_t;

/* This order is the same as htc_power_supplies[]
 * And it's also the same as htc_cable_status_update()
 */
typedef enum {
      CHARGER_BATTERY = 0,
      CHARGER_USB,
      CHARGER_AC
} charger_type_t;

struct battery_info_reply {
      u32 batt_id;            /* Battery ID from ADC */
      u32 batt_vol;           /* Battery voltage from ADC */
      u32 batt_temp;          /* Battery Temperature (C) from formula and ADC */
      u32 batt_current; /* Battery current from ADC */
      u32 level;        /* formula */
      u32 charging_source;    /* 0: no cable, 1:usb, 2:AC */
      u32 charging_enabled;   /* 0: Disable, 1: Enable */
      u32 full_bat;           /* Full capacity of battery (mAh) */
};

struct htc_battery_info {
      int present;
      unsigned long update_time;

      /* lock to protect the battery info */
      struct mutex lock;

      /* lock held while calling the arm9 to query the battery info */
      struct mutex rpc_lock;
      struct battery_info_reply rep;
};

static struct msm_rpc_endpoint *endpoint;

static struct htc_battery_info htc_batt_info;

static unsigned int cache_time = 1000;

static int htc_battery_initial = 0;

static enum power_supply_property htc_battery_properties[] = {
      POWER_SUPPLY_PROP_STATUS,
      POWER_SUPPLY_PROP_HEALTH,
      POWER_SUPPLY_PROP_PRESENT,
      POWER_SUPPLY_PROP_TECHNOLOGY,
      POWER_SUPPLY_PROP_CAPACITY,
};

static enum power_supply_property htc_power_properties[] = {
      POWER_SUPPLY_PROP_ONLINE,
};

static char *supply_list[] = {
      "battery",
};

/* HTC dedicated attributes */
static ssize_t htc_battery_show_property(struct device *dev,
                                struct device_attribute *attr,
                                char *buf);

static int htc_power_get_property(struct power_supply *psy, 
                            enum power_supply_property psp,
                            union power_supply_propval *val);

static int htc_battery_get_property(struct power_supply *psy, 
                            enum power_supply_property psp,
                            union power_supply_propval *val);

static struct power_supply htc_power_supplies[] = {
      {
            .name = "battery",
            .type = POWER_SUPPLY_TYPE_BATTERY,
            .properties = htc_battery_properties,
            .num_properties = ARRAY_SIZE(htc_battery_properties),
            .get_property = htc_battery_get_property,
      },
      {
            .name = "usb",
            .type = POWER_SUPPLY_TYPE_USB,
            .supplied_to = supply_list,
            .num_supplicants = ARRAY_SIZE(supply_list),
            .properties = htc_power_properties,
            .num_properties = ARRAY_SIZE(htc_power_properties),
            .get_property = htc_power_get_property,
      },
      {
            .name = "ac",
            .type = POWER_SUPPLY_TYPE_MAINS,
            .supplied_to = supply_list,
            .num_supplicants = ARRAY_SIZE(supply_list),
            .properties = htc_power_properties,
            .num_properties = ARRAY_SIZE(htc_power_properties),
            .get_property = htc_power_get_property,
      },
};


/* -------------------------------------------------------------------------- */

#if defined(CONFIG_DEBUG_FS)
int htc_battery_set_charging(batt_ctl_t ctl);
static int batt_debug_set(void *data, u64 val)
{
      return htc_battery_set_charging((batt_ctl_t) val);
}

static int batt_debug_get(void *data, u64 *val)
{
      return -ENOSYS;
}

DEFINE_SIMPLE_ATTRIBUTE(batt_debug_fops, batt_debug_get, batt_debug_set, "%llu\n");
static int __init batt_debug_init(void)
{
      struct dentry *dent;

      dent = debugfs_create_dir("htc_battery", 0);
      if (IS_ERR(dent))
            return PTR_ERR(dent);

      debugfs_create_file("charger_state", 0644, dent, NULL, &batt_debug_fops);

      return 0;
}

device_initcall(batt_debug_init);
#endif

static int init_batt_gpio(void)
{
      if (gpio_request(GPIO_BATTERY_DETECTION, "batt_detect") < 0)
            goto gpio_failed;
      if (gpio_request(GPIO_BATTERY_CHARGER_EN, "charger_en") < 0)
            goto gpio_failed;
      if (gpio_request(GPIO_BATTERY_CHARGER_CURRENT, "charge_current") < 0)
            goto gpio_failed;

      return 0;

gpio_failed:      
      return -EINVAL;
      
}

/* 
 *    battery_charging_ctrl - battery charing control.
 *    @ctl:             battery control command
 *
 */
static int battery_charging_ctrl(batt_ctl_t ctl)
{
      int result = 0;

      switch (ctl) {
      case DISABLE:
            BATT("charger OFF\n");
            /* 0 for enable; 1 disable */
            result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 1);
            break;
      case ENABLE_SLOW_CHG:
            BATT("charger ON (SLOW)\n");
            result = gpio_direction_output(GPIO_BATTERY_CHARGER_CURRENT, 0);
            result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 0);
            break;
      case ENABLE_FAST_CHG:
            BATT("charger ON (FAST)\n");
            result = gpio_direction_output(GPIO_BATTERY_CHARGER_CURRENT, 1);
            result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 0);
            break;
      default:
            printk(KERN_ERR "Not supported battery ctr called.!\n");
            result = -EINVAL;
            break;
      }
      
      return result;
}

int htc_battery_set_charging(batt_ctl_t ctl)
{
      int rc;
      
      if ((rc = battery_charging_ctrl(ctl)) < 0)
            goto result;
      
      if (!htc_battery_initial) {
            htc_batt_info.rep.charging_enabled = ctl & 0x3;
      } else {
            mutex_lock(&htc_batt_info.lock);
            htc_batt_info.rep.charging_enabled = ctl & 0x3;
            mutex_unlock(&htc_batt_info.lock);
      }
result:     
      return rc;
}

int htc_battery_status_update(u32 curr_level)
{
      int notify;
      if (!htc_battery_initial)
            return 0;

      mutex_lock(&htc_batt_info.lock);
      notify = (htc_batt_info.rep.level != curr_level);
      htc_batt_info.rep.level = curr_level;
      mutex_unlock(&htc_batt_info.lock);

      if (notify)
            power_supply_changed(&htc_power_supplies[CHARGER_BATTERY]);
      return 0;
}

int htc_cable_status_update(int status)
{
      int rc = 0;
      unsigned source;

      if (!htc_battery_initial)
            return 0;
      
      mutex_lock(&htc_batt_info.lock);
      switch(status) {
      case CHARGER_BATTERY:
            BATT("cable NOT PRESENT\n");
            htc_batt_info.rep.charging_source = CHARGER_BATTERY;
            break;
      case CHARGER_USB:
            BATT("cable USB\n");
            htc_batt_info.rep.charging_source = CHARGER_USB;
            break;
      case CHARGER_AC:
            BATT("cable AC\n");
            htc_batt_info.rep.charging_source = CHARGER_AC;
            break;
      default:
            printk(KERN_ERR "%s: Not supported cable status received!\n",
                        __FUNCTION__);
            rc = -EINVAL;
      }
      source = htc_batt_info.rep.charging_source;
      mutex_unlock(&htc_batt_info.lock);

      msm_hsusb_set_vbus_state(source == CHARGER_USB);
      if (source == CHARGER_USB) {
            wake_lock(&vbus_wake_lock);
      } else {
            /* give userspace some time to see the uevent and update
             * LED state or whatnot...
             */
            wake_lock_timeout(&vbus_wake_lock, HZ / 2);
      }

      /* if the power source changes, all power supplies may change state */
      power_supply_changed(&htc_power_supplies[CHARGER_BATTERY]);
      power_supply_changed(&htc_power_supplies[CHARGER_USB]);
      power_supply_changed(&htc_power_supplies[CHARGER_AC]);

      return rc;
}

static int htc_get_batt_info(struct battery_info_reply *buffer)
{
      struct rpc_request_hdr req;
      
      struct htc_get_batt_info_rep {
            struct rpc_reply_hdr hdr;
            struct battery_info_reply info;
      } rep;
      
      int rc;

      if (buffer == NULL) 
            return -EINVAL;

      rc = msm_rpc_call_reply(endpoint, HTC_PROCEDURE_GET_BATT_INFO,
                        &req, sizeof(req),
                        &rep, sizeof(rep),
                        5 * HZ);
      if ( rc < 0 ) 
            return rc;
      
      mutex_lock(&htc_batt_info.lock);
      buffer->batt_id         = be32_to_cpu(rep.info.batt_id);
      buffer->batt_vol        = be32_to_cpu(rep.info.batt_vol);
      buffer->batt_temp             = be32_to_cpu(rep.info.batt_temp);
      buffer->batt_current          = be32_to_cpu(rep.info.batt_current);
      buffer->level                 = be32_to_cpu(rep.info.level);
      buffer->charging_source       = be32_to_cpu(rep.info.charging_source);
      buffer->charging_enabled      = be32_to_cpu(rep.info.charging_enabled);
      buffer->full_bat        = be32_to_cpu(rep.info.full_bat);
      mutex_unlock(&htc_batt_info.lock);

      return 0;
}

#if 0
static int htc_get_cable_status(void)
{
      
      struct rpc_request_hdr req;
      
      struct htc_get_cable_status_rep {
            struct rpc_reply_hdr hdr;
            int status;
      } rep;

      int rc;

      rc = msm_rpc_call_reply(endpoint, HTC_PROCEDURE_GET_CABLE_STATUS,
                        &req, sizeof(req),
                        &rep, sizeof(rep),
                        5 * HZ);
      if (rc < 0) 
            return rc;

      return be32_to_cpu(rep.status);
}
#endif

/* -------------------------------------------------------------------------- */
static int htc_power_get_property(struct power_supply *psy, 
                            enum power_supply_property psp,
                            union power_supply_propval *val)
{
      charger_type_t charger;
      
      mutex_lock(&htc_batt_info.lock);
      charger = htc_batt_info.rep.charging_source;
      mutex_unlock(&htc_batt_info.lock);

      switch (psp) {
      case POWER_SUPPLY_PROP_ONLINE:
            if (psy->type == POWER_SUPPLY_TYPE_MAINS)
                  val->intval = (charger ==  CHARGER_AC ? 1 : 0);
            else if (psy->type == POWER_SUPPLY_TYPE_USB)
                  val->intval = (charger ==  CHARGER_USB ? 1 : 0);
            else
                  val->intval = 0;
            break;
      default:
            return -EINVAL;
      }
      
      return 0;
}

static int htc_battery_get_charging_status(void)
{
      u32 level;
      charger_type_t charger; 
      int ret;
      
      mutex_lock(&htc_batt_info.lock);
      charger = htc_batt_info.rep.charging_source;
      
      switch (charger) {
      case CHARGER_BATTERY:
            ret = POWER_SUPPLY_STATUS_NOT_CHARGING;
            break;
      case CHARGER_USB:
      case CHARGER_AC:
            level = htc_batt_info.rep.level;
            if (level == 100)
                  ret = POWER_SUPPLY_STATUS_FULL;
            else
                  ret = POWER_SUPPLY_STATUS_CHARGING;
            break;
      default:
            ret = POWER_SUPPLY_STATUS_UNKNOWN;
      }
      mutex_unlock(&htc_batt_info.lock);
      return ret;
}

static int htc_battery_get_property(struct power_supply *psy, 
                            enum power_supply_property psp,
                            union power_supply_propval *val)
{
      switch (psp) {
      case POWER_SUPPLY_PROP_STATUS:
            val->intval = htc_battery_get_charging_status();
            break;
      case POWER_SUPPLY_PROP_HEALTH:
            val->intval = POWER_SUPPLY_HEALTH_GOOD;
            break;
      case POWER_SUPPLY_PROP_PRESENT:
            val->intval = htc_batt_info.present;
            break;
      case POWER_SUPPLY_PROP_TECHNOLOGY:
            val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
            break;
      case POWER_SUPPLY_PROP_CAPACITY:
            mutex_lock(&htc_batt_info.lock);
            val->intval = htc_batt_info.rep.level;
            mutex_unlock(&htc_batt_info.lock);
            break;
      default:          
            return -EINVAL;
      }
      
      return 0;
}

#define HTC_BATTERY_ATTR(_name)                                         \
{                                                           \
      .attr = { .name = #_name, .mode = S_IRUGO, .owner = THIS_MODULE },      \
      .show = htc_battery_show_property,                          \
      .store = NULL,                                              \
}

static struct device_attribute htc_battery_attrs[] = {
      HTC_BATTERY_ATTR(batt_id),
      HTC_BATTERY_ATTR(batt_vol),
      HTC_BATTERY_ATTR(batt_temp),
      HTC_BATTERY_ATTR(batt_current),
      HTC_BATTERY_ATTR(charging_source),
      HTC_BATTERY_ATTR(charging_enabled),
      HTC_BATTERY_ATTR(full_bat),
};

enum {
      BATT_ID = 0,
      BATT_VOL,
      BATT_TEMP,
      BATT_CURRENT,
      CHARGING_SOURCE,
      CHARGING_ENABLED,
      FULL_BAT,
};

static int htc_rpc_set_delta(unsigned delta)
{
      struct set_batt_delta_req {
            struct rpc_request_hdr hdr;
            uint32_t data;
      } req;

      req.data = cpu_to_be32(delta);
      return msm_rpc_call(endpoint, HTC_PROCEDURE_SET_BATT_DELTA,
                      &req, sizeof(req), 5 * HZ);
}


static ssize_t htc_battery_set_delta(struct device *dev,
                             struct device_attribute *attr,
                             const char *buf, size_t count)
{
      int rc;
      unsigned long delta = 0;
      
      delta = simple_strtoul(buf, NULL, 10);

      if (delta > 100)
            return -EINVAL;

      mutex_lock(&htc_batt_info.rpc_lock);
      rc = htc_rpc_set_delta(delta);
      mutex_unlock(&htc_batt_info.rpc_lock);
      if (rc < 0)
            return rc;
      return count;
}

static struct device_attribute htc_set_delta_attrs[] = {
      __ATTR(delta, S_IWUSR | S_IWGRP, NULL, htc_battery_set_delta),
};

static int htc_battery_create_attrs(struct device * dev)
{
      int i, j, rc;
      
      for (i = 0; i < ARRAY_SIZE(htc_battery_attrs); i++) {
            rc = device_create_file(dev, &htc_battery_attrs[i]);
            if (rc)
                  goto htc_attrs_failed;
      }

      for (j = 0; j < ARRAY_SIZE(htc_set_delta_attrs); j++) {
            rc = device_create_file(dev, &htc_set_delta_attrs[j]);
            if (rc)
                  goto htc_delta_attrs_failed;
      }
      
      goto succeed;
      
htc_attrs_failed:
      while (i--)
            device_remove_file(dev, &htc_battery_attrs[i]);
htc_delta_attrs_failed:
      while (j--)
            device_remove_file(dev, &htc_set_delta_attrs[i]);
succeed:    
      return rc;
}

static ssize_t htc_battery_show_property(struct device *dev,
                               struct device_attribute *attr,
                               char *buf)
{
      int i = 0;
      const ptrdiff_t off = attr - htc_battery_attrs;
      
      /* rpc lock is used to prevent two threads from calling
       * into the get info rpc at the same time
       */

      mutex_lock(&htc_batt_info.rpc_lock);
      /* check cache time to decide if we need to update */
      if (htc_batt_info.update_time &&
            time_before(jiffies, htc_batt_info.update_time +
                                msecs_to_jiffies(cache_time)))
                goto dont_need_update;
      
      if (htc_get_batt_info(&htc_batt_info.rep) < 0) {
            printk(KERN_ERR "%s: rpc failed!!!\n", __FUNCTION__);
      } else {
            htc_batt_info.update_time = jiffies;
      }
dont_need_update:
      mutex_unlock(&htc_batt_info.rpc_lock);

      mutex_lock(&htc_batt_info.lock);
      switch (off) {
      case BATT_ID:
            i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
                         htc_batt_info.rep.batt_id);
            break;
      case BATT_VOL:
            i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
                         htc_batt_info.rep.batt_vol);
            break;
      case BATT_TEMP:
            i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
                         htc_batt_info.rep.batt_temp);
            break;
      case BATT_CURRENT:
            i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
                         htc_batt_info.rep.batt_current);
            break;
      case CHARGING_SOURCE:
            i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
                         htc_batt_info.rep.charging_source);
            break;
      case CHARGING_ENABLED:
            i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
                         htc_batt_info.rep.charging_enabled);
            break;            
      case FULL_BAT:
            i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
                         htc_batt_info.rep.full_bat);
            break;
      default:
            i = -EINVAL;
      }     
      mutex_unlock(&htc_batt_info.lock);
      
      return i;
}

static int htc_battery_probe(struct platform_device *pdev)
{
      int i, rc;

      /* init battery gpio */
      if ((rc = init_batt_gpio()) < 0) {
            printk(KERN_ERR "%s: init battery gpio failed!\n", __FUNCTION__);
            return rc;
      }

      /* init structure data member */
      htc_batt_info.update_time     = jiffies;
      htc_batt_info.present         = gpio_get_value(GPIO_BATTERY_DETECTION);
      
      /* init rpc */
      endpoint = msm_rpc_connect(APP_BATT_PROG, APP_BATT_VER, 0);
      if (IS_ERR(endpoint)) {
            printk(KERN_ERR "%s: init rpc failed! rc = %ld\n",
                   __FUNCTION__, PTR_ERR(endpoint));
            return rc;
      }

      /* init power supplier framework */
      for (i = 0; i < ARRAY_SIZE(htc_power_supplies); i++) {
            rc = power_supply_register(&pdev->dev, &htc_power_supplies[i]);
            if (rc)
                  printk(KERN_ERR "Failed to register power supply (%d)\n", rc);    
      }

      /* create htc detail attributes */
      htc_battery_create_attrs(htc_power_supplies[CHARGER_BATTERY].dev);

      /* After battery driver gets initialized, send rpc request to inquiry
       * the battery status in case of we lost some info
       */
      htc_battery_initial = 1;

      mutex_lock(&htc_batt_info.rpc_lock);
      if (htc_get_batt_info(&htc_batt_info.rep) < 0)
            printk(KERN_ERR "%s: get info failed\n", __FUNCTION__);

      htc_cable_status_update(htc_batt_info.rep.charging_source);
      battery_charging_ctrl(htc_batt_info.rep.charging_enabled ?
                        ENABLE_SLOW_CHG : DISABLE);

      if (htc_rpc_set_delta(1) < 0)
            printk(KERN_ERR "%s: set delta failed\n", __FUNCTION__);
      htc_batt_info.update_time = jiffies;
      mutex_unlock(&htc_batt_info.rpc_lock);

      if (htc_batt_info.rep.charging_enabled == 0)
            battery_charging_ctrl(DISABLE);
      
      return 0;
}

static struct platform_driver htc_battery_driver = {
      .probe      = htc_battery_probe,
      .driver     = {
            .name = APP_BATT_PDEV_NAME,
            .owner      = THIS_MODULE,
      },
};

/* batt_mtoa server definitions */
#define BATT_MTOA_PROG                    0x30100000
#define BATT_MTOA_VERS                    0
#define RPC_BATT_MTOA_NULL                0
#define RPC_BATT_MTOA_SET_CHARGING_PROC         1
#define RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC  2
#define RPC_BATT_MTOA_LEVEL_UPDATE_PROC         3

struct rpc_batt_mtoa_set_charging_args {
      int enable;
};

struct rpc_batt_mtoa_cable_status_update_args {
      int status;
};

struct rpc_dem_battery_update_args {
      uint32_t level;
};

static int handle_battery_call(struct msm_rpc_server *server,
                         struct rpc_request_hdr *req, unsigned len)
{     
      switch (req->procedure) {
      case RPC_BATT_MTOA_NULL:
            return 0;

      case RPC_BATT_MTOA_SET_CHARGING_PROC: {
            struct rpc_batt_mtoa_set_charging_args *args;
            args = (struct rpc_batt_mtoa_set_charging_args *)(req + 1);
            args->enable = be32_to_cpu(args->enable);
            BATT("set_charging: enable=%d\n",args->enable);
            htc_battery_set_charging(args->enable);
            return 0;
      }
      case RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC: {
            struct rpc_batt_mtoa_cable_status_update_args *args;
            args = (struct rpc_batt_mtoa_cable_status_update_args *)(req + 1);
            args->status = be32_to_cpu(args->status);
            BATT("cable_status_update: status=%d\n",args->status);
            htc_cable_status_update(args->status);
            return 0;
      }
      case RPC_BATT_MTOA_LEVEL_UPDATE_PROC: {
            struct rpc_dem_battery_update_args *args;
            args = (struct rpc_dem_battery_update_args *)(req + 1);
            args->level = be32_to_cpu(args->level);
            BATT("dem_battery_update: level=%d\n",args->level);
            htc_battery_status_update(args->level);
            return 0;
      }
      default:
            printk(KERN_ERR "%s: program 0x%08x:%d: unknown procedure %d\n",
                   __FUNCTION__, req->prog, req->vers, req->procedure);
            return -ENODEV;
      }
}

static struct msm_rpc_server battery_server = {
      .prog = BATT_MTOA_PROG,
      .vers = BATT_MTOA_VERS,
      .rpc_call = handle_battery_call,
};

static int __init htc_battery_init(void)
{
      wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
      mutex_init(&htc_batt_info.lock);
      mutex_init(&htc_batt_info.rpc_lock);
      msm_rpc_create_server(&battery_server);
      platform_driver_register(&htc_battery_driver);
      return 0;
}

module_init(htc_battery_init);
MODULE_DESCRIPTION("HTC Battery Driver");
MODULE_LICENSE("GPL");


Generated by  Doxygen 1.6.0   Back to index