619 lines
13 KiB
C
619 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/alarmtimer.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <uapi/linux/qg.h>
|
|
#include "qg-sdam.h"
|
|
#include "qg-core.h"
|
|
#include "qg-reg.h"
|
|
#include "qg-defs.h"
|
|
#include "qg-iio.h"
|
|
#include "qg-util.h"
|
|
|
|
static inline bool is_sticky_register(u32 addr)
|
|
{
|
|
if ((addr & 0xFF) == QG_STATUS2_REG)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int qg_read(struct qpnp_qg *chip, u32 addr, u8 *val, int len)
|
|
{
|
|
int rc, i;
|
|
u32 dummy = 0;
|
|
|
|
rc = regmap_bulk_read(chip->regmap, addr, val, len);
|
|
if (rc < 0) {
|
|
pr_err("Failed regmap_read for address %04x rc=%d\n", addr, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (is_sticky_register(addr)) {
|
|
/* write to the sticky register to clear it */
|
|
rc = regmap_write(chip->regmap, addr, dummy);
|
|
if (rc < 0) {
|
|
pr_err("Failed regmap_write for %04x rc=%d\n",
|
|
addr, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (*chip->debug_mask & QG_DEBUG_BUS_READ) {
|
|
pr_info("length %d addr=%04x\n", len, addr);
|
|
for (i = 0; i < len; i++)
|
|
pr_info("val[%d]: %02x\n", i, val[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qg_write(struct qpnp_qg *chip, u32 addr, u8 *val, int len)
|
|
{
|
|
int rc, i;
|
|
|
|
mutex_lock(&chip->bus_lock);
|
|
|
|
if (len > 1)
|
|
rc = regmap_bulk_write(chip->regmap, addr, val, len);
|
|
else
|
|
rc = regmap_write(chip->regmap, addr, *val);
|
|
|
|
if (rc < 0) {
|
|
pr_err("Failed regmap_write for address %04x rc=%d\n",
|
|
addr, rc);
|
|
goto out;
|
|
}
|
|
|
|
if (*chip->debug_mask & QG_DEBUG_BUS_WRITE) {
|
|
pr_info("length %d addr=%04x\n", len, addr);
|
|
for (i = 0; i < len; i++)
|
|
pr_info("val[%d]: %02x\n", i, val[i]);
|
|
}
|
|
out:
|
|
mutex_unlock(&chip->bus_lock);
|
|
return rc;
|
|
}
|
|
|
|
int qg_masked_write(struct qpnp_qg *chip, int addr, u32 mask, u32 val)
|
|
{
|
|
int rc;
|
|
|
|
mutex_lock(&chip->bus_lock);
|
|
|
|
rc = regmap_update_bits(chip->regmap, addr, mask, val);
|
|
if (rc < 0) {
|
|
pr_err("Failed regmap_update_bits for address %04x rc=%d\n",
|
|
addr, rc);
|
|
goto out;
|
|
}
|
|
|
|
if (*chip->debug_mask & QG_DEBUG_BUS_WRITE)
|
|
pr_info("addr=%04x mask: %02x val: %02x\n", addr, mask, val);
|
|
|
|
out:
|
|
mutex_unlock(&chip->bus_lock);
|
|
return rc;
|
|
}
|
|
|
|
int qg_read_raw_data(struct qpnp_qg *chip, int addr, u32 *data)
|
|
{
|
|
int rc;
|
|
u8 reg[2] = {0};
|
|
|
|
rc = qg_read(chip, chip->qg_base + addr, ®[0], 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read QG addr %d rc=%d\n", addr, rc);
|
|
return rc;
|
|
}
|
|
|
|
*data = reg[0] | (reg[1] << 8);
|
|
|
|
return rc;
|
|
}
|
|
|
|
s64 qg_iraw_to_ua(struct qpnp_qg *chip, int iraw)
|
|
{
|
|
if (chip->qg_subtype == QG_ADC_IBAT_5A)
|
|
return div_s64(152588LL * (s64)iraw, 1000);
|
|
else
|
|
return div_s64(305176LL * (s64)iraw, 1000);
|
|
}
|
|
|
|
int get_fifo_length(struct qpnp_qg *chip, u32 *fifo_length, bool rt)
|
|
{
|
|
int rc;
|
|
u8 reg = 0;
|
|
u32 addr;
|
|
|
|
addr = rt ? QG_STATUS3_REG : QG_S2_NORMAL_MEAS_CTL2_REG;
|
|
rc = qg_read(chip, chip->qg_base + addr, ®, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read FIFO length rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (rt) {
|
|
*fifo_length = reg & COUNT_FIFO_RT_MASK;
|
|
} else {
|
|
*fifo_length = (reg & FIFO_LENGTH_MASK) >> FIFO_LENGTH_SHIFT;
|
|
*fifo_length += 1;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int get_sample_count(struct qpnp_qg *chip, u32 *sample_count)
|
|
{
|
|
int rc;
|
|
u8 reg = 0;
|
|
|
|
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG,
|
|
®, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read FIFO sample count rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
*sample_count = 1 << ((reg & NUM_OF_ACCUM_MASK) + 1);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define QG_CLK_RATE 32000
|
|
#define QG_ACTUAL_CLK_RATE 32764
|
|
int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval)
|
|
{
|
|
int rc;
|
|
u8 reg = 0;
|
|
|
|
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL3_REG,
|
|
®, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read FIFO sample interval rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
*sample_interval = reg * 10;
|
|
|
|
if (chip->wa_flags & QG_CLK_ADJUST_WA) {
|
|
*sample_interval = DIV_ROUND_CLOSEST(
|
|
*sample_interval * QG_CLK_RATE, QG_ACTUAL_CLK_RATE);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int get_rtc_time(unsigned long *rtc_time)
|
|
{
|
|
struct rtc_time tm;
|
|
struct rtc_device *rtc;
|
|
int rc;
|
|
|
|
rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
|
|
if (rtc == NULL) {
|
|
pr_err("Failed to open rtc device (%s)\n",
|
|
CONFIG_RTC_HCTOSYS_DEVICE);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = rtc_read_time(rtc, &tm);
|
|
if (rc) {
|
|
pr_err("Failed to read rtc time (%s) : %d\n",
|
|
CONFIG_RTC_HCTOSYS_DEVICE, rc);
|
|
goto close_time;
|
|
}
|
|
|
|
rc = rtc_valid_tm(&tm);
|
|
if (rc) {
|
|
pr_err("Invalid RTC time (%s): %d\n",
|
|
CONFIG_RTC_HCTOSYS_DEVICE, rc);
|
|
goto close_time;
|
|
}
|
|
|
|
*rtc_time = rtc_tm_to_time64(&tm);
|
|
|
|
close_time:
|
|
rtc_class_close(rtc);
|
|
return rc;
|
|
}
|
|
|
|
int get_fifo_done_time(struct qpnp_qg *chip, bool rt, int *time_ms)
|
|
{
|
|
int rc, length = 0;
|
|
u32 sample_count = 0, sample_interval = 0, acc_count = 0;
|
|
|
|
rc = get_fifo_length(chip, &length, rt ? true : false);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = get_sample_count(chip, &sample_count);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = get_sample_interval(chip, &sample_interval);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
*time_ms = length * sample_count * sample_interval;
|
|
|
|
if (rt) {
|
|
rc = qg_read(chip, chip->qg_base + QG_ACCUM_CNT_RT_REG,
|
|
(u8 *)&acc_count, 1);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
*time_ms += ((sample_count - acc_count) * sample_interval);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_usb_available(struct qpnp_qg *chip)
|
|
{
|
|
if (chip->usb_psy)
|
|
return true;
|
|
|
|
chip->usb_psy = power_supply_get_by_name("usb");
|
|
if (!chip->usb_psy)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool is_dc_available(struct qpnp_qg *chip)
|
|
{
|
|
if (chip->dc_psy)
|
|
return true;
|
|
|
|
chip->dc_psy = power_supply_get_by_name("dc");
|
|
if (!chip->dc_psy)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool is_usb_present(struct qpnp_qg *chip)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
|
|
if (is_usb_available(chip))
|
|
power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_PRESENT, &pval);
|
|
|
|
return pval.intval ? true : false;
|
|
}
|
|
|
|
bool is_dc_present(struct qpnp_qg *chip)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
|
|
if (is_dc_available(chip))
|
|
power_supply_get_property(chip->dc_psy,
|
|
POWER_SUPPLY_PROP_PRESENT, &pval);
|
|
|
|
return pval.intval ? true : false;
|
|
}
|
|
|
|
bool is_input_present(struct qpnp_qg *chip)
|
|
{
|
|
return is_usb_present(chip) || is_dc_present(chip);
|
|
}
|
|
|
|
bool is_parallel_available(struct qpnp_qg *chip)
|
|
{
|
|
if (is_chan_valid(chip, PARALLEL_CHARGING_ENABLED))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool is_cp_available(struct qpnp_qg *chip)
|
|
{
|
|
if (chip->cp_psy)
|
|
return true;
|
|
|
|
chip->cp_psy = power_supply_get_by_name("charge_pump_master");
|
|
if (!chip->cp_psy)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool is_parallel_enabled(struct qpnp_qg *chip)
|
|
{
|
|
int val = 0;
|
|
|
|
if (is_parallel_available(chip))
|
|
qg_read_iio_chan(chip, PARALLEL_CHARGING_ENABLED, &val);
|
|
else if (is_cp_available(chip))
|
|
qg_read_iio_chan(chip, CP_CHARGING_ENABLED, &val);
|
|
|
|
return val ? true : false;
|
|
}
|
|
|
|
int qg_write_monotonic_soc(struct qpnp_qg *chip, int msoc)
|
|
{
|
|
u8 reg = 0;
|
|
int rc;
|
|
|
|
reg = (msoc * 255) / 100;
|
|
rc = qg_write(chip, chip->qg_base + QG_SOC_MONOTONIC_REG,
|
|
®, 1);
|
|
if (rc < 0)
|
|
pr_err("Failed to update QG_SOC_MONOTINIC reg rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int qg_get_battery_temp(struct qpnp_qg *chip, int *temp)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (chip->battery_missing) {
|
|
*temp = 250;
|
|
return 0;
|
|
}
|
|
|
|
rc = iio_read_channel_processed(chip->batt_therm_chan, temp);
|
|
if (rc < 0) {
|
|
pr_err("Failed reading BAT_TEMP over ADC rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
pr_debug("batt_temp = %d\n", *temp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qg_get_battery_current(struct qpnp_qg *chip, int *ibat_ua)
|
|
{
|
|
int rc = 0, last_ibat = 0;
|
|
|
|
if (chip->battery_missing) {
|
|
*ibat_ua = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (chip->qg_mode == QG_V_MODE) {
|
|
*ibat_ua = chip->qg_v_ibat;
|
|
return 0;
|
|
}
|
|
|
|
/* hold data */
|
|
rc = qg_masked_write(chip, chip->qg_base + QG_DATA_CTL2_REG,
|
|
BURST_AVG_HOLD_FOR_READ_BIT,
|
|
BURST_AVG_HOLD_FOR_READ_BIT);
|
|
if (rc < 0) {
|
|
pr_err("Failed to hold burst-avg data rc=%d\n", rc);
|
|
goto release;
|
|
}
|
|
|
|
rc = qg_read(chip, chip->qg_base + QG_LAST_BURST_AVG_I_DATA0_REG,
|
|
(u8 *)&last_ibat, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read LAST_BURST_AVG_I reg, rc=%d\n", rc);
|
|
goto release;
|
|
}
|
|
|
|
last_ibat = sign_extend32(last_ibat, 15);
|
|
*ibat_ua = qg_iraw_to_ua(chip, last_ibat);
|
|
|
|
release:
|
|
/* release */
|
|
qg_masked_write(chip, chip->qg_base + QG_DATA_CTL2_REG,
|
|
BURST_AVG_HOLD_FOR_READ_BIT, 0);
|
|
return rc;
|
|
}
|
|
|
|
int qg_get_battery_voltage(struct qpnp_qg *chip, int *vbat_uv)
|
|
{
|
|
int rc = 0;
|
|
u64 last_vbat = 0;
|
|
|
|
if (chip->battery_missing) {
|
|
*vbat_uv = 3700000;
|
|
return 0;
|
|
}
|
|
|
|
rc = qg_read(chip, chip->qg_base + QG_LAST_ADC_V_DATA0_REG,
|
|
(u8 *)&last_vbat, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read LAST_ADV_V reg, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
*vbat_uv = V_RAW_TO_UV(last_vbat);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int qg_get_vbat_avg(struct qpnp_qg *chip, int *vbat_uv)
|
|
{
|
|
int rc = 0;
|
|
u64 last_vbat = 0;
|
|
|
|
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_AVG_V_DATA0_REG,
|
|
(u8 *)&last_vbat, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read S2_NORMAL_AVG_V reg, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
*vbat_uv = V_RAW_TO_UV(last_vbat);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qg_get_ibat_avg(struct qpnp_qg *chip, int *ibat_ua)
|
|
{
|
|
int rc = 0;
|
|
int last_ibat = 0;
|
|
|
|
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_AVG_I_DATA0_REG,
|
|
(u8 *)&last_ibat, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read S2_NORMAL_AVG_I reg, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (last_ibat == FIFO_I_RESET_VAL) {
|
|
/* First FIFO is not complete, read instantaneous IBAT */
|
|
rc = qg_get_battery_current(chip, ibat_ua);
|
|
if (rc < 0)
|
|
pr_err("Failed to read inst. IBAT rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
last_ibat = sign_extend32(last_ibat, 15);
|
|
*ibat_ua = qg_iraw_to_ua(chip, last_ibat);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool is_chan_valid(struct qpnp_qg *chip,
|
|
enum qg_ext_iio_channels chan)
|
|
{
|
|
int rc;
|
|
|
|
if (IS_ERR(chip->ext_iio_chans[chan]))
|
|
return false;
|
|
|
|
if (!chip->ext_iio_chans[chan]) {
|
|
chip->ext_iio_chans[chan] = iio_channel_get(chip->dev,
|
|
qg_ext_iio_chan_name[chan]);
|
|
if (IS_ERR(chip->ext_iio_chans[chan])) {
|
|
rc = PTR_ERR(chip->ext_iio_chans[chan]);
|
|
if (rc == -EPROBE_DEFER)
|
|
chip->ext_iio_chans[chan] = NULL;
|
|
|
|
pr_err("Failed to get IIO channel %s, rc=%d\n",
|
|
qg_ext_iio_chan_name[chan], rc);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int qg_read_iio_chan(struct qpnp_qg *chip,
|
|
enum qg_ext_iio_channels chan, int *val)
|
|
{
|
|
int rc;
|
|
|
|
if (is_chan_valid(chip, chan)) {
|
|
rc = iio_read_channel_processed(
|
|
chip->ext_iio_chans[chan], val);
|
|
return (rc < 0) ? rc : 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int qg_write_iio_chan(struct qpnp_qg *chip,
|
|
enum qg_ext_iio_channels chan, int val)
|
|
{
|
|
if (is_chan_valid(chip, chan))
|
|
return iio_write_channel_raw(chip->ext_iio_chans[chan],
|
|
val);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int qg_read_int_iio_chan(struct iio_channel *iio_chan_list, int chan_id,
|
|
int *val)
|
|
{
|
|
int rc;
|
|
|
|
do {
|
|
if (iio_chan_list->channel->channel == chan_id) {
|
|
rc = iio_read_channel_processed(iio_chan_list,
|
|
val);
|
|
return (rc < 0) ? rc : 0;
|
|
}
|
|
} while (iio_chan_list++);
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
int qg_read_range_data_from_node(struct device_node *node,
|
|
const char *prop_str, struct range_data *ranges,
|
|
int max_threshold, u32 max_value)
|
|
{
|
|
int rc = 0, i, length, per_tuple_length, tuples;
|
|
|
|
if (!node || !prop_str || !ranges) {
|
|
pr_err("Invalid parameters passed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_count_elems_of_size(node, prop_str, sizeof(u32));
|
|
if (rc < 0) {
|
|
pr_err("Count %s failed, rc=%d\n", prop_str, rc);
|
|
return rc;
|
|
}
|
|
|
|
length = rc;
|
|
per_tuple_length = sizeof(struct range_data) / sizeof(u32);
|
|
if (length % per_tuple_length) {
|
|
pr_err("%s length (%d) should be multiple of %d\n",
|
|
prop_str, length, per_tuple_length);
|
|
return -EINVAL;
|
|
}
|
|
tuples = length / per_tuple_length;
|
|
|
|
if (tuples > MAX_STEP_CHG_ENTRIES) {
|
|
pr_err("too many entries(%d), only %d allowed\n",
|
|
tuples, MAX_STEP_CHG_ENTRIES);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_read_u32_array(node, prop_str,
|
|
(u32 *)ranges, length);
|
|
if (rc) {
|
|
pr_err("Read %s failed, rc=%d\n", prop_str, rc);
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < tuples; i++) {
|
|
if (ranges[i].low_threshold >
|
|
ranges[i].high_threshold) {
|
|
pr_err("%s thresholds should be in ascendant ranges\n",
|
|
prop_str);
|
|
rc = -EINVAL;
|
|
goto clean;
|
|
}
|
|
|
|
if (i != 0) {
|
|
if (ranges[i - 1].high_threshold >
|
|
ranges[i].low_threshold) {
|
|
pr_err("%s thresholds should be in ascendant ranges\n",
|
|
prop_str);
|
|
rc = -EINVAL;
|
|
goto clean;
|
|
}
|
|
}
|
|
|
|
if (ranges[i].low_threshold > max_threshold)
|
|
ranges[i].low_threshold = max_threshold;
|
|
if (ranges[i].high_threshold > max_threshold)
|
|
ranges[i].high_threshold = max_threshold;
|
|
if (ranges[i].value > max_value)
|
|
ranges[i].value = max_value;
|
|
}
|
|
|
|
return rc;
|
|
clean:
|
|
memset(ranges, 0, tuples * sizeof(struct range_data));
|
|
return rc;
|
|
}
|