6db4831e98
Android 14
382 lines
9.8 KiB
C
382 lines
9.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/platform_device.h>
|
|
#include <dt-bindings/mfd/mt6362.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/buffer.h>
|
|
#include <linux/iio/triggered_buffer.h>
|
|
#include <linux/iio/trigger_consumer.h>
|
|
|
|
#define MT6362_REG_DEVINFO (0x00)
|
|
#define MT6362_REG_TMINFO (0x0F)
|
|
#define MT6362_REG_ADCCFG1 (0xA4)
|
|
#define MT6362_REG_ADCCFG3 (0xA6)
|
|
#define MT6362_REG_ADCEN1 (0xA7)
|
|
#define MT6362_CHRPT_BASEADDR (0xAA)
|
|
#define MT6362_CHG_STAT2 (0xE2)
|
|
|
|
#define MT6362_CHIPREV_MASK (0x0F)
|
|
#define MT6362_CHIPREV_E2 (0x2)
|
|
#define MT6362_TMID3_MASK BIT(4)
|
|
#define MT6362_STSYSMIN_MASK BIT(1)
|
|
#define MT6362_ADCEN_MASK BIT(7)
|
|
|
|
/* ADC conversion time in microseconds */
|
|
#define MT6362_TIME_PERCH (1300)
|
|
#define MT6362_LTIME_PERCH (2200)
|
|
#define MT6362_DESIRECH_SHIFT (4)
|
|
#define MT6362_IRQRPTCH_SHIFT (0)
|
|
#define MT6362_VSYSMINST_CNT (3)
|
|
|
|
#define MT6362_ADCIBAT_OFFSET (100 * 1000)
|
|
#define MT6362_ADCSYSMIN_OFFSET (150 * 1000)
|
|
|
|
#define MT6362_CHRPT_ADDR(_idx) (MT6362_CHRPT_BASEADDR + ((_idx) - 1) * 2)
|
|
|
|
struct mt6362_adc_data {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct mutex adc_lock;
|
|
struct completion adc_comp;
|
|
bool ibat_offset_flag;
|
|
bool conv_ltime_flag;
|
|
};
|
|
|
|
static const int mt6362_adcch_offsets[] = {
|
|
0, 0, 0, 0, 0, 0, -64, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
};
|
|
|
|
static const int mt6362_adcch_times[] = {
|
|
/* chgvin, vsys, vbat, ibus, ibat, vddp, tempjc*/
|
|
6250, 1250, 1250, 2500, 2500, 1250, 1,
|
|
/* verfts, ts, pdvbus, cc1, cc2, sbu1, sbu2 */
|
|
1250, 1250, 25000, 10300, 10300, 2575, 2575,
|
|
/* zcv */
|
|
1250,
|
|
};
|
|
|
|
static int mt6362_adc_is_sysmin_state(struct mt6362_adc_data *data, bool *state)
|
|
{
|
|
unsigned int val = 0;
|
|
int i, rv;
|
|
|
|
*state = true;
|
|
for (i = 0; i < MT6362_VSYSMINST_CNT; i++) {
|
|
rv = regmap_read(data->regmap, MT6362_CHG_STAT2, &val);
|
|
if (rv)
|
|
return rv;
|
|
|
|
/* either one is not in vsys min, treat as VBAT>VSYSMIN */
|
|
if (!(val & MT6362_STSYSMIN_MASK)) {
|
|
*state = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_adc_read_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
int *val, int *val2, long mask)
|
|
{
|
|
struct mt6362_adc_data *data = iio_priv(indio_dev);
|
|
u8 oneshot_ch;
|
|
u16 raw = 0;
|
|
unsigned int conv_time =
|
|
data->conv_ltime_flag ? MT6362_LTIME_PERCH : MT6362_TIME_PERCH;
|
|
bool state;
|
|
int rv;
|
|
|
|
mutex_lock(&data->adc_lock);
|
|
if (chan->scan_index == MT6362_ADCCH_ZCV)
|
|
goto direct_read;
|
|
|
|
/* change ADC_EN=0 to prevent the other channels routine task */
|
|
rv = regmap_update_bits(data->regmap,
|
|
MT6362_REG_ADCCFG1, MT6362_ADCEN_MASK, 0);
|
|
if (rv)
|
|
goto out_read;
|
|
|
|
/* config onshot channel and irq reported channel */
|
|
oneshot_ch = chan->channel << MT6362_DESIRECH_SHIFT;
|
|
oneshot_ch |= chan->channel << MT6362_IRQRPTCH_SHIFT;
|
|
rv = regmap_write(data->regmap, MT6362_REG_ADCCFG3, oneshot_ch);
|
|
if (rv)
|
|
goto out_read;
|
|
|
|
/* change ADC_EN=1 to start oneshot channel as the first run */
|
|
rv = regmap_update_bits(data->regmap, MT6362_REG_ADCCFG1,
|
|
MT6362_ADCEN_MASK, MT6362_ADCEN_MASK);
|
|
if (rv)
|
|
goto out_read;
|
|
|
|
reinit_completion(&data->adc_comp);
|
|
wait_for_completion_timeout(&data->adc_comp,
|
|
usecs_to_jiffies(2 * conv_time));
|
|
|
|
/* clear oneshot channel and irq report channel */
|
|
rv = regmap_write(data->regmap, MT6362_REG_ADCCFG3, 0);
|
|
if (rv)
|
|
goto out_read;
|
|
direct_read:
|
|
/* dummy write high byte then read */
|
|
rv = regmap_write(data->regmap, chan->address, 0);
|
|
if (rv)
|
|
goto out_read;
|
|
rv = regmap_bulk_read(data->regmap, chan->address, &raw, 2);
|
|
if (rv)
|
|
goto out_read;
|
|
|
|
raw = be16_to_cpu(raw);
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
*val = raw;
|
|
break;
|
|
case IIO_CHAN_INFO_PROCESSED:
|
|
*val = mt6362_adcch_offsets[chan->scan_index];
|
|
*val += (raw * mt6362_adcch_times[chan->scan_index]);
|
|
|
|
if (chan->scan_index == MT6362_ADCCH_IBAT &&
|
|
data->ibat_offset_flag) {
|
|
rv = mt6362_adc_is_sysmin_state(data, &state);
|
|
if (rv)
|
|
goto out_read;
|
|
if (state)
|
|
*val += MT6362_ADCSYSMIN_OFFSET;
|
|
|
|
*val -= MT6362_ADCIBAT_OFFSET;
|
|
}
|
|
break;
|
|
default:
|
|
rv = -EINVAL;
|
|
}
|
|
out_read:
|
|
mutex_unlock(&data->adc_lock);
|
|
|
|
return rv ? : IIO_VAL_INT;
|
|
}
|
|
|
|
static const struct iio_info mt6362_adc_info = {
|
|
.read_raw = mt6362_adc_read_raw,
|
|
};
|
|
|
|
#define MT6362_ADC_CHAN(_idx, _si, _type) \
|
|
{\
|
|
.type = (_type), \
|
|
.channel = (_idx), \
|
|
.address = MT6362_CHRPT_ADDR(_idx), \
|
|
.scan_index = MT6362_ADCCH_##_si, \
|
|
.scan_type = { \
|
|
.sign = 's', \
|
|
.realbits = 32, \
|
|
.storagebits = 32, \
|
|
}, \
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
|
BIT(IIO_CHAN_INFO_PROCESSED), \
|
|
.datasheet_name = "ADC_CH" #_idx, \
|
|
.indexed = 1, \
|
|
}
|
|
|
|
static const struct iio_chan_spec mt6362_adc_channels[] = {
|
|
MT6362_ADC_CHAN(1, CHGVINDIV5, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(3, VSYS, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(4, VBAT, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(5, IBUS, IIO_CURRENT),
|
|
MT6362_ADC_CHAN(6, IBAT, IIO_CURRENT),
|
|
MT6362_ADC_CHAN(7, RESV5, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(8, TEMPJC, IIO_TEMP),
|
|
MT6362_ADC_CHAN(9, VREFTS, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(10, TS, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(11, PDVBUSDIV10, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(12, PDCC1DIV4, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(13, PDCC2DIV4, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(14, PDSBU1DIV4, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(15, PDSBU2DIV4, IIO_VOLTAGE),
|
|
MT6362_ADC_CHAN(17, ZCV, IIO_VOLTAGE),
|
|
IIO_CHAN_SOFT_TIMESTAMP(15),
|
|
};
|
|
|
|
static irqreturn_t mt6362_adc_trigger_handler(int irq, void *p)
|
|
{
|
|
struct iio_poll_func *pf = p;
|
|
struct iio_dev *indio_dev = pf->indio_dev;
|
|
int vals[16] = {0}; /* 14 ch s32 numbers + 1 s64 timestamp */
|
|
int dummy = 0, i, rv;
|
|
|
|
for_each_set_bit(i,
|
|
indio_dev->active_scan_mask, indio_dev->masklength) {
|
|
const struct iio_chan_spec *chan = indio_dev->channels + i;
|
|
|
|
rv = mt6362_adc_read_raw(indio_dev, chan, vals + i,
|
|
&dummy, IIO_CHAN_INFO_PROCESSED);
|
|
if (rv) {
|
|
dev_warn(&indio_dev->dev, "failed to get %d val\n", i);
|
|
goto out_trigger;
|
|
}
|
|
}
|
|
iio_push_to_buffers_with_timestamp(indio_dev,
|
|
vals, iio_get_time_ns(indio_dev));
|
|
out_trigger:
|
|
iio_trigger_notify_done(indio_dev->trig);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_adc_donei_irq(int irq, void *devid)
|
|
{
|
|
struct mt6362_adc_data *data = devid;
|
|
|
|
complete(&data->adc_comp);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int mt6362_adc_init(struct mt6362_adc_data *data)
|
|
{
|
|
/* adc en = 1, zcv = 0, all channels to be disabled */
|
|
const u8 adc_configs[] = { 0x80, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
return regmap_bulk_write(data->regmap, MT6362_REG_ADCCFG1,
|
|
adc_configs, sizeof(adc_configs));
|
|
}
|
|
|
|
static int mt6362_adc_init_offset_flags(struct mt6362_adc_data *data)
|
|
{
|
|
unsigned int devinfo, tminfo;
|
|
int rv;
|
|
|
|
rv = regmap_read(data->regmap, MT6362_REG_DEVINFO, &devinfo);
|
|
if (rv)
|
|
return rv;
|
|
rv = regmap_read(data->regmap, MT6362_REG_TMINFO, &tminfo);
|
|
if (rv)
|
|
return rv;
|
|
|
|
/* if rev > e2, adc convert time will be another one */
|
|
if ((devinfo & MT6362_CHIPREV_MASK) > MT6362_CHIPREV_E2)
|
|
data->conv_ltime_flag = true;
|
|
|
|
/* if rev > e2 or tmid3 == 1, need ibat offset, otherwise not */
|
|
if ((devinfo & MT6362_CHIPREV_MASK) > MT6362_CHIPREV_E2 ||
|
|
(tminfo & MT6362_TMID3_MASK))
|
|
data->ibat_offset_flag = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_adc_probe(struct platform_device *pdev)
|
|
{
|
|
struct iio_dev *indio_dev;
|
|
struct mt6362_adc_data *data;
|
|
int irq, rv;
|
|
|
|
dev_info(&pdev->dev, "%s\n", __func__);
|
|
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
|
|
if (!indio_dev)
|
|
return -ENOMEM;
|
|
data = iio_priv(indio_dev);
|
|
|
|
data->dev = &pdev->dev;
|
|
mutex_init(&data->adc_lock);
|
|
init_completion(&data->adc_comp);
|
|
|
|
data->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!data->regmap) {
|
|
dev_err(&pdev->dev, "failed to allocate regmap\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
rv = mt6362_adc_init_offset_flags(data);
|
|
if (rv) {
|
|
dev_notice(&pdev->dev, "failed to init adc offset flags\n");
|
|
return rv;
|
|
}
|
|
|
|
rv = mt6362_adc_init(data);
|
|
if (rv) {
|
|
dev_err(&pdev->dev, "failed to init adc [%d]\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
indio_dev->name = dev_name(&pdev->dev);
|
|
indio_dev->dev.parent = &pdev->dev;
|
|
indio_dev->info = &mt6362_adc_info;
|
|
indio_dev->modes = INDIO_DIRECT_MODE;
|
|
indio_dev->channels = mt6362_adc_channels;
|
|
indio_dev->num_channels = ARRAY_SIZE(mt6362_adc_channels);
|
|
|
|
rv = devm_iio_triggered_buffer_setup(&pdev->dev, indio_dev, NULL,
|
|
mt6362_adc_trigger_handler, NULL);
|
|
if (rv) {
|
|
dev_err(&pdev->dev, "failed to allocate iio trigger buffer\n");
|
|
return rv;
|
|
}
|
|
|
|
irq = platform_get_irq_byname(pdev, "adc_donei");
|
|
if (irq <= 0) {
|
|
dev_err(&pdev->dev, "failed to get irq number\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rv = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
|
mt6362_adc_donei_irq, 0, NULL, data);
|
|
if (rv) {
|
|
dev_err(&pdev->dev, "failed to request irq\n");
|
|
return rv;
|
|
}
|
|
|
|
rv = devm_iio_device_register(&pdev->dev, indio_dev);
|
|
if (rv) {
|
|
dev_err(&pdev->dev, "failed to register iio device\n");
|
|
return rv;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, indio_dev);
|
|
|
|
dev_info(&pdev->dev, "%s: successful\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id __maybe_unused mt6362_adc_ofid_tbls[] = {
|
|
{ .compatible = "mediatek,mt6362-adc", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt6362_adc_ofid_tbls);
|
|
|
|
static struct platform_driver mt6362_adc_driver = {
|
|
.driver = {
|
|
.name = "mt6362-adc",
|
|
.of_match_table = of_match_ptr(mt6362_adc_ofid_tbls),
|
|
},
|
|
.probe = mt6362_adc_probe,
|
|
};
|
|
#if 0
|
|
module_platform_driver(mt6362_adc_driver);
|
|
#else
|
|
static int __init mt6362_adc_driver_init(void)
|
|
{
|
|
return platform_driver_register(&mt6362_adc_driver);
|
|
}
|
|
|
|
static void __exit mt6362_adc_driver_exit(void)
|
|
{
|
|
platform_driver_unregister(&mt6362_adc_driver);
|
|
}
|
|
subsys_initcall(mt6362_adc_driver_init);
|
|
module_exit(mt6362_adc_driver_exit);
|
|
#endif
|
|
|
|
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
|
|
MODULE_DESCRIPTION("MT6362 SPMI ADC Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION("1.0.0");
|