blob: c066d9635bf8b48f7ad675f393e2ffe2310d0e5c [file] [log] [blame]
/*****************************************************************************
* Copyright 2001 - 2013 Broadcom Corporation. All rights reserved.
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2, available at
* http://www.gnu.org/licenses/old-license/gpl-2.0.html (the "GPL").
*
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a
* license other than the GPL, without Broadcom's express prior written
* consent.
*
*****************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/power_supply.h>
#include <linux/ktime.h>
#include <linux/sort.h>
#include <linux/wakelock.h>
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#endif
#include <linux/power/bcmpmu59xxx-thermal-throttle.h>
#include <linux/mfd/bcmpmu59xxx.h>
#include <linux/power/bcmpmu-fg.h>
#include <linux/mfd/bcmpmu59xxx_reg.h>
#ifdef DEBUG
#define DEBUG_MASK (BCMPMU_PRINT_ERROR | BCMPMU_PRINT_INIT | BCMPMU_PRINT_FLOW)
#else
#define DEBUG_MASK (BCMPMU_PRINT_ERROR | BCMPMU_PRINT_INIT)
#endif
static u32 debug_mask = DEBUG_MASK;
#define pr_throttle(debug_level, args...) \
do { \
if (debug_mask & BCMPMU_PRINT_##debug_level) { \
pr_info("[THROTTLE]:"args); \
} \
} while (0)
#define to_bcmpmu_throttle_data(ptr, mem) container_of((ptr), \
struct bcmpmu_throttle_data, mem)
#define TEMP_MULTI_FACTOR 10
#define ADC_READ_TRIES 10
#define ADC_RETRY_DELAY 100 /* 100ms */
#define TEMP_READ_DEBOUNCE 3
#define ACLD_MAX_WAIT_COUNT 10
#define TEMP_OFFSET 50
#define ADC_DIE_TEMP_SAMPLES 8
struct bcmpmu_chrgr_trim_reg {
u32 addr;
u8 def_val;
u8 saved_val;
};
struct bcmpmu_throttle_data {
struct bcmpmu59xxx *bcmpmu;
struct mutex mutex_work;
struct bcmpmu_throttle_pdata *pdata;
struct workqueue_struct *throttle_wq;
struct delayed_work throttle_work;
struct notifier_block usb_det_nb;
struct notifier_block acld_nb;
struct notifier_block chrgr_status_nb;
enum bcmpmu_chrgr_type_t chrgr_type;
bool temp_algo_running;
bool throttle_algo_enabled;
bool throttle_scheduled;
/* Variables for restoring charger state */
int icc_fc_saved;
struct bcmpmu_chrgr_trim_reg *chrgr_trim_reg;
int chrgr_trim_reg_sz;
s8 zone_index;
s8 previous_index;
u8 temp_db_cnt;
u8 high_temp_db_cnt;
bool acld_algo_finished;
u8 acld_wait_count;
bool cooling;
};
int bcmpmu_throttle_get_temp(struct bcmpmu_throttle_data *tdata, u8 channel,
u8 mode)
{
int temp_samples[ADC_DIE_TEMP_SAMPLES] = {0};
struct bcmpmu_adc_result result;
int retries;
static int temp_prev = 0xffff;
int ret = 0, i = 0;
bool mean = true;
do {
retries = ADC_READ_TRIES;
while (retries--) {
ret = bcmpmu_adc_read(tdata->bcmpmu, channel,
mode, &result);
if (!ret)
break;
msleep(ADC_RETRY_DELAY);
}
BUG_ON(retries <= 0);
if ((temp_prev == 0xffff) ||
((result.conv < (temp_prev + TEMP_OFFSET)) &&
(result.conv > (temp_prev - TEMP_OFFSET)))) {
temp_prev = result.conv;
mean = false;
break;
}
temp_samples[i] = result.conv;
msleep(ADC_RETRY_DELAY);
i++;
} while (i < ADC_DIE_TEMP_SAMPLES);
if (mean)
temp_prev = interquartile_mean(temp_samples, i);
return temp_prev;
}
static void bcmpmu_throttle_restore_charger_state
(struct bcmpmu_throttle_data *tdata)
{
struct bcmpmu59xxx *bcmpmu = tdata->bcmpmu;
int ret = 0;
u8 reg = 0;
u8 num;
pr_throttle(FLOW,
"Restored Charger state\n");
/* Restore Charger registers to previous state */
bcmpmu_set_icc_fc(bcmpmu, tdata->icc_fc_saved);
pr_throttle(VERBOSE, "icc_fc=%d\n", bcmpmu_get_icc_fc(bcmpmu));
for (num = 0; num < tdata->chrgr_trim_reg_sz; num++) {
ret = bcmpmu->write_dev(bcmpmu,
tdata->chrgr_trim_reg[num].addr,
tdata->chrgr_trim_reg[num].saved_val);
if (ret)
pr_throttle(ERROR, "Register[0x%08x] write Failed\n",
tdata->chrgr_trim_reg[num].addr);
ret = bcmpmu->read_dev(bcmpmu,
tdata->chrgr_trim_reg[num].addr, &reg);
if (ret)
pr_throttle(ERROR, "Register[0x%08x] readback Failed\n",
tdata->chrgr_trim_reg[num].addr);
pr_throttle(VERBOSE, "Restored Register[0x%08x] = 0x%x\n",
tdata->chrgr_trim_reg[num].addr, reg);
}
}
static void bcmpmu_throttle_store_charger_state
(struct bcmpmu_throttle_data *tdata)
{
struct bcmpmu59xxx *bcmpmu = tdata->bcmpmu;
int ret = 0;
u8 reg = 0;
u8 num;
/* Store Charger registers */
pr_throttle(FLOW, "Saved Charger state\n");
tdata->icc_fc_saved = bcmpmu_get_icc_fc(bcmpmu);
pr_throttle(VERBOSE, "icc_fc=%d\n", tdata->icc_fc_saved);
for (num = 0; num < tdata->chrgr_trim_reg_sz; num++) {
ret = bcmpmu->read_dev(bcmpmu,
tdata->chrgr_trim_reg[num].addr, &reg);
if (ret)
pr_throttle(ERROR, "Register[0x%08x] read Failed\n",
tdata->chrgr_trim_reg[num].addr);
else {
tdata->chrgr_trim_reg[num].saved_val = reg;
pr_throttle(VERBOSE, "Stored Register[0x%08x] = 0x%x\n",
tdata->chrgr_trim_reg[num].addr, reg);
}
}
}
static void bcmpmu_set_chrgr_trim_default(struct bcmpmu_throttle_data *tdata)
{
int i;
struct bcmpmu59xxx *bcmpmu = tdata->bcmpmu;
for (i = 0; i < tdata->chrgr_trim_reg_sz; i++)
bcmpmu->write_dev(bcmpmu, tdata->chrgr_trim_reg[i].addr,
tdata->chrgr_trim_reg[i].def_val);
}
static void bcmpmu_throttle_post_event(struct bcmpmu_throttle_data *tdata)
{
pr_throttle(FLOW, "%s Posting Status %d\n",
__func__, tdata->temp_algo_running);
bcmpmu_call_notifier(tdata->bcmpmu,
PMU_THEMAL_THROTTLE_STATUS, &tdata->temp_algo_running);
}
static void bcmpmu_throttle_algo(struct bcmpmu_throttle_data *tdata)
{
struct bcmpmu59xxx *bcmpmu = tdata->bcmpmu;
struct batt_temp_curr_map *temp_curr_lut;
int lut_sz = tdata->pdata->temp_curr_lut_sz;
int temp, index;
int cc_curr;
temp_curr_lut = tdata->pdata->temp_curr_lut;
temp = bcmpmu_throttle_get_temp(tdata, tdata->pdata->temp_adc_channel,
tdata->pdata->temp_adc_req_mode);
if (tdata->temp_algo_running) {
pr_throttle(VERBOSE,
"Temp: %d, Zone: %d , Ibat Limit: %d, Throttle ON\n",
temp, tdata->zone_index,
(tdata->zone_index != -1) ?
temp_curr_lut[tdata->zone_index].curr : 0);
pr_throttle(FLOW,
"PMU Die Temp: Threshold Crossed: %d\n",
temp_curr_lut[tdata->zone_index].temp);
pr_throttle(FLOW,
"Dir: %s, Curr Temp: %d, Action: %s, Current Limit: %d\n",
tdata->cooling ? "Fall" : "Rise",
temp,
(tdata->zone_index == (lut_sz - 1)) ?
"Dis-Charging" : "Charging",
temp_curr_lut[tdata->zone_index].curr);
} else
pr_throttle(FLOW, "PMU Die Temp, Curr Temp: %d, State: OFF\n",
temp);
/* Make sure that the ADC temperature reading
* is correct by debouncing
*/
if (temp < temp_curr_lut[0].temp)
index = -1;
else if (temp >= temp_curr_lut[lut_sz-1].temp) {
pr_throttle(FLOW,
"Temp(%d) reached higher cut-off=%d\n",
temp, temp_curr_lut[lut_sz-1].temp);
pr_throttle(VERBOSE, "HW might disable the charging\n");
index = lut_sz - 1;
} else {
for (index = 0; index < lut_sz; index++) {
if ((temp >= temp_curr_lut[index].temp) &&
(temp < temp_curr_lut[index+1].temp))
break;
else
continue;
}
}
pr_throttle(VERBOSE, "Current index=%d\n", index);
pr_throttle(VERBOSE, "zone_index=%d\n", tdata->zone_index);
pr_throttle(VERBOSE, "temp_db_cnt=%d\n", tdata->temp_db_cnt);
pr_throttle(VERBOSE, "high_temp_db_cnt=%d\n", tdata->high_temp_db_cnt);
pr_throttle(VERBOSE, "previous_index=%d\n", tdata->previous_index);
if (index == tdata->zone_index) {
pr_throttle(VERBOSE, "Same Zone, Nothing needs to be done\n");
tdata->temp_db_cnt = 0;
tdata->high_temp_db_cnt = 0;
return;
}
if (index > tdata->zone_index) {
tdata->high_temp_db_cnt++;
tdata->temp_db_cnt = 0;
if (tdata->high_temp_db_cnt >= TEMP_READ_DEBOUNCE) {
if (index != tdata->previous_index) {
pr_throttle(FLOW,
"Index Adjusted from %d to %d\n",
index, tdata->previous_index);
index = tdata->previous_index;
}
} else {
pr_throttle(FLOW,
"Zone changed,High temp Debounce : %d\n",
tdata->high_temp_db_cnt);
tdata->previous_index = index;
return;
}
tdata->cooling = false;
} else if (index < tdata->zone_index) {
if (temp < (temp_curr_lut[tdata->zone_index].temp -
tdata->pdata->hysteresis_temp))
tdata->temp_db_cnt++;
tdata->high_temp_db_cnt = 0;
if (tdata->temp_db_cnt < TEMP_READ_DEBOUNCE) {
pr_throttle(FLOW,
"Zone changed,Hyst Debounce : %d\n",
tdata->temp_db_cnt);
return;
}
tdata->cooling = true;
}
tdata->zone_index = index;
tdata->temp_db_cnt = 0;
tdata->high_temp_db_cnt = 0;
if (index == -1) {
pr_throttle(FLOW,
"Normal Temp Zone, Throttling will be Stopped\n");
if (tdata->temp_algo_running) {
bcmpmu_throttle_restore_charger_state(tdata);
tdata->temp_algo_running = false;
bcmpmu_throttle_post_event(tdata);
}
return;
}
if (!tdata->temp_algo_running) {
bcmpmu_throttle_store_charger_state(tdata);
tdata->temp_algo_running = true;
bcmpmu_throttle_post_event(tdata);
}
pr_throttle(FLOW, "Temp(%d) High, reached %d limit i.e. Zone: %d",
temp, temp_curr_lut[index].temp, index);
pr_throttle(FLOW, "Charging current set to %d,Temp Algorithm : %s\n",
temp_curr_lut[index].curr,
(tdata->temp_algo_running == 1) ? "ON" : "OFF");
bcmpmu_set_chrgr_trim_default(tdata);
cc_curr = bcmpmu_get_icc_fc(tdata->bcmpmu);
if (tdata->cooling)
bcmpmu_set_icc_fc(bcmpmu, temp_curr_lut[index].curr);
else if (cc_curr > temp_curr_lut[index].curr)
bcmpmu_set_icc_fc(bcmpmu, temp_curr_lut[index].curr);
else
pr_throttle(FLOW, "Already charging at lower current\n");
}
static void bcmpmu_throttle_algo_init(struct bcmpmu_throttle_data *tdata)
{
/* Intialize the variables */
tdata->temp_db_cnt = 0;
tdata->zone_index = -1;
}
static void bcmpmu_throttle_work(struct work_struct *work)
{
struct bcmpmu_throttle_data *tdata =
to_bcmpmu_throttle_data(work, throttle_work.work);
pr_throttle(VERBOSE, "%s called, charger type = %d\n",
__func__, tdata->chrgr_type);
mutex_lock(&tdata->mutex_work);
tdata->throttle_scheduled = true;
mutex_unlock(&tdata->mutex_work);
if (bcmpmu_is_acld_supported(tdata->bcmpmu, tdata->chrgr_type)) {
if (tdata->acld_algo_finished) {
bcmpmu_throttle_algo(tdata);
} else {
if (tdata->acld_wait_count >= ACLD_MAX_WAIT_COUNT) {
/* No Event from ACLD,
* so Forcefully set ACLD as finished
*/
pr_throttle(FLOW, "No ACLD FINISH event\n");
tdata->acld_algo_finished = true;
tdata->acld_wait_count = 0;
} else {
tdata->acld_wait_count++;
pr_throttle(FLOW,
"Waiting for ACLD FINISH Event, tdata->acld_wait_count = %d\n",
tdata->acld_wait_count);
}
}
} else
bcmpmu_throttle_algo(tdata);
queue_delayed_work(tdata->throttle_wq, &tdata->throttle_work,
msecs_to_jiffies(tdata->pdata->throttle_poll_time));
return;
}
static int bcmpmu_throttle_event_handler(struct notifier_block *nb,
unsigned long event, void *data)
{
struct bcmpmu_throttle_data *tdata;
bool enable;
pr_throttle(FLOW, "%s:event:%lu\n", __func__, event);
switch (event) {
case PMU_ACCY_EVT_OUT_CHRGR_TYPE:
tdata = to_bcmpmu_throttle_data(nb, usb_det_nb);
tdata->chrgr_type = *(enum bcmpmu_chrgr_type_t *)data;
if (tdata->chrgr_type == PMU_CHRGR_TYPE_NONE) {
pr_throttle(FLOW,
"Charger Removed, Disabling Thermal Throttling\n");
if (tdata->temp_algo_running) {
bcmpmu_throttle_restore_charger_state(tdata);
tdata->temp_algo_running = false;
}
cancel_delayed_work_sync(&tdata->throttle_work);
tdata->acld_algo_finished = false;
tdata->throttle_scheduled = false;
tdata->acld_wait_count = 0;
} else if (tdata->throttle_algo_enabled &&
(!tdata->throttle_scheduled)) {
bcmpmu_throttle_algo_init(tdata);
queue_delayed_work(tdata->throttle_wq,
&tdata->throttle_work, 0);
pr_throttle(FLOW,
"Charger Connected, Enabling Thermal Throttling\n");
} else
pr_throttle(FLOW,
"Charger Connected, But throttle_ctrl flag is disabled\n");
break;
case PMU_ACLD_EVT_ACLD_STATUS:
enable = *(bool *)data;
if (enable) {
tdata = to_bcmpmu_throttle_data(nb, acld_nb);
tdata->acld_algo_finished = false;
tdata->acld_wait_count = 0;
pr_throttle(FLOW, "ACLD algo START Event Received\n");
} else {
tdata = to_bcmpmu_throttle_data(nb, acld_nb);
tdata->acld_algo_finished = true;
pr_throttle(FLOW, "ACLD algo FINISH Event Received\n");
}
break;
case PMU_CHRGR_EVT_CHRG_STATUS:
enable = *(bool *)data;
pr_throttle(FLOW, "%s: ===== chrgr_status %d\n",
__func__, enable);
tdata = to_bcmpmu_throttle_data(nb, chrgr_status_nb);
mutex_lock(&tdata->mutex_work);
if (enable && tdata->chrgr_type &&
tdata->throttle_algo_enabled &&
(!tdata->throttle_scheduled)) {
queue_delayed_work(tdata->throttle_wq,
&tdata->throttle_work, 0);
pr_throttle(FLOW,
"Charger Connected, Enabling Thermal Throttling\n");
} else if ((!enable) && tdata->throttle_scheduled) {
if (tdata->zone_index !=
(tdata->pdata->temp_curr_lut_sz - 1)) {
pr_throttle(FLOW,
"Charging Disabled, Disabling Thermal Throttling\n");
cancel_delayed_work_sync(&tdata->throttle_work);
tdata->throttle_scheduled = false;
tdata->acld_wait_count = 0;
}
} else {
/* dump status */
pr_throttle(ERROR,
"Charger Status Wrong! l=%d\n", __LINE__);
pr_throttle(ERROR, "enable=%d chrgr_type=%d\n",
enable, tdata->chrgr_type);
pr_throttle(ERROR, "throttle_algo_enabled=%d\n",
tdata->throttle_algo_enabled);
pr_throttle(ERROR, "throttle_scheduled=%d\n",
tdata->throttle_scheduled);
}
mutex_unlock(&tdata->mutex_work);
break;
}
return 0;
}
static int bcmpmu_throttle_debugfs_ctrl(void *data, u64 throttle_ctrl)
{
struct bcmpmu_throttle_data *tdata = data;
if (throttle_ctrl) {
if (!tdata->throttle_algo_enabled) {
if (tdata->chrgr_type != PMU_CHRGR_TYPE_NONE) {
bcmpmu_throttle_algo_init(tdata);
queue_delayed_work(tdata->throttle_wq,
&tdata->throttle_work, 0);
} else
pr_throttle(FLOW,
"No Charger, Throttling starts after charger is connected\n");
tdata->throttle_algo_enabled = true;
pr_throttle(FLOW, "Thermal Throttling Enabled\n");
} else
pr_throttle(FLOW, "Throttling Already Enabled\n");
} else {
if (tdata->throttle_algo_enabled) {
if (tdata->temp_algo_running) {
bcmpmu_throttle_restore_charger_state(tdata);
bcmpmu_throttle_post_event(tdata);
tdata->temp_algo_running = false;
}
if (tdata->chrgr_type != PMU_CHRGR_TYPE_NONE)
cancel_delayed_work_sync(&tdata->throttle_work);
tdata->throttle_algo_enabled = false;
pr_throttle(FLOW, "Thermal Throttling Disabled\n");
} else
pr_throttle(FLOW, "Throttling Already Disabled\n");
}
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(throttle_ctrl_fops,
NULL, bcmpmu_throttle_debugfs_ctrl, "%llu\n");
static int bcmpmu_throttle_debugfs_open(struct inode *inode,
struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static ssize_t bcmpmu_throttle_debugfs_set_lut(struct file *file,
char const __user *buf,
size_t count, loff_t *offset)
{
struct bcmpmu_throttle_data *tdata = file->private_data;
u32 len = 0, idx[3];
char *str_ptr;
int entries = 0;
char input_str[100];
memset(input_str, 0, 100);
if (count > (size_t)100)
count = 100;
if (copy_from_user(input_str, buf, count))
return -EFAULT;
str_ptr = &input_str[0];
input_str[count-1] = '\0';
pr_throttle(VERBOSE, "input_str:%s:length=%d\n", input_str, count);
while (*str_ptr) {
sscanf(str_ptr, "%d%n", &idx[entries], &len);
str_ptr += len;
pr_throttle(VERBOSE, "idx[%d]=%d,len=%d\n",
entries, idx[entries], len);
entries++;
}
if (entries != 3) {
pr_throttle(ERROR, "Invalid Number of Arguments\n");
return count;
}
if (idx[0] >= tdata->pdata->temp_curr_lut_sz) {
pr_throttle(ERROR, "Invalid Index Argument\n");
return count;
}
tdata->pdata->temp_curr_lut[idx[0]].temp = idx[1];
tdata->pdata->temp_curr_lut[idx[0]].curr = idx[2];
/* Restart the throttling Algo so that the
* new values will take affect.
*/
bcmpmu_throttle_debugfs_ctrl(tdata, 0);
bcmpmu_throttle_debugfs_ctrl(tdata, 1);
return count;
}
static ssize_t bcmpmu_throttle_debugfs_get_lut(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct bcmpmu_throttle_data *tdata = file->private_data;
char out_str[400];
u32 len = 0;
u8 loop;
memset(out_str, 0, sizeof(out_str));
len += snprintf(out_str+len, sizeof(out_str)-len,
"Temperature current Lookup table:\n");
for (loop = 0; loop < tdata->pdata->temp_curr_lut_sz; loop++)
len += snprintf(out_str+len, sizeof(out_str)-len,
"index-%d--> {%d, %d}\n", loop,
tdata->pdata->temp_curr_lut[loop].temp,
tdata->pdata->temp_curr_lut[loop].curr);
len += snprintf(out_str+len, sizeof(out_str)-len,
"To Update table, use the below format\n");
len += snprintf(out_str+len, sizeof(out_str)-len,
"echo <index> <temperature> <current> > temp_curr_data\n");
len += snprintf(out_str+len, sizeof(out_str)-len,
"Note: Throttle fails, if Temp not in ascending order\n");
return simple_read_from_buffer(user_buf, count, ppos,
out_str, len);
}
static const struct file_operations temp_curr_lut_fops = {
.open = bcmpmu_throttle_debugfs_open,
.write = bcmpmu_throttle_debugfs_set_lut,
.read = bcmpmu_throttle_debugfs_get_lut,
};
static ssize_t bcmpmu_throttle_debugfs_set_ntcht(struct file *file,
char const __user *buf,
size_t count, loff_t *offset)
{
struct bcmpmu_throttle_data *tdata = file->private_data;
int ntcht_rise;
int ntcht_fall;
int ret;
int len;
char input_str[100];
if (count > 100)
len = 100;
else
len = count;
memset(input_str, 0, 100);
if (copy_from_user(input_str, buf, len))
return -EFAULT;
sscanf(input_str, "%d %d\n", &ntcht_rise, &ntcht_fall);
pr_throttle(FLOW, "NTCHT_rise= %d NTCHT_fall= %d\n,",
ntcht_rise, ntcht_fall);
if ((ntcht_rise <= ntcht_fall) ||
(ntcht_rise < 0) ||
(ntcht_fall < 0)) {
pr_throttle(ERROR, "Invalide parameters\n");
return -EINVAL;
}
ret = bcmpmu_usb_set(tdata->bcmpmu,
BCMPMU_USB_CTRL_SET_NTCHT_RISE, ntcht_rise);
if (ret)
return ret;
ret = bcmpmu_usb_set(tdata->bcmpmu,
BCMPMU_USB_CTRL_SET_NTCHT_FALL, ntcht_fall);
if (ret)
return ret;
return count;
}
static ssize_t bcmpmu_throttle_debugfs_get_ntcht(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct bcmpmu_throttle_data *tdata = file->private_data;
int ntcht_rise, ntcht_fall;
char out_str[100];
int len = 0;
int ret;
memset(out_str, 0, sizeof(out_str));
ret = bcmpmu_usb_get(tdata->bcmpmu,
BCMPMU_USB_CTRL_GET_NTCHT_RISE, &ntcht_rise);
if (ret)
return ret;
ret = bcmpmu_usb_get(tdata->bcmpmu,
BCMPMU_USB_CTRL_GET_NTCHT_FALL, &ntcht_fall);
if (ret)
return ret;
len += snprintf(out_str, sizeof(out_str) - 1, "%s %d %s %d\n",
"NTCHT_RISE:", ntcht_rise / 10,
"NTCHT_FALL:", ntcht_fall / 10);
return simple_read_from_buffer(user_buf, count, ppos,
out_str, len);
}
static const struct file_operations pmu_ntcht_fops = {
.open = bcmpmu_throttle_debugfs_open,
.write = bcmpmu_throttle_debugfs_set_ntcht,
.read = bcmpmu_throttle_debugfs_get_ntcht,
};
static ssize_t bcmpmu_throttle_debugfs_set_ntcct(struct file *file,
char const __user *buf,
size_t count, loff_t *offset)
{
struct bcmpmu_throttle_data *tdata = file->private_data;
int ntcct_rise;
int ntcct_fall;
int ret;
int len;
char input_str[100];
if (count > 100)
len = 100;
else
len = count;
memset(input_str, 0, 100);
if (copy_from_user(input_str, buf, len))
return -EFAULT;
sscanf(input_str, "%d %d\n", &ntcct_rise, &ntcct_fall);
pr_throttle(FLOW, "NTCCT_rise= %d NTCCT_fall= %d\n,",
ntcct_rise, ntcct_fall);
ret = bcmpmu_usb_set(tdata->bcmpmu,
BCMPMU_USB_CTRL_SET_NTCCT_RISE, ntcct_rise);
if (ret)
return ret;
ret = bcmpmu_usb_set(tdata->bcmpmu,
BCMPMU_USB_CTRL_SET_NTCCT_FALL, ntcct_fall);
if (ret)
return ret;
return count;
}
static ssize_t bcmpmu_throttle_debugfs_get_ntcct(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct bcmpmu_throttle_data *tdata = file->private_data;
int ntcct_rise, ntcct_fall;
char out_str[100];
int len = 0;
int ret;
memset(out_str, 0, sizeof(out_str));
ret = bcmpmu_usb_get(tdata->bcmpmu,
BCMPMU_USB_CTRL_GET_NTCCT_RISE, &ntcct_rise);
if (ret)
return ret;
ret = bcmpmu_usb_get(tdata->bcmpmu,
BCMPMU_USB_CTRL_GET_NTCCT_FALL, &ntcct_fall);
if (ret)
return ret;
len += snprintf(out_str, sizeof(out_str) - 1, "%s %d %s %d\n",
"NTCCT_RISE:", ntcct_rise / 10,
"NTCCT_FALL:", ntcct_fall / 10);
return simple_read_from_buffer(user_buf, count, ppos,
out_str, len);
}
static const struct file_operations pmu_ntcct_fops = {
.open = bcmpmu_throttle_debugfs_open,
.write = bcmpmu_throttle_debugfs_set_ntcct,
.read = bcmpmu_throttle_debugfs_get_ntcct,
};
static void bcmpmu_throttle_debugfs_init(struct bcmpmu_throttle_data *tdata)
{
struct dentry *dentry_throttle_dir;
struct dentry *dentry_throttle_file;
struct bcmpmu59xxx *bcmpmu = tdata->bcmpmu;
if (!bcmpmu || !bcmpmu->dent_bcmpmu) {
pr_throttle(ERROR, "%s: dentry_bcmpmu is NULL", __func__);
return;
}
dentry_throttle_dir =
debugfs_create_dir("THROTTLE", bcmpmu->dent_bcmpmu);
if (IS_ERR_OR_NULL(dentry_throttle_dir))
goto debugfs_clean;
dentry_throttle_file = debugfs_create_file("throttle_ctrl",
S_IWUSR | S_IRUSR, dentry_throttle_dir, tdata,
&throttle_ctrl_fops);
if (IS_ERR_OR_NULL(dentry_throttle_file))
goto debugfs_clean;
dentry_throttle_file = debugfs_create_file("ntcht",
S_IWUSR | S_IRUSR, dentry_throttle_dir, tdata,
&pmu_ntcht_fops);
if (IS_ERR_OR_NULL(dentry_throttle_file))
goto debugfs_clean;
dentry_throttle_file = debugfs_create_file("ntcct",
S_IWUSR | S_IRUSR, dentry_throttle_dir, tdata,
&pmu_ntcct_fops);
if (IS_ERR_OR_NULL(dentry_throttle_file))
goto debugfs_clean;
dentry_throttle_file = debugfs_create_u32("poll_time",
S_IWUSR | S_IRUSR, dentry_throttle_dir,
&tdata->pdata->throttle_poll_time);
if (IS_ERR_OR_NULL(dentry_throttle_file))
goto debugfs_clean;
dentry_throttle_file = debugfs_create_u32("hysteresis_temp",
S_IWUSR | S_IRUSR, dentry_throttle_dir,
&tdata->pdata->hysteresis_temp);
if (IS_ERR_OR_NULL(dentry_throttle_file))
goto debugfs_clean;
dentry_throttle_file = debugfs_create_file("temp_curr_data",
S_IWUSR | S_IRUSR, dentry_throttle_dir, tdata,
&temp_curr_lut_fops);
if (IS_ERR_OR_NULL(dentry_throttle_file))
goto debugfs_clean;
dentry_throttle_file = debugfs_create_u32("debug_mask",
S_IWUSR | S_IRUSR, dentry_throttle_dir, &debug_mask);
if (IS_ERR_OR_NULL(dentry_throttle_file))
goto debugfs_clean;
return;
debugfs_clean:
if (!IS_ERR_OR_NULL(dentry_throttle_dir))
debugfs_remove_recursive(dentry_throttle_dir);
}
static int bcmpmu_throttle_probe(struct platform_device *pdev)
{
int ret = 0;
int i;
struct bcmpmu59xxx *bcmpmu = dev_get_drvdata(pdev->dev.parent);
struct bcmpmu_throttle_data *tdata;
struct bcmpmu_chrgr_trim_reg trim_reg;
u32 charger_type;
pr_throttle(FLOW, "%s\n", __func__);
tdata = kzalloc(sizeof(struct bcmpmu_throttle_data), GFP_KERNEL);
if (tdata == NULL) {
pr_throttle(FLOW, "%s failed to alloc mem\n", __func__);
return -ENOMEM;
}
tdata->pdata =
(struct bcmpmu_throttle_pdata *)pdev->dev.platform_data;
tdata->bcmpmu = bcmpmu;
tdata->chrgr_trim_reg_sz = tdata->pdata->chrgr_trim_reg_lut_sz;
tdata->chrgr_trim_reg = kzalloc((sizeof(trim_reg) *
tdata->chrgr_trim_reg_sz), GFP_KERNEL);
if (tdata->chrgr_trim_reg == NULL) {
pr_throttle(FLOW, "%s %d failed to alloc mem\n",
__func__, __LINE__);
kfree(tdata);
return -ENOMEM;
}
for (i = 0; i < tdata->chrgr_trim_reg_sz; i++) {
tdata->chrgr_trim_reg[i].addr =
tdata->pdata->chrgr_trim_reg_lut[i].addr;
tdata->chrgr_trim_reg[i].def_val =
tdata->pdata->chrgr_trim_reg_lut[i].val;
}
/* Initialize private data */
tdata->zone_index = -1;
tdata->throttle_algo_enabled = true;
tdata->acld_algo_finished = false;
tdata->temp_algo_running = false;
tdata->throttle_scheduled = false;
tdata->cooling = false;
mutex_init(&tdata->mutex_work);
tdata->throttle_wq =
create_singlethread_workqueue("bcmpmu_throttle_wq");
if (IS_ERR_OR_NULL(tdata->throttle_wq)) {
ret = PTR_ERR(tdata->throttle_wq);
pr_throttle(ERROR, "%s Failed to create WQ\n", __func__);
goto error;
}
INIT_DELAYED_WORK(&tdata->throttle_work, bcmpmu_throttle_work);
tdata->usb_det_nb.notifier_call = bcmpmu_throttle_event_handler;
ret = bcmpmu_add_notifier(PMU_ACCY_EVT_OUT_CHRGR_TYPE,
&tdata->usb_det_nb);
if (ret) {
pr_throttle(FLOW, "%s Failed to add notifier\n", __func__);
goto destroy_workq;
}
tdata->acld_nb.notifier_call = bcmpmu_throttle_event_handler;
ret = bcmpmu_add_notifier(PMU_ACLD_EVT_ACLD_STATUS,
&tdata->acld_nb);
if (ret) {
pr_throttle(FLOW,
"%s Failed to add acld notifier\n", __func__);
goto unreg_usb_det_nb;
}
tdata->chrgr_status_nb.notifier_call = bcmpmu_throttle_event_handler;
ret = bcmpmu_add_notifier(PMU_CHRGR_EVT_CHRG_STATUS,
&tdata->chrgr_status_nb);
if (ret) {
pr_throttle(FLOW,
"%s Failed to add chrgr st notifier\n", __func__);
goto unreg_acld_nb;
}
bcmpmu_usb_get(bcmpmu, BCMPMU_USB_CTRL_GET_CHRGR_TYPE, &charger_type);
if (charger_type != PMU_CHRGR_TYPE_NONE)
queue_delayed_work(tdata->throttle_wq,
&tdata->throttle_work, 0);
#ifdef CONFIG_DEBUG_FS
bcmpmu_throttle_debugfs_init(tdata);
#endif
return 0;
unreg_usb_det_nb:
bcmpmu_remove_notifier(PMU_ACCY_EVT_OUT_CHRGR_TYPE,
&tdata->usb_det_nb);
unreg_acld_nb:
bcmpmu_remove_notifier(PMU_ACLD_EVT_ACLD_STATUS,
&tdata->usb_det_nb);
destroy_workq:
destroy_workqueue(tdata->throttle_wq);
error:
kfree(tdata->chrgr_trim_reg);
kfree(tdata);
return 0;
}
static int bcmpmu_throttle_remove(struct platform_device *pdev)
{
pr_throttle(FLOW, "%s\n", __func__);
return 0;
}
static struct platform_driver bcmpmu_throttle_driver = {
.driver = {
.name = "bcmpmu_thermal_throttle",
},
.probe = bcmpmu_throttle_probe,
.remove = bcmpmu_throttle_remove,
};
static int __init bcmpmu_throttle_init(void)
{
return platform_driver_register(&bcmpmu_throttle_driver);
}
module_init(bcmpmu_throttle_init);
static void __exit bcmpmu_throttle_exit(void)
{
platform_driver_unregister(&bcmpmu_throttle_driver);
}
module_exit(bcmpmu_throttle_exit);
MODULE_DESCRIPTION("Broadcom PMU Thermal Throttle Driver");
MODULE_LICENSE("GPL");