Index: drivers/power/qci_battery.c |
diff --git a/drivers/power/qci_battery.c b/drivers/power/qci_battery.c |
new file mode 100755 |
index 0000000000000000000000000000000000000000..593d7201435d07c13db0719b0a552e31e3b5325d |
--- /dev/null |
+++ b/drivers/power/qci_battery.c |
@@ -0,0 +1,356 @@ |
+/* Quanta I2C Battery Driver |
+ * |
+ * Copyright (C) 2009 Quanta Computer 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. |
+ * |
+ */ |
+ |
+/* |
+ * |
+ * The Driver with I/O communications via the I2C Interface for ST15 platform. |
+ * And it is only working on the nuvoTon WPCE775x Embedded Controller. |
+ * |
+ */ |
+ |
+#include <linux/module.h> |
+#include <linux/err.h> |
+#include <linux/platform_device.h> |
+#include <linux/power_supply.h> |
+#include <linux/sched.h> |
+#include <linux/gpio.h> |
+#include <linux/i2c.h> |
+#include <linux/wpce775x.h> |
+ |
+#include "qci_battery.h" |
+ |
+struct qci_bat_info { |
+ u8 type_id; |
+ u8 power_flag; |
+ u8 ec_ver_lsb; |
+ u8 ec_ver_msb; |
+ u8 mbat_rsoc; |
+ u8 mbat_volt_lsb; |
+ u8 mbat_volt_msb; |
+ u8 mbat_status; |
+ u8 mbchg_status; |
+ u8 mbat_temp_lsb; |
+ u8 mbat_temp_msb; |
+}; |
+ |
+/* General structure to hold the driver data */ |
+struct i2cbat_drv_data { |
+ struct i2c_client *bi2c_client; |
+ struct work_struct work; |
+ char batt_data[I2C_BAT_BUFFER_LEN+1]; |
+ unsigned int qcibat_irq; |
+ unsigned int qcibat_gpio; |
+ struct qci_bat_info bif; |
+}; |
+ |
+static struct i2cbat_drv_data context; |
+/********************************************************************* |
+ * Power |
+ *********************************************************************/ |
+ |
+static int qci_ac_get_prop(struct power_supply *psy, |
+ enum power_supply_property psp, |
+ union power_supply_propval *val) |
+{ |
+ int ret = 0; |
+ switch (psp) { |
+ case POWER_SUPPLY_PROP_ONLINE: |
+ if (context.bif.power_flag & EC_FLAG_ADAPTER_IN) |
+ val->intval = EC_ADAPTER_PRESENT; |
+ else |
+ val->intval = EC_ADAPTER_NOT_PRESENT; |
+ break; |
+ default: |
+ ret = -EINVAL; |
+ break; |
+ } |
+ return ret; |
+} |
+ |
+static enum power_supply_property qci_ac_props[] = { |
+ POWER_SUPPLY_PROP_ONLINE, |
+}; |
+ |
+static enum power_supply_property qci_bat_props[] = { |
+ POWER_SUPPLY_PROP_STATUS, |
+ POWER_SUPPLY_PROP_PRESENT, |
+ POWER_SUPPLY_PROP_HEALTH, |
+ POWER_SUPPLY_PROP_TECHNOLOGY, |
+ POWER_SUPPLY_PROP_VOLTAGE_AVG, |
+ POWER_SUPPLY_PROP_CURRENT_AVG, |
+ POWER_SUPPLY_PROP_CAPACITY, |
+ POWER_SUPPLY_PROP_TEMP, |
+ POWER_SUPPLY_PROP_TEMP_AMBIENT, |
+ POWER_SUPPLY_PROP_MANUFACTURER, |
+ POWER_SUPPLY_PROP_SERIAL_NUMBER, |
+ POWER_SUPPLY_PROP_CHARGE_COUNTER, |
+}; |
+ |
+static int qbat_get_status(union power_supply_propval *val) |
+{ |
+ if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) |
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN; |
+ else if (context.bif.mbchg_status & CHG_STATUS_BAT_INCHARGE) |
+ val->intval = POWER_SUPPLY_STATUS_CHARGING; |
+ else if (context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_FULL) |
+ val->intval = POWER_SUPPLY_STATUS_FULL; |
+ else |
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
+ |
+ return 0; |
+} |
+ |
+static int qbat_get_present(union power_supply_propval *val) |
+{ |
+ if (context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) |
+ val->intval = EC_BAT_PRESENT; |
+ else |
+ val->intval = EC_BAT_NOT_PRESENT; |
+ return 0; |
+} |
+ |
+static int qbat_get_health(union power_supply_propval *val) |
+{ |
+ if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) |
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; |
+ else |
+ val->intval = POWER_SUPPLY_HEALTH_GOOD; |
+ return 0; |
+} |
+ |
+static int qbat_get_voltage_avg(union power_supply_propval *val) |
+{ |
+ val->intval = (context.bif.mbat_volt_msb << 8 | |
+ context.bif.mbat_volt_lsb); |
+ return 0; |
+} |
+ |
+static int qbat_get_capacity(union power_supply_propval *val) |
+{ |
+ if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) |
+ val->intval = 0xFF; |
+ else |
+ val->intval = context.bif.mbat_rsoc; |
+ return 0; |
+} |
+ |
+static int qbat_get_temp_avg(union power_supply_propval *val) |
+{ |
+ if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) |
+ val->intval = 0xFFFF; |
+ else |
+ val->intval = ((context.bif.mbat_temp_msb << 8) | |
+ context.bif.mbat_temp_lsb) - 2731; |
+ return 0; |
+} |
+ |
+static int qbat_get_mfr(union power_supply_propval *val) |
+{ |
+ val->strval = "Unknown"; |
+ return 0; |
+} |
+ |
+static int qbat_get_tech(union power_supply_propval *val) |
+{ |
+ if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) |
+ val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; |
+ else |
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
+ return 0; |
+} |
+ |
+/********************************************************************* |
+ * Battery properties |
+ *********************************************************************/ |
+static int qbat_get_property(struct power_supply *psy, |
+ enum power_supply_property psp, |
+ union power_supply_propval *val) |
+{ |
+ int ret = 0; |
+ switch (psp) { |
+ case POWER_SUPPLY_PROP_STATUS: |
+ ret = qbat_get_status(val); |
+ break; |
+ case POWER_SUPPLY_PROP_PRESENT: |
+ ret = qbat_get_present(val); |
+ break; |
+ case POWER_SUPPLY_PROP_HEALTH: |
+ ret = qbat_get_health(val); |
+ break; |
+ case POWER_SUPPLY_PROP_MANUFACTURER: |
+ ret = qbat_get_mfr(val); |
+ break; |
+ case POWER_SUPPLY_PROP_TECHNOLOGY: |
+ ret = qbat_get_tech(val); |
+ break; |
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
+ ret = qbat_get_voltage_avg(val); |
+ break; |
+ case POWER_SUPPLY_PROP_CURRENT_AVG: |
+ break; |
+ case POWER_SUPPLY_PROP_CAPACITY: |
+ ret = qbat_get_capacity(val); |
+ break; |
+ case POWER_SUPPLY_PROP_TEMP: |
+ ret = qbat_get_temp_avg(val); |
+ break; |
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT: |
+ ret = qbat_get_temp_avg(val); |
+ break; |
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER: |
+ break; |
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER: |
+ break; |
+ default: |
+ ret = -EINVAL; |
+ break; |
+ } |
+ |
+ return ret; |
+} |
+ |
+/********************************************************************* |
+ * Initialisation |
+ *********************************************************************/ |
+ |
+static struct power_supply qci_ac = { |
+ .name = "ac", |
+ .type = POWER_SUPPLY_TYPE_MAINS, |
+ .properties = qci_ac_props, |
+ .num_properties = ARRAY_SIZE(qci_ac_props), |
+ .get_property = qci_ac_get_prop, |
+}; |
+ |
+static struct power_supply qci_bat = { |
+ .name = "battery", |
+ .type = POWER_SUPPLY_TYPE_BATTERY, |
+ .properties = qci_bat_props, |
+ .num_properties = ARRAY_SIZE(qci_bat_props), |
+ .get_property = qbat_get_property, |
+ .use_for_apm = 1, |
+}; |
+ |
+static irqreturn_t qbat_interrupt(int irq, void *dev_id) |
+{ |
+ struct i2cbat_drv_data *ibat_drv_data = dev_id; |
+ schedule_work(&ibat_drv_data->work); |
+ return IRQ_HANDLED; |
+} |
+ |
+static int qci_get_bat_info(struct i2c_client *client, char *ec_data) |
+{ |
+ struct i2c_msg bat_msg; |
+ bat_msg.addr = client->addr; |
+ bat_msg.flags = I2C_M_RD; |
+ bat_msg.len = I2C_BAT_BUFFER_LEN; |
+ bat_msg.buf = ec_data; |
+ return i2c_transfer(client->adapter, &bat_msg, 1); |
+} |
+ |
+static void qbat_work(struct work_struct *_work) |
+{ |
+ struct i2cbat_drv_data *ibat_drv_data = |
+ container_of(_work, struct i2cbat_drv_data, work); |
+ struct i2c_client *ibatclient = ibat_drv_data->bi2c_client; |
+ |
+ qci_get_bat_info(ibatclient, ibat_drv_data->batt_data); |
+ memcpy(&context.bif, |
+ ibat_drv_data->batt_data, |
+ sizeof(struct qci_bat_info)); |
+ power_supply_changed(&qci_ac); |
+ power_supply_changed(&qci_bat); |
+} |
+ |
+static struct platform_device *bat_pdev; |
+ |
+static int __init qbat_init(void) |
+{ |
+ int err = 0; |
+ |
+ context.bi2c_client = wpce_get_i2c_client(); |
+ if (context.bi2c_client == NULL) |
+ return -1; |
+ |
+ i2c_set_clientdata(context.bi2c_client, &context); |
+ context.qcibat_gpio = context.bi2c_client->irq; |
+ |
+ /*battery device register*/ |
+ bat_pdev = platform_device_register_simple("battery", 0, NULL, 0); |
+ if (IS_ERR(bat_pdev)) |
+ return PTR_ERR(bat_pdev); |
+ |
+ err = power_supply_register(&bat_pdev->dev, &qci_ac); |
+ if (err) |
+ goto ac_failed; |
+ |
+ qci_bat.name = bat_pdev->name; |
+ err = power_supply_register(&bat_pdev->dev, &qci_bat); |
+ if (err) |
+ goto battery_failed; |
+ |
+ /*battery irq configure*/ |
+ INIT_WORK(&context.work, qbat_work); |
+ err = gpio_request(context.qcibat_gpio, "qci-bat"); |
+ if (err) { |
+ dev_err(&context.bi2c_client->dev, |
+ "[BAT] err gpio request\n"); |
+ goto gpio_request_fail; |
+ } |
+ context.qcibat_irq = gpio_to_irq(context.qcibat_gpio); |
+ err = request_irq(context.qcibat_irq, qbat_interrupt, |
+ IRQF_TRIGGER_FALLING, BATTERY_ID_NAME, &context); |
+ if (err) { |
+ dev_err(&context.bi2c_client->dev, |
+ "[BAT] unable to get IRQ\n"); |
+ goto request_irq_fail; |
+ } |
+ err = qci_get_bat_info(context.bi2c_client, context.batt_data); |
+ |
+ goto success; |
+ |
+request_irq_fail: |
+ gpio_free(context.qcibat_gpio); |
+ |
+gpio_request_fail: |
+ power_supply_unregister(&qci_bat); |
+ |
+battery_failed: |
+ power_supply_unregister(&qci_ac); |
+ |
+ac_failed: |
+ platform_device_unregister(bat_pdev); |
+ |
+ i2c_set_clientdata(context.bi2c_client, NULL); |
+success: |
+ return err; |
+} |
+ |
+static void __exit qbat_exit(void) |
+{ |
+ free_irq(context.qcibat_irq, &context); |
+ gpio_free(context.qcibat_gpio); |
+ power_supply_unregister(&qci_bat); |
+ power_supply_unregister(&qci_ac); |
+ platform_device_unregister(bat_pdev); |
+ i2c_set_clientdata(context.bi2c_client, NULL); |
+} |
+ |
+late_initcall(qbat_init); |
+module_exit(qbat_exit); |
+ |
+MODULE_AUTHOR("Quanta Computer Inc."); |
+MODULE_DESCRIPTION("Quanta Embedded Controller I2C Battery Driver"); |
+MODULE_LICENSE("GPL v2"); |
+ |