blob: 9a82d18861534cebb92b47e9b2b147983ccb64a1 [file] [log] [blame]
/*
* Copyright (C) 2014 Nest Labs
*
* 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 version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/err.h>
#include <linux/iio/consumer.h>
#include <linux/iio/types.h>
#include <linux/iio/iio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/printk.h>
#include <linux/mutex.h>
#include <linux/delay.h>
struct adc_div_device {
struct power_supply *bat_ps;
struct iio_channel *chan;
unsigned int r1_ohm;
unsigned int r2_ohm;
unsigned int enable_gpio;
unsigned int delay_us;
struct mutex adc_lock;
};
static enum power_supply_property adc_div_bat_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
static int adc_div_bat_get_property(struct power_supply *bat_ps,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct adc_div_device *dev = power_supply_get_drvdata(bat_ps);
int ret = 0;
switch (prop) {
case POWER_SUPPLY_PROP_ONLINE:
/* Always online */
val->intval = 1;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
BUG_ON(!dev->chan);
if (gpio_is_valid(dev->enable_gpio)) {
mutex_lock(&dev->adc_lock);
gpio_set_value(dev->enable_gpio, 1);
usleep_range(dev->delay_us, dev->delay_us + 500);
}
/* Read ADC voltage and then compensate for the divider */
ret = iio_read_channel_processed(dev->chan, &val->intval);
val->intval *= dev->r1_ohm + dev->r2_ohm;
val->intval /= dev->r2_ohm;
/* Processed IIO returns mV but power supply returns uV */
val->intval *= 1000;
if (gpio_is_valid(dev->enable_gpio)) {
gpio_set_value(dev->enable_gpio, 0);
mutex_unlock(&dev->adc_lock);
}
break;
default:
ret = -EINVAL;
}
return ret;
}
static const struct power_supply_desc bat_ps_desc =
{
.name = "adc-div-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = adc_div_bat_props,
.num_properties = ARRAY_SIZE(adc_div_bat_props),
.get_property = adc_div_bat_get_property,
};
static int adc_div_probe(struct platform_device *pdev)
{
struct adc_div_device *dev;
struct device_node *np;
struct power_supply_config adc_div_cfg = {};
int ret;
/* Check for OF node */
np = pdev->dev.of_node;
if (!np) {
dev_err(&pdev->dev, "Missing DT node\n");
return -ENODEV;
}
/* Claim memory */
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
platform_set_drvdata(pdev, dev);
/* Read DT Properties */
/* Request the enable gpio if given */
dev->enable_gpio = of_get_named_gpio(np, "vbatt-adc-input-enable-gpio", 0);
if (gpio_is_valid(dev->enable_gpio)) {
ret = devm_gpio_request_one(&pdev->dev, dev->enable_gpio, GPIOF_DIR_OUT, "vbatt-adc-input-enable-gpio");
if (ret) {
dev_info(&pdev->dev, "failed to register vbatt-adc-input-enable-gpio %d\n", dev->enable_gpio);
return ret;
} else {
dev_info(&pdev->dev, "register vbatt-adc-input-enable-gpio %d\n", dev->enable_gpio);
}
}
/* Set the delay us if given */
ret = of_property_read_u32(np, "delay-us", &dev->delay_us);
if (ret)
dev->delay_us = 0;
dev_info(&pdev->dev, "register delay-us %d\n", dev->delay_us);
ret = of_property_read_u32(np, "divider-r1-ohm", &dev->r1_ohm);
if (ret) {
dev_err(&pdev->dev, "Couldn't get r1 property from DT\n");
return ret;
}
ret = of_property_read_u32(np, "divider-r2-ohm", &dev->r2_ohm);
if (ret) {
dev_err(&pdev->dev, "Couldn't get r2 property from DT\n");
return ret;
}
if (!dev->r1_ohm || !dev->r2_ohm) {
dev_err(&pdev->dev, "Divider values must be non-zero\n");
return -EINVAL;
}
dev->chan = iio_channel_get(&pdev->dev, NULL);
if (IS_ERR(dev->chan)) {
dev_err(&pdev->dev, "Couldn't get ADC channel error %ld \n", PTR_ERR(dev->chan));
if (PTR_ERR(dev->chan) == -ENODEV) {
return -EPROBE_DEFER;
} else {
return PTR_ERR(dev->chan);
}
}
iio_channel_write(dev->chan, 12458, 0, IIO_CHAN_INFO_SAMP_FREQ);
mutex_init(&dev->adc_lock);
adc_div_cfg.drv_data = dev;
dev->bat_ps = power_supply_register(&pdev->dev, &bat_ps_desc, &adc_div_cfg);
return 0;
}
static int adc_div_remove(struct platform_device *pdev)
{
struct adc_div_device *dev = platform_get_drvdata(pdev);
power_supply_unregister(dev->bat_ps);
iio_channel_release(dev->chan);
return 0;
}
static const struct of_device_id adc_div_match[] = {
{ .compatible = "adc-div-battery", },
{ },
};
static struct platform_driver adc_div_driver = {
.probe = adc_div_probe,
.remove = adc_div_remove,
.driver = {
.name = "adc-div-battery",
.of_match_table = adc_div_match,
},
};
module_platform_driver(adc_div_driver);
MODULE_AUTHOR("Tim Kryger <tkryger@nestlabs.com>");
MODULE_DESCRIPTION("Generic ADC Divider Battery driver");
MODULE_LICENSE("GPL v2");