347 lines
8.9 KiB
C
347 lines
8.9 KiB
C
/*
|
|
* iio_dc_xpwr_gpadc.c - Dollar Cove(Xpower) GPADC Driver
|
|
*
|
|
* Copyright (C) 2014 Intel Corporation
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*
|
|
* 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 of the License.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
* Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/device.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/intel_mid_pm.h>
|
|
#include <linux/rpmsg.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/mfd/intel_mid_pmic.h>
|
|
#include <linux/iio/adc/dc_xpwr_gpadc.h>
|
|
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/machine.h>
|
|
#include <linux/iio/buffer.h>
|
|
#include <linux/iio/driver.h>
|
|
#include <linux/iio/types.h>
|
|
#include <linux/iio/consumer.h>
|
|
|
|
#define DC_PMIC_ADC_EN_REG 0x82
|
|
#define ADC_EN_VBAT (1 << 7)
|
|
#define ADC_EN_BAT_CUR (1 << 6)
|
|
#define ADC_EN_PMICTEMP (1 << 5)
|
|
#define ADC_EN_SYSTHERM (1 << 4)
|
|
#define ADC_EN_BATTEMP (1 << 0)
|
|
#define ADC_EN_MASK 0xF1
|
|
|
|
#define DC_PMIC_ADC_CNTL_REG 0x84
|
|
#define DC_PMIC_TSP_CNTL_REG 0x85
|
|
#define ADC_PMIC_TEMP_DATAH_REG 0x56
|
|
#define ADC_PMIC_TEMP_DATAL_REG 0x57
|
|
#define ADC_TSP_DATAH_REG 0x58
|
|
#define ADC_TSP_DATAL_REG 0x59
|
|
#define ADC_SYST_DATAH_REG 0x5A
|
|
#define ADC_SYST_DATAL_REG 0x5B
|
|
#define ADC_VBAT_DATAH_REG 0x78
|
|
#define ADC_VBAT_DATAL_REG 0x79
|
|
#define ADC_BAT_CCUR_DATAH_REG 0x7A
|
|
#define ADC_BAT_CCUR_DATAL_REG 0x7B
|
|
#define ADC_BAT_DCIR_DATAH_REG 0x7C
|
|
#define ADC_BAT_DCIR_DATAL_REG 0x7D
|
|
|
|
#define ADC_CHANNEL0_MASK (1 << 0)
|
|
#define ADC_CHANNEL1_MASK (1 << 1)
|
|
#define ADC_CHANNEL2_MASK (1 << 2)
|
|
#define ADC_CHANNEL3_MASK (1 << 3)
|
|
#define ADC_CHANNEL4_MASK (1 << 4)
|
|
#define ADC_CHANNEL5_MASK (1 << 5)
|
|
|
|
#define ADC_BAT_CUR_DATAL_MASK 0x1F
|
|
#define ADC_NON_BAT_CUR_DATAL_MASK 0x0F
|
|
|
|
#define ADC_TS_PIN_CNRTL_REG 0x84
|
|
#define ADC_TS_PIN_ON 0xF2
|
|
|
|
#define DEV_NAME "dollar_cove_adc"
|
|
|
|
static struct gpadc_regmap_t {
|
|
char *name;
|
|
int rslth; /* GPADC Conversion Result Register Addr High */
|
|
int rsltl; /* GPADC Conversion Result Register Addr Low */
|
|
} gpadc_regmaps[GPADC_CH_NUM] = {
|
|
{"BATTEMP", 0x58, 0x59, },
|
|
{"PMICTEMP", 0x56, 0x57, },
|
|
{"SYSTEMP0", 0x5A, 0x5B, },
|
|
{"BATCCUR", 0x7A, 0x7B, },
|
|
{"BATDCUR", 0x7C, 0x7D, },
|
|
{"VBAT", 0x78, 0x79, },
|
|
};
|
|
|
|
struct gpadc_info {
|
|
struct mutex lock;
|
|
struct device *dev;
|
|
};
|
|
|
|
#define ADC_CHANNEL(_type, _channel, _datasheet_name) \
|
|
{ \
|
|
.indexed = 1, \
|
|
.type = _type, \
|
|
.channel = _channel, \
|
|
.datasheet_name = _datasheet_name, \
|
|
}
|
|
|
|
static const struct iio_chan_spec const dc_xpwr_adc_channels[] = {
|
|
ADC_CHANNEL(IIO_TEMP, 0, "CH0"),
|
|
ADC_CHANNEL(IIO_TEMP, 1, "CH1"),
|
|
ADC_CHANNEL(IIO_TEMP, 2, "CH2"),
|
|
ADC_CHANNEL(IIO_CURRENT, 3, "CH3"),
|
|
ADC_CHANNEL(IIO_CURRENT, 4, "CH4"),
|
|
ADC_CHANNEL(IIO_VOLTAGE, 5, "CH5"),
|
|
};
|
|
|
|
#define ADC_MAP(_adc_channel_label, \
|
|
_consumer_dev_name, \
|
|
_consumer_channel) \
|
|
{ \
|
|
.adc_channel_label = _adc_channel_label, \
|
|
.consumer_dev_name = _consumer_dev_name, \
|
|
.consumer_channel = _consumer_channel, \
|
|
}
|
|
|
|
static struct iio_map iio_maps[] = {
|
|
ADC_MAP("CH0", "THERMAL", "BATTEMP"),
|
|
ADC_MAP("CH1", "THERMAL", "PMICTEMP"),
|
|
ADC_MAP("CH2", "THERMAL", "SYSTEMP0"),
|
|
ADC_MAP("CH3", "CURRENT", "BATCCUR"),
|
|
ADC_MAP("CH4", "CURRENT", "BATDCUR"),
|
|
ADC_MAP("CH5", "VIBAT", "VBAT"),
|
|
ADC_MAP("CH1", "byt_cr_thermal", "PMICTEMP"),
|
|
ADC_MAP("CH2", "byt_cr_thermal", "SYSTEMP0"),
|
|
};
|
|
|
|
/**
|
|
* iio_dc_xpwr_gpadc_sample - do gpadc sample.
|
|
* @indio_dev: industrial IO GPADC device handle
|
|
* @ch: gpadc bit set of channels to sample, for example, set ch = (1<<0)|(1<<2)
|
|
* means you are going to sample both channel 0 and 2 at the same time.
|
|
* @res:gpadc sampling result
|
|
*
|
|
* Returns 0 on success or an error code.
|
|
*
|
|
* This function may sleep.
|
|
*/
|
|
static int iio_dc_xpwr_gpadc_sample(struct iio_dev *indio_dev,
|
|
int ch, struct gpadc_result *res)
|
|
{
|
|
struct gpadc_info *info = iio_priv(indio_dev);
|
|
int i;
|
|
u8 th, tl;
|
|
|
|
mutex_lock(&info->lock);
|
|
for (i = 0; i < GPADC_CH_NUM; i++) {
|
|
if (ch & (1 << i)) {
|
|
th = intel_mid_pmic_readb(gpadc_regmaps[i].rslth);
|
|
tl = intel_mid_pmic_readb(gpadc_regmaps[i].rsltl);
|
|
res->data[i] = (th << 4) + ((tl >> 4) & 0x0F);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&info->lock);
|
|
return 0;
|
|
}
|
|
|
|
static int dc_xpwr_adc_read_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
int *val, int *val2, long m)
|
|
{
|
|
int ret;
|
|
int ch = chan->channel;
|
|
struct gpadc_info *info = iio_priv(indio_dev);
|
|
struct gpadc_result res;
|
|
|
|
ret = iio_dc_xpwr_gpadc_sample(indio_dev, (1 << ch), &res);
|
|
if (ret) {
|
|
dev_err(info->dev, "sample failed\n");
|
|
return ret;
|
|
}
|
|
|
|
*val = res.data[ch];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dc_xpwr_adc_read_all_raw(struct iio_channel *chan,
|
|
int *val)
|
|
{
|
|
int ret;
|
|
int i, num = 0;
|
|
int ch = 0;
|
|
int *channels;
|
|
struct gpadc_info *info = iio_priv(chan->indio_dev);
|
|
struct gpadc_result res;
|
|
|
|
while (chan[num].indio_dev)
|
|
num++;
|
|
|
|
channels = kzalloc(sizeof(int) * num, GFP_KERNEL);
|
|
if (channels == NULL)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
channels[i] = chan[i].channel->channel;
|
|
ch |= (1 << channels[i]);
|
|
}
|
|
|
|
ret = iio_dc_xpwr_gpadc_sample(chan->indio_dev, ch, &res);
|
|
if (ret) {
|
|
dev_err(info->dev, "sample failed\n");
|
|
goto end;
|
|
}
|
|
|
|
for (i = 0; i < num; i++)
|
|
val[i] = res.data[channels[i]];
|
|
|
|
end:
|
|
kfree(channels);
|
|
return ret;
|
|
}
|
|
|
|
static const struct iio_info dc_xpwr_adc_info = {
|
|
.read_raw = &dc_xpwr_adc_read_raw,
|
|
.read_all_raw = &dc_xpwr_adc_read_all_raw,
|
|
.driver_module = THIS_MODULE,
|
|
};
|
|
|
|
static int dc_xpwr_gpadc_probe(struct platform_device *pdev)
|
|
{
|
|
int err;
|
|
struct gpadc_info *info;
|
|
struct iio_dev *indio_dev;
|
|
|
|
indio_dev = iio_device_alloc(sizeof(struct gpadc_info));
|
|
if (indio_dev == NULL) {
|
|
dev_err(&pdev->dev, "allocating iio device failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
info = iio_priv(indio_dev);
|
|
info->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, indio_dev);
|
|
mutex_init(&info->lock);
|
|
|
|
/* Current Source from TS pin always ON */
|
|
intel_mid_pmic_writeb(ADC_TS_PIN_CNRTL_REG, ADC_TS_PIN_ON);
|
|
|
|
/*
|
|
* To enable X-power PMIC Fuel Gauge functionality
|
|
* ADC channels(VBATT, IBATT and TS channels)
|
|
* must be enabled all the time.
|
|
*/
|
|
intel_mid_pmic_writeb(DC_PMIC_ADC_EN_REG, ADC_EN_MASK);
|
|
|
|
indio_dev->dev.parent = &pdev->dev;
|
|
indio_dev->name = pdev->name;
|
|
indio_dev->channels = dc_xpwr_adc_channels;
|
|
indio_dev->num_channels = ARRAY_SIZE(dc_xpwr_adc_channels);
|
|
indio_dev->info = &dc_xpwr_adc_info;
|
|
indio_dev->modes = INDIO_DIRECT_MODE;
|
|
err = iio_map_array_register(indio_dev, iio_maps);
|
|
if (err)
|
|
goto err_free_device;
|
|
|
|
err = iio_device_register(indio_dev);
|
|
if (err < 0)
|
|
goto err_array_unregister;
|
|
|
|
dev_info(&pdev->dev, "dc_xpwr adc probed\n");
|
|
|
|
return 0;
|
|
|
|
err_array_unregister:
|
|
iio_map_array_unregister(indio_dev);
|
|
err_free_device:
|
|
iio_device_free(indio_dev);
|
|
return err;
|
|
}
|
|
|
|
static int dc_xpwr_gpadc_remove(struct platform_device *pdev)
|
|
{
|
|
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
|
|
|
iio_device_unregister(indio_dev);
|
|
iio_map_array_unregister(indio_dev);
|
|
iio_device_free(indio_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int dc_xpwr_gpadc_suspend(struct device *dev)
|
|
{
|
|
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
|
struct gpadc_info *info = iio_priv(indio_dev);
|
|
|
|
mutex_lock(&info->lock);
|
|
return 0;
|
|
}
|
|
|
|
static int dc_xpwr_gpadc_resume(struct device *dev)
|
|
{
|
|
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
|
struct gpadc_info *info = iio_priv(indio_dev);
|
|
|
|
mutex_unlock(&info->lock);
|
|
return 0;
|
|
}
|
|
#else
|
|
#define dc_xpwr_gpadc_suspend NULL
|
|
#define dc_xpwr_gpadc_resume NULL
|
|
#endif
|
|
|
|
static const struct dev_pm_ops dc_xpwr_gpadc_pm_ops = {
|
|
.suspend = dc_xpwr_gpadc_suspend,
|
|
.resume = dc_xpwr_gpadc_resume,
|
|
};
|
|
|
|
static struct platform_driver dc_xpwr_gpadc_driver = {
|
|
.probe = dc_xpwr_gpadc_probe,
|
|
.remove = dc_xpwr_gpadc_remove,
|
|
.driver = {
|
|
.name = DEV_NAME,
|
|
.pm = &dc_xpwr_gpadc_pm_ops,
|
|
},
|
|
};
|
|
|
|
static int __init dc_pmic_adc_init(void)
|
|
{
|
|
return platform_driver_register(&dc_xpwr_gpadc_driver);
|
|
}
|
|
device_initcall(dc_pmic_adc_init);
|
|
|
|
static void __exit dc_pmic_adc_exit(void)
|
|
{
|
|
platform_driver_unregister(&dc_xpwr_gpadc_driver);
|
|
}
|
|
module_exit(dc_pmic_adc_exit);
|
|
|
|
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
|
|
MODULE_DESCRIPTION("Dollar Cove(Xpower) GPADC Driver");
|
|
MODULE_LICENSE("GPL");
|