902 lines
23 KiB
C
Executable file
902 lines
23 KiB
C
Executable file
/*
|
|
* s2mps25-powermeter.c
|
|
*
|
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd
|
|
* http://www.samsung.com
|
|
*
|
|
* 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; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mfd/samsung/s2mps25.h>
|
|
#include <linux/mfd/samsung/s2mps25-regulator.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/pmic_class.h>
|
|
#include <linux/interrupt.h>
|
|
|
|
/* Power-meter registers */
|
|
#define ADC_INTP (0x00)
|
|
#define ADC_INTM (0x01)
|
|
#define ADC_CTRL0 (0x03)
|
|
#define ADC_CTRL1 (0x04)
|
|
#define ADC_CTRL2 (0x05)
|
|
#define ADC_CTRL3 (0x06)
|
|
#define PERIOD_CTRL0 (0x07)
|
|
#define PERIOD_CTRL1 (0x08)
|
|
#define MUX0SEL (0x09)
|
|
#define MUX1SEL (0x0A)
|
|
#define MUX2SEL (0x0B)
|
|
#define MUX3SEL (0x0C)
|
|
#define MUX4SEL (0x0D)
|
|
#define MUX5SEL (0x0E)
|
|
#define MUX6SEL (0x0F)
|
|
#define MUX7SEL (0x10)
|
|
#define MUX8SEL (0x11)
|
|
#define TH_CTRL0 (0x13)
|
|
#define TH_CTRL1 (0x14)
|
|
#define TH_CTRL2 (0x15)
|
|
#define TH_CTRL3 (0x16)
|
|
#define ADC0_ACC0 (0x1F)
|
|
#define ADC1_ACC0 (0x24)
|
|
#define ADC2_ACC0 (0x29)
|
|
#define ADC3_ACC0 (0x2E)
|
|
#define ADC4_ACC0 (0x33)
|
|
#define ADC5_ACC0 (0x38)
|
|
#define ADC6_ACC0 (0x3D)
|
|
#define ADC7_ACC0 (0x42)
|
|
#define ACC_COUNT_L (0x47)
|
|
#define ACC_COUNT_H (0x48)
|
|
#define PM0_LPF_DATA_L (0x49)
|
|
#define PM0_LPF_DATA_H (0x4A)
|
|
#define PM1_LPF_DATA_L (0x4B)
|
|
#define PM1_LPF_DATA_H (0x4C)
|
|
#define PM2_LPF_DATA_L (0x4D)
|
|
#define PM2_LPF_DATA_H (0x4E)
|
|
#define PM3_LPF_DATA_L (0x4F)
|
|
#define PM3_LPF_DATA_H (0x50)
|
|
#define PM4_LPF_DATA_L (0x51)
|
|
#define PM4_LPF_DATA_H (0x52)
|
|
#define PM5_LPF_DATA_L (0x53)
|
|
#define PM5_LPF_DATA_H (0x54)
|
|
#define PM6_LPF_DATA_L (0x55)
|
|
#define PM6_LPF_DATA_H (0x56)
|
|
#define PM7_LPF_DATA_L (0x57)
|
|
#define PM7_LPF_DATA_H (0x58)
|
|
#define NTC0_DATA_L (0x59)
|
|
#define NTC0_DATA_H (0x5A)
|
|
#define NTC1_DATA_L (0x5B)
|
|
#define NTC1_DATA_H (0x5C)
|
|
#define ADC_CONFIG (0x5F)
|
|
|
|
#define NTC_DONE_MASK (0x02)
|
|
#define NTC_EN_MASK (0x06)
|
|
#define NTC_SMP_NUM_32 (0x05)
|
|
#define NTC_SMP_NUM_64 (0x06)
|
|
#define NTC_SMP_MASK (0x07)
|
|
#define NTC_MAX (0x02)
|
|
|
|
/* Power-meter setting */
|
|
#define NANO_MICRO (1000000) /* convert a nanometer into a micrometer for framework service */
|
|
#define MAX_ADC_OUTPUT (5)
|
|
#define BUCK_START (0x01)
|
|
#define BUCK_END (0x0A)
|
|
#define ADC_ENABLE (0x01)
|
|
#define ADC_DISABLE (0x00)
|
|
#define ADCEN_MASK (0x01)
|
|
#define ASYNC_RD (0x02)
|
|
|
|
#define ADC_B1 (0x01)
|
|
#define ADC_B2 (0x02)
|
|
#define ADC_B3 (0x03)
|
|
#define ADC_B4 (0x04)
|
|
#define ADC_B5 (0x05)
|
|
#define ADC_B6 (0x06)
|
|
#define ADC_B7 (0x07)
|
|
#define ADC_B8 (0x08)
|
|
#define ADC_B9 (0x09)
|
|
#define ADC_B10 (0x0A)
|
|
|
|
#define POWER_B1 (9155300) /* unit: nW */
|
|
#define POWER_B2 (18311000)
|
|
#define POWER_B3 (18311000)
|
|
#define POWER_B4 (18311000)
|
|
#define POWER_B5 (18311000)
|
|
#define POWER_B6 (9155300)
|
|
#define POWER_B7 (9155300)
|
|
#define POWER_B8 (27465800)
|
|
#define POWER_B9 (27465800)
|
|
#define POWER_B10 (27465800)
|
|
|
|
struct adc_info {
|
|
struct i2c_client *i2c;
|
|
struct mutex adc_lock;
|
|
uint64_t *power_val;
|
|
uint8_t *adc_reg;
|
|
uint8_t pmic_rev;
|
|
|
|
/* NTC irq */
|
|
int ntc_irq[NTC_MAX];
|
|
int ntc_cnt[NTC_MAX];
|
|
struct mutex irq_lock;
|
|
};
|
|
|
|
/* only use BUCK1~6 */
|
|
static const uint64_t power_coeffs[] =
|
|
{POWER_B1, POWER_B2, POWER_B3, POWER_B4, POWER_B5,
|
|
POWER_B6, POWER_B7, POWER_B8, POWER_B9, POWER_B10};
|
|
|
|
enum s2mps25_adc_ch {
|
|
ADC_CH0 = 0,
|
|
ADC_CH1,
|
|
ADC_CH2,
|
|
ADC_CH3,
|
|
ADC_CH4,
|
|
ADC_CH5,
|
|
ADC_CH6,
|
|
ADC_CH7,
|
|
};
|
|
|
|
static const uint8_t adc_channel_reg[] = {
|
|
[ADC_CH0] = ADC0_ACC0,
|
|
[ADC_CH1] = ADC1_ACC0,
|
|
[ADC_CH2] = ADC2_ACC0,
|
|
[ADC_CH3] = ADC3_ACC0,
|
|
[ADC_CH4] = ADC4_ACC0,
|
|
[ADC_CH5] = ADC5_ACC0,
|
|
[ADC_CH6] = ADC5_ACC0,
|
|
[ADC_CH7] = ADC5_ACC0,
|
|
};
|
|
|
|
static const uint8_t adc_mux_val[] = {
|
|
[ADC_B1] = 0x01,
|
|
[ADC_B2] = 0x02,
|
|
[ADC_B3] = 0x02,
|
|
[ADC_B4] = 0x04,
|
|
[ADC_B5] = 0x04,
|
|
[ADC_B6] = 0x06,
|
|
[ADC_B7] = 0x07,
|
|
[ADC_B8] = 0x08,
|
|
[ADC_B9] = 0x08,
|
|
[ADC_B10] = 0x08,
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC)
|
|
static int adc_assign_mux_channel(struct adc_info *adc_meter);
|
|
|
|
static uint64_t adc_get_count(struct adc_info *adc_meter)
|
|
{
|
|
struct i2c_client *i2c = adc_meter->i2c;
|
|
uint8_t acc_count_l = 0, acc_count_h = 0;
|
|
uint64_t count = 0;
|
|
int ret = 0;
|
|
|
|
ret = s2mps25_read_reg(i2c, ACC_COUNT_L, &acc_count_l);
|
|
if (ret)
|
|
pr_err("%s: failed to read register\n", __func__);
|
|
|
|
ret = s2mps25_read_reg(i2c, ACC_COUNT_H, &acc_count_h);
|
|
if (ret)
|
|
pr_err("%s: failed to read register\n", __func__);
|
|
|
|
count = (acc_count_h << 8) | (acc_count_l);
|
|
|
|
return count;
|
|
}
|
|
|
|
static uint64_t adc_get_acc(struct adc_info *adc_meter, int chan)
|
|
{
|
|
struct i2c_client *i2c = adc_meter->i2c;
|
|
uint8_t adc_acc[MAX_ADC_OUTPUT] = {0};
|
|
uint64_t acc = 0;
|
|
int ret = 0;
|
|
size_t i = 0;
|
|
|
|
for (i = 0; i < MAX_ADC_OUTPUT; i++) {
|
|
ret = s2mps25_read_reg(i2c, adc_channel_reg[chan] + i, adc_acc + i);
|
|
if (ret) {
|
|
pr_err("%s: failed to read register\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
|
|
acc = ((uint64_t)(adc_acc[4] & 0x3F) << 32) | (uint64_t)(adc_acc[3] << 24) |
|
|
(adc_acc[2] << 16) | (adc_acc[1] << 8) | (adc_acc[0]);
|
|
|
|
return acc;
|
|
}
|
|
|
|
static uint64_t adc_get_pout(struct adc_info *adc_meter, int chan)
|
|
{
|
|
uint64_t pout = 0, acc = 0, count = 0;
|
|
|
|
acc = adc_get_acc(adc_meter, chan);
|
|
count = adc_get_count(adc_meter);
|
|
|
|
/* Calculate power output */
|
|
pout = acc / count;
|
|
|
|
return pout;
|
|
}
|
|
|
|
static int adc_check_async(struct adc_info *adc_meter)
|
|
{
|
|
struct i2c_client *i2c = adc_meter->i2c;
|
|
int ret = 0;
|
|
uint8_t val = 0;
|
|
|
|
ret = s2mps25_update_reg(i2c, ADC_CTRL0, ASYNC_RD, ASYNC_RD);
|
|
if (ret)
|
|
goto err;
|
|
|
|
usleep_range(2000, 2100);
|
|
ret = s2mps25_read_reg(i2c, ADC_CTRL0, &val);
|
|
if (ret)
|
|
goto err;
|
|
|
|
pr_info("%s: check async clear(0x%02hhx)\n", __func__, val);
|
|
|
|
if (val & ASYNC_RD)
|
|
goto err;
|
|
|
|
return 0;
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
static int adc_read_data(struct adc_info *adc_meter, int channel)
|
|
{
|
|
size_t i = 0;
|
|
|
|
mutex_lock(&adc_meter->adc_lock);
|
|
|
|
if (adc_check_async(adc_meter) < 0) {
|
|
pr_err("%s: adc_check_async fail\n", __func__);
|
|
mutex_unlock(&adc_meter->adc_lock);
|
|
goto err;
|
|
}
|
|
|
|
if (channel < 0)
|
|
for (i = 0; i < ARRAY_SIZE(adc_channel_reg); i++)
|
|
adc_meter->power_val[i] = adc_get_pout(adc_meter, i);
|
|
else
|
|
adc_meter->power_val[channel] = adc_get_pout(adc_meter, channel);
|
|
|
|
mutex_unlock(&adc_meter->adc_lock);
|
|
|
|
return 0;
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
static uint64_t get_coeff_p(uint8_t adc_reg_num)
|
|
{
|
|
uint64_t coeff = 0;
|
|
|
|
coeff = power_coeffs[adc_reg_num - BUCK_START];
|
|
|
|
return coeff;
|
|
}
|
|
|
|
static ssize_t adc_val_power_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
size_t i = 0;
|
|
int cnt = 0, chan = 0;
|
|
|
|
for(i = 0; i < ARRAY_SIZE(adc_channel_reg); i++) {
|
|
if ((adc_meter->adc_reg[i] < BUCK_START) &&
|
|
(adc_meter->adc_reg[i] > BUCK_END))
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"Power-Meter supports only BUCK%d~%d\n",
|
|
BUCK_START, BUCK_END);
|
|
}
|
|
|
|
if (adc_read_data(adc_meter, -1) < 0)
|
|
goto err;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(adc_channel_reg); i++) {
|
|
chan = ADC_CH0 + i;
|
|
cnt += snprintf(buf + cnt, PAGE_SIZE, "CH%d[0x%02hhx]:%7llu uW (%7llu) ",
|
|
chan,
|
|
adc_meter->adc_reg[chan],
|
|
(adc_meter->power_val[chan] * get_coeff_p(adc_meter->adc_reg[chan])) / NANO_MICRO,
|
|
adc_meter->power_val[chan]);
|
|
|
|
if (i == ARRAY_SIZE(adc_channel_reg) / 2 - 1)
|
|
cnt += snprintf(buf + cnt, PAGE_SIZE, "\n");
|
|
}
|
|
cnt += snprintf(buf + cnt, PAGE_SIZE, "\n");
|
|
|
|
return cnt;
|
|
err:
|
|
return snprintf(buf, PAGE_SIZE, "Not clear ASYNC_RD\n");
|
|
}
|
|
|
|
static ssize_t adc_en_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
uint8_t val = 0;
|
|
|
|
ret = s2mps25_read_reg(adc_meter->i2c, ADC_CTRL1, &val);
|
|
if (ret)
|
|
pr_err("%s: failed to read register\n", __func__);
|
|
|
|
if (val & ADCEN_MASK)
|
|
return snprintf(buf, PAGE_SIZE, "ADC enable(0x%02hhx)\n", val);
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "ADC disable(0x%02hhx)\n", val);
|
|
}
|
|
|
|
static ssize_t adc_en_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
uint8_t temp = 0, val = 0;
|
|
|
|
ret = kstrtou8(buf, 16, &temp);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
if (temp == ADCEN_MASK)
|
|
val = temp;
|
|
|
|
ret = s2mps25_update_reg(adc_meter->i2c, ADC_CTRL1, val, ADCEN_MASK);
|
|
if (ret)
|
|
pr_err("%s: failed to update register\n", __func__);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int convert_adc_val(struct device *dev, char *buf, int channel)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
uint64_t coeff_p = get_coeff_p(adc_meter->adc_reg[channel]);
|
|
|
|
if (adc_read_data(adc_meter, channel) < 0)
|
|
return snprintf(buf, PAGE_SIZE, "Not clear ASYNC_RD\n");
|
|
|
|
return snprintf(buf, PAGE_SIZE, "CH%d[0x%02hhx]:%7llu uW (%7llu)\n",
|
|
channel, adc_meter->adc_reg[channel],
|
|
(adc_meter->power_val[channel] * coeff_p) / NANO_MICRO,
|
|
adc_meter->power_val[channel]);
|
|
}
|
|
|
|
static ssize_t adc_val_0_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return convert_adc_val(dev, buf, ADC_CH0);
|
|
}
|
|
|
|
static ssize_t adc_val_1_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return convert_adc_val(dev, buf, ADC_CH1);
|
|
}
|
|
|
|
static ssize_t adc_val_2_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return convert_adc_val(dev, buf, ADC_CH2);
|
|
}
|
|
|
|
static ssize_t adc_val_3_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return convert_adc_val(dev, buf, ADC_CH3);
|
|
}
|
|
|
|
static ssize_t adc_val_4_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return convert_adc_val(dev, buf, ADC_CH4);
|
|
}
|
|
|
|
static ssize_t adc_val_5_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return convert_adc_val(dev, buf, ADC_CH5);
|
|
}
|
|
|
|
static ssize_t adc_val_6_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return convert_adc_val(dev, buf, ADC_CH6);
|
|
}
|
|
|
|
static ssize_t adc_val_7_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return convert_adc_val(dev, buf, ADC_CH7);
|
|
}
|
|
|
|
static ssize_t adc_reg_0_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_reg[ADC_CH0]);
|
|
}
|
|
|
|
static ssize_t adc_reg_1_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_reg[ADC_CH1]);
|
|
}
|
|
|
|
static ssize_t adc_reg_2_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_reg[ADC_CH2]);
|
|
}
|
|
|
|
static ssize_t adc_reg_3_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_reg[ADC_CH3]);
|
|
}
|
|
|
|
static ssize_t adc_reg_4_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_reg[ADC_CH4]);
|
|
}
|
|
|
|
static ssize_t adc_reg_5_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_reg[ADC_CH5]);
|
|
}
|
|
|
|
static ssize_t adc_reg_6_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_reg[ADC_CH6]);
|
|
}
|
|
|
|
static ssize_t adc_reg_7_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_reg[ADC_CH7]);
|
|
}
|
|
|
|
static void adc_reg_update(struct adc_info *adc_meter)
|
|
{
|
|
if (s2mps25_adc_set_enable(adc_meter, ADC_DISABLE) < 0)
|
|
pr_err("%s: s2mps25_adc_set_enable fail\n", __func__);
|
|
|
|
if (adc_assign_mux_channel(adc_meter) < 0)
|
|
pr_err("%s: adc_assign_mux_channel fail\n", __func__);
|
|
|
|
if (s2mps25_adc_set_enable(adc_meter, ADC_ENABLE) < 0)
|
|
pr_err("%s: s2mps25_adc_set_enable fail\n", __func__);
|
|
}
|
|
|
|
static uint8_t buf_to_adc_reg(const char *buf)
|
|
{
|
|
uint8_t adc_reg_num = 0;
|
|
|
|
if (kstrtou8(buf, 16, &adc_reg_num))
|
|
return 0;
|
|
|
|
if (adc_reg_num >= BUCK_START && adc_reg_num <= BUCK_END)
|
|
return adc_reg_num;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t assign_adc_reg(struct device *dev, const char *buf,
|
|
size_t count, int channel)
|
|
{
|
|
struct adc_info *adc_meter = dev_get_drvdata(dev);
|
|
|
|
uint8_t adc_reg_num = buf_to_adc_reg(buf);
|
|
if (!adc_reg_num)
|
|
return -EINVAL;
|
|
else {
|
|
adc_meter->adc_reg[channel] = adc_reg_num;
|
|
adc_reg_update(adc_meter);
|
|
return count;
|
|
}
|
|
}
|
|
|
|
static ssize_t adc_reg_0_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
return assign_adc_reg(dev, buf, count, ADC_CH0);
|
|
}
|
|
|
|
static ssize_t adc_reg_1_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
return assign_adc_reg(dev, buf, count, ADC_CH1);
|
|
}
|
|
|
|
static ssize_t adc_reg_2_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
return assign_adc_reg(dev, buf, count, ADC_CH2);
|
|
}
|
|
|
|
static ssize_t adc_reg_3_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
return assign_adc_reg(dev, buf, count, ADC_CH3);
|
|
}
|
|
|
|
static ssize_t adc_reg_4_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
return assign_adc_reg(dev, buf, count, ADC_CH4);
|
|
}
|
|
static ssize_t adc_reg_5_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
return assign_adc_reg(dev, buf, count, ADC_CH5);
|
|
}
|
|
static ssize_t adc_reg_6_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
return assign_adc_reg(dev, buf, count, ADC_CH6);
|
|
}
|
|
static ssize_t adc_reg_7_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
return assign_adc_reg(dev, buf, count, ADC_CH7);
|
|
}
|
|
|
|
static struct pmic_device_attribute powermeter_attr[] = {
|
|
PMIC_ATTR(power_val_all, S_IRUGO, adc_val_power_show, NULL),
|
|
PMIC_ATTR(adc_en, S_IRUGO | S_IWUSR, adc_en_show, adc_en_store),
|
|
PMIC_ATTR(adc_val_0, S_IRUGO, adc_val_0_show, NULL),
|
|
PMIC_ATTR(adc_val_1, S_IRUGO, adc_val_1_show, NULL),
|
|
PMIC_ATTR(adc_val_2, S_IRUGO, adc_val_2_show, NULL),
|
|
PMIC_ATTR(adc_val_3, S_IRUGO, adc_val_3_show, NULL),
|
|
PMIC_ATTR(adc_val_4, S_IRUGO, adc_val_4_show, NULL),
|
|
PMIC_ATTR(adc_val_5, S_IRUGO, adc_val_5_show, NULL),
|
|
PMIC_ATTR(adc_val_6, S_IRUGO, adc_val_6_show, NULL),
|
|
PMIC_ATTR(adc_val_7, S_IRUGO, adc_val_7_show, NULL),
|
|
PMIC_ATTR(adc_reg_0, S_IRUGO | S_IWUSR, adc_reg_0_show, adc_reg_0_store),
|
|
PMIC_ATTR(adc_reg_1, S_IRUGO | S_IWUSR, adc_reg_1_show, adc_reg_1_store),
|
|
PMIC_ATTR(adc_reg_2, S_IRUGO | S_IWUSR, adc_reg_2_show, adc_reg_2_store),
|
|
PMIC_ATTR(adc_reg_3, S_IRUGO | S_IWUSR, adc_reg_3_show, adc_reg_3_store),
|
|
PMIC_ATTR(adc_reg_4, S_IRUGO | S_IWUSR, adc_reg_4_show, adc_reg_4_store),
|
|
PMIC_ATTR(adc_reg_5, S_IRUGO | S_IWUSR, adc_reg_5_show, adc_reg_5_store),
|
|
PMIC_ATTR(adc_reg_6, S_IRUGO | S_IWUSR, adc_reg_6_show, adc_reg_6_store),
|
|
PMIC_ATTR(adc_reg_7, S_IRUGO | S_IWUSR, adc_reg_7_show, adc_reg_7_store),
|
|
};
|
|
|
|
static int s2mps25_create_powermeter_sysfs(struct s2mps25_dev *s2mps25)
|
|
{
|
|
struct device *s2mps25_adc_dev;
|
|
struct device *dev = s2mps25->dev;
|
|
char device_name[32] = {0, };
|
|
int err = -ENODEV, i = 0;
|
|
|
|
pr_info("%s()\n", __func__);
|
|
|
|
/* Dynamic allocation for device name */
|
|
snprintf(device_name, sizeof(device_name) - 1, "%s-powermeter@%s",
|
|
dev_driver_string(dev), dev_name(dev));
|
|
|
|
s2mps25_adc_dev = pmic_device_create(s2mps25->adc_meter, device_name);
|
|
s2mps25->powermeter_dev = s2mps25_adc_dev;
|
|
|
|
/* Create sysfs entries */
|
|
for (i = 0; i < ARRAY_SIZE(powermeter_attr); i++) {
|
|
err = device_create_file(s2mps25_adc_dev, &powermeter_attr[i].dev_attr);
|
|
if (err)
|
|
goto remove_pmic_device;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_PM)
|
|
if (!IS_ERR_OR_NULL(s2mps25->ap_pmic_dev)) {
|
|
err = sysfs_create_link(&s2mps25->ap_pmic_dev->kobj,
|
|
&s2mps25_adc_dev->kobj, "power_meter");
|
|
if (err)
|
|
pr_err("%s: fail to create link for power_meter(%d)\n",
|
|
__func__, err);
|
|
}
|
|
#endif /* CONFIG_SEC_PM */
|
|
|
|
return 0;
|
|
|
|
remove_pmic_device:
|
|
for (i--; i >= 0; i--)
|
|
device_remove_file(s2mps25_adc_dev, &powermeter_attr[i].dev_attr);
|
|
pmic_device_destroy(s2mps25_adc_dev->devt);
|
|
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int s2mps25_adc_set_enable(struct adc_info *adc_meter, int en)
|
|
{
|
|
int ret = 0;
|
|
uint8_t val = 0;
|
|
|
|
if (en)
|
|
val = ADC_ENABLE;
|
|
else
|
|
val = ADC_DISABLE;
|
|
|
|
/* [W/A][EVT0] power-meter force disable */
|
|
if ((adc_meter->pmic_rev & 0x0F) == 0x00) {
|
|
pr_err("%s: power-meter force disable !!! pmic_rev(%#x)\n", __func__, adc_meter->pmic_rev);
|
|
val = ADC_DISABLE;
|
|
}
|
|
|
|
ret = s2mps25_update_reg(adc_meter->i2c, ADC_CTRL1, val, ADCEN_MASK);
|
|
if (ret)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(s2mps25_adc_set_enable);
|
|
|
|
static int adc_assign_mux_channel(struct adc_info *adc_meter)
|
|
{
|
|
int ret = 0, count = 0;
|
|
ssize_t i = 0;
|
|
|
|
for (i = MUX0SEL; i <= MUX5SEL; i++) {
|
|
uint8_t mux_val = adc_mux_val[adc_meter->adc_reg[count++]];
|
|
|
|
ret = s2mps25_write_reg(adc_meter->i2c, i, mux_val);
|
|
if (ret) {
|
|
pr_err("%s: failed to write register\n", __func__);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
pr_info("%s: Done\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adc_set_channel(struct adc_info *adc_meter)
|
|
{
|
|
/* Assign BUCKs for MUX channel */
|
|
adc_meter->adc_reg[ADC_CH0] = ADC_B1;
|
|
adc_meter->adc_reg[ADC_CH1] = ADC_B2;
|
|
adc_meter->adc_reg[ADC_CH2] = ADC_B4;
|
|
adc_meter->adc_reg[ADC_CH3] = ADC_B6;
|
|
adc_meter->adc_reg[ADC_CH4] = ADC_B7;
|
|
adc_meter->adc_reg[ADC_CH5] = ADC_B8;
|
|
adc_meter->adc_reg[ADC_CH6] = ADC_B8;
|
|
adc_meter->adc_reg[ADC_CH7] = ADC_B8;
|
|
|
|
if (adc_assign_mux_channel(adc_meter) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adc_init_hw(struct adc_info *adc_meter)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Enable ACC power mode */
|
|
ret = s2mps25_write_reg(adc_meter->i2c, ADC_CTRL3, 0x3F);
|
|
if (ret)
|
|
goto err;
|
|
|
|
/* EVT1 Power-meter sampling delay option */
|
|
if (adc_meter->pmic_rev) {
|
|
ret = s2mps25_update_reg(adc_meter->i2c, ADC_CTRL0, 0x0C, 0x0C);
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
int s2mps25_adc_ntc_init(struct adc_info *adc_meter)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* ADC configuration */
|
|
ret = s2mps25_write_reg(adc_meter->i2c, ADC_CONFIG, 0x0B);
|
|
if (ret)
|
|
goto err;
|
|
|
|
/* PM period, 1.038ms, minimum is '1' */
|
|
ret = s2mps25_write_reg(adc_meter->i2c, PERIOD_CTRL0, 0x11);
|
|
if (ret)
|
|
goto err;
|
|
|
|
/* NTC period, 10.01ms, minimum is '1' */
|
|
ret = s2mps25_write_reg(adc_meter->i2c, PERIOD_CTRL1, 0xA4);
|
|
if (ret)
|
|
goto err;
|
|
|
|
/* NTC SMP_NUM */
|
|
ret = s2mps25_update_reg(adc_meter->i2c, ADC_CTRL2, NTC_SMP_NUM_32, NTC_SMP_MASK);
|
|
if (ret)
|
|
goto err;
|
|
|
|
/* NTC mux selection */
|
|
ret = s2mps25_write_reg(adc_meter->i2c, MUX8SEL, 0xED);
|
|
if (ret)
|
|
goto err;
|
|
|
|
/* CHUB0 threshold & hys. Int. is occurred on 75 degree, disappeared on 70 degree */
|
|
ret = s2mps25_write_reg(adc_meter->i2c, TH_CTRL0, 0xCB);
|
|
if (ret)
|
|
goto err;
|
|
ret = s2mps25_write_reg(adc_meter->i2c, TH_CTRL1, 0x51);
|
|
if (ret)
|
|
goto err;
|
|
/* CHUB1 threshold & hys. Int. is occurred on 75 degree, disappeared on 70 degree */
|
|
ret = s2mps25_write_reg(adc_meter->i2c, TH_CTRL2, 0xCB);
|
|
if (ret)
|
|
goto err;
|
|
ret = s2mps25_write_reg(adc_meter->i2c, TH_CTRL3, 0x51);
|
|
if (ret)
|
|
goto err;
|
|
|
|
return ret;
|
|
err:
|
|
pr_err("%s: failed to register\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
static irqreturn_t s2mps25_ntc_irq(int irq, void *data)
|
|
{
|
|
struct adc_info *adc_meter = data;
|
|
|
|
mutex_lock(&adc_meter->irq_lock);
|
|
|
|
if (adc_meter->ntc_irq[0] == irq) {
|
|
adc_meter->ntc_cnt[0]++;
|
|
pr_info("[PMIC] %s: NTC0_OVER IRQ: %d, %d\n", __func__, adc_meter->ntc_cnt[0], irq);
|
|
} else if (adc_meter->ntc_irq[1] == irq) {
|
|
adc_meter->ntc_cnt[1]++;
|
|
pr_info("[PMIC] %s: NTC1_OVER IRQ: %d, %d\n", __func__, adc_meter->ntc_cnt[1], irq);
|
|
}
|
|
|
|
mutex_unlock(&adc_meter->irq_lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int s2mps25_adc_set_interrupt(struct s2mps25_dev *s2mps25, struct adc_info *adc_meter)
|
|
{
|
|
int i, ret = 0;
|
|
static char ntc_name[NTC_MAX][32] = {0, };
|
|
|
|
/* [W/A][EVT0] NTC force disable */
|
|
if ((adc_meter->pmic_rev & 0x0F) == 0x00) {
|
|
pr_err("%s: NTC force disable !!! pmic_rev(%#x)\n", __func__, adc_meter->pmic_rev);
|
|
return 0;
|
|
}
|
|
|
|
/* Set NTC0 ~ NTC1_EN */
|
|
ret = s2mps25_update_reg(adc_meter->i2c, ADC_CTRL1, NTC_EN_MASK, NTC_EN_MASK);
|
|
if (ret)
|
|
goto err;
|
|
|
|
for (i = 0; i < NTC_MAX; i++) {
|
|
adc_meter->ntc_irq[i] = s2mps25->irq_base +
|
|
S2MPS25_ADC_IRQ_NTC0_OVER_INTP + i;
|
|
|
|
/* Dynamic allocation for device name */
|
|
snprintf(ntc_name[i], sizeof(ntc_name[i]) - 1, "%s-NTC-IRQ%d@%s",
|
|
dev_driver_string(s2mps25->dev), i, dev_name(s2mps25->dev));
|
|
|
|
/* Set NTC IRQ */
|
|
ret = devm_request_threaded_irq(s2mps25->dev, adc_meter->ntc_irq[i], NULL,
|
|
s2mps25_ntc_irq, 0, ntc_name[i], adc_meter);
|
|
if (ret < 0) {
|
|
dev_err(s2mps25->dev, "%s: Failed to request NTC IRQ: %d: %d\n",
|
|
__func__, adc_meter->ntc_irq[i], ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
err:
|
|
pr_err("%s: failed to set interrupts\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
void s2mps25_powermeter_init(struct s2mps25_dev *s2mps25)
|
|
{
|
|
struct adc_info *adc_meter;
|
|
size_t size;
|
|
|
|
pr_info("[PMIC] %s: start\n", __func__);
|
|
|
|
size = sizeof(struct adc_info);
|
|
adc_meter = devm_kzalloc(s2mps25->dev, size, GFP_KERNEL);
|
|
if (!adc_meter) {
|
|
pr_err("%s: adc_meter alloc fail.\n", __func__);
|
|
return;
|
|
}
|
|
|
|
size = sizeof(uint64_t) * ARRAY_SIZE(adc_channel_reg);
|
|
adc_meter->power_val = devm_kzalloc(s2mps25->dev, size, GFP_KERNEL);
|
|
|
|
size = sizeof(uint8_t) * ARRAY_SIZE(adc_channel_reg);
|
|
adc_meter->adc_reg = devm_kzalloc(s2mps25->dev, size, GFP_KERNEL);
|
|
|
|
adc_meter->pmic_rev = s2mps25->pmic_rev;
|
|
adc_meter->i2c = s2mps25->adc_i2c;
|
|
mutex_init(&adc_meter->adc_lock);
|
|
mutex_init(&adc_meter->irq_lock);
|
|
s2mps25->adc_meter = adc_meter;
|
|
|
|
if (adc_set_channel(adc_meter) < 0) {
|
|
pr_err("%s: adc_set_channel fail\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (adc_init_hw(adc_meter) < 0) {
|
|
pr_err("%s: adc_init_hw fail\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (s2mps25_adc_set_enable(adc_meter, ADC_ENABLE) < 0) {
|
|
pr_err("%s: s2mps25_adc_set_enable fail\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (s2mps25_adc_ntc_init(adc_meter) < 0) {
|
|
pr_err("%s: s2mps25_adc_ntc_init fail\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (s2mps25_adc_set_interrupt(s2mps25, adc_meter) < 0) {
|
|
pr_err("%s: s2mps25_adc_set_interrupt fail\n", __func__);
|
|
return;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC)
|
|
if (s2mps25_create_powermeter_sysfs(s2mps25) < 0) {
|
|
pr_info("%s: failed to create sysfs\n", __func__);
|
|
return;
|
|
}
|
|
#endif
|
|
pr_info("[PMIC] %s: end\n", __func__);
|
|
}
|
|
EXPORT_SYMBOL_GPL(s2mps25_powermeter_init);
|
|
|
|
void s2mps25_powermeter_deinit(struct s2mps25_dev *s2mps25)
|
|
{
|
|
#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC)
|
|
struct device *s2mps25_adc_dev = s2mps25->powermeter_dev;
|
|
int i = 0;
|
|
|
|
/* Remove sysfs entries */
|
|
for (i = 0; i < ARRAY_SIZE(powermeter_attr); i++)
|
|
device_remove_file(s2mps25_adc_dev, &powermeter_attr[i].dev_attr);
|
|
pmic_device_destroy(s2mps25_adc_dev->devt);
|
|
#endif
|
|
/* ADC turned off */
|
|
s2mps25_adc_set_enable(s2mps25->adc_meter, ADC_DISABLE);
|
|
}
|
|
EXPORT_SYMBOL(s2mps25_powermeter_deinit);
|