blob: cc45785eb94dc02c3f9aae0ca4835b8447a7bdba [file] [log] [blame]
/*
* Copyright (C) 2017, Teknique Ltd.
* Driver for battery and charger on Nest Rose Quartz
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
/* to enable debugging logs -- load module, then run this:
* echo 1 > /sys/devices/battery.7/power_supply/rq_battery/debug_enabled
*/
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/of_gpio.h>
#include <plat/adc.h>
#include <linux/delay.h>
#include <linux/power/nest_rq_battery.h>
struct ntc_compensation {
int temp_c;
unsigned int ohm;
};
static const struct ntc_compensation ntc_table[] = {
{ .temp_c = -40, .ohm = 188500 },
{ .temp_c = -35, .ohm = 144290 },
{ .temp_c = -30, .ohm = 111330 },
{ .temp_c = -25, .ohm = 86560 },
{ .temp_c = -20, .ohm = 67790 },
{ .temp_c = -15, .ohm = 53460 },
{ .temp_c = -10, .ohm = 42450 },
{ .temp_c = -5, .ohm = 33930 },
{ .temp_c = 0, .ohm = 27280 },
{ .temp_c = 5, .ohm = 22070 },
{ .temp_c = 10, .ohm = 17960 },
{ .temp_c = 15, .ohm = 14700 },
{ .temp_c = 20, .ohm = 12090 },
{ .temp_c = 25, .ohm = 10000 },
{ .temp_c = 30, .ohm = 8310 },
{ .temp_c = 35, .ohm = 6940 },
{ .temp_c = 40, .ohm = 5830 },
{ .temp_c = 45, .ohm = 4910 },
{ .temp_c = 50, .ohm = 4160 },
{ .temp_c = 55, .ohm = 3540 },
{ .temp_c = 60, .ohm = 3020 },
{ .temp_c = 65, .ohm = 2590 },
{ .temp_c = 70, .ohm = 2230 },
{ .temp_c = 75, .ohm = 1920 },
{ .temp_c = 80, .ohm = 1670 },
{ .temp_c = 85, .ohm = 1450 },
{ .temp_c = 90, .ohm = 1270 },
{ .temp_c = 95, .ohm = 1110 },
{ .temp_c = 100, .ohm = 975 },
{ .temp_c = 105, .ohm = 860 },
{ .temp_c = 110, .ohm = 760 },
{ .temp_c = 115, .ohm = 674 },
{ .temp_c = 120, .ohm = 599 },
{ .temp_c = 125, .ohm = 534 },
};
static int nest_rq_battery_sysfs_init(struct nest_rq_battery *batt);
//4.35V and 4.10V, converted to uV
const int VMAX_SETTINGS[] = {410 * 10000, 435 * 10000};
static int high_vmax = 1;
static struct timer_list update_timer;
static int vbridge_voltage = 0;
static int charger_enabled = 1;
static int vbridge_disabled = 0;
static int heater_enabled = 0;
static int debug_enabled = 0;
static int nlmodel;
struct nest_rq_battery *nest_rq_battery;
EXPORT_SYMBOL(nest_rq_battery);
#define UPDATE_TIMER_MS 10 * 1000 //10 Seconds
#define VBRIDGE_WAIT_US 8*1000 //Disconnect vbridge for 8ms before reading VIN
#define CHARGER_WAIT_US 8*1000 //Disconnect charger for 8ms before reading voltage
#define DEAD_BATTERY_UV 3400 * 1000 //3.4v in uV
#define VBRIDGE_SAMPLES_NUM 15
// T=1/HZ AC=60Hz, T=0.016 sec, sample rate = 10, => 1.6ms wait period
#define VBRIDGE_SAMPLE_WAIT_US 1600
#define DEFAULT_DOUBLE_HEATER_HW_VERSION 10
#define DEBUG(d, fmt, ...) \
do {\
if (debug_enabled)\
{\
dev_info((d), (fmt), ##__VA_ARGS__);\
}\
} while(0)
static inline struct nest_rq_battery *battery_to_nest_rq_battery(struct power_supply *psy)
{
return container_of(psy, struct nest_rq_battery, battery);
}
static inline struct nest_rq_battery *charger_to_nest_rq_battery(struct power_supply *psy)
{
return container_of(psy, struct nest_rq_battery, charger);
}
static inline int adc_to_voltage(int raw_adc)
{
const int REF_MV = 3300; //3v3 Reference
const int ADC_RES = 12; //12 bit ADC
return (raw_adc * REF_MV) >> ADC_RES;
}
void vbridge_disable(struct nest_rq_battery *batt, int state)
{
dev_dbg(batt->charger.dev, "disable VBRIDGE: %s", state?"true":"false");
vbridge_disabled = state;
gpio_set_value(batt->pdata->vbridge_disable_gpio, state);
};
static int read_battery_voltage(struct nest_rq_battery * pbatt)
{
int adc;
int volts;
adc = ambarella_adc_read_level(pbatt->pdata->volt_adc_chan);
if (adc < 0)
{
dev_err(pbatt->dev, "error %d reading adc channel %d\n", adc, pbatt->pdata->volt_adc_chan);
return -1;
}
volts = adc_to_voltage(adc);
//ADC reading is volts * 0.68,
//Result should be in uV
return volts * 1000 * 100 / 68;
}
static int read_vbridge_voltage(struct nest_rq_battery *batt)
{
int adc;
int volts = -1;
int i;
// sample the AC half weave and pick highest value
for (i=0; i< VBRIDGE_SAMPLES_NUM; i++)
{
int cur_volts = 0;
adc = ambarella_adc_read_level(batt->pdata->vbridge_adc_chan);
if (adc < 0)
{
dev_err(batt->dev, "error %d reading adc channel %d\n", adc, batt->pdata->vbridge_adc_chan);
return -1;
}
cur_volts = adc_to_voltage(adc);
//vbridge voltage is volts * 10 / 1.414
cur_volts = cur_volts * 10000 / 1414;
if (cur_volts > volts)
volts = cur_volts;
usleep_range(VBRIDGE_SAMPLE_WAIT_US, VBRIDGE_SAMPLE_WAIT_US);
}
//we turn the charger and heater off if voltsn is < 10V
if (volts < 10000 && volts >= 0)
{
dev_dbg(batt->dev, " vbridge voltage (%d) < 10.000V. Disable heater\n", volts);
heater_enabled = 0;
gpio_set_value(batt->pdata->heater_enable_gpio, heater_enabled);
}
return volts;
}
static inline u64 div64_u64_safe(u64 dividend, u64 divisor)
{
if (divisor == 0 && dividend == 0)
return 0;
if (divisor == 0)
return UINT_MAX;
return div64_u64(dividend, divisor);
}
static int ntc_get_ohm(int uv)
{
const int PULLUP_MV = 4500; //4.5v
const int PULLUP_OHM = 4870;
const int PULLDOWN_OHM = 13700;
u64 v = uv;
u64 pmv = PULLUP_MV;
u64 n, puo, pdo;
puo = PULLUP_OHM;
pdo = PULLDOWN_OHM;
n = div64_u64_safe(pdo * puo * v, pdo * (pmv - v) - puo * v);
if (n > INT_MAX)
n = INT_MAX;
return n;
}
static void lookup_comp(unsigned int ohm,int *i_low, int *i_high)
{
int start, end, mid;
int n_comp = ARRAY_SIZE(ntc_table);
/*
* Handle special cases: Resistance is higher than or equal to
* resistance in first table entry, or resistance is lower or equal
* to resistance in last table entry.
* In these cases, return i_low == i_high, either pointing to the
* beginning or to the end of the table depending on the condition.
*/
if (ohm >= ntc_table[0].ohm) {
*i_low = 0;
*i_high = 0;
return;
}
if (ohm <= ntc_table[n_comp - 1].ohm) {
*i_low = n_comp - 1;
*i_high = n_comp - 1;
return;
}
/* Do a binary search on compensation table */
start = 0;
end = n_comp;
while (start < end) {
mid = start + (end - start) / 2;
/*
* start <= mid < end
* ntc_table[start].ohm > ohm >= ntc_table[end].ohm
*
* We could check for "ohm == ntc_table[mid].ohm" here, but
* that is a quite unlikely condition, and we would have to
* check again after updating start. Check it at the end instead
* for simplicity.
*/
if (ohm >= ntc_table[mid].ohm) {
end = mid;
} else {
start = mid + 1;
/*
* ohm >= ntc_table[start].ohm might be true here,
* since we set start to mid + 1. In that case, we are
* done. We could keep going, but the condition is quite
* likely to occur, so it is worth checking for it.
*/
if (ohm >= ntc_table[start].ohm)
end = start;
}
/*
* start <= end
* ntc_table[start].ohm >= ohm >= ntc_table[end].ohm
*/
}
/*
* start == end
* ohm >= ntc_table[end].ohm
*/
*i_low = end;
if (ohm == ntc_table[end].ohm)
*i_high = end;
else
*i_high = end - 1;
}
static int get_temp_mc(unsigned int ohm)
{
int low, high;
int temp;
lookup_comp(ohm, &low, &high);
if (low == high) {
/* Unable to use linear approximation */
temp = ntc_table[low].temp_c * 10;
} else {
temp = ntc_table[low].temp_c * 10 +
((ntc_table[high].temp_c - ntc_table[low].temp_c) *
10 * ((int)ohm - (int)ntc_table[low].ohm)) /
((int)ntc_table[high].ohm - (int)ntc_table[low].ohm);
}
return temp;
}
static int read_battery_temp(struct nest_rq_battery *pbatt, int* temp)
{
int adc;
int volts;
int ohms;
int mc;
int n_comp = ARRAY_SIZE(ntc_table);
unsigned long flags;
if (!temp) {
dev_err(pbatt->dev, "temp var is NULL.");
return -1;
}
spin_lock_irqsave(&pbatt->lock, flags);
// ACOK like is read two times (before and after reading ADC) and
// ADC value will be discarded if ACOK is high.
if (vbridge_disabled || gpio_get_value(pbatt->pdata->acok_gpio))
{
spin_unlock_irqrestore(&pbatt->lock, flags);
dev_err(pbatt->dev, "VBRIDGE is disabled or AC is not present. Can't read battery temperature.");
return -1;
}
adc = ambarella_adc_read_level(pbatt->pdata->temp_adc_chan);
spin_unlock_irqrestore(&pbatt->lock, flags);
if (gpio_get_value(pbatt->pdata->acok_gpio))
{
dev_warn(pbatt->dev, "AC is not present. Can't read battery temperature.");
return -1;
}
if (adc < 0)
{
dev_err(pbatt->dev, "error %d reading adc channel %d\n", adc, pbatt->pdata->temp_adc_chan);
return -1;
}
volts = adc_to_voltage(adc);
ohms = ntc_get_ohm(volts);
if (ohms < ntc_table[n_comp - 1].ohm)
{
dev_warn(pbatt->dev, "wrong ohms value (%d) getting from adc (%d). Skip it.\n", ohms, adc);
return -1;
}
mc = get_temp_mc(ohms);
DEBUG(pbatt->dev, " temp_adc %d volts: %d ohms: %d mc:%d\n", adc, volts, ohms, mc);
*temp = mc;
return 0;
}
static void update_timer_func(unsigned long pbat)
{
struct nest_rq_battery* pbatt = (struct nest_rq_battery *) pbat;
const struct nest_rq_battery_platform_data *pdata = pbatt->pdata;
// check temperature
int temp;
int ret = read_battery_temp(pbatt, &temp);
if (!ret)
{
int fast_charge = 0;
charger_enabled = 1;
// Enable/Disable heater based on temperature
// heater use depends on VIN, so it is enabled/disabled by vbridge timer
if (temp < 250) //heater below 25C
{
DEBUG(pbatt->dev, " Temperature is below 25C: %d - enabling heater\n", temp);
if (vbridge_voltage > 10000) // > 10.000V
heater_enabled = 1;
}
else if (temp > 270) //heater off above 27C
{
DEBUG(pbatt->dev, " Temperature is above 27C: %d - disabling heater\n", temp);
heater_enabled = 0;
}
// Enable/Disable charging/fast charging based on temperature
if (temp <= 0 || temp >= 600)
{
DEBUG(pbatt->dev, " temperature is outside safe realm: %d - disabling charger\n", temp);
charger_enabled = 0;
fast_charge = 0;
}
else if (temp <= 100) // 0-10C, trickle charge
{
DEBUG(pbatt->dev, " temperature is below 10C: %d - disabling fast_charge\n", temp);
fast_charge = 0;
}
else if (temp >= 150 && temp <= 400) // 15-40C, fast charge
{
DEBUG(pbatt->dev, " temperature in sweet spot between 150 and 400 - %d - fast charge enabled\n", temp);
fast_charge = 1;
}
else if (temp >= 450 && temp <= 600) // 45-60C, trickle charge
{
DEBUG(pbatt->dev, " temperature between 45 and 60 %d - trickle charging\n", temp);
fast_charge = 0;
}
// Set battery charging voltage based on temperature
if (temp < 100) // < 10C, 4.1V
{
DEBUG(pbatt->dev, " temperature is below 10C: %d - set charging voltage to 4.1V\n", temp);
high_vmax = 0;
}
else if (temp > 150 && temp < 250) // 15-25C, 4.35V
{
DEBUG(pbatt->dev, " temperature is between 15C and 40C: %d - set charging voltage to 4.35V\n", temp);
high_vmax = 1;
}
else if (temp > 300) // > 30C, 4.1V
{
DEBUG(pbatt->dev, " temperature is above 30C: %d - set charging voltage to 4.1V\n", temp);
high_vmax = 0;
}
// For 10-15C and 25-30C, keep previous charging voltage
// For newer hardware version limit the maximum voltage to 4.1V,
if (nlmodel >= DEFAULT_DOUBLE_HEATER_HW_VERSION)
{
DEBUG(pbatt->dev, " HW version needs voltage up to 4.1V only\n");
high_vmax = 0;
}
gpio_set_value(pdata->charger_rate_gpio, fast_charge);
gpio_set_value(pdata->vmax_gpio, high_vmax);
gpio_set_value(pdata->heater_enable_gpio, heater_enabled);
gpio_set_value(pdata->charger_enable_gpio, charger_enabled);
}
mod_timer(&update_timer, jiffies + msecs_to_jiffies(UPDATE_TIMER_MS));
}
static enum power_supply_property nest_rq_battery_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
static enum power_supply_property nest_rq_charger_properties[] = {
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
};
static int nest_rq_battery_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct nest_rq_battery *battery = battery_to_nest_rq_battery(psy);
const struct nest_rq_battery_platform_data *pdata = battery->pdata;
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = gpio_get_value(pdata->status_gpio) ? POWER_SUPPLY_STATUS_NOT_CHARGING : POWER_SUPPLY_STATUS_CHARGING;
break;
case POWER_SUPPLY_PROP_TEMP:
if (read_battery_temp(battery, &val->intval))
ret = -EINVAL;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = read_battery_voltage(battery);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int nest_rq_charger_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct nest_rq_battery *battery = charger_to_nest_rq_battery(psy);
const struct nest_rq_battery_platform_data *pdata = battery->pdata;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = gpio_get_value(pdata->charger_rate_gpio) ?
POWER_SUPPLY_CHARGE_TYPE_FAST : POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
val->intval = VMAX_SETTINGS[gpio_get_value(pdata->vmax_gpio)];
break;
default:
return -EINVAL;
}
return 0;
}
int nest_rq_charger_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct nest_rq_battery *battery = charger_to_nest_rq_battery(psy);
const struct nest_rq_battery_platform_data *pdata = battery->pdata;
switch (psp) {
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
if (val->intval == VMAX_SETTINGS[0])
gpio_set_value(pdata->vmax_gpio, 0);
else if (val->intval == VMAX_SETTINGS[1])
gpio_set_value(pdata->vmax_gpio, 1);
else
return -EINVAL;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
if (val->intval == 0)
gpio_set_value(pdata->charger_rate_gpio, 0);
else if (val->intval == 1)
gpio_set_value(pdata->charger_rate_gpio, 1);
else
return -EINVAL;
break;
default:
return -EPERM;
}
return 0;
}
int nest_rq_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
return 0;
}
int nest_rq_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
return 1;
default:
break;
}
return 0;
}
struct nest_rq_battery_platform_data *nest_rq_battery_of_populate_pdata(struct device *dev,
struct device_node *np)
{
struct nest_rq_battery_platform_data *pdata;
dev_info(dev, " module populate pdata\n");
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->disconnect_gpio = of_get_named_gpio(np, "disconnect-gpio", 0);
pdata->status_gpio = of_get_named_gpio(np, "status-gpio", 0);
pdata->vmax_gpio = of_get_named_gpio(np, "vmax-gpio", 0);
pdata->adc_enable_gpio = of_get_named_gpio(np, "adc-enable-gpio", 0);
pdata->charger_enable_gpio = of_get_named_gpio(np, "charger-enable-gpio", 0);
pdata->charger_rate_gpio = of_get_named_gpio(np, "charger-rate-gpio", 0);
pdata->heater_enable_gpio = of_get_named_gpio(np, "heater-enable-gpio", 0);
pdata->vbridge_disable_gpio = of_get_named_gpio(np, "vbridge-disable-gpio", 0);
pdata->acok_gpio = of_get_named_gpio(np, "acok-gpio", 0);
of_property_read_u32(np, "temp-adc", &pdata->temp_adc_chan);
of_property_read_u32(np, "voltage-adc", &pdata->volt_adc_chan);
of_property_read_u32(np, "vbridge-adc", &pdata->vbridge_adc_chan);
return pdata;
}
EXPORT_SYMBOL_GPL(nest_rq_battery_of_populate_pdata);
static int nest_rq_battery_probe(struct platform_device *pdev)
{
struct nest_rq_battery_platform_data *pdata = pdev->dev.platform_data;
struct device_node *np = pdev->dev.of_node;
struct power_supply *battery;
struct power_supply *charger;
int ret;
dev_info(&pdev->dev, " module probe\n");
if (!pdata) {
if (np) {
pdata = nest_rq_battery_of_populate_pdata(&pdev->dev, np);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
}
if (!gpio_is_valid(pdata->disconnect_gpio)) {
dev_err(&pdev->dev, "Invalid disconnect_gpio pin\n");
return -EINVAL;
}
if (!gpio_is_valid(pdata->status_gpio)) {
dev_err(&pdev->dev, "Invalid status_gpio pin\n");
return -EINVAL;
}
if (!gpio_is_valid(pdata->vmax_gpio)) {
dev_err(&pdev->dev, "Invalid vmax_gpio pin\n");
return -EINVAL;
}
if (!gpio_is_valid(pdata->adc_enable_gpio)) {
dev_err(&pdev->dev, "Invalid vmax_gpio pin\n");
return -EINVAL;
}
if (!gpio_is_valid(pdata->charger_enable_gpio)) {
dev_err(&pdev->dev, "Invalid charger_enable_gpio pin\n");
return -EINVAL;
}
if (!gpio_is_valid(pdata->charger_rate_gpio)) {
dev_err(&pdev->dev, "Invalid charger_rate_gpio pin\n");
return -EINVAL;
}
if (!gpio_is_valid(pdata->heater_enable_gpio)) {
dev_err(&pdev->dev, "Invalid heater_enable_gpio pin\n");
return -EINVAL;
}
if (!gpio_is_valid(pdata->vbridge_disable_gpio)) {
dev_err(&pdev->dev, "Invalid vbridge_disable_gpio pin\n");
return -EINVAL;
}
if (!gpio_is_valid(pdata->acok_gpio)) {
dev_err(&pdev->dev, "Invalid acok_gpio pin\n");
return -EINVAL;
}
nest_rq_battery = devm_kzalloc(&pdev->dev, sizeof(*nest_rq_battery),
GFP_KERNEL);
if (!nest_rq_battery) {
dev_err(&pdev->dev, "Failed to alloc driver structure\n");
return -ENOMEM;
}
spin_lock_init(&nest_rq_battery->lock);
nest_rq_battery->dev = &pdev->dev;
battery = &nest_rq_battery->battery;
battery->name = pdata->name ? pdata->name : "rq_battery";
battery->type = POWER_SUPPLY_TYPE_BATTERY;
battery->properties = nest_rq_battery_properties;
battery->num_properties = ARRAY_SIZE(nest_rq_battery_properties);
battery->get_property = nest_rq_battery_get_property;
battery->property_is_writeable = nest_rq_battery_property_is_writeable;
battery->supplied_to = pdata->supplied_to;
battery->num_supplicants = pdata->num_supplicants;
charger = &nest_rq_battery->charger;
charger->name = pdata->name ? pdata->name : "rq_charger";
charger->type = POWER_SUPPLY_TYPE_UNKNOWN;
charger->properties = nest_rq_charger_properties;
charger->num_properties = ARRAY_SIZE(nest_rq_charger_properties);
charger->get_property = nest_rq_charger_get_property;
charger->set_property = nest_rq_charger_set_property;
charger->property_is_writeable = nest_rq_charger_property_is_writeable;
charger->supplied_to = pdata->supplied_to;
charger->num_supplicants = pdata->num_supplicants;
ret = gpio_request(pdata->disconnect_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request disconnect_gpio pin: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_request(pdata->status_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request status_gpio pin: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_request(pdata->vmax_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request vmax_gpio: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_request(pdata->adc_enable_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request adc_enable_gpio: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_request(pdata->charger_enable_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request charger_enable_gpio: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_request(pdata->charger_rate_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request charger_rate_gpio: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_request(pdata->heater_enable_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request heater_enable_gpio: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_request(pdata->vbridge_disable_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request vbridge_disable_gpio: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_request(pdata->acok_gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request acok_gpio: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_input(pdata->status_gpio);
if (ret) {
dev_err(&pdev->dev, "Failed to set status_gpio to input: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_output(pdata->disconnect_gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set disconnect_gpio to output: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_output(pdata->vmax_gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set vmax_gpio to output: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_output(pdata->adc_enable_gpio, 1);
if (ret) {
dev_err(&pdev->dev, "Failed to set adc_enable_gpio to output: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_output(pdata->charger_enable_gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set charger_enable_gpio to output: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_output(pdata->charger_rate_gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set charger_rate_gpio to output: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_output(pdata->heater_enable_gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set heater_enable_gpio to output: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_output(pdata->vbridge_disable_gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set vbridge_disable_gpio to output: %d\n", ret);
goto err_gpio_free;
}
ret = gpio_direction_output(pdata->acok_gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set acok_gpio to output: %d\n", ret);
goto err_gpio_free;
}
nest_rq_battery->pdata = pdata;
ret = power_supply_register(&pdev->dev, battery);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register power supply: %d\n",
ret);
goto err_gpio_free;
}
ret = power_supply_register(&pdev->dev, charger);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register power supply: %d\n",
ret);
goto err_gpio_free;
}
platform_set_drvdata(pdev, nest_rq_battery);
ret = nest_rq_battery_sysfs_init(nest_rq_battery);
if (ret) {
dev_err(charger->dev, "failed to create sysfs entries: %d\n", ret);
goto err_gpio_free;
}
setup_timer(&update_timer, update_timer_func, (unsigned long) nest_rq_battery);
mod_timer(&update_timer, jiffies + msecs_to_jiffies(UPDATE_TIMER_MS));
//grab first vbridge reading
vbridge_voltage = read_vbridge_voltage(nest_rq_battery);
update_timer_func((unsigned long) nest_rq_battery);
return 0;
err_gpio_free:
if (gpio_is_valid(pdata->disconnect_gpio))
gpio_free(pdata->disconnect_gpio);
if (gpio_is_valid(pdata->status_gpio))
gpio_free(pdata->status_gpio);
if (gpio_is_valid(pdata->vmax_gpio))
gpio_free(pdata->vmax_gpio);
if (gpio_is_valid(pdata->adc_enable_gpio))
gpio_free(pdata->adc_enable_gpio);
if (gpio_is_valid(pdata->charger_enable_gpio))
gpio_free(pdata->charger_enable_gpio);
if (gpio_is_valid(pdata->charger_rate_gpio))
gpio_free(pdata->charger_rate_gpio);
if (gpio_is_valid(pdata->heater_enable_gpio))
gpio_free(pdata->heater_enable_gpio);
if (gpio_is_valid(pdata->vbridge_disable_gpio))
gpio_free(pdata->heater_enable_gpio);
if (gpio_is_valid(pdata->acok_gpio))
gpio_free(pdata->acok_gpio);
power_supply_unregister(battery);
power_supply_unregister(charger);
return ret;
}
/* Custom sysfs entries for battery */
static ssize_t battery_connected_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const char* CONNECTED_SETTINGS[] = {"Connected", "Disconnected"};
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
return sprintf(buf, "%s\n", CONNECTED_SETTINGS[gpio_get_value(batt->pdata->disconnect_gpio)]);
}
static ssize_t battery_connected_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
struct check {
char* str;
int val;
};
//The GPIO is active high (high == disconnected), so the .val's here are inverted
struct check check_values[] = {
{.str = "0", .val = 1},
{.str = "1", .val = 0},
};
int i;
dev_info(dev, " setting battery_connected");
for (i = 0; i < ARRAY_SIZE(check_values); i++)
{
struct check chk = check_values[i];
if (sysfs_streq(buf, chk.str))
{
gpio_set_value(batt->pdata->disconnect_gpio, chk.val);
return count;
}
}
dev_warn(dev, "Invalid state, must be one of: 0,1");
return count;
}
/* Custom sysfs entries for battery */
static ssize_t heater_enabled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const char* CONNECTED_SETTINGS[] = {"Disabled", "Enabled"};
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
return sprintf(buf, "%s\n", CONNECTED_SETTINGS[gpio_get_value(batt->pdata->heater_enable_gpio)]);
}
static ssize_t heater_enabled_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
heater_enabled = 1;
if (sysfs_streq(buf, "0"))
heater_enabled = 0;
dev_info(dev, " setting heater_enabled:%s\n", heater_enabled ? "true" : "false");
gpio_set_value(batt->pdata->heater_enable_gpio, heater_enabled);
return count;
}
/* Custom sysfs entries for battery */
static ssize_t charger_enabled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const char* ENABLE_SETTINGS[] = {"Disabled", "Enabled"};
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
return sprintf(buf, "%s\n", ENABLE_SETTINGS[gpio_get_value(batt->pdata->charger_enable_gpio)]);
}
static ssize_t charger_enabled_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
struct check {
char* str;
int val;
};
struct check check_values[] = {
{.str = "0", .val = 0},
{.str = "1", .val = 1},
};
int i;
for (i = 0; i < ARRAY_SIZE(check_values); i++)
{
struct check chk = check_values[i];
if (sysfs_streq(buf, chk.str))
{
gpio_set_value(batt->pdata->charger_enable_gpio, chk.val);
charger_enabled = chk.val;
dev_info(dev, " setting charger_enabled:%s\n",
charger_enabled ? "true" : "false");
return count;
}
}
dev_warn(dev, "Invalid state, must be one of: 0,1");
return count;
}
static ssize_t vbridge_enabled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const char* ENABLE_SETTINGS[] = {"Enabled", "Disabled"};
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
return sprintf(buf, "%s\n", ENABLE_SETTINGS[gpio_get_value(batt->pdata->vbridge_disable_gpio)]);
}
static ssize_t vbridge_enabled_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
struct check {
char* str;
int val;
};
//The GPIO is for 'vbridge_disabled', so the .val's here are inverted
struct check check_values[] = {
{.str = "0", .val = 1},
{.str = "1", .val = 0},
};
int i;
for (i = 0; i < ARRAY_SIZE(check_values); i++)
{
struct check chk = check_values[i];
if (sysfs_streq(buf, chk.str))
{
nest_rq_battery_vbridge_disable(batt, chk.val);
dev_info(dev, " setting vbridge_enabled:%s\n",
vbridge_disabled ? "false" : "true");
return count;
}
}
dev_warn(dev, "Invalid state, must be one of: 0,1");
return count;
}
static ssize_t vbridge_voltage_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nest_rq_battery *batt = dev_get_drvdata(dev->parent);
vbridge_voltage = read_vbridge_voltage(batt);
return sprintf(buf, "%d\n", vbridge_voltage);
}
static ssize_t debug_enabled_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
debug_enabled = 1;
if (sysfs_streq(buf, "0"))
debug_enabled = 0;
dev_info(dev, " setting debug_enabled:%s\n", debug_enabled ? "true" : " false");
return count;
}
static ssize_t debug_enabled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", debug_enabled);
}
static ssize_t nlmodel_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", nlmodel);
}
static ssize_t nlmodel_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
if (kstrtoint(buf, 0, &nlmodel))
nlmodel = DEFAULT_DOUBLE_HEATER_HW_VERSION;
return count;
}
static DEVICE_ATTR(connected, 0644, battery_connected_show, battery_connected_store);
static DEVICE_ATTR(debug_enabled, 0644, debug_enabled_show, debug_enabled_store);
static DEVICE_ATTR(heater_enabled, 0644, heater_enabled_show, heater_enabled_store);
static DEVICE_ATTR(charger_enabled, 0644, charger_enabled_show, charger_enabled_store);
static DEVICE_ATTR(vbridge_enabled, 0644, vbridge_enabled_show, vbridge_enabled_store);
static DEVICE_ATTR(vbridge_voltage_now, 0444, vbridge_voltage_show, NULL);
static DEVICE_ATTR(nlmodel, 0644, nlmodel_show, nlmodel_store);
static int nest_rq_battery_sysfs_init(struct nest_rq_battery *batt)
{
int ret = 0;
dev_info(batt->dev, " initializing sysfs interface\n");
ret = device_create_file(batt->battery.dev, &dev_attr_connected);
if (ret < 0)
dev_warn(batt->battery.dev, "failed to add battery_connected sysfs: %d\n", ret);
ret = device_create_file(batt->battery.dev, &dev_attr_heater_enabled);
if (ret < 0)
dev_warn(batt->battery.dev, "failed to add heater_enabled sysfs: %d\n", ret);
ret = device_create_file(batt->battery.dev, &dev_attr_debug_enabled);
if (ret < 0)
dev_warn(batt->battery.dev, "failed to add debug_enabled sysfs: %d\n", ret);
ret = device_create_file(batt->charger.dev, &dev_attr_charger_enabled);
if (ret < 0)
dev_warn(batt->charger.dev, "failed to add charger_enabled sysfs: %d\n", ret);
ret = device_create_file(batt->charger.dev, &dev_attr_vbridge_voltage_now);
if (ret < 0)
dev_warn(batt->charger.dev, "failed to add vbridge_voltage sysfs: %d\n", ret);
ret = device_create_file(batt->charger.dev, &dev_attr_vbridge_enabled);
if (ret < 0)
dev_warn(batt->charger.dev, "failed to add vbridge_enabled sysfs: %d\n", ret);
ret = device_create_file(batt->charger.dev, &dev_attr_nlmodel);
if (ret < 0)
dev_warn(batt->charger.dev, "failed to add nlmodel sysfs: %d\n", ret);
return 0;
}
static int nest_rq_battery_remove(struct platform_device *pdev)
{
struct nest_rq_battery *battery = platform_get_drvdata(pdev);
dev_info(&pdev->dev, " module removal\n");
del_timer_sync(&update_timer);
power_supply_unregister(&battery->battery);
power_supply_unregister(&battery->charger);
gpio_free(battery->pdata->disconnect_gpio);
gpio_free(battery->pdata->status_gpio);
gpio_free(battery->pdata->vmax_gpio);
gpio_free(battery->pdata->adc_enable_gpio);
gpio_free(battery->pdata->charger_enable_gpio);
gpio_free(battery->pdata->charger_rate_gpio);
gpio_free(battery->pdata->heater_enable_gpio);
gpio_free(battery->pdata->vbridge_disable_gpio);
gpio_free(battery->pdata->acok_gpio);
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct of_device_id nest_rq_battery_match[] = {
{ .compatible = "nest-rq-battery" },
{ }
};
MODULE_DEVICE_TABLE(of, nest_rq_battery_match);
static struct platform_driver nest_rq_battery_driver = {
.probe = nest_rq_battery_probe,
.remove = nest_rq_battery_remove,
.driver = {
.name = "rq_battery",
.owner = THIS_MODULE,
.of_match_table = nest_rq_battery_match,
},
};
void nest_rq_battery_vbridge_disable(struct nest_rq_battery *batt, int state)
{
unsigned long flags;
spin_lock_irqsave(&batt->lock, flags);
vbridge_disable(batt, state);
spin_unlock_irqrestore(&batt->lock, flags);
};
EXPORT_SYMBOL(nest_rq_battery_vbridge_disable);
int nest_rq_battery_get_temp(struct nest_rq_battery *batt, int *temp)
{
return read_battery_temp(batt, temp);
};
EXPORT_SYMBOL(nest_rq_battery_get_temp);
int nest_rq_battery_get_voltage(struct nest_rq_battery *batt)
{
return read_battery_voltage(batt);;
};
EXPORT_SYMBOL(nest_rq_battery_get_voltage);
module_platform_driver(nest_rq_battery_driver);
MODULE_AUTHOR("Jonathan Miles <jonathan.miles@teknique.com>");
MODULE_DESCRIPTION("Driver for battery and charger on Nest Rose Quartz");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:nest-rq-battery");