535 lines
14 KiB
C
535 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2020 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/version.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/buffer.h>
|
|
#include <linux/iio/kfifo_buf.h>
|
|
|
|
#include "../inc/mt6360_pmu.h"
|
|
#include "../inc/mt6360_pmu_adc.h"
|
|
|
|
struct mt6360_pmu_adc_info {
|
|
struct device *dev;
|
|
struct mt6360_pmu_info *mpi;
|
|
struct task_struct *scan_task;
|
|
struct completion adc_complete;
|
|
struct mutex adc_lock;
|
|
ktime_t last_off_timestamps[MAX_CHANNEL];
|
|
};
|
|
|
|
static const struct mt6360_adc_platform_data def_platform_data = {
|
|
.adc_wait_t = 0x1,
|
|
.adc_idle_t = 0xa,
|
|
.zcv_en = 0,
|
|
};
|
|
|
|
static int mt6360_adc_get_process_val(struct mt6360_pmu_adc_info *info,
|
|
int chan_idx, int *val)
|
|
{
|
|
int ret = 0;
|
|
|
|
switch (chan_idx) {
|
|
case USBID_CHANNEL:
|
|
case VREF_TS_CHANNEL:
|
|
case TS_CHANNEL:
|
|
*val *= 1250;
|
|
break;
|
|
case TEMP_JC_CHANNEL:
|
|
*val = (*val * 105 - 8000) / 100;
|
|
break;
|
|
case VBAT_CHANNEL:
|
|
case VSYS_CHANNEL:
|
|
case CHG_VDDP_CHANNEL:
|
|
*val *= 1250;
|
|
break;
|
|
case VBUSDIV5_CHANNEL:
|
|
*val *= 6250;
|
|
break;
|
|
case VBUSDIV2_CHANNEL:
|
|
case IBAT_CHANNEL:
|
|
*val *= 2500;
|
|
break;
|
|
case IBUS_CHANNEL:
|
|
ret = mt6360_pmu_reg_read(info->mpi, MT6360_PMU_CHG_CTRL3);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (((ret & 0xfc) >> 2) < 0x6)
|
|
*val *= 1900;
|
|
else
|
|
*val *= 2500;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#define ADC_RETRY_CNT (10)
|
|
static void mt6360_pmu_adc_irq_enable(const char *name, int en);
|
|
|
|
static int mt6360_adc_read_raw(struct iio_dev *iio_dev,
|
|
const struct iio_chan_spec *chan,
|
|
int *val, int *val2, long mask)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = iio_priv(iio_dev);
|
|
long timeout;
|
|
u8 tmp[2], rpt[3];
|
|
ktime_t start_t, predict_end_t;
|
|
int retry_cnt = 0, ret;
|
|
|
|
mt_dbg(&iio_dev->dev, "%s: channel [%d] s\n", __func__, chan->channel);
|
|
mutex_lock(&mpai->adc_lock);
|
|
/* select preferred channel that we want */
|
|
ret = mt6360_pmu_reg_update_bits(mpai->mpi, MT6360_PMU_ADC_RPT_1,
|
|
0xf0, chan->channel << 4);
|
|
if (ret < 0)
|
|
goto err_adc_init;
|
|
/* enable adc channel we want and adc_en */
|
|
memset(tmp, 0, sizeof(tmp));
|
|
tmp[0] |= (1 << 7);
|
|
tmp[(chan->channel / 8) ? 0 : 1] |= (1 << (chan->channel % 8));
|
|
ret = mt6360_pmu_reg_block_write(mpai->mpi,
|
|
MT6360_PMU_ADC_CONFIG, 2, tmp);
|
|
if (ret < 0)
|
|
goto err_adc_init;
|
|
start_t = ktime_get();
|
|
predict_end_t = ktime_add_ms(mpai->last_off_timestamps[chan->channel],
|
|
50);
|
|
if (ktime_after(start_t, predict_end_t))
|
|
predict_end_t = ktime_add_ms(start_t, 25);
|
|
else
|
|
predict_end_t = ktime_add_ms(start_t, 75);
|
|
mt6360_pmu_adc_irq_enable("adc_donei", 1);
|
|
retry:
|
|
if (retry_cnt++ > ADC_RETRY_CNT) {
|
|
dev_err(mpai->dev, "reach adc retry cnt\n");
|
|
goto err_adc_conv;
|
|
}
|
|
reinit_completion(&mpai->adc_complete);
|
|
/* wait for conversion to complete */
|
|
timeout = wait_for_completion_timeout(&mpai->adc_complete,
|
|
msecs_to_jiffies(600));
|
|
if (timeout == 0) {
|
|
ret = -ETIMEDOUT;
|
|
goto err_adc_conv;
|
|
}
|
|
memset(rpt, 0, sizeof(rpt));
|
|
ret = mt6360_pmu_reg_block_read(mpai->mpi,
|
|
MT6360_PMU_ADC_RPT_1, 3, rpt);
|
|
if (ret < 0)
|
|
goto err_adc_conv;
|
|
/* get report channel */
|
|
if ((rpt[0] & 0x0f) != chan->channel) {
|
|
mt_dbg(&iio_dev->dev,
|
|
"not wanted channel report [%02x]\n", rpt[0]);
|
|
goto retry;
|
|
}
|
|
if (!ktime_after(ktime_get(), predict_end_t)) {
|
|
dev_dbg(&iio_dev->dev, "time is not after 50ms chan_time\n");
|
|
goto retry;
|
|
}
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
*val = (rpt[1] << 8) | rpt[2];
|
|
break;
|
|
case IIO_CHAN_INFO_PROCESSED:
|
|
*val = (rpt[1] << 8) | rpt[2];
|
|
ret = mt6360_adc_get_process_val(mpai, chan->channel, val);
|
|
if (ret < 0)
|
|
goto err_adc_conv;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
ret = IIO_VAL_INT;
|
|
err_adc_conv:
|
|
mt6360_pmu_adc_irq_enable("adc_donei", 0);
|
|
/* whatever disable all channels, except adc_en */
|
|
memset(tmp, 0, sizeof(tmp));
|
|
tmp[0] |= (1 << 7);
|
|
mt6360_pmu_reg_block_write(mpai->mpi, MT6360_PMU_ADC_CONFIG, 2, tmp);
|
|
mt6360_pmu_reg_update_bits(mpai->mpi, MT6360_PMU_ADC_RPT_1, 0xf0, 0xf0);
|
|
mpai->last_off_timestamps[chan->channel] = ktime_get();
|
|
err_adc_init:
|
|
mutex_unlock(&mpai->adc_lock);
|
|
mt_dbg(&iio_dev->dev, "%s: channel [%d] e\n", __func__, chan->channel);
|
|
return ret;
|
|
}
|
|
|
|
static const struct iio_info mt6360_adc_iio_info = {
|
|
.read_raw = mt6360_adc_read_raw,
|
|
};
|
|
|
|
#define MT6360_ADC_CHAN(idx, _type) { \
|
|
.type = _type, \
|
|
.channel = idx##_CHANNEL, \
|
|
.scan_index = idx##_CHANNEL, \
|
|
.scan_type = { \
|
|
.sign = 's', \
|
|
.realbits = 32, \
|
|
.storagebits = 32, \
|
|
.shift = 0, \
|
|
.endianness = IIO_CPU, \
|
|
}, \
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
|
BIT(IIO_CHAN_INFO_PROCESSED), \
|
|
.datasheet_name = #idx, \
|
|
.indexed = 1, \
|
|
}
|
|
|
|
static const struct iio_chan_spec mt6360_adc_channels[] = {
|
|
MT6360_ADC_CHAN(USBID, IIO_VOLTAGE),
|
|
MT6360_ADC_CHAN(VBUSDIV5, IIO_VOLTAGE),
|
|
MT6360_ADC_CHAN(VBUSDIV2, IIO_VOLTAGE),
|
|
MT6360_ADC_CHAN(VSYS, IIO_VOLTAGE),
|
|
MT6360_ADC_CHAN(VBAT, IIO_VOLTAGE),
|
|
MT6360_ADC_CHAN(IBUS, IIO_CURRENT),
|
|
MT6360_ADC_CHAN(IBAT, IIO_CURRENT),
|
|
MT6360_ADC_CHAN(CHG_VDDP, IIO_VOLTAGE),
|
|
MT6360_ADC_CHAN(TEMP_JC, IIO_TEMP),
|
|
MT6360_ADC_CHAN(VREF_TS, IIO_VOLTAGE),
|
|
MT6360_ADC_CHAN(TS, IIO_VOLTAGE),
|
|
IIO_CHAN_SOFT_TIMESTAMP(MAX_CHANNEL),
|
|
};
|
|
|
|
static irqreturn_t mt6360_pmu_bat_ovp_adc_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = iio_priv(data);
|
|
|
|
dev_warn(mpai->dev, "%s\n", __func__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6360_pmu_adc_wakeup_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = iio_priv(data);
|
|
|
|
dev_dbg(mpai->dev, "%s\n", __func__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6360_pmu_adc_donei_handler(int irq, void *data)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = iio_priv(data);
|
|
|
|
mt_dbg(mpai->dev, "%s\n", __func__);
|
|
complete(&mpai->adc_complete);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct mt6360_pmu_irq_desc mt6360_pmu_adc_irq_desc[] = {
|
|
MT6360_PMU_IRQDESC(bat_ovp_adc_evt),
|
|
MT6360_PMU_IRQDESC(adc_wakeup_evt),
|
|
MT6360_PMU_IRQDESC(adc_donei),
|
|
};
|
|
|
|
static void mt6360_pmu_adc_irq_enable(const char *name, int en)
|
|
{
|
|
struct mt6360_pmu_irq_desc *irq_desc;
|
|
int i = 0;
|
|
|
|
if (unlikely(!name))
|
|
return;
|
|
for (i = 0; i < ARRAY_SIZE(mt6360_pmu_adc_irq_desc); i++) {
|
|
irq_desc = mt6360_pmu_adc_irq_desc + i;
|
|
if (unlikely(!irq_desc->name))
|
|
continue;
|
|
if (!strcmp(irq_desc->name, name)) {
|
|
if (en)
|
|
enable_irq(irq_desc->irq);
|
|
else
|
|
disable_irq_nosync(irq_desc->irq);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mt6360_pmu_adc_irq_register(struct platform_device *pdev)
|
|
{
|
|
struct mt6360_pmu_irq_desc *irq_desc;
|
|
int i, ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt6360_pmu_adc_irq_desc); i++) {
|
|
irq_desc = mt6360_pmu_adc_irq_desc + i;
|
|
if (unlikely(!irq_desc->name))
|
|
continue;
|
|
ret = platform_get_irq_byname(pdev, irq_desc->name);
|
|
if (ret < 0)
|
|
continue;
|
|
irq_desc->irq = ret;
|
|
ret = devm_request_threaded_irq(&pdev->dev, irq_desc->irq, NULL,
|
|
irq_desc->irq_handler,
|
|
IRQF_TRIGGER_FALLING,
|
|
irq_desc->name,
|
|
platform_get_drvdata(pdev));
|
|
if (ret < 0)
|
|
dev_err(&pdev->dev,
|
|
"request %s irq fail\n", irq_desc->name);
|
|
}
|
|
}
|
|
|
|
static int mt6360_adc_scan_task_threadfn(void *data)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = data;
|
|
struct iio_dev *indio_dev = iio_priv_to_dev(mpai);
|
|
int channel_vals[MAX_CHANNEL];
|
|
int i, bit, var = 0;
|
|
int ret;
|
|
|
|
dev_dbg(mpai->dev, "%s ++\n", __func__);
|
|
while (!kthread_should_stop()) {
|
|
memset(channel_vals, 0, sizeof(channel_vals));
|
|
i = 0;
|
|
for_each_set_bit(bit, indio_dev->active_scan_mask,
|
|
indio_dev->masklength) {
|
|
ret = mt6360_adc_read_raw(indio_dev,
|
|
mt6360_adc_channels + bit,
|
|
&var, NULL,
|
|
IIO_CHAN_INFO_PROCESSED);
|
|
if (ret < 0)
|
|
dev_err(mpai->dev, "get adc[%d] fail\n", bit);
|
|
if (kthread_should_stop())
|
|
break;
|
|
channel_vals[i++] = var;
|
|
}
|
|
if (kthread_should_stop())
|
|
break;
|
|
iio_push_to_buffers_with_timestamp(indio_dev, channel_vals,
|
|
iio_get_time_ns(indio_dev));
|
|
}
|
|
dev_dbg(mpai->dev, "%s --\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6360_adc_iio_post_enable(struct iio_dev *iio_dev)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = iio_priv(iio_dev);
|
|
|
|
dev_dbg(&iio_dev->dev, "%s ++\n", __func__);
|
|
mpai->scan_task = kthread_run(mt6360_adc_scan_task_threadfn, mpai,
|
|
"scan_thread.%s", dev_name(&iio_dev->dev));
|
|
dev_dbg(&iio_dev->dev, "%s --\n", __func__);
|
|
return PTR_ERR_OR_ZERO(mpai->scan_task);
|
|
}
|
|
|
|
static int mt6360_adc_iio_pre_disable(struct iio_dev *iio_dev)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = iio_priv(iio_dev);
|
|
|
|
dev_dbg(&iio_dev->dev, "%s ++\n", __func__);
|
|
if (mpai->scan_task) {
|
|
kthread_stop(mpai->scan_task);
|
|
mpai->scan_task = NULL;
|
|
}
|
|
dev_dbg(&iio_dev->dev, "%s --\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static const struct iio_buffer_setup_ops mt6360_adc_iio_setup_ops = {
|
|
.postenable = mt6360_adc_iio_post_enable,
|
|
.predisable = mt6360_adc_iio_pre_disable,
|
|
};
|
|
|
|
static int mt6360_adc_iio_device_register(struct iio_dev *indio_dev)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = iio_priv(indio_dev);
|
|
struct iio_buffer *buffer;
|
|
int ret;
|
|
|
|
dev_dbg(mpai->dev, "%s ++\n", __func__);
|
|
indio_dev->name = dev_name(mpai->dev);
|
|
indio_dev->dev.parent = mpai->dev;
|
|
indio_dev->dev.of_node = mpai->dev->of_node;
|
|
indio_dev->info = &mt6360_adc_iio_info;
|
|
indio_dev->channels = mt6360_adc_channels;
|
|
indio_dev->num_channels = ARRAY_SIZE(mt6360_adc_channels);
|
|
indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
|
|
indio_dev->setup_ops = &mt6360_adc_iio_setup_ops;
|
|
buffer = devm_iio_kfifo_allocate(mpai->dev);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
iio_device_attach_buffer(indio_dev, buffer);
|
|
ret = devm_iio_device_register(mpai->dev, indio_dev);
|
|
if (ret < 0) {
|
|
dev_err(mpai->dev, "iio device register fail\n");
|
|
return ret;
|
|
}
|
|
dev_dbg(mpai->dev, "%s --\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static inline int mt6360_pmu_adc_reset(struct mt6360_pmu_adc_info *info)
|
|
{
|
|
u8 tmp[3] = {0x80, 0, 0};
|
|
ktime_t all_off_time;
|
|
int i;
|
|
|
|
all_off_time = ktime_get();
|
|
for (i = 0; i < MAX_CHANNEL; i++)
|
|
info->last_off_timestamps[i] = all_off_time;
|
|
/* enable adc_en, clear adc_chn_en/zcv/en/adc_wait_t/adc_idle_t */
|
|
return mt6360_pmu_reg_block_write(info->mpi,
|
|
MT6360_PMU_ADC_CONFIG, 3, tmp);
|
|
}
|
|
|
|
static const struct mt6360_pdata_prop mt6360_pdata_props[] = {
|
|
};
|
|
|
|
static int mt6360_adc_apply_pdata(struct mt6360_pmu_adc_info *mpai,
|
|
struct mt6360_adc_platform_data *pdata)
|
|
{
|
|
int ret;
|
|
|
|
dev_dbg(mpai->dev, "%s ++\n", __func__);
|
|
ret = mt6360_pdata_apply_helper(mpai->mpi, pdata, mt6360_pdata_props,
|
|
ARRAY_SIZE(mt6360_pdata_props));
|
|
if (ret < 0)
|
|
return ret;
|
|
dev_dbg(mpai->dev, "%s --\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static const struct mt6360_val_prop mt6360_val_props[] = {
|
|
MT6360_DT_VALPROP(adc_wait_t, struct mt6360_adc_platform_data),
|
|
MT6360_DT_VALPROP(adc_idle_t, struct mt6360_adc_platform_data),
|
|
MT6360_DT_VALPROP(zcv_en, struct mt6360_adc_platform_data),
|
|
};
|
|
|
|
static int mt6360_adc_parse_dt_data(struct device *dev,
|
|
struct mt6360_adc_platform_data *pdata)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
|
|
dev_dbg(dev, "%s ++\n", __func__);
|
|
memcpy(pdata, &def_platform_data, sizeof(*pdata));
|
|
mt6360_dt_parser_helper(np, (void *)pdata,
|
|
mt6360_val_props, ARRAY_SIZE(mt6360_val_props));
|
|
dev_dbg(dev, "%s --\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6360_pmu_adc_probe(struct platform_device *pdev)
|
|
{
|
|
struct mt6360_adc_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
|
struct mt6360_pmu_adc_info *mpai;
|
|
struct iio_dev *indio_dev;
|
|
bool use_dt = pdev->dev.of_node;
|
|
int ret;
|
|
|
|
dev_dbg(&pdev->dev, "%s\n", __func__);
|
|
if (use_dt) {
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
ret = mt6360_adc_parse_dt_data(&pdev->dev, pdata);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "parse dt fail\n");
|
|
return ret;
|
|
}
|
|
pdev->dev.platform_data = pdata;
|
|
}
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "no platform data specified\n");
|
|
return -EINVAL;
|
|
}
|
|
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*mpai));
|
|
if (!indio_dev)
|
|
return -ENOMEM;
|
|
mpai = iio_priv(indio_dev);
|
|
mpai->dev = &pdev->dev;
|
|
mpai->mpi = dev_get_drvdata(pdev->dev.parent);
|
|
init_completion(&mpai->adc_complete);
|
|
mutex_init(&mpai->adc_lock);
|
|
platform_set_drvdata(pdev, indio_dev);
|
|
|
|
/* first reset all channels before use */
|
|
ret = mt6360_pmu_adc_reset(mpai);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "adc reset fail\n");
|
|
return ret;
|
|
}
|
|
/* apply platform data */
|
|
ret = mt6360_adc_apply_pdata(mpai, pdata);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "apply pdata fail\n");
|
|
return ret;
|
|
}
|
|
/* adc iio device register */
|
|
ret = mt6360_adc_iio_device_register(indio_dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "iio dev register fail\n");
|
|
return ret;
|
|
}
|
|
/* irq register */
|
|
mt6360_pmu_adc_irq_register(pdev);
|
|
/* default disable adc_donei irq by default */
|
|
mt6360_pmu_adc_irq_enable("adc_donei", 0);
|
|
dev_info(&pdev->dev, "%s: successfully probed\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6360_pmu_adc_remove(struct platform_device *pdev)
|
|
{
|
|
struct mt6360_pmu_adc_info *mpai = platform_get_drvdata(pdev);
|
|
|
|
dev_dbg(&pdev->dev, "%s\n", __func__);
|
|
if (mpai->scan_task)
|
|
kthread_stop(mpai->scan_task);
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused mt6360_pmu_adc_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused mt6360_pmu_adc_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(mt6360_pmu_adc_pm_ops,
|
|
mt6360_pmu_adc_suspend, mt6360_pmu_adc_resume);
|
|
|
|
static const struct of_device_id __maybe_unused mt6360_pmu_adc_of_id[] = {
|
|
{ .compatible = "mediatek,mt6360_pmu_adc", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt6360_pmu_adc_of_id);
|
|
|
|
static const struct platform_device_id mt6360_pmu_adc_id[] = {
|
|
{ "mt6360_pmu_adc", 0 },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, mt6360_pmu_adc_id);
|
|
|
|
static struct platform_driver mt6360_pmu_adc_driver = {
|
|
.driver = {
|
|
.name = "mt6360_pmu_adc",
|
|
.owner = THIS_MODULE,
|
|
.pm = &mt6360_pmu_adc_pm_ops,
|
|
.of_match_table = of_match_ptr(mt6360_pmu_adc_of_id),
|
|
},
|
|
.probe = mt6360_pmu_adc_probe,
|
|
.remove = mt6360_pmu_adc_remove,
|
|
.id_table = mt6360_pmu_adc_id,
|
|
};
|
|
module_platform_driver(mt6360_pmu_adc_driver);
|
|
|
|
MODULE_AUTHOR("CY_Huang <cy_huang@richtek.com>");
|
|
MODULE_DESCRIPTION("MT6360 PMU ADC Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION("1.0.1");
|