Chromium Code Reviews| 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(); |
|
Mandeep Singh Baines
2010/05/11 16:59:08
It would be better if wpce775x exported a high-lev
|
| + 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); |
|
Mandeep Singh Baines
2010/05/11 16:59:08
Not sure this is a good idea. Another way to guara
|
| +module_exit(qbat_exit); |
| + |
| +MODULE_AUTHOR("Quanta Computer Inc."); |
| +MODULE_DESCRIPTION("Quanta Embedded Controller I2C Battery Driver"); |
| +MODULE_LICENSE("GPL v2"); |
| + |