blob: 7b4d97e8c27f6d3bf7a23f0cb5c271f0f4a8ef57 [file] [log] [blame]
/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/power_supply.h>
#include <linux/spmi.h>
/* Interrupt offsets */
#define INT_RT_STS(base) (base + 0x10)
#define INT_SET_TYPE(base) (base + 0x11)
#define INT_POLARITY_HIGH(base) (base + 0x12)
#define INT_POLARITY_LOW(base) (base + 0x13)
#define INT_LATCHED_CLR(base) (base + 0x14)
#define INT_EN_SET(base) (base + 0x15)
#define INT_EN_CLR(base) (base + 0x16)
#define INT_LATCHED_STS(base) (base + 0x18)
#define INT_PENDING_STS(base) (base + 0x19)
#define INT_MID_SEL(base) (base + 0x1A)
#define INT_PRIORITY(base) (base + 0x1B)
/* BMS Register Offsets */
#define BMS1_REVISION1 0x0
#define BMS1_REVISION2 0x1
#define BMS1_STATUS1 0x8
#define BMS1_MODE_CTL 0X40
/* Columb counter clear registers */
#define BMS1_CC_DATA_CTL 0x42
#define BMS1_CC_CLEAR_CTRL 0x43
/* OCV limit registers */
#define BMS1_OCV_USE_LOW_LIMIT_THR0 0x48
#define BMS1_OCV_USE_LOW_LIMIT_THR1 0x49
#define BMS1_OCV_USE_HIGH_LIMIT_THR0 0x4A
#define BMS1_OCV_USE_HIGH_LIMIT_THR1 0x4B
#define BMS1_OCV_USE_LIMIT_CTL 0x4C
/* CC interrupt threshold */
#define BMS1_CC_THR0 0x7A
#define BMS1_CC_THR1 0x7B
#define BMS1_CC_THR2 0x7C
#define BMS1_CC_THR3 0x7D
#define BMS1_CC_THR4 0x7E
/* OCV for r registers */
#define BMS1_OCV_FOR_R_DATA0 0x80
#define BMS1_OCV_FOR_R_DATA1 0x81
#define BMS1_VSENSE_FOR_R_DATA0 0x82
#define BMS1_VSENSE_FOR_R_DATA1 0x83
/* Columb counter data */
#define BMS1_CC_DATA0 0x8A
#define BMS1_CC_DATA1 0x8B
#define BMS1_CC_DATA2 0x8C
#define BMS1_CC_DATA3 0x8D
#define BMS1_CC_DATA4 0x8E
/* OCV for soc data */
#define BMS1_OCV_FOR_SOC_DATA0 0x90
#define BMS1_OCV_FOR_SOC_DATA1 0x91
#define BMS1_VSENSE_PON_DATA0 0x94
#define BMS1_VSENSE_PON_DATA1 0x95
#define BMS1_VBAT_AVG_DATA0 0x9E
#define BMS1_VBAT_AVG_DATA1 0x9F
/* Extra bms registers */
#define BMS1_BMS_DATA_REG_0 0xB0
#define BMS1_BMS_DATA_REG_1 0xB1
#define BMS1_BMS_DATA_REG_2 0xB2
#define BMS1_BMS_DATA_REG_3 0xB3
#define QPNP_BMS_DEV_NAME "qcom,qpnp-bms"
struct qpnp_bms_chip {
struct device *dev;
struct power_supply bms_psy;
struct spmi_device *spmi;
u16 base;
u8 revision1;
u8 revision2;
int charger_status;
bool online;
/* platform data */
unsigned int r_sense_mohm;
unsigned int v_cutoff;
unsigned int max_voltage;
unsigned int r_conn_mohm;
int shutdown_soc_valid_limit;
int adjust_soc_low_threshold;
int adjust_soc_high_threshold;
int chg_term;
};
static struct of_device_id qpnp_bms_match_table[] = {
{ .compatible = QPNP_BMS_DEV_NAME },
{}
};
static char *qpnp_bms_supplicants[] = {
"battery"
};
static enum power_supply_property msm_bms_power_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
};
static int qpnp_read_wrapper(struct qpnp_bms_chip *chip, u8 *val,
u16 base, int count)
{
int rc;
struct spmi_device *spmi = chip->spmi;
rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, base, val, count);
if (rc)
pr_err("SPMI read failed rc=%d\n", rc);
return 0;
}
/* Returns capacity as a SoC percentage between 0 and 100 */
static int get_prop_bms_capacity(struct qpnp_bms_chip *chip)
{
/* return 50 until a real algorithm is implemented */
return 50;
}
/* Returns instantaneous current in uA */
static int get_prop_bms_current_now(struct qpnp_bms_chip *chip)
{
/* temporarily return 0 until a real algorithm is put in */
return 0;
}
/* Returns full charge design in uAh */
static int get_prop_bms_charge_full_design(struct qpnp_bms_chip *chip)
{
/* temporarily return 0 until a real algorithm is put in */
return 0;
}
static void set_prop_bms_online(struct qpnp_bms_chip *chip, bool online)
{
chip->online = online;
}
static void set_prop_bms_status(struct qpnp_bms_chip *chip, int status)
{
chip->charger_status = status;
}
static void qpnp_bms_external_power_changed(struct power_supply *psy)
{
}
static int qpnp_bms_power_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct qpnp_bms_chip *chip = container_of(psy, struct qpnp_bms_chip,
bms_psy);
switch (psp) {
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = get_prop_bms_capacity(chip);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = get_prop_bms_current_now(chip);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = get_prop_bms_charge_full_design(chip);
break;
default:
return -EINVAL;
}
return 0;
}
static int qpnp_bms_power_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct qpnp_bms_chip *chip = container_of(psy, struct qpnp_bms_chip,
bms_psy);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
set_prop_bms_online(chip, val->intval);
break;
case POWER_SUPPLY_PROP_STATUS:
set_prop_bms_status(chip, (bool)val->intval);
break;
default:
return -EINVAL;
}
return 0;
}
#define SPMI_PROPERTY_READ(chip_prop, qpnp_spmi_property, retval, errlabel)\
do { \
retval = of_property_read_u32(spmi->dev.of_node, \
"qcom,bms-" qpnp_spmi_property, \
&chip->chip_prop); \
if (retval) { \
pr_err("Error reading " #qpnp_spmi_property \
" property %d\n", rc); \
goto errlabel; \
} \
} while (0)
static int __devinit
qpnp_bms_probe(struct spmi_device *spmi)
{
struct qpnp_bms_chip *chip;
struct resource *bms_resource;
int rc;
chip = kzalloc(sizeof *chip, GFP_KERNEL);
if (chip == NULL) {
pr_err("kzalloc() failed.\n");
return -ENOMEM;
}
chip->dev = &(spmi->dev);
chip->spmi = spmi;
bms_resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
if (!bms_resource) {
dev_err(&spmi->dev, "Unable to get BMS base address\n");
return -ENXIO;
}
chip->base = bms_resource->start;
rc = qpnp_read_wrapper(chip, &chip->revision1,
chip->base + BMS1_REVISION1, 1);
if (rc) {
pr_err("error reading version register %d\n", rc);
goto error_read;
}
rc = qpnp_read_wrapper(chip, &chip->revision2,
chip->base + BMS1_REVISION2, 1);
if (rc) {
pr_err("Error reading version register %d\n", rc);
goto error_read;
}
SPMI_PROPERTY_READ(r_sense_mohm, "r-sense-mohm", rc, error_read);
SPMI_PROPERTY_READ(v_cutoff, "v-cutoff-uv", rc, error_read);
SPMI_PROPERTY_READ(max_voltage, "max-voltage-uv", rc, error_read);
SPMI_PROPERTY_READ(r_conn_mohm, "r-conn-mohm", rc, error_read);
SPMI_PROPERTY_READ(shutdown_soc_valid_limit,
"shutdown-soc-valid-limit", rc, error_read);
SPMI_PROPERTY_READ(adjust_soc_low_threshold,
"adjust-soc-low-threshold", rc, error_read);
SPMI_PROPERTY_READ(adjust_soc_high_threshold,
"adjust-soc-high-threshold", rc, error_read);
SPMI_PROPERTY_READ(chg_term, "chg-term-ua", rc, error_read);
pr_debug("dts data: r_sense_mohm:%d, v_cutoff:%d, max_v:%d, r_conn:%d, shutdown_soc: %d, adjust_soc_low:%d, adjust_soc_high:%d, chg_term:%d\n",
chip->r_sense_mohm, chip->v_cutoff,
chip->max_voltage, chip->r_conn_mohm,
chip->shutdown_soc_valid_limit,
chip->adjust_soc_low_threshold,
chip->adjust_soc_high_threshold,
chip->chg_term);
dev_set_drvdata(&spmi->dev, chip);
device_init_wakeup(&spmi->dev, 1);
/* setup & register the battery power supply */
chip->bms_psy.name = "bms";
chip->bms_psy.type = POWER_SUPPLY_TYPE_BMS;
chip->bms_psy.properties = msm_bms_power_props;
chip->bms_psy.num_properties = ARRAY_SIZE(msm_bms_power_props);
chip->bms_psy.get_property = qpnp_bms_power_get_property;
chip->bms_psy.set_property = qpnp_bms_power_set_property;
chip->bms_psy.external_power_changed =
qpnp_bms_external_power_changed;
chip->bms_psy.supplied_to = qpnp_bms_supplicants;
chip->bms_psy.num_supplicants = ARRAY_SIZE(qpnp_bms_supplicants);
rc = power_supply_register(chip->dev, &chip->bms_psy);
if (rc < 0) {
pr_err("power_supply_register bms failed rc = %d\n", rc);
goto unregister_dc;
}
pr_info("probe success\n");
return 0;
unregister_dc:
power_supply_unregister(&chip->bms_psy);
dev_set_drvdata(&spmi->dev, NULL);
error_read:
kfree(chip);
return rc;
}
static int __devexit
qpnp_bms_remove(struct spmi_device *spmi)
{
struct qpnp_bms_chip *chip = dev_get_drvdata(&spmi->dev);
dev_set_drvdata(&spmi->dev, NULL);
kfree(chip);
return 0;
}
static struct spmi_driver qpnp_bms_driver = {
.probe = qpnp_bms_probe,
.remove = __devexit_p(qpnp_bms_remove),
.driver = {
.name = QPNP_BMS_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = qpnp_bms_match_table,
},
};
static int __init qpnp_bms_init(void)
{
pr_info("QPNP BMS INIT\n");
return spmi_driver_register(&qpnp_bms_driver);
}
static void __exit qpnp_bms_exit(void)
{
pr_info("QPNP BMS EXIT\n");
return spmi_driver_unregister(&qpnp_bms_driver);
}
module_init(qpnp_bms_init);
module_exit(qpnp_bms_exit);
MODULE_DESCRIPTION("QPNP BMS Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" QPNP_BMS_DEV_NAME);