983 lines
25 KiB
C
983 lines
25 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2021 MediaTek Inc.
|
|
*
|
|
* Author: ChiYuan Huang <cy_huang@richtek.com>
|
|
*/
|
|
|
|
#include <dt-bindings/iio/adc/mediatek,mt6375_auxadc.h>
|
|
#include <linux/alarmtimer.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/iio-opaque.h>
|
|
|
|
#define FGADC_R_CON0 0x2E5
|
|
#define SYSTEM_INFO_CON2_H 0x2FE
|
|
#define HK_TOP_RST_CON0 0x30F
|
|
#define HK_TOP_INT_CON0_SET 0x311
|
|
#define HK_TOP_INT_CON0_CLR 0x312
|
|
#define HK_TOP_INT_CON1_SET 0x314
|
|
#define HK_TOP_INT_CON1_CLR 0x315
|
|
#define HK_TOP_INT_MASK_CON0 0x316
|
|
#define HK_TOP_INT_MASK_CON0_SET 0x317
|
|
#define HK_TOP_INT_MASK_CON0_CLR 0x318
|
|
#define HK_TOP_INT_MASK_CON1 0x319
|
|
#define HK_TOP_INT_STATUS0 0x31C
|
|
#define HK_TOP_INT_RAW_STATUS1 0x31F
|
|
#define HK_TOP_WKEY 0x328
|
|
#define AUXADC_OUT_CH3 0x408
|
|
#define AUXADC_OUT_CH11 0x40A
|
|
#define AUXADC_OUT_CH0 0x410
|
|
#define AUXADC_OUT_IMP_AVG 0x41C
|
|
#define AUXADC_RQST0 0x438
|
|
#define AUXADC_IMP0 0x4A8
|
|
#define AUXADC_IMP1 0x4A9
|
|
#define RG_AUXADC_LBAT0 0x4AD
|
|
#define RG_AUXADC_LBAT2_0 0x4B9
|
|
#define RG_AUXADC_NAG_0 0x4D2
|
|
|
|
#define VBAT0_FLAG BIT(0)
|
|
#define RG_RESET_MASK BIT(1)
|
|
#define VREF_ENMASK BIT(4)
|
|
#define BATON_ENMASK BIT(3)
|
|
#define BATSNS_ENMASK BIT(0)
|
|
#define ADC_OUT_RDY BIT(7)
|
|
#define AUXADC_IMP_ENMASK BIT(0)
|
|
#define AUXADC_IMP_PRDSEL_MASK GENMASK(1, 0)
|
|
#define AUXADC_IMP_CNTSEL_MASK GENMASK(3, 2)
|
|
#define AUXADC_IMP_CNTSEL_SHFT 2
|
|
#define INT_RAW_AUXADC_IMP BIT(0)
|
|
#define NUM_IRQ_REG 2
|
|
|
|
#define AUXADC_LBAT_EN_MASK BIT(0)
|
|
#define AUXADC_LBAT2_EN_MASK BIT(0)
|
|
#define AUXADC_NAG_IRQ_EN_MASK BIT(5)
|
|
#define AUXADC_NAG_EN_MASK BIT(0)
|
|
|
|
struct mt6375_priv {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct irq_domain *domain;
|
|
struct irq_chip irq_chip;
|
|
struct mutex adc_lock;
|
|
struct mutex irq_lock;
|
|
struct regulator *isink_load;
|
|
int imix_r;
|
|
int irq;
|
|
u8 unmask_buf[NUM_IRQ_REG];
|
|
int pre_uisoc;
|
|
struct alarm vbat0_alarm;
|
|
struct work_struct vbat0_work;
|
|
atomic_t vbat0_flag;
|
|
struct wakeup_source *vbat0_ws;
|
|
struct lock_class_key info_exist_key;
|
|
};
|
|
|
|
#define VBAT0_POLL_TIME_SEC 5
|
|
#define ALARM_COUNT_MAX 12
|
|
static const int vbat_event[] = { RG_INT_STATUS_BAT_H, RG_INT_STATUS_BAT_L,
|
|
RG_INT_STATUS_BAT2_H, RG_INT_STATUS_BAT2_L,
|
|
RG_INT_STATUS_NAG_C_DLTV };
|
|
|
|
static const struct {
|
|
u16 addr;
|
|
u8 mask;
|
|
} vbat_event_regs[] = {
|
|
{ RG_AUXADC_LBAT0, AUXADC_LBAT_EN_MASK },
|
|
{ RG_AUXADC_LBAT0, AUXADC_LBAT_EN_MASK },
|
|
{ RG_AUXADC_LBAT2_0, AUXADC_LBAT2_EN_MASK },
|
|
{ RG_AUXADC_LBAT2_0, AUXADC_LBAT2_EN_MASK },
|
|
{ RG_AUXADC_NAG_0, AUXADC_NAG_IRQ_EN_MASK | AUXADC_NAG_EN_MASK },
|
|
};
|
|
|
|
#define AUXADC_CHAN(_idx, _resolution, _type, _info) { \
|
|
.type = _type, \
|
|
.channel = MT6375_AUXADC_##_idx, \
|
|
.scan_index = MT6375_AUXADC_##_idx, \
|
|
.datasheet_name = #_idx, \
|
|
.scan_type = { \
|
|
.sign = 'u', \
|
|
.realbits = _resolution, \
|
|
.storagebits = 16, \
|
|
.endianness = IIO_CPU, \
|
|
}, \
|
|
.indexed = 1, \
|
|
.info_mask_separate = _info \
|
|
}
|
|
|
|
#define AUXADC_CHAN_PROCESSED(_idx, _resolution, _type) \
|
|
AUXADC_CHAN(_idx, _resolution, _type, BIT(IIO_CHAN_INFO_PROCESSED))
|
|
|
|
#define AUXADC_CHAN_RAW_SCALE(_idx, _resolution, _type) \
|
|
AUXADC_CHAN(_idx, _resolution, _type, \
|
|
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE))
|
|
|
|
static const struct iio_chan_spec auxadc_channels[] = {
|
|
AUXADC_CHAN_RAW_SCALE(BATSNS, 15, IIO_VOLTAGE),
|
|
AUXADC_CHAN_RAW_SCALE(BATON, 12, IIO_VOLTAGE),
|
|
AUXADC_CHAN_PROCESSED(IMP, 15, IIO_VOLTAGE),
|
|
AUXADC_CHAN_RAW_SCALE(IMIX_R, 16, IIO_RESISTANCE),
|
|
AUXADC_CHAN_RAW_SCALE(VREF, 12, IIO_VOLTAGE),
|
|
AUXADC_CHAN_RAW_SCALE(BATSNS_DBG, 15, IIO_VOLTAGE)
|
|
};
|
|
|
|
static int auxadc_get_chg_vbat(struct mt6375_priv *priv, int *chg_vbat)
|
|
{
|
|
static struct iio_channel *chg_vbat_chan;
|
|
int ret = 0, vbat;
|
|
|
|
if (IS_ERR_OR_NULL(chg_vbat_chan))
|
|
chg_vbat_chan = devm_iio_channel_get(priv->dev, "chg_vbat");
|
|
if (IS_ERR(chg_vbat_chan))
|
|
return PTR_ERR(chg_vbat_chan);
|
|
|
|
ret = iio_read_channel_processed(chg_vbat_chan, &vbat);
|
|
if (ret < 0)
|
|
return ret;
|
|
*chg_vbat = vbat / 1000;
|
|
return ret;
|
|
}
|
|
|
|
static int auxadc_read_channel(struct mt6375_priv *priv, int chan, int dbits, int *val)
|
|
{
|
|
unsigned int enable, out_reg, rdy_val;
|
|
u16 raw_val;
|
|
int ret, chg_vbat = 0;
|
|
|
|
if (chan == MT6375_AUXADC_VREF) {
|
|
enable = VREF_ENMASK;
|
|
out_reg = AUXADC_OUT_CH11;
|
|
} else if (chan == MT6375_AUXADC_BATON) {
|
|
enable = BATON_ENMASK;
|
|
out_reg = AUXADC_OUT_CH3;
|
|
} else if (chan == MT6375_AUXADC_BATSNS) {
|
|
if (atomic_read(&priv->vbat0_flag)) {
|
|
ret = auxadc_get_chg_vbat(priv, &chg_vbat);
|
|
dev_info(priv->dev, "%s: use chg_vbat:%d(%d)\n", __func__, chg_vbat, ret);
|
|
if (ret >= 0)
|
|
*val = chg_vbat;
|
|
return ret ? ret : IIO_VAL_INT;
|
|
}
|
|
|
|
enable = BATSNS_ENMASK;
|
|
out_reg = AUXADC_OUT_CH0;
|
|
} else {
|
|
enable = BATSNS_ENMASK;
|
|
out_reg = AUXADC_OUT_CH0;
|
|
}
|
|
|
|
ret = regmap_write(priv->regmap, AUXADC_RQST0, enable);
|
|
if (ret)
|
|
return ret;
|
|
|
|
usleep_range(1000, 1200);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, out_reg + 1, rdy_val, rdy_val & ADC_OUT_RDY,
|
|
500, 11*1000);
|
|
if (ret == -ETIMEDOUT)
|
|
dev_err(priv->dev, "(%d) channel timeout\n", chan);
|
|
|
|
ret = regmap_raw_read(priv->regmap, out_reg, &raw_val, sizeof(raw_val));
|
|
if (ret)
|
|
return ret;
|
|
|
|
*val = raw_val & (BIT(dbits) - 1);
|
|
|
|
return IIO_VAL_INT;
|
|
}
|
|
|
|
static int gauge_get_imp_ibat(struct mt6375_priv *priv)
|
|
{
|
|
struct power_supply *psy;
|
|
union power_supply_propval prop;
|
|
int ret;
|
|
|
|
psy = power_supply_get_by_name("mtk-gauge");
|
|
if (!psy)
|
|
return 0;
|
|
|
|
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, &prop);
|
|
if (ret)
|
|
return ret;
|
|
|
|
power_supply_put(psy);
|
|
return prop.intval;
|
|
}
|
|
|
|
static int auxadc_read_imp(struct mt6375_priv *priv, int *vbat, int *ibat)
|
|
{
|
|
unsigned int wait_time_in_ms, regval;
|
|
const unsigned int prd_sel[] = { 6, 8, 10, 12 };
|
|
const unsigned int cnt_sel[] = { 1, 2, 4, 8 };
|
|
u16 raw_val;
|
|
int ret;
|
|
int dbits = auxadc_channels[MT6375_AUXADC_IMP].scan_type.realbits;
|
|
|
|
if (atomic_read(&priv->vbat0_flag)) {
|
|
dev_info(priv->dev, "%s: vbat cell abnormal, return -EIO\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
ret = regmap_write(priv->regmap, AUXADC_IMP0, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_write(priv->regmap, HK_TOP_INT_CON1_CLR, INT_RAW_AUXADC_IMP);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_write(priv->regmap, HK_TOP_INT_CON1_SET, INT_RAW_AUXADC_IMP);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_read(priv->regmap, AUXADC_IMP1, ®val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
wait_time_in_ms = prd_sel[regval & AUXADC_IMP_PRDSEL_MASK];
|
|
wait_time_in_ms *= cnt_sel[(regval & AUXADC_IMP_CNTSEL_MASK) >> AUXADC_IMP_CNTSEL_SHFT];
|
|
|
|
ret = regmap_write(priv->regmap, AUXADC_IMP0, AUXADC_IMP_ENMASK);
|
|
if (ret)
|
|
return ret;
|
|
|
|
msleep(wait_time_in_ms);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, HK_TOP_INT_RAW_STATUS1, regval,
|
|
(regval & INT_RAW_AUXADC_IMP), 100, 1000);
|
|
if (ret == -ETIMEDOUT)
|
|
dev_err(priv->dev, "IMP channel timeout\n");
|
|
|
|
ret = regmap_raw_read(priv->regmap, AUXADC_OUT_IMP_AVG, &raw_val, sizeof(raw_val));
|
|
if (ret)
|
|
return ret;
|
|
raw_val &= (BIT(dbits) - 1);
|
|
*vbat = div_s64((s64)raw_val * 7360, BIT(dbits));
|
|
|
|
ret = regmap_write(priv->regmap, AUXADC_IMP0, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*ibat = gauge_get_imp_ibat(priv);
|
|
|
|
return IIO_VAL_INT;
|
|
}
|
|
|
|
static int auxadc_read_scale(struct mt6375_priv *priv, int chan, int dbits, int *val1, int *val2)
|
|
{
|
|
switch (chan) {
|
|
case MT6375_AUXADC_BATSNS:
|
|
if (atomic_read(&priv->vbat0_flag)) {
|
|
*val1 = 1;
|
|
*val2 = 1;
|
|
} else {
|
|
*val1 = 7360;
|
|
*val2 = BIT(dbits);
|
|
}
|
|
return IIO_VAL_FRACTIONAL;
|
|
case MT6375_AUXADC_BATSNS_DBG:
|
|
*val1 = 7360;
|
|
*val2 = BIT(dbits);
|
|
return IIO_VAL_FRACTIONAL;
|
|
case MT6375_AUXADC_BATON:
|
|
case MT6375_AUXADC_VREF:
|
|
*val1 = 2760;
|
|
*val2 = BIT(dbits);
|
|
return IIO_VAL_FRACTIONAL;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int auxadc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val1,
|
|
int *val2, long mask)
|
|
{
|
|
struct mt6375_priv *priv = iio_priv(indio_dev);
|
|
int dbits = chan->scan_type.realbits;
|
|
int ch_idx = chan->channel;
|
|
int ret;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_PROCESSED:
|
|
switch (ch_idx) {
|
|
case MT6375_AUXADC_IMP:
|
|
mutex_lock(&priv->adc_lock);
|
|
pm_stay_awake(priv->dev);
|
|
ret = auxadc_read_imp(priv, val1, val2);
|
|
pm_relax(priv->dev);
|
|
mutex_unlock(&priv->adc_lock);
|
|
return ret;
|
|
}
|
|
break;
|
|
case IIO_CHAN_INFO_RAW:
|
|
switch (ch_idx) {
|
|
case MT6375_AUXADC_BATSNS:
|
|
case MT6375_AUXADC_BATON:
|
|
case MT6375_AUXADC_VREF:
|
|
case MT6375_AUXADC_BATSNS_DBG:
|
|
mutex_lock(&priv->adc_lock);
|
|
pm_stay_awake(priv->dev);
|
|
ret = auxadc_read_channel(priv, ch_idx, dbits, val1);
|
|
pm_relax(priv->dev);
|
|
mutex_unlock(&priv->adc_lock);
|
|
return ret;
|
|
case MT6375_AUXADC_IMIX_R:
|
|
*val1 = priv->imix_r;
|
|
return IIO_VAL_INT;
|
|
}
|
|
break;
|
|
case IIO_CHAN_INFO_SCALE:
|
|
return auxadc_read_scale(priv, ch_idx, dbits, val1, val2);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const struct iio_info auxadc_iio_info = {
|
|
.read_raw = auxadc_read_raw,
|
|
};
|
|
|
|
static void auxadc_irq_lock(struct irq_data *data)
|
|
{
|
|
struct mt6375_priv *priv = irq_data_get_irq_chip_data(data);
|
|
|
|
mutex_lock(&priv->irq_lock);
|
|
}
|
|
|
|
static void auxadc_irq_sync_unlock(struct irq_data *data)
|
|
{
|
|
struct mt6375_priv *priv = irq_data_get_irq_chip_data(data);
|
|
int idx = data->hwirq / 8, bits = BIT(data->hwirq % 8), ret;
|
|
unsigned int reg;
|
|
|
|
if (priv->unmask_buf[idx] & bits)
|
|
reg = HK_TOP_INT_CON0_SET + idx * 3;
|
|
else
|
|
reg = HK_TOP_INT_CON0_CLR + idx * 3;
|
|
|
|
ret = regmap_write(priv->regmap, reg, bits);
|
|
if (ret)
|
|
dev_err(priv->dev, "Failed to set/clr irq con %d\n", data->hwirq);
|
|
|
|
mutex_unlock(&priv->irq_lock);
|
|
}
|
|
|
|
static void auxadc_irq_disable(struct irq_data *data)
|
|
{
|
|
struct mt6375_priv *priv = irq_data_get_irq_chip_data(data);
|
|
|
|
priv->unmask_buf[data->hwirq / 8] &= ~BIT(data->hwirq % 8);
|
|
}
|
|
|
|
static void auxadc_irq_enable(struct irq_data *data)
|
|
{
|
|
struct mt6375_priv *priv = irq_data_get_irq_chip_data(data);
|
|
|
|
priv->unmask_buf[data->hwirq / 8] |= BIT(data->hwirq % 8);
|
|
}
|
|
|
|
static int auxadc_irq_map(struct irq_domain *h, unsigned int virq,
|
|
irq_hw_number_t hw)
|
|
{
|
|
struct mt6375_priv *priv = h->host_data;
|
|
|
|
irq_set_chip_data(virq, priv);
|
|
irq_set_chip(virq, &priv->irq_chip);
|
|
irq_set_nested_thread(virq, 1);
|
|
irq_set_parent(virq, priv->irq);
|
|
irq_set_noprobe(virq);
|
|
return 0;
|
|
}
|
|
|
|
static const struct irq_domain_ops auxadc_domain_ops = {
|
|
.map = auxadc_irq_map,
|
|
.xlate = irq_domain_xlate_onetwocell,
|
|
};
|
|
|
|
static int auxadc_vbat_is_valid(struct mt6375_priv *priv, bool *valid)
|
|
{
|
|
static struct iio_channel *auxadc_vbat_chan;
|
|
int ret = 0, chg_vbat = 0, auxadc_vbat = 0;
|
|
|
|
if (IS_ERR_OR_NULL(auxadc_vbat_chan))
|
|
auxadc_vbat_chan = devm_iio_channel_get(priv->dev, "auxadc_vbat");
|
|
if (IS_ERR(auxadc_vbat_chan))
|
|
return PTR_ERR(auxadc_vbat_chan);
|
|
|
|
ret = auxadc_get_chg_vbat(priv, &chg_vbat);
|
|
dev_info(priv->dev, "%s: chg_vbat = %d(%d)\n", __func__,
|
|
chg_vbat, ret);
|
|
|
|
ret |= iio_read_channel_processed(auxadc_vbat_chan, &auxadc_vbat);
|
|
dev_info(priv->dev, "%s: auxadc_vbat = %d(%d)\n", __func__,
|
|
auxadc_vbat, ret);
|
|
|
|
if (!ret && abs(chg_vbat - auxadc_vbat) > 1000) {
|
|
dev_info(priv->dev, "%s: unexpected vbat cell!!\n", __func__);
|
|
*valid = false;
|
|
} else
|
|
*valid = true;
|
|
return ret;
|
|
}
|
|
|
|
static int auxadc_handle_vbat0(struct mt6375_priv *priv, bool is_vbat0)
|
|
{
|
|
struct power_supply *chg_psy;
|
|
union power_supply_propval val;
|
|
int i, ret;
|
|
|
|
/* set/clr vbat0 bits */
|
|
ret = regmap_update_bits(priv->regmap, SYSTEM_INFO_CON2_H, VBAT0_FLAG,
|
|
is_vbat0 ? 0xFF : 0);
|
|
if (ret < 0) {
|
|
dev_notice(priv->dev, "%s: failed to clear vbat0 flag\n", __func__);
|
|
return ret;
|
|
}
|
|
/* notify gauge & charger */
|
|
chg_psy = devm_power_supply_get_by_phandle(priv->dev, "charger");
|
|
if (IS_ERR_OR_NULL(chg_psy))
|
|
return PTR_ERR(chg_psy);
|
|
|
|
val.intval = is_vbat0 ? true : false;
|
|
ret = power_supply_set_property(chg_psy, POWER_SUPPLY_PROP_ENERGY_EMPTY,
|
|
&val);
|
|
power_supply_changed(chg_psy);
|
|
|
|
/* mask/unmask irq & disable function */
|
|
for (i = 0; i < ARRAY_SIZE(vbat_event); i++) {
|
|
if (is_vbat0) {
|
|
ret = regmap_update_bits(priv->regmap,
|
|
vbat_event_regs[i].addr,
|
|
vbat_event_regs[i].mask,
|
|
0);
|
|
disable_irq_nosync(irq_find_mapping(priv->domain,
|
|
vbat_event[i]));
|
|
} else {
|
|
ret = regmap_update_bits(priv->regmap,
|
|
vbat_event_regs[i].addr,
|
|
vbat_event_regs[i].mask, 0xFF);
|
|
enable_irq(irq_find_mapping(priv->domain,
|
|
vbat_event[i]));
|
|
}
|
|
}
|
|
|
|
atomic_set(&priv->vbat0_flag, is_vbat0);
|
|
return ret;
|
|
}
|
|
|
|
static void auxadc_vbat0_poll_work(struct work_struct *work)
|
|
{
|
|
struct mt6375_priv *priv = container_of(work, struct mt6375_priv,
|
|
vbat0_work);
|
|
bool valid;
|
|
ktime_t add;
|
|
int ret;
|
|
|
|
__pm_stay_awake(priv->vbat0_ws);
|
|
ret = auxadc_vbat_is_valid(priv, &valid);
|
|
if (ret < 0 || !valid) {
|
|
dev_info(priv->dev, "%s: restart timer\n", __func__);
|
|
add = ktime_set(VBAT0_POLL_TIME_SEC, 0);
|
|
#ifdef CONFIG_MTK_GAUGE_VBAT0_DEBUG
|
|
alarm_forward_now(&priv->vbat0_alarm, add);
|
|
alarm_restart(&priv->vbat0_alarm);
|
|
#endif
|
|
__pm_relax(priv->vbat0_ws);
|
|
return;
|
|
}
|
|
|
|
dev_info(priv->dev, "%s: vbat recover\n", __func__);
|
|
if (auxadc_handle_vbat0(priv, false))
|
|
dev_notice(priv->dev, "%s: failed to handle vbat0\n", __func__);
|
|
__pm_relax(priv->vbat0_ws);
|
|
}
|
|
|
|
static enum alarmtimer_restart vbat0_alarm_poll_func(
|
|
struct alarm *alarm, ktime_t now)
|
|
{
|
|
struct mt6375_priv *priv = container_of(alarm, struct mt6375_priv,
|
|
vbat0_alarm);
|
|
schedule_work(&priv->vbat0_work);
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
|
|
static int auxadc_check_vbat_event(struct mt6375_priv *priv, u8 *status_buf)
|
|
{
|
|
int i, ret = 0, idx_i, idx_j;
|
|
bool valid;
|
|
ktime_t now, add;
|
|
|
|
if (atomic_read(&priv->vbat0_flag))
|
|
return ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(vbat_event); i++) {
|
|
idx_i = vbat_event[i] / 8;
|
|
idx_j = vbat_event[i] % 8;
|
|
if (status_buf[idx_i] & BIT(idx_j))
|
|
break;
|
|
}
|
|
if (i == ARRAY_SIZE(vbat_event)) {
|
|
dev_info(priv->dev, "%s: without related event\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
ret = auxadc_vbat_is_valid(priv, &valid);
|
|
if (ret < 0 || valid)
|
|
return ret;
|
|
|
|
ret = auxadc_handle_vbat0(priv, true);
|
|
if (ret < 0) {
|
|
dev_notice(priv->dev, "%s: failed to handle vbat0\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/* start alarm */
|
|
now = ktime_get_boottime();
|
|
add = ktime_set(VBAT0_POLL_TIME_SEC, 0);
|
|
alarm_start(&priv->vbat0_alarm, ktime_add(now, add));
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t auxadc_irq_thread(int irq, void *data)
|
|
{
|
|
struct mt6375_priv *priv = data;
|
|
static const u8 no_status[NUM_IRQ_REG];
|
|
static const u8 mask[NUM_IRQ_REG] = { 0x3F, 0x02 };
|
|
u8 status_buf[NUM_IRQ_REG], status;
|
|
bool handled = false;
|
|
int i, j, ret;
|
|
|
|
ret = regmap_raw_read(priv->regmap, HK_TOP_INT_STATUS0, status_buf, sizeof(status_buf));
|
|
if (ret) {
|
|
dev_err(priv->dev, "Error reading INT status\n");
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (!memcmp(status_buf, no_status, NUM_IRQ_REG))
|
|
return IRQ_HANDLED;
|
|
|
|
ret = auxadc_check_vbat_event(priv, status_buf);
|
|
if (ret < 0)
|
|
dev_info(priv->dev, "check vbat event failed\n");
|
|
|
|
/* mask all irqs */
|
|
for (i = 0; i < NUM_IRQ_REG; i++) {
|
|
ret = regmap_write(priv->regmap,
|
|
HK_TOP_INT_MASK_CON0_SET + i * 3, mask[i]);
|
|
if (ret)
|
|
dev_err(priv->dev, "Failed to mask irq[%d]\n", i);
|
|
}
|
|
|
|
for (i = 0; i < NUM_IRQ_REG; i++) {
|
|
status = status_buf[i] & priv->unmask_buf[i];
|
|
if (!status)
|
|
continue;
|
|
|
|
for (j = 0; j < 8; j++) {
|
|
if (!(status & BIT(j)))
|
|
continue;
|
|
|
|
handle_nested_irq(irq_find_mapping(priv->domain, i * 8 + j));
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
/* after process, unmask all irqs */
|
|
for (i = 0; i < NUM_IRQ_REG; i++) {
|
|
ret = regmap_write(priv->regmap,
|
|
HK_TOP_INT_MASK_CON0_CLR + i * 3, mask[i]);
|
|
if (ret)
|
|
dev_err(priv->dev, "Failed to unmask irq[%d]\n", i);
|
|
}
|
|
|
|
ret = regmap_raw_write(priv->regmap, HK_TOP_INT_STATUS0, status_buf, sizeof(status_buf));
|
|
if (ret)
|
|
dev_err(priv->dev, "Error clear INT status\n");
|
|
|
|
return handled ? IRQ_HANDLED : IRQ_NONE;
|
|
}
|
|
|
|
static int auxadc_add_irq_chip(struct mt6375_priv *priv)
|
|
{
|
|
int i, ret;
|
|
|
|
for (i = 0; i < NUM_IRQ_REG; i++) {
|
|
ret = regmap_write(priv->regmap, HK_TOP_INT_CON0_CLR + i * 3, 0xFF);
|
|
if (ret) {
|
|
dev_err(priv->dev, "Failed to disable irq con [%d]\n", i);
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_write(priv->regmap, HK_TOP_INT_MASK_CON0 + i * 3, 0);
|
|
if (ret) {
|
|
dev_err(priv->dev, "Failed to init irq mask [%d]\n", i);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Default mask AUXADC_IMP */
|
|
ret = regmap_update_bits(priv->regmap, HK_TOP_INT_MASK_CON1, INT_RAW_AUXADC_IMP,
|
|
INT_RAW_AUXADC_IMP);
|
|
if (ret) {
|
|
dev_err(priv->dev, "Failed to defaut unmask AUXADC_IMP\n");
|
|
return ret;
|
|
}
|
|
|
|
priv->irq_chip.name = dev_name(priv->dev);
|
|
priv->irq_chip.irq_bus_lock = auxadc_irq_lock;
|
|
priv->irq_chip.irq_bus_sync_unlock = auxadc_irq_sync_unlock;
|
|
priv->irq_chip.irq_disable = auxadc_irq_disable;
|
|
priv->irq_chip.irq_enable = auxadc_irq_enable;
|
|
|
|
priv->domain = irq_domain_add_linear(priv->dev->of_node, NUM_IRQ_REG * 8,
|
|
&auxadc_domain_ops, priv);
|
|
if (!priv->domain) {
|
|
dev_err(priv->dev, "Failed to create IRQ domain\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = request_threaded_irq(priv->irq, NULL, auxadc_irq_thread, IRQF_SHARED | IRQF_ONESHOT,
|
|
dev_name(priv->dev), priv);
|
|
if (ret) {
|
|
dev_err(priv->dev, "Failed to request IRQ %d for %s: %d\n", priv->irq,
|
|
dev_name(priv->dev), ret);
|
|
goto err_irq;
|
|
}
|
|
|
|
enable_irq_wake(priv->irq);
|
|
return 0;
|
|
|
|
err_irq:
|
|
irq_domain_remove(priv->domain);
|
|
return ret;
|
|
}
|
|
|
|
static void auxadc_del_irq_chip(void *data)
|
|
{
|
|
struct mt6375_priv *priv = data;
|
|
unsigned int virq;
|
|
int hwirq;
|
|
|
|
free_irq(priv->irq, priv);
|
|
|
|
for (hwirq = 0; hwirq < NUM_IRQ_REG * 8; hwirq++) {
|
|
virq = irq_find_mapping(priv->domain, hwirq);
|
|
if (virq)
|
|
irq_dispose_mapping(virq);
|
|
}
|
|
|
|
irq_domain_remove(priv->domain);
|
|
}
|
|
|
|
static int auxadc_reset(struct mt6375_priv *priv)
|
|
{
|
|
u8 reset_key[2] = { 0x63, 0x63 };
|
|
int ret;
|
|
|
|
ret = regmap_raw_write(priv->regmap, HK_TOP_WKEY, reset_key, sizeof(reset_key));
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_write(priv->regmap, HK_TOP_RST_CON0, RG_RESET_MASK);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_write(priv->regmap, HK_TOP_RST_CON0, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
reset_key[0] = reset_key[1] = 0;
|
|
return regmap_raw_write(priv->regmap, HK_TOP_WKEY, reset_key, sizeof(reset_key));
|
|
}
|
|
|
|
static int auxadc_get_uisoc(void)
|
|
{
|
|
struct power_supply *psy;
|
|
union power_supply_propval prop;
|
|
int ret;
|
|
|
|
psy = power_supply_get_by_name("battery");
|
|
if (!psy)
|
|
return -ENODEV;
|
|
|
|
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &prop);
|
|
if (ret || prop.intval < 0)
|
|
return -EINVAL;
|
|
|
|
power_supply_put(psy);
|
|
return prop.intval;
|
|
}
|
|
|
|
static int auxadc_get_rac(struct mt6375_priv *priv)
|
|
{
|
|
int vbat_1 = 0, vbat_2 = 0;
|
|
int ibat_1 = 0, ibat_2 = 0;
|
|
int rac = 0, ret = 0;
|
|
int retry_count = 0;
|
|
|
|
/* to make sure dummy load has been disabled */
|
|
if (regulator_is_enabled(priv->isink_load))
|
|
regulator_disable(priv->isink_load);
|
|
|
|
do {
|
|
|
|
mutex_lock(&priv->adc_lock);
|
|
ret = auxadc_read_imp(priv, &vbat_1, &ibat_1);
|
|
mutex_unlock(&priv->adc_lock);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* enable_dummy_load */
|
|
ret = regulator_enable(priv->isink_load);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mdelay(50);
|
|
|
|
mutex_lock(&priv->adc_lock);
|
|
ret = auxadc_read_imp(priv, &vbat_2, &ibat_2);
|
|
mutex_unlock(&priv->adc_lock);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* disable_dummy_load */
|
|
ret = regulator_disable(priv->isink_load);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mdelay(50);
|
|
|
|
/* translate to 0.1mV */
|
|
vbat_1 *= 10;
|
|
vbat_2 *= 10;
|
|
|
|
/* Cal.Rac: 70mA <= c_diff <= 120mA, 4mV <= v_diff <= 200mV */
|
|
if ((ibat_2 - ibat_1) >= 700 && (ibat_2 - ibat_1) <= 1200 &&
|
|
(vbat_1 - vbat_2) >= 40 && (vbat_1 - vbat_2) <= 2000) {
|
|
/*m-ohm */
|
|
rac = ((vbat_1 - vbat_2) * 1000) / (ibat_2 - ibat_1);
|
|
if (rac < 0)
|
|
ret = (rac - (rac * 2)) * 1;
|
|
else
|
|
ret = rac * 1;
|
|
if (ret < 50)
|
|
ret = -1;
|
|
} else
|
|
ret = -1;
|
|
|
|
dev_info(priv->dev, "v1=%d,v2=%d,c1=%d,c2=%d,v_diff=%d,c_diff=%d,rac=%d,ret=%d,retry=%d\n",
|
|
vbat_1, vbat_2, ibat_1, ibat_2,
|
|
(vbat_1 - vbat_2), (ibat_2 - ibat_1),
|
|
rac, ret, retry_count);
|
|
|
|
if (++retry_count >= 3)
|
|
break;
|
|
} while (ret == -1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define IMIX_R_MIN_MOHM 100
|
|
#define IMIX_R_CALI_CNT 2
|
|
|
|
static int auxadc_cali_imix_r(struct mt6375_priv *priv)
|
|
{
|
|
struct power_supply *psy;
|
|
int cur_uisoc = auxadc_get_uisoc();
|
|
int i, imix_r_avg = 0, rac_val[IMIX_R_CALI_CNT];
|
|
|
|
psy = power_supply_get_by_name("mtk-gauge");
|
|
if (!psy) {
|
|
dev_info(priv->dev, "%s gauge disabled, skip\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
power_supply_put(psy);
|
|
|
|
if (cur_uisoc < 0 || cur_uisoc == priv->pre_uisoc) {
|
|
dev_info(priv->dev, "%s, pre_SOC=%d SOC= %d, skip\n", __func__,
|
|
priv->pre_uisoc, cur_uisoc);
|
|
return 0;
|
|
}
|
|
|
|
priv->pre_uisoc = cur_uisoc;
|
|
|
|
for (i = 0; i < IMIX_R_CALI_CNT; i++) {
|
|
rac_val[i] = auxadc_get_rac(priv);
|
|
if (rac_val[i] < 0)
|
|
return -EIO;
|
|
|
|
imix_r_avg += rac_val[i];
|
|
}
|
|
|
|
imix_r_avg /= IMIX_R_CALI_CNT;
|
|
if (imix_r_avg > IMIX_R_MIN_MOHM)
|
|
priv->imix_r = imix_r_avg;
|
|
|
|
dev_info(priv->dev, "[%s] %d, %d, ravg:%d\n", __func__, rac_val[0], rac_val[1], imix_r_avg);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_auxadc_parse_dt(struct mt6375_priv *priv)
|
|
{
|
|
int ret = 0;
|
|
struct device_node *np;
|
|
u32 val = 0;
|
|
|
|
np = of_find_compatible_node(NULL, NULL, "mediatek,pmic-auxadc");
|
|
if (!np)
|
|
return -ENODEV;
|
|
|
|
np = of_get_child_by_name(np, "imix_r");
|
|
if (!np) {
|
|
dev_notice(priv->dev, "no imix_r(%d)\n", ret);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "val", &val);
|
|
if (ret) {
|
|
dev_notice(priv->dev, "no imix_r(%d)\n", ret);
|
|
return ret;
|
|
}
|
|
priv->imix_r = val;
|
|
dev_info(priv->dev, "%s: imix_r = %d\n", __func__, priv->imix_r);
|
|
return ret;
|
|
}
|
|
|
|
static void mt6375_unregister_lockdep_key(void *data)
|
|
{
|
|
struct lock_class_key *key = data;
|
|
|
|
lockdep_unregister_key(key);
|
|
}
|
|
|
|
static int mt6375_auxadc_probe(struct platform_device *pdev)
|
|
{
|
|
struct mt6375_priv *priv;
|
|
struct iio_dev *indio_dev;
|
|
struct iio_dev_opaque *iio_dev_opaque;
|
|
int ret;
|
|
|
|
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
|
|
if (!indio_dev)
|
|
return -ENOMEM;
|
|
|
|
priv = iio_priv(indio_dev);
|
|
priv->dev = &pdev->dev;
|
|
mutex_init(&priv->adc_lock);
|
|
mutex_init(&priv->irq_lock);
|
|
priv->pre_uisoc = 101;
|
|
atomic_set(&priv->vbat0_flag, 0);
|
|
device_init_wakeup(&pdev->dev, true);
|
|
platform_set_drvdata(pdev, priv);
|
|
priv->vbat0_ws = wakeup_source_register(&pdev->dev, "vbat0_ws");
|
|
lockdep_register_key(&priv->info_exist_key);
|
|
iio_dev_opaque = to_iio_dev_opaque(indio_dev);
|
|
lockdep_set_class(&iio_dev_opaque->info_exist_lock, &priv->info_exist_key);
|
|
|
|
ret = devm_add_action_or_reset(&pdev->dev, mt6375_unregister_lockdep_key,
|
|
&priv->info_exist_key);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = mt6375_auxadc_parse_dt(priv);
|
|
if (ret) {
|
|
dev_notice(&pdev->dev, "Failed to parse dt\n");
|
|
return ret;
|
|
}
|
|
|
|
priv->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!priv->regmap) {
|
|
dev_err(&pdev->dev, "Failed to get parent regmap\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = auxadc_reset(priv);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to reset\n");
|
|
return ret;
|
|
}
|
|
|
|
priv->irq = platform_get_irq(pdev, 0);
|
|
if (priv->irq < 0) {
|
|
dev_err(&pdev->dev, "Failed to get gm30 irq\n");
|
|
return priv->irq;
|
|
}
|
|
|
|
ret = auxadc_add_irq_chip(priv);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to add irq chip\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = devm_add_action_or_reset(&pdev->dev, auxadc_del_irq_chip, priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
INIT_WORK(&priv->vbat0_work, auxadc_vbat0_poll_work);
|
|
alarm_init(&priv->vbat0_alarm, ALARM_BOOTTIME, vbat0_alarm_poll_func);
|
|
|
|
priv->isink_load = devm_regulator_get_exclusive(&pdev->dev, "isink_load");
|
|
if (IS_ERR(priv->isink_load)) {
|
|
dev_err(&pdev->dev, "Failed to get isink_load regulator [%d]\n",
|
|
PTR_ERR(priv->isink_load));
|
|
return PTR_ERR(priv->isink_load);
|
|
}
|
|
|
|
indio_dev->name = dev_name(&pdev->dev);
|
|
indio_dev->dev.parent = &pdev->dev;
|
|
indio_dev->info = &auxadc_iio_info;
|
|
indio_dev->modes = INDIO_DIRECT_MODE;
|
|
indio_dev->channels = auxadc_channels;
|
|
indio_dev->num_channels = ARRAY_SIZE(auxadc_channels);
|
|
|
|
return devm_iio_device_register(&pdev->dev, indio_dev);
|
|
}
|
|
|
|
static int mt6375_auxadc_suspend_late(struct device *dev)
|
|
{
|
|
struct mt6375_priv *priv = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = auxadc_cali_imix_r(priv);
|
|
if (ret)
|
|
dev_err(dev, "calibrate imix_r ret=[%d]\n", ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops mt6375_auxadc_dev_pm_ops = {
|
|
SET_LATE_SYSTEM_SLEEP_PM_OPS(mt6375_auxadc_suspend_late, NULL)
|
|
};
|
|
|
|
static const struct of_device_id __maybe_unused mt6375_auxadc_of_match[] = {
|
|
{ .compatible = "mediatek,mt6375-auxadc", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt6375_auxadc_of_match);
|
|
|
|
static struct platform_driver mt6375_auxadc_driver = {
|
|
.probe = mt6375_auxadc_probe,
|
|
.driver = {
|
|
.name = "mt6375-auxadc",
|
|
.of_match_table = mt6375_auxadc_of_match,
|
|
.pm = &mt6375_auxadc_dev_pm_ops,
|
|
},
|
|
};
|
|
module_platform_driver(mt6375_auxadc_driver);
|
|
|
|
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
|
|
MODULE_DESCRIPTION("MT6375 AUXADC Driver");
|
|
MODULE_LICENSE("GPL v2");
|