mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-27 18:25:05 +00:00
Device-specific headers belong in driver/ or chip/. The include/ directory should be for common interfaces. Code should not normally need to include driver-specific headers. If it does, it should use the full relative path from the EC project root (for example, drivers/charger/bq24715.h). Change-Id: Id23db37a431e2d802a74ec601db6f69b613352ba Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/173746 Reviewed-by: Bill Richardson <wfrichar@chromium.org>
949 lines
24 KiB
C
949 lines
24 KiB
C
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
/* USB charging control for spring board */
|
|
|
|
#include "adc.h"
|
|
#include "adc_chip.h"
|
|
#include "battery.h"
|
|
#include "chipset.h"
|
|
#include "clock.h"
|
|
#include "console.h"
|
|
#include "driver/tsu6721.h"
|
|
#include "extpower.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "keyboard_protocol.h"
|
|
#include "pmu_tpschrome.h"
|
|
#include "pwm.h"
|
|
/* TODO(rspangler): files in common should not use chip registers directly */
|
|
#include "registers.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
#define PWM_FREQUENCY 32000 /* Hz */
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_USBCHARGE, outstr)
|
|
#define CPRINTF(format, args...) cprintf(CC_USBCHARGE, format, ## args)
|
|
|
|
/* ILIM pin control */
|
|
enum ilim_config {
|
|
ILIM_CONFIG_MANUAL_OFF,
|
|
ILIM_CONFIG_MANUAL_ON,
|
|
ILIM_CONFIG_PWM,
|
|
};
|
|
|
|
/* Devices that need VBUS power */
|
|
#define POWERED_5000_DEVICE_TYPE (TSU6721_TYPE_OTG)
|
|
#define POWERED_3300_DEVICE_TYPE (TSU6721_TYPE_JIG_UART_ON)
|
|
|
|
/* Toad cable */
|
|
#define TOAD_DEVICE_TYPE (TSU6721_TYPE_UART | TSU6721_TYPE_AUDIO3)
|
|
|
|
/* Voltage threshold of D+ for video */
|
|
#define VIDEO_ID_THRESHOLD 1300
|
|
|
|
/*
|
|
* Mapping from PWM duty to current:
|
|
* Current = A + B * PWM_Duty
|
|
*/
|
|
#define PWM_MAPPING_A 2958
|
|
#define PWM_MAPPING_B (-29)
|
|
|
|
/* Map current in milli-amps to PWM duty cycle percentage */
|
|
#define MA_TO_PWM(curr) (((curr) - PWM_MAPPING_A) / PWM_MAPPING_B)
|
|
|
|
/* PWM controlled current limit */
|
|
#define I_LIMIT_100MA MA_TO_PWM(100)
|
|
#define I_LIMIT_500MA MA_TO_PWM(500)
|
|
#define I_LIMIT_1000MA MA_TO_PWM(1000)
|
|
#define I_LIMIT_1500MA MA_TO_PWM(1500)
|
|
#define I_LIMIT_2000MA MA_TO_PWM(2000)
|
|
#define I_LIMIT_2400MA MA_TO_PWM(2400)
|
|
#define I_LIMIT_3000MA 0
|
|
|
|
/* PWM control loop parameters */
|
|
#define PWM_CTRL_MAX_DUTY I_LIMIT_100MA /* Minimum current */
|
|
#define PWM_CTRL_BEGIN_OFFSET 90
|
|
#define PWM_CTRL_OC_MARGIN 15
|
|
#define PWM_CTRL_OC_DETECT_TIME (1200 * MSEC)
|
|
#define PWM_CTRL_OC_BACK_OFF 3
|
|
#define PWM_CTRL_OC_RETRY 2
|
|
#define PWM_CTRL_STEP_DOWN 3
|
|
#define PWM_CTRL_STEP_UP 5
|
|
#define PWM_CTRL_VBUS_HARD_LOW 4400
|
|
#define PWM_CTRL_VBUS_LOW 4500
|
|
#define PWM_CTRL_VBUS_HIGH 4700 /* Must be higher than 4.5V */
|
|
#define PWM_CTRL_VBUS_HIGH_500MA 4550
|
|
|
|
/* Delay before notifying kernel of device type change */
|
|
#define BATTERY_KEY_DELAY (PWM_CTRL_OC_DETECT_TIME + 400 * MSEC)
|
|
|
|
/* Delay for signals to settle */
|
|
#define DELAY_POWER_MS 20
|
|
#define DELAY_USB_DP_DN_MS 20
|
|
#define DELAY_ID_MUX_MS 30
|
|
#define CABLE_DET_POLL_MS 100
|
|
#define CABLE_DET_POLL_COUNT 6
|
|
|
|
/* Current sense resistor values */
|
|
#define R_INPUT_MOHM 20 /* mOhm */
|
|
#define R_BATTERY_MOHM 33 /* mOhm */
|
|
|
|
static int current_dev_type = TSU6721_TYPE_NONE;
|
|
static int nominal_pwm_duty;
|
|
static int current_pwm_duty;
|
|
static int user_pwm_duty = -1;
|
|
|
|
static int pending_tsu6721_reset;
|
|
static int pending_adc_watchdog_disable;
|
|
static int pending_dev_type_update;
|
|
static int pending_video_power_off;
|
|
static int restore_id_mux;
|
|
|
|
static enum {
|
|
ADC_WATCH_NONE,
|
|
ADC_WATCH_TOAD,
|
|
ADC_WATCH_USB,
|
|
} current_watchdog = ADC_WATCH_NONE;
|
|
|
|
struct {
|
|
int type;
|
|
const char *name;
|
|
} const known_dev_types[] = {
|
|
{TSU6721_TYPE_OTG, "OTG"},
|
|
{TSU6721_TYPE_USB_HOST, "USB"},
|
|
{TSU6721_TYPE_CHG12, "Type-1/2-Chg"},
|
|
{TSU6721_TYPE_NON_STD_CHG, "Non-Std-Chg"},
|
|
{TSU6721_TYPE_DCP, "DCP"},
|
|
{TSU6721_TYPE_CDP, "CDP"},
|
|
{TSU6721_TYPE_U200_CHG, "U200-Chg"},
|
|
{TSU6721_TYPE_APPLE_CHG, "Apple-Chg"},
|
|
{TSU6721_TYPE_JIG_UART_ON, "Video"},
|
|
{TSU6721_TYPE_AUDIO3, "Audio-3"},
|
|
{TSU6721_TYPE_UART, "UART"},
|
|
{TSU6721_TYPE_VBUS_DEBOUNCED, "Power"} };
|
|
|
|
/*
|
|
* Last time we see a power source removed. Also records the power source
|
|
* type and PWM duty cycle at that moment.
|
|
* Index: 0 = Unknown power source.
|
|
* 1 = Recognized power source.
|
|
*/
|
|
static timestamp_t power_removed_time[2];
|
|
static uint32_t power_removed_type[2];
|
|
static int power_removed_pwm_duty[2];
|
|
static int oc_detect_retry[2] = {PWM_CTRL_OC_RETRY, PWM_CTRL_OC_RETRY};
|
|
|
|
/* PWM duty cycle limit based on over current event */
|
|
static int over_current_pwm_duty;
|
|
|
|
static enum ilim_config current_ilim_config = ILIM_CONFIG_MANUAL_OFF;
|
|
|
|
static const int apple_charger_type[4] = {I_LIMIT_500MA,
|
|
I_LIMIT_1000MA,
|
|
I_LIMIT_2000MA,
|
|
I_LIMIT_2400MA};
|
|
|
|
static int video_power_enabled;
|
|
|
|
#define NON_STD_CHARGER_REDETECT_DELAY (600 * MSEC)
|
|
static enum {
|
|
NO_REDETECT,
|
|
REDETECT_SCHEDULED,
|
|
REDETECTED,
|
|
} charger_need_redetect = NO_REDETECT;
|
|
static timestamp_t charger_redetection_time;
|
|
|
|
static int get_video_power(void)
|
|
{
|
|
return video_power_enabled;
|
|
}
|
|
|
|
static void set_video_power(int enabled)
|
|
{
|
|
int power_good;
|
|
|
|
pmu_enable_fet(FET_VIDEO, enabled, enabled ? &power_good : NULL);
|
|
if (enabled && !power_good)
|
|
pmu_enable_fet(FET_VIDEO, 0, NULL);
|
|
video_power_enabled = enabled;
|
|
}
|
|
|
|
static void ilim_use_gpio(void)
|
|
{
|
|
pwm_enable(PWM_CH_ILIM, 0);
|
|
gpio_set_flags(GPIO_ILIM, GPIO_OUTPUT);
|
|
}
|
|
|
|
/**
|
|
* Set ILIM pin control type.
|
|
*/
|
|
static void ilim_config(enum ilim_config config)
|
|
{
|
|
if (config == current_ilim_config)
|
|
return;
|
|
current_ilim_config = config;
|
|
|
|
switch (config) {
|
|
case ILIM_CONFIG_MANUAL_OFF:
|
|
case ILIM_CONFIG_MANUAL_ON:
|
|
ilim_use_gpio();
|
|
gpio_set_level(GPIO_ILIM,
|
|
config == ILIM_CONFIG_MANUAL_ON ? 1 : 0);
|
|
break;
|
|
case ILIM_CONFIG_PWM:
|
|
pwm_enable(PWM_CH_ILIM, 1);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return Apple charger current limit.
|
|
*/
|
|
static int apple_charger_current(void)
|
|
{
|
|
int vp, vn;
|
|
int type = 0;
|
|
int data[ADC_CH_COUNT];
|
|
|
|
/* TODO(victoryang): Handle potential race condition. */
|
|
tsu6721_disable_interrupts();
|
|
tsu6721_mux(TSU6721_MUX_USB);
|
|
/* Wait 20ms for signal to stablize */
|
|
msleep(DELAY_USB_DP_DN_MS);
|
|
adc_read_all_channels(data);
|
|
vp = data[ADC_CH_USB_DP_SNS];
|
|
vn = data[ADC_CH_USB_DN_SNS];
|
|
tsu6721_mux(TSU6721_MUX_AUTO);
|
|
tsu6721_enable_interrupts();
|
|
if (vp > 1215)
|
|
type |= 0x2;
|
|
if (vn > 1215)
|
|
type |= 0x1;
|
|
|
|
return apple_charger_type[type];
|
|
}
|
|
|
|
static int hard_current_limit(int limit)
|
|
{
|
|
/*
|
|
* The PWM duty cycle goes lower than the nominal
|
|
* cycle for PWM_CTRL_OC_MARGIN. Therefore, increase duty cycle by
|
|
* PWM_CTRL_OC_MARGIN avoids going over the hard limit.
|
|
* (Note that lower PWM cycle translates to higher current)
|
|
*/
|
|
return MIN(limit + PWM_CTRL_OC_MARGIN, 100);
|
|
}
|
|
|
|
static int video_dev_type(int device_type)
|
|
{
|
|
return (device_type & ~TSU6721_TYPE_USB_HOST) |
|
|
TSU6721_TYPE_JIG_UART_ON;
|
|
}
|
|
|
|
static int usb_video_id_present(void)
|
|
{
|
|
return adc_read_channel(ADC_CH_USB_DP_SNS) > VIDEO_ID_THRESHOLD;
|
|
}
|
|
|
|
static int usb_poll_video_id(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < CABLE_DET_POLL_COUNT; ++i) {
|
|
msleep(CABLE_DET_POLL_MS);
|
|
if (usb_video_id_present())
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int probe_video(int device_type)
|
|
{
|
|
tsu6721_disable_interrupts();
|
|
gpio_set_level(GPIO_ID_MUX, 1);
|
|
msleep(DELAY_ID_MUX_MS);
|
|
|
|
if (usb_poll_video_id()) {
|
|
/* Not USB host but video */
|
|
device_type = video_dev_type(device_type);
|
|
return device_type;
|
|
} else {
|
|
if (adc_read_channel(ADC_CH_USB_VBUS_SNS) > 3500) {
|
|
/*
|
|
* Either USB host or video dongle.
|
|
* Leave ID_MUX high so we see the change on
|
|
* DP_SNS if any.
|
|
*
|
|
* ADC watchdog is responsible for sensing a
|
|
* detach event and switch back ID_MUX.
|
|
*/
|
|
return device_type;
|
|
} else {
|
|
/* Unhandled unpowered video dongle. Ignore it. */
|
|
gpio_set_level(GPIO_ID_MUX, 0);
|
|
msleep(DELAY_ID_MUX_MS);
|
|
tsu6721_enable_interrupts();
|
|
return TSU6721_TYPE_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set PWM duty cycle.
|
|
*/
|
|
static void set_pwm_duty_cycle(int percent)
|
|
{
|
|
if (current_ilim_config != ILIM_CONFIG_PWM)
|
|
ilim_config(ILIM_CONFIG_PWM);
|
|
if (percent < 0)
|
|
percent = 0;
|
|
if (percent > 100)
|
|
percent = 100;
|
|
pwm_set_duty(PWM_CH_ILIM, percent);
|
|
current_pwm_duty = percent;
|
|
}
|
|
|
|
/**
|
|
* Returns next lower PWM duty cycle, or -1 for unchanged duty cycle.
|
|
*/
|
|
static int pwm_get_next_lower(void)
|
|
{
|
|
if (current_pwm_duty > nominal_pwm_duty -
|
|
PWM_CTRL_OC_MARGIN &&
|
|
current_pwm_duty > over_current_pwm_duty &&
|
|
current_pwm_duty > 0)
|
|
return MAX(current_pwm_duty - PWM_CTRL_STEP_DOWN, 0);
|
|
return -1;
|
|
}
|
|
|
|
static int pwm_check_vbus_low(int vbus, int battery_current)
|
|
{
|
|
if (battery_current >= 0)
|
|
return vbus < PWM_CTRL_VBUS_LOW && current_pwm_duty < 100;
|
|
else
|
|
return vbus < PWM_CTRL_VBUS_HARD_LOW && current_pwm_duty < 100;
|
|
}
|
|
|
|
static int pwm_check_vbus_high(int vbus)
|
|
{
|
|
if (vbus > PWM_CTRL_VBUS_HIGH)
|
|
return 1;
|
|
if (vbus > PWM_CTRL_VBUS_HIGH_500MA && current_pwm_duty > I_LIMIT_500MA)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void pwm_nominal_duty_cycle(int percent)
|
|
{
|
|
int new_percent = percent;
|
|
|
|
new_percent += PWM_CTRL_BEGIN_OFFSET;
|
|
new_percent = MIN(new_percent, PWM_CTRL_MAX_DUTY);
|
|
|
|
set_pwm_duty_cycle(new_percent);
|
|
nominal_pwm_duty = percent;
|
|
}
|
|
|
|
static void adc_watch_vbus(int high, int low)
|
|
{
|
|
adc_enable_watchdog(STM32_AIN(5), high, low);
|
|
task_clear_pending_irq(STM32_IRQ_ADC_1);
|
|
task_enable_irq(STM32_IRQ_ADC_1);
|
|
}
|
|
|
|
static void adc_watch_toad(void)
|
|
{
|
|
/* Watch VBUS and interrupt if voltage goes under 3V. */
|
|
adc_watch_vbus(4095, 1800);
|
|
current_watchdog = ADC_WATCH_TOAD;
|
|
}
|
|
|
|
static void adc_watch_usb(void)
|
|
{
|
|
/* Watch VBUS and interrupt if voltage goes under 3V. */
|
|
adc_watch_vbus(4095, 1800);
|
|
current_watchdog = ADC_WATCH_USB;
|
|
}
|
|
|
|
static int usb_has_power_input(int dev_type)
|
|
{
|
|
if (dev_type & TSU6721_TYPE_JIG_UART_ON)
|
|
return 1;
|
|
return (dev_type & TSU6721_TYPE_VBUS_DEBOUNCED) &&
|
|
!(dev_type & POWERED_5000_DEVICE_TYPE);
|
|
}
|
|
|
|
static int usb_need_boost(int dev_type)
|
|
{
|
|
if (dev_type & POWERED_5000_DEVICE_TYPE)
|
|
return 0;
|
|
if (chipset_in_state(CHIPSET_STATE_ON | CHIPSET_STATE_SUSPEND))
|
|
return 1;
|
|
return (dev_type != TSU6721_TYPE_NONE);
|
|
}
|
|
|
|
static void usb_boost_power_hook(int power_on)
|
|
{
|
|
if (current_dev_type == TSU6721_TYPE_NONE)
|
|
gpio_set_level(GPIO_BOOST_EN, power_on);
|
|
else if (current_dev_type & TSU6721_TYPE_JIG_UART_ON)
|
|
set_video_power(power_on);
|
|
}
|
|
|
|
static int usb_charger_removed(int dev_type)
|
|
{
|
|
if (!(current_dev_type & TSU6721_TYPE_VBUS_DEBOUNCED))
|
|
return 0;
|
|
|
|
/* Charger is removed */
|
|
if (dev_type == TSU6721_TYPE_NONE)
|
|
return 1;
|
|
|
|
/*
|
|
* Device type changed from known type to unknown type. Assuming
|
|
* it went away and came back.
|
|
*/
|
|
if ((current_dev_type != TSU6721_TYPE_VBUS_DEBOUNCED) &&
|
|
(dev_type == TSU6721_TYPE_VBUS_DEBOUNCED))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Detect over-current events.
|
|
*
|
|
* When a power source is removed, record time, power source type, and PWM duty
|
|
* cycle. Then when we see a power source, compare type and calculate time
|
|
* difference to determine if we have just encountered an over current event.
|
|
*/
|
|
static void usb_detect_overcurrent(int dev_type)
|
|
{
|
|
if (usb_charger_removed(dev_type)) {
|
|
int idx = !(current_dev_type == TSU6721_TYPE_VBUS_DEBOUNCED);
|
|
power_removed_time[idx] = get_time();
|
|
power_removed_type[idx] = current_dev_type;
|
|
/*
|
|
* TODO(victoryang): Record the maximum current seen during
|
|
* retry?
|
|
*/
|
|
power_removed_pwm_duty[idx] = current_pwm_duty;
|
|
} else if (dev_type & TSU6721_TYPE_VBUS_DEBOUNCED) {
|
|
int idx = !(dev_type == TSU6721_TYPE_VBUS_DEBOUNCED);
|
|
timestamp_t now = get_time();
|
|
now.val -= power_removed_time[idx].val;
|
|
if (now.val >= PWM_CTRL_OC_DETECT_TIME) {
|
|
oc_detect_retry[idx] = PWM_CTRL_OC_RETRY;
|
|
return;
|
|
}
|
|
if (power_removed_type[idx] == dev_type) {
|
|
if (oc_detect_retry[idx] > 0) {
|
|
CPRINTF("[%T USB overcurrent: Retry (%d)]\n",
|
|
oc_detect_retry[idx]);
|
|
oc_detect_retry[idx]--;
|
|
return;
|
|
}
|
|
over_current_pwm_duty = power_removed_pwm_duty[idx] +
|
|
PWM_CTRL_OC_BACK_OFF;
|
|
CPRINTF("[%T USB overcurrent: Limited to %d%%]\n",
|
|
over_current_pwm_duty);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Supply 5V VBUS if needed.
|
|
*
|
|
* If we toggle power output, wait for a moment, and then update device
|
|
* type. To avoid race condition, check if power requirement changes during
|
|
* this time.
|
|
*/
|
|
static int usb_manage_boost(int dev_type)
|
|
{
|
|
int need_boost;
|
|
int retry_limit = 3;
|
|
|
|
do {
|
|
if (retry_limit-- <= 0)
|
|
break;
|
|
|
|
need_boost = usb_need_boost(dev_type);
|
|
if (need_boost != gpio_get_level(GPIO_BOOST_EN)) {
|
|
gpio_set_level(GPIO_BOOST_EN, need_boost);
|
|
msleep(DELAY_POWER_MS);
|
|
dev_type = tsu6721_get_device_type();
|
|
if (gpio_get_level(GPIO_ID_MUX))
|
|
dev_type = video_dev_type(dev_type);
|
|
}
|
|
} while (need_boost == !usb_need_boost(dev_type));
|
|
|
|
return dev_type;
|
|
}
|
|
|
|
/**
|
|
* Update ILIM current limit according to device type.
|
|
*/
|
|
static void usb_update_ilim(int dev_type)
|
|
{
|
|
if (usb_has_power_input(dev_type)) {
|
|
/* Limit USB port current. 500mA for not listed types. */
|
|
int current_limit = I_LIMIT_500MA;
|
|
if (dev_type & TSU6721_TYPE_CHG12)
|
|
current_limit = I_LIMIT_3000MA;
|
|
else if (dev_type & TSU6721_TYPE_APPLE_CHG)
|
|
current_limit = apple_charger_current();
|
|
else if (dev_type & TSU6721_TYPE_CDP)
|
|
current_limit = I_LIMIT_1500MA;
|
|
else if (dev_type & TSU6721_TYPE_DCP)
|
|
current_limit = hard_current_limit(I_LIMIT_1500MA);
|
|
else if (dev_type & TSU6721_TYPE_JIG_UART_ON)
|
|
current_limit = hard_current_limit(I_LIMIT_2000MA);
|
|
else if (dev_type & TOAD_DEVICE_TYPE)
|
|
current_limit = hard_current_limit(I_LIMIT_500MA);
|
|
else if (dev_type == TSU6721_TYPE_VBUS_DEBOUNCED)
|
|
current_limit = hard_current_limit(I_LIMIT_100MA);
|
|
|
|
pwm_nominal_duty_cycle(current_limit);
|
|
} else {
|
|
ilim_config(ILIM_CONFIG_MANUAL_ON);
|
|
}
|
|
}
|
|
|
|
static void usb_log_dev_type(int dev_type)
|
|
{
|
|
int i = sizeof(known_dev_types) / sizeof(known_dev_types[0]);
|
|
|
|
CPRINTF("[%T USB: 0x%06x", dev_type);
|
|
for (--i; i >= 0; --i)
|
|
if (dev_type & known_dev_types[i].type)
|
|
CPRINTF(" %s", known_dev_types[i].name);
|
|
CPRINTF("]\n");
|
|
}
|
|
|
|
static void send_battery_key_deferred(void)
|
|
{
|
|
keyboard_send_battery_key();
|
|
}
|
|
DECLARE_DEFERRED(send_battery_key_deferred);
|
|
|
|
static void notify_dev_type_change(int dev_type)
|
|
{
|
|
usb_log_dev_type(dev_type);
|
|
current_dev_type = dev_type;
|
|
hook_call_deferred(send_battery_key_deferred, BATTERY_KEY_DELAY);
|
|
}
|
|
|
|
static void usb_device_change(int dev_type)
|
|
{
|
|
|
|
if (current_dev_type == dev_type)
|
|
return;
|
|
|
|
over_current_pwm_duty = 0;
|
|
|
|
/*
|
|
* Video output is recognized incorrectly as USB host. When we see
|
|
* USB host, probe for video output.
|
|
*/
|
|
if (dev_type & TSU6721_TYPE_USB_HOST)
|
|
dev_type = probe_video(dev_type);
|
|
|
|
usb_detect_overcurrent(dev_type);
|
|
|
|
dev_type = usb_manage_boost(dev_type);
|
|
|
|
/* Supply 3.3V VBUS if needed. */
|
|
if (dev_type & POWERED_3300_DEVICE_TYPE)
|
|
set_video_power(1);
|
|
|
|
usb_update_ilim(dev_type);
|
|
|
|
if ((dev_type & TOAD_DEVICE_TYPE) &&
|
|
(dev_type & TSU6721_TYPE_VBUS_DEBOUNCED))
|
|
adc_watch_toad();
|
|
else if (dev_type & TSU6721_TYPE_USB_HOST)
|
|
adc_watch_usb();
|
|
|
|
if (dev_type != current_dev_type) {
|
|
if ((dev_type & TSU6721_TYPE_NON_STD_CHG ||
|
|
dev_type == TSU6721_TYPE_VBUS_DEBOUNCED) &&
|
|
charger_need_redetect == NO_REDETECT) {
|
|
/* Schedule redetection */
|
|
charger_need_redetect = REDETECT_SCHEDULED;
|
|
charger_redetection_time = get_time();
|
|
charger_redetection_time.val +=
|
|
NON_STD_CHARGER_REDETECT_DELAY;
|
|
} else if (dev_type != TSU6721_TYPE_VBUS_DEBOUNCED &&
|
|
!(dev_type & TSU6721_TYPE_NON_STD_CHG)) {
|
|
/* Not non-std charger. Disarm redetection timer. */
|
|
charger_need_redetect = NO_REDETECT;
|
|
}
|
|
notify_dev_type_change(dev_type);
|
|
}
|
|
|
|
if (dev_type)
|
|
disable_sleep(SLEEP_MASK_USB_PWR);
|
|
else
|
|
enable_sleep(SLEEP_MASK_USB_PWR);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* External API */
|
|
|
|
/*
|
|
* TODO: Init here until we can do with HOOK_INIT. Just need to set prio so we
|
|
* init before the charger task does.
|
|
*/
|
|
void extpower_charge_init(void)
|
|
{
|
|
set_pwm_duty_cycle(I_LIMIT_500MA);
|
|
|
|
/*
|
|
* Somehow TSU6721 comes up slowly. Let's wait for a moment before
|
|
* accessing it.
|
|
* TODO(victoryang): Investigate slow init issue.
|
|
*/
|
|
msleep(500);
|
|
|
|
tsu6721_reset();
|
|
gpio_enable_interrupt(GPIO_USB_CHG_INT);
|
|
msleep(100); /* TSU6721 doesn't work properly right away. */
|
|
|
|
extpower_charge_update(1);
|
|
}
|
|
|
|
void extpower_charge_update(int force_update)
|
|
{
|
|
int int_val = 0;
|
|
|
|
if (restore_id_mux) {
|
|
gpio_set_level(GPIO_ID_MUX, 0);
|
|
msleep(DELAY_ID_MUX_MS);
|
|
restore_id_mux = 0;
|
|
}
|
|
|
|
if (pending_adc_watchdog_disable) {
|
|
current_watchdog = ADC_WATCH_NONE;
|
|
adc_disable_watchdog();
|
|
pending_adc_watchdog_disable = 0;
|
|
}
|
|
|
|
if (pending_video_power_off) {
|
|
set_video_power(0);
|
|
pending_video_power_off = 0;
|
|
}
|
|
|
|
if (pending_tsu6721_reset) {
|
|
tsu6721_reset();
|
|
force_update = 1;
|
|
pending_tsu6721_reset = 0;
|
|
}
|
|
|
|
if (pending_dev_type_update) {
|
|
force_update = 1;
|
|
pending_dev_type_update = 0;
|
|
}
|
|
|
|
/*
|
|
* Check device type except when:
|
|
* 1. Current device type is non-standard charger or undetermined
|
|
* charger type. This is handled by charger re-detection.
|
|
* 2. ID_MUX=1. This is handled by ADC watchdog.
|
|
*/
|
|
if (current_dev_type != TSU6721_TYPE_VBUS_DEBOUNCED &&
|
|
!(current_dev_type & TSU6721_TYPE_NON_STD_CHG) &&
|
|
gpio_get_level(GPIO_ID_MUX) == 0)
|
|
force_update |= (tsu6721_get_device_type() != current_dev_type);
|
|
|
|
if (!force_update)
|
|
int_val = tsu6721_get_interrupts();
|
|
|
|
if (int_val & TSU6721_INT_DETACH)
|
|
usb_device_change(TSU6721_TYPE_NONE);
|
|
else if (int_val || force_update)
|
|
usb_device_change(tsu6721_get_device_type());
|
|
}
|
|
|
|
int extpower_charge_needs_update(void)
|
|
{
|
|
return tsu6721_peek_interrupts();
|
|
}
|
|
|
|
int extpower_is_present(void)
|
|
{
|
|
static int last_vbus;
|
|
int vbus, vbus_good;
|
|
|
|
if (!gpio_get_level(GPIO_BOOST_EN))
|
|
return 0;
|
|
|
|
/*
|
|
* UVLO is 4.1V. We consider AC bad when its voltage drops below 4.2V
|
|
* for two consecutive samples. This is to give PWM a chance to bring
|
|
* voltage up.
|
|
*/
|
|
vbus = adc_read_channel(ADC_CH_USB_VBUS_SNS);
|
|
vbus_good = (vbus >= 4200 || last_vbus >= 4200);
|
|
last_vbus = vbus;
|
|
|
|
return vbus_good;
|
|
}
|
|
|
|
void extpower_interrupt(enum gpio_signal signal)
|
|
{
|
|
task_wake(TASK_ID_CHARGER);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Hooks */
|
|
|
|
static void adc_watchdog_interrupt(void)
|
|
{
|
|
switch (current_watchdog) {
|
|
case ADC_WATCH_USB:
|
|
restore_id_mux = 1;
|
|
/* Fall through */
|
|
case ADC_WATCH_TOAD:
|
|
pending_tsu6721_reset = 1;
|
|
pending_adc_watchdog_disable = 1;
|
|
task_disable_irq(STM32_IRQ_ADC_1);
|
|
task_wake(TASK_ID_CHARGER);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
DECLARE_IRQ(STM32_IRQ_ADC_1, adc_watchdog_interrupt, 2);
|
|
|
|
static void usb_boost_pwr_on_hook(void)
|
|
{
|
|
usb_boost_power_hook(1);
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_PRE_INIT, usb_boost_pwr_on_hook, HOOK_PRIO_DEFAULT);
|
|
|
|
static void usb_boost_pwr_off_hook(void)
|
|
{
|
|
usb_boost_power_hook(0);
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, usb_boost_pwr_off_hook, HOOK_PRIO_DEFAULT);
|
|
|
|
static void pwm_tweak(void)
|
|
{
|
|
int vbus, current;
|
|
int next;
|
|
|
|
if (current_ilim_config != ILIM_CONFIG_PWM)
|
|
return;
|
|
|
|
vbus = adc_read_channel(ADC_CH_USB_VBUS_SNS);
|
|
if (battery_current(¤t))
|
|
current = 0;
|
|
|
|
if (user_pwm_duty >= 0) {
|
|
if (current_pwm_duty != user_pwm_duty)
|
|
set_pwm_duty_cycle(user_pwm_duty);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If VBUS voltage is too low:
|
|
* - If battery is discharging, throttling more is going to draw
|
|
* more current from the battery, so do nothing unless VBUS is
|
|
* about to be lower than AC good threshold.
|
|
* - Otherwise, throttle input current to raise VBUS voltage.
|
|
* If VBUS voltage is high enough, allow more current until we hit
|
|
* current limit target.
|
|
*/
|
|
if (pwm_check_vbus_low(vbus, current)) {
|
|
set_pwm_duty_cycle(current_pwm_duty + PWM_CTRL_STEP_UP);
|
|
CPRINTF("[%T PWM duty up %d%%]\n", current_pwm_duty);
|
|
} else if (pwm_check_vbus_high(vbus)) {
|
|
next = pwm_get_next_lower();
|
|
if (next >= 0) {
|
|
set_pwm_duty_cycle(next);
|
|
CPRINTF("[%T PWM duty down %d%%]\n", current_pwm_duty);
|
|
}
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_SECOND, pwm_tweak, HOOK_PRIO_DEFAULT);
|
|
|
|
static void usb_detach_video(void)
|
|
{
|
|
if (!(current_dev_type & TSU6721_TYPE_JIG_UART_ON))
|
|
return;
|
|
pending_video_power_off = 1;
|
|
restore_id_mux = 1;
|
|
pending_tsu6721_reset = 1;
|
|
task_wake(TASK_ID_CHARGER);
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, usb_detach_video, HOOK_PRIO_DEFAULT);
|
|
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, usb_detach_video, HOOK_PRIO_DEFAULT);
|
|
|
|
static void usb_monitor_detach(void)
|
|
{
|
|
int vbus;
|
|
|
|
if (!(current_dev_type & TSU6721_TYPE_JIG_UART_ON))
|
|
return;
|
|
|
|
if (!usb_video_id_present()) {
|
|
usb_detach_video();
|
|
return;
|
|
}
|
|
|
|
/* Check if there is external power */
|
|
vbus = adc_read_channel(ADC_CH_USB_VBUS_SNS);
|
|
if (get_video_power() && vbus > 4000) {
|
|
set_video_power(0);
|
|
notify_dev_type_change(current_dev_type |
|
|
TSU6721_TYPE_VBUS_DEBOUNCED);
|
|
} else if (!get_video_power() && vbus <= 4000) {
|
|
set_pwm_duty_cycle(100);
|
|
set_video_power(1);
|
|
notify_dev_type_change(current_dev_type &
|
|
~TSU6721_TYPE_VBUS_DEBOUNCED);
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_SECOND, usb_monitor_detach, HOOK_PRIO_DEFAULT);
|
|
|
|
static void usb_monitor_cable_det(void)
|
|
{
|
|
if (!(current_dev_type & TSU6721_TYPE_USB_HOST))
|
|
return;
|
|
|
|
if (usb_video_id_present())
|
|
adc_watchdog_interrupt();
|
|
}
|
|
DECLARE_HOOK(HOOK_SECOND, usb_monitor_cable_det, HOOK_PRIO_DEFAULT);
|
|
|
|
static void usb_charger_redetect(void)
|
|
{
|
|
if (charger_need_redetect != REDETECT_SCHEDULED)
|
|
return;
|
|
|
|
if (timestamp_expired(charger_redetection_time, NULL)) {
|
|
CPRINTF("[%T USB Redetecting]\n");
|
|
/*
|
|
* TSU6721 doesn't update device type if power or ID pin
|
|
* is present. Therefore, if the device type is the same,
|
|
* we need to reset TSU6721 to force a redetection.
|
|
*/
|
|
if (tsu6721_get_device_type() == current_dev_type)
|
|
pending_tsu6721_reset = 1;
|
|
else
|
|
pending_dev_type_update = 1;
|
|
if (gpio_get_level(GPIO_ID_MUX))
|
|
restore_id_mux = 1;
|
|
charger_need_redetect = REDETECTED;
|
|
task_wake(TASK_ID_CHARGER);
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_SECOND, usb_charger_redetect, HOOK_PRIO_DEFAULT);
|
|
|
|
/*****************************************************************************/
|
|
/*
|
|
* Console commands for debugging.
|
|
* TODO(victoryang): Gate with CONFIG flag after charging control is done.
|
|
*/
|
|
|
|
static int command_ilim(int argc, char **argv)
|
|
{
|
|
char *e;
|
|
int v;
|
|
|
|
if (argc >= 2) {
|
|
if (parse_bool(argv[1], &v)) {
|
|
ilim_config(v ? ILIM_CONFIG_MANUAL_ON :
|
|
ILIM_CONFIG_MANUAL_OFF);
|
|
} else {
|
|
v = strtoi(argv[1], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM1;
|
|
set_pwm_duty_cycle(v);
|
|
}
|
|
}
|
|
|
|
if (current_ilim_config == ILIM_CONFIG_MANUAL_ON)
|
|
ccprintf("ILIM is GPIO high\n");
|
|
else if (current_ilim_config == ILIM_CONFIG_MANUAL_OFF)
|
|
ccprintf("ILIM is GPIO low\n");
|
|
else
|
|
ccprintf("ILIM is PWM duty cycle %d%%\n",
|
|
pwm_get_duty(PWM_CH_ILIM));
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(ilim, command_ilim,
|
|
"[percent | on | off]",
|
|
"Set or show ILIM duty cycle/GPIO value",
|
|
NULL);
|
|
|
|
static int command_batdebug(int argc, char **argv)
|
|
{
|
|
int val;
|
|
ccprintf("VBUS = %d mV\n", adc_read_channel(ADC_CH_USB_VBUS_SNS));
|
|
ccprintf("VAC = %d mV\n", pmu_adc_read(ADC_VAC, ADC_FLAG_KEEP_ON)
|
|
* 17000 / 1024);
|
|
ccprintf("IAC = %d mA\n", pmu_adc_read(ADC_IAC, ADC_FLAG_KEEP_ON)
|
|
* (1000 / R_INPUT_MOHM) * 33 / 1024);
|
|
ccprintf("VBAT = %d mV\n", pmu_adc_read(ADC_VBAT, ADC_FLAG_KEEP_ON)
|
|
* 17000 / 1024);
|
|
ccprintf("IBAT = %d mA\n", pmu_adc_read(ADC_IBAT, 0)
|
|
* (1000 / R_BATTERY_MOHM) * 40 / 1024);
|
|
ccprintf("PWM = %d%%\n", pwm_get_duty(PWM_CH_ILIM));
|
|
battery_current(&val);
|
|
ccprintf("Battery Current = %d mA\n", val);
|
|
battery_voltage(&val);
|
|
ccprintf("Battery Voltage= %d mV\n", val);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(batdebug, command_batdebug,
|
|
NULL, NULL, NULL);
|
|
|
|
/*****************************************************************************/
|
|
/* Host commands */
|
|
|
|
static int ext_power_command_current_limit(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_ext_power_current_limit *p = args->params;
|
|
|
|
if (system_is_locked())
|
|
return EC_RES_ACCESS_DENIED;
|
|
|
|
user_pwm_duty = ((int)(p->limit) - PWM_MAPPING_A) / PWM_MAPPING_B;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_EXT_POWER_CURRENT_LIMIT,
|
|
ext_power_command_current_limit,
|
|
EC_VER_MASK(0));
|
|
|
|
static int power_command_info(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_power_info *r = args->response;
|
|
|
|
r->voltage_ac = adc_read_channel(ADC_CH_USB_VBUS_SNS);
|
|
r->voltage_system = pmu_adc_read(ADC_VAC, ADC_FLAG_KEEP_ON)
|
|
* 17000 / 1024;
|
|
r->current_system = pmu_adc_read(ADC_IAC, 0)
|
|
* (1000 / R_INPUT_MOHM) * 33 / 1024;
|
|
r->usb_dev_type = current_dev_type;
|
|
|
|
/* Approximate value by PWM duty cycle */
|
|
r->usb_current_limit = PWM_MAPPING_A + PWM_MAPPING_B * current_pwm_duty;
|
|
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_POWER_INFO, power_command_info, EC_VER_MASK(0));
|