diff options
Diffstat (limited to 'driver')
-rw-r--r-- | driver/battery/bq20z453.c | 37 | ||||
-rw-r--r-- | driver/battery/bq27541.c | 219 | ||||
-rw-r--r-- | driver/battery/link.c | 131 | ||||
-rw-r--r-- | driver/battery/smart.c | 356 | ||||
-rw-r--r-- | driver/build.mk | 34 | ||||
-rw-r--r-- | driver/charger/bq24192.c | 273 | ||||
-rw-r--r-- | driver/charger/bq24707a.c | 167 | ||||
-rw-r--r-- | driver/charger/bq24715.c | 192 | ||||
-rw-r--r-- | driver/charger/bq24725.c | 166 | ||||
-rw-r--r-- | driver/charger/bq24738.c | 188 | ||||
-rw-r--r-- | driver/led/ds2413.c | 166 | ||||
-rw-r--r-- | driver/led/lp5562.c | 161 | ||||
-rw-r--r-- | driver/regulator_ir357x.c | 247 | ||||
-rw-r--r-- | driver/temp_sensor/g781.c | 210 | ||||
-rw-r--r-- | driver/temp_sensor/tmp006.c | 444 | ||||
-rw-r--r-- | driver/usb_switch_tsu6721.c | 252 |
16 files changed, 3243 insertions, 0 deletions
diff --git a/driver/battery/bq20z453.c b/driver/battery/bq20z453.c new file mode 100644 index 0000000000..cda3a7cbdb --- /dev/null +++ b/driver/battery/bq20z453.c @@ -0,0 +1,37 @@ +/* Copyright (c) 2012 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. + * + * Smart battery driver for BQ20Z453. + */ + +#include "battery_smart.h" +#include "host_command.h" + +#define PARAM_CUT_OFF 0x0010 + +int battery_command_cut_off(struct host_cmd_handler_args *args) +{ + /* + * TODO: Since this is a host command, the i2c bus is claimed by host. + * Thus, we would send back the response in advanced so that + * the host can release the bus and then EC can send command to + * battery. + * + * Refactoring this via task is a way. However, it is wasteful. + * Need a light-weight solution. + */ + args->result = EC_RES_SUCCESS; + host_send_response(args); + + /* This function would try to claim i2c and then send to battery. */ + sb_write(SB_MANUFACTURER_ACCESS, PARAM_CUT_OFF); + + return EC_RES_SUCCESS; + /* + * Not sure if there is a side-effect since this could send result + * back to host TWICE. + */ +} +DECLARE_HOST_COMMAND(EC_CMD_BATTERY_CUT_OFF, battery_command_cut_off, + EC_VER_MASK(0)); diff --git a/driver/battery/bq27541.c b/driver/battery/bq27541.c new file mode 100644 index 0000000000..3dce19ea54 --- /dev/null +++ b/driver/battery/bq27541.c @@ -0,0 +1,219 @@ +/* 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. + * + * Battery driver for BQ27541. + */ + +#include "battery.h" +#include "console.h" +#include "i2c.h" +#include "util.h" + +#define BQ27541_ADDR 0xaa +#define BQ27541_TYPE_ID 0x0541 + +#define REG_CTRL 0x00 +#define REG_AT_RATE 0x02 +#define REG_AT_RATE_TIME_TO_EMPTY 0x04 +#define REG_TEMPERATURE 0x06 +#define REG_VOLTAGE 0x08 +#define REG_FLAGS 0x0a +#define REG_NOMINAL_CAPACITY 0x0c +#define REG_FULL_AVAILABLE_CAPACITY 0x0e +#define REG_REMAINING_CAPACITY 0x10 +#define REG_FULL_CHARGE_CAPACITY 0x12 +#define REG_AVERAGE_CURRENT 0x14 +#define REG_TIME_TO_EMPTY 0x16 +#define REG_TIME_TO_FULL 0x18 +#define REG_STANDBY_CURRENT 0x1a +#define REG_STANDBY_TIME_TO_EMPTY 0x1c +#define REG_MAX_LOAD_CURRENT 0x1e +#define REG_MAX_LOAD_TIME_TO_EMPTY 0x20 +#define REG_AVAILABLE_ENERGY 0x22 +#define REG_AVERAGE_POEWR 0x24 +#define REG_TT_EAT_CONSTANT_POWER 0x26 +#define REG_CYCLE_COUNT 0x2a +#define REG_STATE_OF_CHARGE 0x2c +#define REG_DESIGN_CAPACITY 0x3c +#define REG_DEVICE_NAME_LENGTH 0x62 +#define MAX_DEVICE_NAME_LENGTH 7 +#define REG_DEVICE_NAME 0x63 + +static int bq27541_read(int offset, int *data) +{ + return i2c_read16(I2C_PORT_HOST, BQ27541_ADDR, offset, data); +} + +static int bq27541_read8(int offset, int *data) +{ + return i2c_read8(I2C_PORT_HOST, BQ27541_ADDR, offset, data); +} + +static int bq27541_write(int offset, int data) +{ + return i2c_write16(I2C_PORT_HOST, BQ27541_ADDR, offset, data); +} + +int bq27541_probe(void) +{ + int rv; + int dev_type; + + rv = bq27541_write(REG_CTRL, 0x1); + rv |= bq27541_read(REG_CTRL, &dev_type); + + if (rv) + return rv; + return (dev_type == BQ27541_TYPE_ID) ? EC_SUCCESS : EC_ERROR_UNKNOWN; +} + +int battery_device_name(char *device_name, int buf_size) +{ + int rv, i, val; + int len = MIN(7, buf_size - 1); + + rv = bq27541_read8(REG_DEVICE_NAME_LENGTH, &val); + if (rv) + return rv; + len = MIN(len, val); + + for (i = 0; i < len; ++i) { + rv |= bq27541_read8(REG_DEVICE_NAME + i, &val); + device_name[i] = val; + } + device_name[i] = '\0'; + + return rv; +} + +int battery_temperature(int *deci_kelvin) +{ + return bq27541_read(REG_TEMPERATURE, deci_kelvin); +} + +int battery_voltage(int *voltage) +{ + return bq27541_read(REG_VOLTAGE, voltage); +} + +int battery_state_of_charge(int *percent) +{ + return bq27541_read(REG_STATE_OF_CHARGE, percent); +} + +int battery_state_of_charge_abs(int *percent) +{ + return battery_state_of_charge(percent); +} + +int battery_remaining_capacity(int *capacity) +{ + return bq27541_read(REG_REMAINING_CAPACITY, capacity); +} + +int battery_full_charge_capacity(int *capacity) +{ + return bq27541_read(REG_FULL_CHARGE_CAPACITY, capacity); +} + +int battery_time_to_empty(int *minutes) +{ + return bq27541_read(REG_TIME_TO_EMPTY, minutes); +} + +int battery_time_to_full(int *minutes) +{ + return bq27541_read(REG_TIME_TO_FULL, minutes); +} + +int battery_cycle_count(int *count) +{ + return bq27541_read(REG_CYCLE_COUNT, count); +} + +int battery_design_capacity(int *capacity) +{ + return bq27541_read(REG_DESIGN_CAPACITY, capacity); +} + +int battery_average_current(int *current) +{ + int rv = bq27541_read(REG_AVERAGE_CURRENT, current); + *current = (int)((int16_t)*current); + return rv; +} + +int battery_current(int *current) +{ + return battery_average_current(current); +} + +int battery_time_at_rate(int rate, int *minutes) +{ + int rv; + + rv = bq27541_write(REG_AT_RATE, rate); + if (rv) + return rv; + return bq27541_read(REG_AT_RATE_TIME_TO_EMPTY, minutes); +} + +int battery_manufacturer_name(char *dest, int size) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int battery_device_chemistry(char *dest, int size) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int battery_serial_number(int *serial) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int battery_desired_voltage(int *voltage) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int battery_design_voltage(int *voltage) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int battery_charging_allowed(int *allowed) +{ + int rv, val; + + rv = bq27541_read(REG_FLAGS, &val); + if (rv) + return rv; + *allowed = (val & 0x100); + return EC_SUCCESS; +} + +int battery_desired_current(int *current) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int battery_get_mode(int *mode) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int battery_is_in_10mw_mode(int *val) +{ + /* Always using mAh unit */ + *val = 0; + return EC_SUCCESS; +} + +int battery_set_10mw_mode(int enabled) +{ + /* Not supported by this battery chip */ + return EC_ERROR_INVAL; +} diff --git a/driver/battery/link.c b/driver/battery/link.c new file mode 100644 index 0000000000..a7877529a8 --- /dev/null +++ b/driver/battery/link.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2012 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. + * + * Battery pack vendor provided charging profile + */ + +#include "battery.h" + +/* + * Design capacity + * Battery capacity = 8200 mAh + * 1C = 8200 mA + */ +#define DESIGN_CAPACITY 8200 + +enum { + TEMP_RANGE_10, + TEMP_RANGE_23, + TEMP_RANGE_35, + TEMP_RANGE_45, + TEMP_RANGE_50, + TEMP_RANGE_MAX +}; + +enum { + VOLT_RANGE_7200, + VOLT_RANGE_8000, + VOLT_RANGE_8400, + VOLT_RANGE_MAX +}; + +/* + * Vendor provided charging method + * temp : < 7.2V, 7.2V ~ 8.0V, 8.0V ~ 8.4V + * - 0 ~ 10 : 0.8A 1.6A 0.8A + * - 10 ~ 23 : 1.6A 4.0A 1.6A + * - 23 ~ 35 : 4.0A 4.0A 4.0A + * - 35 ~ 45 : 1.6A 4.0A 1.6A + * - 45 ~ 50 : 0.8A 1.6A 0.8A + */ +static const int const current_limit[TEMP_RANGE_MAX][VOLT_RANGE_MAX] = { + { 800, 1600, 800}, + {1600, 4000, 1600}, + {4000, 4000, 4000}, + {1600, 4000, 1600}, + { 800, 1600, 800}, +}; + +const struct battery_temperature_ranges bat_temp_ranges = { + /* + * Operational temperature range + * 0 <= T_charge <= 50 deg C + * -20 <= T_discharge <= 60 deg C + */ + .start_charging_min_c = 0, + .start_charging_max_c = 50, + .charging_min_c = 0, + .charging_max_c = 50, + .discharging_min_c = -20, + .discharging_max_c = 60, +}; + +static const struct battery_info info = { + /* + * Design voltage + * max = 8.4V + * normal = 7.4V + * min = 6.0V + */ + .voltage_max = 8400, + .voltage_normal = 7400, + .voltage_min = 6000, + + /* Pre-charge current: I <= 0.01C */ + .precharge_current = 64, /* mA */ +}; + +static inline void limit_value(int *val, int limit) +{ + if (*val > limit) + *val = limit; +} + +const struct battery_info *battery_get_info(void) +{ + return &info; +} + +void battery_vendor_params(struct batt_params *batt) +{ + int *desired_current = &batt->desired_current; + int temp_range, volt_range; + int bat_temp_c = DECI_KELVIN_TO_CELSIUS(batt->temperature); + + /* Limit charging voltage */ + if (batt->desired_voltage > info.voltage_max) + batt->desired_voltage = info.voltage_max; + + /* Don't charge if outside of allowable temperature range */ + if (bat_temp_c >= bat_temp_ranges.charging_max_c || + bat_temp_c < bat_temp_ranges.charging_min_c) { + batt->desired_voltage = 0; + batt->desired_current = 0; + return; + } + + if (bat_temp_c <= 10) + temp_range = TEMP_RANGE_10; + else if (bat_temp_c <= 23) + temp_range = TEMP_RANGE_23; + else if (bat_temp_c <= 35) + temp_range = TEMP_RANGE_35; + else if (bat_temp_c <= 45) + temp_range = TEMP_RANGE_45; + else + temp_range = TEMP_RANGE_50; + + if (batt->voltage < 7200) + volt_range = VOLT_RANGE_7200; + else if (batt->voltage < 8000) + volt_range = VOLT_RANGE_8000; + else + volt_range = VOLT_RANGE_8400; + + limit_value(desired_current, current_limit[temp_range][volt_range]); + + /* If battery wants current, give it at least the precharge current */ + if (*desired_current > 0 && *desired_current < info.precharge_current) + *desired_current = info.precharge_current; +} diff --git a/driver/battery/smart.c b/driver/battery/smart.c new file mode 100644 index 0000000000..29af6286ad --- /dev/null +++ b/driver/battery/smart.c @@ -0,0 +1,356 @@ +/* Copyright (c) 2012 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. + * + * Smart battery driver. + */ + +#include "battery.h" +#include "battery_smart.h" +#include "host_command.h" +#include "i2c.h" +#include "timer.h" + +test_mockable int sbc_read(int cmd, int *param) +{ + return i2c_read16(I2C_PORT_CHARGER, CHARGER_ADDR, cmd, param); +} + +test_mockable int sbc_write(int cmd, int param) +{ + return i2c_write16(I2C_PORT_CHARGER, CHARGER_ADDR, cmd, param); +} + +int sb_read(int cmd, int *param) +{ + return i2c_read16(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param); +} + +int sb_write(int cmd, int param) +{ + return i2c_write16(I2C_PORT_BATTERY, BATTERY_ADDR, cmd, param); +} + +int battery_get_mode(int *mode) +{ + return sb_read(SB_BATTERY_MODE, mode); +} + +int battery_set_mode(int mode) +{ + return sb_write(SB_BATTERY_MODE, mode); +} + +int battery_is_in_10mw_mode(int *ret) +{ + int val; + int rv = battery_get_mode(&val); + if (rv) + return rv; + *ret = val & MODE_CAPACITY; + return EC_SUCCESS; +} + +int battery_set_10mw_mode(int enabled) +{ + int val, rv; + rv = battery_get_mode(&val); + if (rv) + return rv; + if (enabled) + val |= MODE_CAPACITY; + else + val &= ~MODE_CAPACITY; + return battery_set_mode(val); +} + +int battery_temperature(int *deci_kelvin) +{ + return sb_read(SB_TEMPERATURE, deci_kelvin); +} + +int battery_voltage(int *voltage) +{ + return sb_read(SB_VOLTAGE, voltage); +} + +int battery_state_of_charge(int *percent) +{ + return sb_read(SB_RELATIVE_STATE_OF_CHARGE, percent); +} + +int battery_state_of_charge_abs(int *percent) +{ + return sb_read(SB_ABSOLUTE_STATE_OF_CHARGE, percent); +} + +int battery_remaining_capacity(int *capacity) +{ + return sb_read(SB_REMAINING_CAPACITY, capacity); +} + +int battery_full_charge_capacity(int *capacity) +{ + return sb_read(SB_FULL_CHARGE_CAPACITY, capacity); +} + +int battery_time_to_empty(int *minutes) +{ + return sb_read(SB_AVERAGE_TIME_TO_EMPTY, minutes); +} + +int battery_run_time_to_empty(int *minutes) +{ + return sb_read(SB_RUN_TIME_TO_EMPTY, minutes); +} + +int battery_time_to_full(int *minutes) +{ + return sb_read(SB_AVERAGE_TIME_TO_FULL, minutes); +} + +int battery_desired_current(int *current) +{ + return sb_read(SB_CHARGING_CURRENT, current); +} + +int battery_desired_voltage(int *voltage) +{ + return sb_read(SB_CHARGING_VOLTAGE, voltage); +} + +int battery_charging_allowed(int *allowed) +{ + int v, c, rv; + + /* + * TODO(rspangler): This re-reads the battery current and voltage, + * which is silly because charge_state.c just read them. + */ + rv = battery_desired_voltage(&v) | battery_desired_current(&c); + if (rv) + return rv; + *allowed = (v != 0) && (c != 0); + return EC_SUCCESS; +} + +/* Read battery status */ +int battery_status(int *status) +{ + return sb_read(SB_BATTERY_STATUS, status); +} + +/* Battery charge cycle count */ +int battery_cycle_count(int *count) +{ + return sb_read(SB_CYCLE_COUNT, count); +} + +/* Designed battery capacity + * unit: mAh or 10mW depends on battery mode + */ +int battery_design_capacity(int *capacity) +{ + return sb_read(SB_DESIGN_CAPACITY, capacity); +} + +/* Designed battery output voltage + * unit: mV + */ +int battery_design_voltage(int *voltage) +{ + return sb_read(SB_DESIGN_VOLTAGE, voltage); +} + +/* Read serial number */ +int battery_serial_number(int *serial) +{ + return sb_read(SB_SERIAL_NUMBER, serial); +} + +/* Read battery discharging current + * unit: mA + * negative value: charging + */ +int battery_current(int *current) +{ + int rv, d; + + rv = sb_read(SB_CURRENT, &d); + if (rv) + return rv; + + *current = (int16_t)d; + return EC_SUCCESS; +} + + +int battery_average_current(int *current) +{ + int rv, d; + + rv = sb_read(SB_AVERAGE_CURRENT, &d); + if (rv) + return rv; + + *current = (int16_t)d; + return EC_SUCCESS; +} + +test_mockable int battery_time_at_rate(int rate, int *minutes) +{ + int rv; + int ok, time; + int loop, cmd, output_sign; + + if (rate == 0) { + *minutes = 0; + return EC_ERROR_INVAL; + } + + rv = sb_write(SB_AT_RATE, rate); + if (rv) + return rv; + loop = 5; + while (loop--) { + rv = sb_read(SB_AT_RATE_OK, &ok); + if (rv) + return rv; + if (ok) { + if (rate > 0) { + cmd = SB_AT_RATE_TIME_TO_FULL; + output_sign = -1; + } else { + cmd = SB_AT_RATE_TIME_TO_EMPTY; + output_sign = 1; + } + rv = sb_read(cmd, &time); + if (rv) + return rv; + + *minutes = (time == 0xffff) ? 0 : output_sign * time; + return EC_SUCCESS; + } else { + /* wait 10ms for AT_RATE_OK */ + msleep(10); + } + } + return EC_ERROR_TIMEOUT; +} + +test_mockable int battery_manufacture_date(int *year, int *month, int *day) +{ + int rv; + int ymd; + + rv = sb_read(SB_SPECIFICATION_INFO, &ymd); + if (rv) + return rv; + + /* battery date format: + * ymd = day + month * 32 + (year - 1980) * 256 + */ + *year = (ymd >> 8) + 1980; + *month = (ymd & 0xff) / 32; + *day = (ymd & 0xff) % 32; + + return EC_SUCCESS; +} + +/* Read manufacturer name */ +test_mockable int battery_manufacturer_name(char *dest, int size) +{ + return i2c_read_string(I2C_PORT_BATTERY, BATTERY_ADDR, + SB_MANUFACTURER_NAME, dest, size); +} + +/* Read device name */ +test_mockable int battery_device_name(char *dest, int size) +{ + return i2c_read_string(I2C_PORT_BATTERY, BATTERY_ADDR, + SB_DEVICE_NAME, dest, size); +} + +/* Read battery type/chemistry */ +test_mockable int battery_device_chemistry(char *dest, int size) +{ + return i2c_read_string(I2C_PORT_BATTERY, BATTERY_ADDR, + SB_DEVICE_CHEMISTRY, dest, size); +} + +/*****************************************************************************/ +/* Smart battery pass-through + */ +#ifdef CONFIG_I2C_PASSTHROUGH +static int host_command_sb_read_word(struct host_cmd_handler_args *args) +{ + int rv; + int val; + const struct ec_params_sb_rd *p = args->params; + struct ec_response_sb_rd_word *r = args->response; + + if (p->reg > 0x1c) + return EC_RES_INVALID_PARAM; + rv = i2c_read16(I2C_PORT_BATTERY, BATTERY_ADDR, p->reg, &val); + if (rv) + return EC_RES_ERROR; + + r->value = val; + args->response_size = sizeof(struct ec_response_sb_rd_word); + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_SB_READ_WORD, + host_command_sb_read_word, + EC_VER_MASK(0)); + +static int host_command_sb_write_word(struct host_cmd_handler_args *args) +{ + int rv; + const struct ec_params_sb_wr_word *p = args->params; + + if (p->reg > 0x1c) + return EC_RES_INVALID_PARAM; + rv = i2c_write16(I2C_PORT_BATTERY, BATTERY_ADDR, p->reg, p->value); + if (rv) + return EC_RES_ERROR; + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_SB_WRITE_WORD, + host_command_sb_write_word, + EC_VER_MASK(0)); + +static int host_command_sb_read_block(struct host_cmd_handler_args *args) +{ + int rv; + const struct ec_params_sb_rd *p = args->params; + struct ec_response_sb_rd_block *r = args->response; + + if ((p->reg != SB_MANUFACTURER_NAME) && + (p->reg != SB_DEVICE_NAME) && + (p->reg != SB_DEVICE_CHEMISTRY) && + (p->reg != SB_MANUFACTURER_DATA)) + return EC_RES_INVALID_PARAM; + rv = i2c_read_string(I2C_PORT_BATTERY, BATTERY_ADDR, p->reg, + r->data, 32); + if (rv) + return EC_RES_ERROR; + + args->response_size = sizeof(struct ec_response_sb_rd_block); + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_SB_READ_BLOCK, + host_command_sb_read_block, + EC_VER_MASK(0)); + +static int host_command_sb_write_block(struct host_cmd_handler_args *args) +{ + /* Not implemented */ + return EC_RES_INVALID_COMMAND; +} +DECLARE_HOST_COMMAND(EC_CMD_SB_WRITE_BLOCK, + host_command_sb_write_block, + EC_VER_MASK(0)); +#endif diff --git a/driver/build.mk b/driver/build.mk new file mode 100644 index 0000000000..013cfb4159 --- /dev/null +++ b/driver/build.mk @@ -0,0 +1,34 @@ +# -*- makefile -*- +# 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. +# +# Drivers for off-chip devices +# + +# Batteries +driver-$(CONFIG_BATTERY_BQ20Z453)+=battery/bq20z453.o +driver-$(CONFIG_BATTERY_BQ27541)+=battery/bq27541.o +driver-$(CONFIG_BATTERY_LINK)+=battery/link.o +driver-$(CONFIG_BATTERY_SMART)+=battery/smart.o + +# Battery charger ICs +driver-$(CONFIG_CHARGER_BQ24192)+=charger/bq24192.o +driver-$(CONFIG_CHARGER_BQ24707A)+=charger/bq24707a.o +driver-$(CONFIG_CHARGER_BQ24715)+=charger/bq24715.o +driver-$(CONFIG_CHARGER_BQ24725)+=charger/bq24725.o +driver-$(CONFIG_CHARGER_BQ24738)+=charger/bq24738.o + +# LED drivers +driver-$(CONFIG_LED_DRIVER_DS2413)+=led/ds2413.o +driver-$(CONFIG_LED_DRIVER_LP5562)+=led/lp5562.o + +# Voltage regulators +driver-$(CONFIG_REGULATOR_IR357X)+=regulator_ir357x.o + +# Temperature sensors +driver-$(CONFIG_TEMP_SENSOR_G781)+=temp_sensor/g781.o +driver-$(CONFIG_TEMP_SENSOR_TMP006)+=temp_sensor/tmp006.o + +# USB switches +driver-$(CONFIG_USB_SWITCH_TSU6721)+=usb_switch_tsu6721.o diff --git a/driver/charger/bq24192.c b/driver/charger/bq24192.c new file mode 100644 index 0000000000..72f778d367 --- /dev/null +++ b/driver/charger/bq24192.c @@ -0,0 +1,273 @@ +/* Copyright (c) 2012 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. + * + * TI bq24192 battery charger driver. + */ + +#include "charger.h" +#include "charger_bq24192.h" +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "i2c.h" +#include "printf.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_CHARGER, outstr) +#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args) + +/* Charger information */ +static const struct charger_info bq24192_charger_info = { + .name = "bq24192", + .voltage_max = 4400, + .voltage_min = 3504, + .voltage_step = 16, + .current_max = 4544, + .current_min = 512, + .current_step = 64, + .input_current_max = 3000, + .input_current_min = 100, + .input_current_step = -1, +}; + +static const int input_current_steps[] = { + 100, 150, 500, 900, 1200, 1500, 2000, 3000}; + +int bq24192_read(int reg, int *value) +{ + return i2c_read8(I2C_PORT_HOST, BQ24192_ADDR, reg, value); +} + +int bq24192_write(int reg, int value) +{ + return i2c_write8(I2C_PORT_HOST, BQ24192_ADDR, reg, value); +} + +static int bq24192_watchdog_reset(void) +{ + int rv, val; + + rv = bq24192_read(BQ24192_REG_POWER_ON_CFG, &val); + if (rv) + return rv; + val |= (1 << 6); + return bq24192_write(BQ24192_REG_POWER_ON_CFG, val) || + bq24192_write(BQ24192_REG_POWER_ON_CFG, val); +} + +static int bq24192_set_terminate_current(int current) +{ + int reg_val, rv; + int val = (current - 128) / 128; + + rv = bq24192_read(BQ24192_REG_PRE_CHG_CURRENT, ®_val); + if (rv) + return rv; + reg_val = (reg_val & ~0xf) | (val & 0xf); + return bq24192_write(BQ24192_REG_PRE_CHG_CURRENT, reg_val); +} + +int charger_enable_otg_power(int enabled) +{ + int val, rv; + + gpio_set_level(GPIO_BCHGR_OTG, enabled); + rv = bq24192_read(BQ24192_REG_POWER_ON_CFG, &val); + if (rv) + return rv; + val = (val & ~0x30) | (enabled ? 0x20 : 0x10); + return bq24192_write(BQ24192_REG_POWER_ON_CFG, val); +} + +int charger_set_input_current(int input_current) +{ + int i, value, rv; + + for (i = 1; i < ARRAY_SIZE(input_current_steps); ++i) + if (input_current_steps[i] > input_current) { + --i; + break; + } + if (i == ARRAY_SIZE(input_current_steps)) + --i; + + rv = bq24192_read(BQ24192_REG_INPUT_CTRL, &value); + if (rv) + return rv; + value = value & ~(0x7); + value |= (i & 0x7); + return bq24192_write(BQ24192_REG_INPUT_CTRL, value); +} + +int charger_get_input_current(int *input_current) +{ + int rv, value; + + rv = bq24192_read(BQ24192_REG_INPUT_CTRL, &value); + if (rv) + return rv; + *input_current = input_current_steps[value & 0x7]; + return EC_SUCCESS; +} + +int charger_manufacturer_id(int *id) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int charger_device_id(int *id) +{ + return bq24192_read(BQ24192_REG_ID, id); +} + +int charger_get_option(int *option) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int charger_set_option(int option) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +const struct charger_info *charger_get_info(void) +{ + return &bq24192_charger_info; +} + +int charger_get_status(int *status) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int charger_set_mode(int mode) +{ + return EC_ERROR_UNIMPLEMENTED; +} + +int charger_get_current(int *current) +{ + int rv, val; + const struct charger_info * const info = charger_get_info(); + + rv = bq24192_read(BQ24192_REG_CHG_CURRENT, &val); + if (rv) + return rv; + val = (val >> 2) & 0x3f; + *current = val * info->current_step + info->current_min; + return EC_SUCCESS; +} + +int charger_set_current(int current) +{ + int rv, val; + const struct charger_info * const info = charger_get_info(); + + current = charger_closest_current(current); + rv = bq24192_read(BQ24192_REG_CHG_CURRENT, &val); + if (rv) + return rv; + val = val & 0x3; + val |= ((current - info->current_min) / info->current_step) << 2; + return bq24192_write(BQ24192_REG_CHG_CURRENT, val); +} + +int charger_get_voltage(int *voltage) +{ + int rv, val; + const struct charger_info * const info = charger_get_info(); + + rv = bq24192_read(BQ24192_REG_CHG_VOLTAGE, &val); + if (rv) + return rv; + val = (val >> 2) & 0x3f; + *voltage = val * info->voltage_step + info->voltage_min; + return EC_SUCCESS; +} + +int charger_set_voltage(int voltage) +{ + int rv, val; + const struct charger_info * const info = charger_get_info(); + + rv = bq24192_read(BQ24192_REG_CHG_VOLTAGE, &val); + if (rv) + return rv; + val = val & 0x3; + val |= ((voltage - info->voltage_min) / info->voltage_step) << 2; + return bq24192_write(BQ24192_REG_CHG_VOLTAGE, val); +} + +/* Charging power state initialization */ +int charger_post_init(void) +{ + /* Input current controlled by extpower module. Do nothing here. */ + return EC_SUCCESS; +} + + +/*****************************************************************************/ +/* Hooks */ + +static void bq24192_init(void) +{ + int val, rv; + + if (charger_device_id(&val) || val != BQ24192_DEVICE_ID) { + CPRINTF("[%T BQ24192 incorrent ID: 0x%02x]\n", val); + return; + } + + /* + * Disable I2C watchdog timer. + * TODO(victoryang): Re-enable watchdog timer and kick it periodically + * in charger task. + */ + rv = bq24192_read(BQ24192_REG_CHG_TERM_TMR, &val); + if (rv) + return; + val &= ~0x30; + rv = bq24192_write(BQ24192_REG_CHG_TERM_TMR, val); + if (rv) + return; + + if (bq24192_set_terminate_current(128)) + return; + + if (bq24192_watchdog_reset()) + return; + + CPRINTF("[%T BQ24192 initialized]\n"); +} +DECLARE_HOOK(HOOK_INIT, bq24192_init, HOOK_PRIO_LAST); + +/*****************************************************************************/ +/* Console commands */ + +static int command_bq24192(int argc, char **argv) +{ + int i; + int value; + int rv; + + ccprintf("REG:"); + for (i = 0; i <= 0xa; ++i) + ccprintf(" %02x", i); + ccprintf("\n"); + + ccprintf("VAL:"); + for (i = 0; i <= 0xa; ++i) { + rv = bq24192_read(i, &value); + if (rv) + return rv; + ccprintf(" %02x", value); + } + ccprintf("\n"); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(bq24192, command_bq24192, + NULL, NULL, NULL); diff --git a/driver/charger/bq24707a.c b/driver/charger/bq24707a.c new file mode 100644 index 0000000000..43bd32eb91 --- /dev/null +++ b/driver/charger/bq24707a.c @@ -0,0 +1,167 @@ +/* Copyright (c) 2012 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. + * + * TI bq24707A battery charger driver. + */ + +#include "battery_smart.h" +#include "charger.h" +#include "charger_bq24707a.h" +#include "console.h" +#include "common.h" +#include "util.h" + +/* Sense resistor configurations and macros */ +#define DEFAULT_SENSE_RESISTOR 10 +#define R_SNS CONFIG_CHARGER_SENSE_RESISTOR +#define R_AC CONFIG_CHARGER_SENSE_RESISTOR_AC +#define REG_TO_CURRENT(REG, RS) ((REG) * DEFAULT_SENSE_RESISTOR / (RS)) +#define CURRENT_TO_REG(CUR, RS) ((CUR) * (RS) / DEFAULT_SENSE_RESISTOR) + +/* + * charge voltage bitmask: 0111 1111 1111 0000 + * charge current bitmask: 0001 1111 1100 0000 + * input current bitmask : 0001 1111 1000 0000 + */ +static const struct charger_info bq24707a_charger_info = { + .name = "bq24707A", + .voltage_max = 19200, + .voltage_min = 1024, + .voltage_step = 16, + .current_max = REG_TO_CURRENT(0x1fc0, R_SNS), + .current_min = REG_TO_CURRENT(0x40, R_SNS), + .current_step = REG_TO_CURRENT(0x40, R_SNS), + .input_current_max = REG_TO_CURRENT(0x1F80, R_AC), + .input_current_min = REG_TO_CURRENT(0x80, R_AC), + .input_current_step = REG_TO_CURRENT(0x80, R_AC), +}; + +/* bq24707a specific interfaces */ + +int charger_set_input_current(int input_current) +{ + return sbc_write(BQ24707_INPUT_CURRENT, + CURRENT_TO_REG(input_current, R_AC)); +} + +int charger_get_input_current(int *input_current) +{ + int rv; + int reg; + + rv = sbc_read(BQ24707_INPUT_CURRENT, ®); + if (rv) + return rv; + + *input_current = REG_TO_CURRENT(reg, R_AC); + + return EC_SUCCESS; +} + +int charger_manufacturer_id(int *id) +{ + return sbc_read(BQ24707_MANUFACTURE_ID, id); +} + +int charger_device_id(int *id) +{ + return sbc_read(BQ24707_DEVICE_ID, id); +} + +int charger_get_option(int *option) +{ + return sbc_read(BQ24707_CHARGE_OPTION, option); +} + +int charger_set_option(int option) +{ + return sbc_write(BQ24707_CHARGE_OPTION, option); +} + +/* Charger interfaces */ + +const struct charger_info *charger_get_info(void) +{ + return &bq24707a_charger_info; +} + +int charger_get_status(int *status) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + /* Default status */ + *status = CHARGER_LEVEL_2; + + if (option & OPTION_CHARGE_INHIBIT) + *status |= CHARGER_CHARGE_INHIBITED; + + return EC_SUCCESS; +} + +int charger_set_mode(int mode) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + if (mode & CHARGE_FLAG_INHIBIT_CHARGE) + option |= OPTION_CHARGE_INHIBIT; + else + option &= ~OPTION_CHARGE_INHIBIT; + return charger_set_option(option); +} + +int charger_get_current(int *current) +{ + int rv; + int reg; + + rv = sbc_read(SB_CHARGING_CURRENT, ®); + if (rv) + return rv; + + *current = REG_TO_CURRENT(reg, R_SNS); + return EC_SUCCESS; +} + +int charger_set_current(int current) +{ + current = charger_closest_current(current); + + return sbc_write(SB_CHARGING_CURRENT, CURRENT_TO_REG(current, R_SNS)); +} + +int charger_get_voltage(int *voltage) +{ + return sbc_read(SB_CHARGING_VOLTAGE, voltage); +} + +int charger_set_voltage(int voltage) +{ + return sbc_write(SB_CHARGING_VOLTAGE, voltage); +} + +/* Charging power state initialization */ +int charger_post_init(void) +{ + /* + * Note: bq24725 power on reset state is: + * watch dog timer = 175 sec + * input current limit = ~1/2 maximum setting + * charging voltage = 0 mV + * charging current = 0 mA + * IOUT = 20x adapter current sense + */ + + /* Set charger input current limit */ + return charger_set_input_current(CONFIG_CHARGER_INPUT_CURRENT); +} diff --git a/driver/charger/bq24715.c b/driver/charger/bq24715.c new file mode 100644 index 0000000000..d90b35b021 --- /dev/null +++ b/driver/charger/bq24715.c @@ -0,0 +1,192 @@ +/* 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. + * + * TI bq24715 battery charger driver. + */ + +#include "battery_smart.h" +#include "charger.h" +#include "charger_bq24715.h" +#include "console.h" +#include "common.h" +#include "util.h" + +/* Sense resistor configurations and macros */ +#define DEFAULT_SENSE_RESISTOR 10 +#define R_SNS CONFIG_CHARGER_SENSE_RESISTOR +#define R_AC CONFIG_CHARGER_SENSE_RESISTOR_AC +#define REG_TO_CURRENT(REG, RS) ((REG) * DEFAULT_SENSE_RESISTOR / (RS)) +#define CURRENT_TO_REG(CUR, RS) ((CUR) * (RS) / DEFAULT_SENSE_RESISTOR) + +/* Note: it is assumed that the sense resistors are 10mOhm. */ + +static const struct charger_info bq24725_charger_info = { + .name = "bq24715", + .voltage_max = CHARGE_V_MAX, + .voltage_min = CHARGE_V_MIN, + .voltage_step = CHARGE_V_STEP, + .current_max = REG_TO_CURRENT(CHARGE_I_MAX, R_SNS), + .current_min = REG_TO_CURRENT(CHARGE_I_MIN, R_SNS), + .current_step = REG_TO_CURRENT(CHARGE_I_STEP, R_SNS), + .input_current_max = REG_TO_CURRENT(INPUT_I_MAX, R_AC), + .input_current_min = REG_TO_CURRENT(INPUT_I_MIN, R_AC), + .input_current_step = REG_TO_CURRENT(INPUT_I_STEP, R_AC), +}; + +int charger_set_input_current(int input_current) +{ + return sbc_write(BQ24715_INPUT_CURRENT, + CURRENT_TO_REG(input_current, R_AC)); +} + +int charger_get_input_current(int *input_current) +{ + int rv; + int reg; + + rv = sbc_read(BQ24715_INPUT_CURRENT, ®); + if (rv) + return rv; + + *input_current = REG_TO_CURRENT(reg, R_AC); + + return EC_SUCCESS; +} + +int charger_manufacturer_id(int *id) +{ + return sbc_read(BQ24715_MANUFACTURER_ID, id); +} + +int charger_device_id(int *id) +{ + return sbc_read(BQ24715_DEVICE_ID, id); +} + +int charger_get_option(int *option) +{ + return sbc_read(BQ24715_CHARGE_OPTION, option); +} + +int charger_set_option(int option) +{ + return sbc_write(BQ24715_CHARGE_OPTION, option); +} + +/* Charger interfaces */ + +const struct charger_info *charger_get_info(void) +{ + return &bq24725_charger_info; +} + +int charger_get_status(int *status) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + /* Default status */ + *status = CHARGER_LEVEL_2; + + if ((option & OPT_CHARGE_INHIBIT_MASK) == OPT_CHARGE_DISABLE) + *status |= CHARGER_CHARGE_INHIBITED; + + return EC_SUCCESS; +} + +int charger_set_mode(int mode) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + option &= ~OPT_CHARGE_INHIBIT_MASK; + if (mode & CHARGE_FLAG_INHIBIT_CHARGE) + option |= OPT_CHARGE_DISABLE; + else + option |= OPT_CHARGE_ENABLE; + return charger_set_option(option); +} + +int charger_get_current(int *current) +{ + int rv; + int reg; + + rv = sbc_read(SB_CHARGING_CURRENT, ®); + if (rv) + return rv; + + *current = REG_TO_CURRENT(reg, R_SNS); + return EC_SUCCESS; +} + +int charger_set_current(int current) +{ + current = charger_closest_current(current); + + return sbc_write(SB_CHARGING_CURRENT, CURRENT_TO_REG(current, R_SNS)); +} + +/* The voltage setting needs to be cached to work with the current + * charging infrastructure and state machine. The reason is that + * the state machine expects to be able to set a 0V charging voltage. + * The bq24715 does not allow this in the hardware register. Therefore + * 0V is handled specially to appease the state machine. */ +static int cached_voltage; + +int charger_get_voltage(int *voltage) +{ + int ret; + + if (cached_voltage == 0) { + *voltage = cached_voltage; + return EC_SUCCESS; + } + + ret = sbc_read(SB_CHARGING_VOLTAGE, &cached_voltage); + + if (ret == EC_SUCCESS) + *voltage = cached_voltage; + + return ret; +} + +int charger_set_voltage(int voltage) +{ + cached_voltage = voltage; + return sbc_write(SB_CHARGING_VOLTAGE, voltage); +} + +/* Charging power state initialization */ +int charger_post_init(void) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + /* Don't be noisy */ + option |= OPT_AUDIO_FREQ_40KHZ_LIMIT; + + /* Always monitor adapter current (40X multiplier). */ + option |= OPT_FIX_IOUT_ALWAYS; + option &= ~OPT_IOUT_MASK; + + rv = charger_set_option(option); + if (rv) + return rv; + + rv = charger_set_input_current(CONFIG_CHARGER_INPUT_CURRENT); + return rv; +} diff --git a/driver/charger/bq24725.c b/driver/charger/bq24725.c new file mode 100644 index 0000000000..3e418c7216 --- /dev/null +++ b/driver/charger/bq24725.c @@ -0,0 +1,166 @@ +/* Copyright (c) 2012 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. + * + * TI bq24725 battery charger driver. + */ + +#include "battery_smart.h" +#include "charger.h" +#include "charger_bq24725.h" +#include "console.h" +#include "common.h" +#include "util.h" + +/* Sense resistor configurations and macros */ +#define DEFAULT_SENSE_RESISTOR 10 +#define R_SNS CONFIG_CHARGER_SENSE_RESISTOR +#define R_AC CONFIG_CHARGER_SENSE_RESISTOR_AC +#define REG_TO_CURRENT(REG, RS) ((REG) * DEFAULT_SENSE_RESISTOR / (RS)) +#define CURRENT_TO_REG(CUR, RS) ((CUR) * (RS) / DEFAULT_SENSE_RESISTOR) + +/* Charger infomation + * charge voltage bitmask: 0111 1111 1111 0000 + * charge current bitmask: 0001 1111 1000 0000 + * input current bitmask : 0000 0000 1000 0000 + */ +static const struct charger_info bq24725_charger_info = { + .name = "bq24725", + .voltage_max = 19200, + .voltage_min = 1024, + .voltage_step = 16, + .current_max = REG_TO_CURRENT(8128, R_SNS), + .current_min = REG_TO_CURRENT(128, R_SNS), + .current_step = REG_TO_CURRENT(128, R_SNS), + .input_current_max = REG_TO_CURRENT(8064, R_AC), + .input_current_min = REG_TO_CURRENT(128, R_AC), + .input_current_step = REG_TO_CURRENT(128, R_AC), +}; + +/* bq24725 specific interfaces */ + +int charger_set_input_current(int input_current) +{ + return sbc_write(BQ24725_INPUT_CURRENT, + CURRENT_TO_REG(input_current, R_AC)); +} + +int charger_get_input_current(int *input_current) +{ + int rv; + int reg; + + rv = sbc_read(BQ24725_INPUT_CURRENT, ®); + if (rv) + return rv; + + *input_current = REG_TO_CURRENT(reg, R_AC); + + return EC_SUCCESS; +} + +int charger_manufacturer_id(int *id) +{ + return sbc_read(BQ24725_MANUFACTURE_ID, id); +} + +int charger_device_id(int *id) +{ + return sbc_read(BQ24725_DEVICE_ID, id); +} + +int charger_get_option(int *option) +{ + return sbc_read(BQ24725_CHARGE_OPTION, option); +} + +int charger_set_option(int option) +{ + return sbc_write(BQ24725_CHARGE_OPTION, option); +} + +/* Charger interfaces */ + +const struct charger_info *charger_get_info(void) +{ + return &bq24725_charger_info; +} + +int charger_get_status(int *status) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + /* Default status */ + *status = CHARGER_LEVEL_2; + + if (option & OPTION_CHARGE_INHIBIT) + *status |= CHARGER_CHARGE_INHIBITED; + + return EC_SUCCESS; +} + +int charger_set_mode(int mode) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + if (mode & CHARGE_FLAG_INHIBIT_CHARGE) + option |= OPTION_CHARGE_INHIBIT; + else + option &= ~OPTION_CHARGE_INHIBIT; + return charger_set_option(option); +} + +int charger_get_current(int *current) +{ + int rv; + int reg; + + rv = sbc_read(SB_CHARGING_CURRENT, ®); + if (rv) + return rv; + + *current = REG_TO_CURRENT(reg, R_SNS); + return EC_SUCCESS; +} + +int charger_set_current(int current) +{ + current = charger_closest_current(current); + + return sbc_write(SB_CHARGING_CURRENT, CURRENT_TO_REG(current, R_SNS)); +} + +int charger_get_voltage(int *voltage) +{ + return sbc_read(SB_CHARGING_VOLTAGE, voltage); +} + +int charger_set_voltage(int voltage) +{ + return sbc_write(SB_CHARGING_VOLTAGE, voltage); +} + +/* Charging power state initialization */ +int charger_post_init(void) +{ + /* + * Note: bq24725 power on reset state is: + * watch dog timer = 175 sec + * input current limit = ~1/2 maximum setting + * charging voltage = 0 mV + * charging current = 0 mA + */ + + /* Set charger input current limit */ + return charger_set_input_current(CONFIG_CHARGER_INPUT_CURRENT); +} diff --git a/driver/charger/bq24738.c b/driver/charger/bq24738.c new file mode 100644 index 0000000000..057e71c12f --- /dev/null +++ b/driver/charger/bq24738.c @@ -0,0 +1,188 @@ +/* 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. + * + * TI bq24738 battery charger driver. + */ + +#include "battery_smart.h" +#include "charger.h" +#include "charger_bq24738.h" +#include "console.h" +#include "common.h" +#include "util.h" + +/* Sense resistor configurations and macros */ +#define DEFAULT_SENSE_RESISTOR 10 +#define R_SNS CONFIG_CHARGER_SENSE_RESISTOR +#define R_AC CONFIG_CHARGER_SENSE_RESISTOR_AC +#define REG_TO_CURRENT(REG, RS) ((REG) * DEFAULT_SENSE_RESISTOR / (RS)) +#define CURRENT_TO_REG(CUR, RS) ((CUR) * (RS) / DEFAULT_SENSE_RESISTOR) + +/* Charger infomation + * charge voltage bitmask: 0111 1111 1111 0000 + * charge current bitmask: 0001 1111 1100 0000 + * input current bitmask : 0000 0000 1000 0000 + */ +static const struct charger_info bq24738_charger_info = { + .name = "bq24738", + .voltage_max = 19200, + .voltage_min = 1024, + .voltage_step = 16, + .current_max = REG_TO_CURRENT(8128, R_SNS), + .current_min = REG_TO_CURRENT(128, R_SNS), + .current_step = REG_TO_CURRENT(64, R_SNS), + .input_current_max = REG_TO_CURRENT(8064, R_AC), + .input_current_min = REG_TO_CURRENT(128, R_AC), + .input_current_step = REG_TO_CURRENT(128, R_AC), +}; + +/* bq24738 specific interfaces */ + +int charger_set_input_current(int input_current) +{ + return sbc_write(BQ24738_INPUT_CURRENT, + CURRENT_TO_REG(input_current, R_AC)); +} + +int charger_get_input_current(int *input_current) +{ + int rv; + int reg; + + rv = sbc_read(BQ24738_INPUT_CURRENT, ®); + if (rv) + return rv; + + *input_current = REG_TO_CURRENT(reg, R_AC); + + return EC_SUCCESS; +} + +int charger_manufacturer_id(int *id) +{ + return sbc_read(BQ24738_MANUFACTURE_ID, id); +} + +int charger_device_id(int *id) +{ + return sbc_read(BQ24738_DEVICE_ID, id); +} + +int charger_get_option(int *option) +{ + return sbc_read(BQ24738_CHARGE_OPTION, option); +} + +int charger_set_option(int option) +{ + return sbc_write(BQ24738_CHARGE_OPTION, option); +} + +/* Charger interfaces */ + +const struct charger_info *charger_get_info(void) +{ + return &bq24738_charger_info; +} + +int charger_get_status(int *status) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + /* Default status */ + *status = CHARGER_LEVEL_2; + + if (option & OPTION_CHARGE_INHIBIT) + *status |= CHARGER_CHARGE_INHIBITED; + + return EC_SUCCESS; +} + +int charger_set_mode(int mode) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + if (mode & CHARGE_FLAG_INHIBIT_CHARGE) + option |= OPTION_CHARGE_INHIBIT; + else + option &= ~OPTION_CHARGE_INHIBIT; + return charger_set_option(option); +} + +int charger_get_current(int *current) +{ + int rv; + int reg; + + rv = sbc_read(SB_CHARGING_CURRENT, ®); + if (rv) + return rv; + + *current = REG_TO_CURRENT(reg, R_SNS); + return EC_SUCCESS; +} + +int charger_set_current(int current) +{ + current = charger_closest_current(current); + + return sbc_write(SB_CHARGING_CURRENT, CURRENT_TO_REG(current, R_SNS)); +} + +int charger_get_voltage(int *voltage) +{ + return sbc_read(SB_CHARGING_VOLTAGE, voltage); +} + +int charger_set_voltage(int voltage) +{ + return sbc_write(SB_CHARGING_VOLTAGE, voltage); +} + +/* Charging power state initialization */ +int charger_post_init(void) +{ + int rv; + int val; + + /* Disable IFAULT_HI. See crosbug.com/p/19868 */ + rv = charger_get_option(&val); + if (rv) + return rv; + val &= ~OPTION_IFAULT_HI_ENABLE; + rv = charger_set_option(val); + if (rv) + return rv; + + /* Set charger input current limit */ + rv = charger_set_input_current(CONFIG_CHARGER_INPUT_CURRENT); + return rv; +} + +int charger_discharge_on_ac(int enable) +{ + int rv; + int option; + + rv = charger_get_option(&option); + if (rv) + return rv; + + if (enable) + rv = charger_set_option(option | OPTION_LEARN_ENABLE); + else + rv = charger_set_option(option & ~OPTION_LEARN_ENABLE); + + return rv; +} diff --git a/driver/led/ds2413.c b/driver/led/ds2413.c new file mode 100644 index 0000000000..9babd75992 --- /dev/null +++ b/driver/led/ds2413.c @@ -0,0 +1,166 @@ +/* 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. + */ + +/* Power LED control for Chrome EC */ + +#include "charge_state.h" +#include "console.h" +#include "hooks.h" +#include "onewire.h" +#include "timer.h" +#include "util.h" + +#define ONEWIRE_RETRIES 10 + +enum led_color { + LED_OFF = 0, + LED_RED, + LED_YELLOW, + LED_GREEN, + LED_COLOR_COUNT /* Number of colors, not a color itself */ +}; + +static const uint8_t led_masks[LED_COLOR_COUNT] = {0xff, 0xfe, 0xfc, 0xfd}; +static const char * const color_names[LED_COLOR_COUNT] = { + "off", "red", "yellow", "green"}; + +/** + * Set the onewire LED GPIO controller outputs + * + * @param mask Mask of outputs to enable + * + * @return EC_SUCCESS, or non-zero if error. + */ +static int led_set_mask(int mask) +{ + int rv; + + /* Reset the 1-wire bus */ + rv = onewire_reset(); + if (rv) + return rv; + + /* Skip ROM, since only one device */ + onewire_write(0xcc); + + /* Write and turn on the LEDs */ + onewire_write(0x5a); + onewire_write(mask); + onewire_write(~mask); /* Repeat inverted */ + + rv = onewire_read(); /* Confirmation byte */ + if (rv != 0xaa) + return EC_ERROR_UNKNOWN; + + /* The next byte is a read-back of the chip status. Since we're only + * using lines as outputs, we can ignore it. */ + return EC_SUCCESS; +} + +static int led_set(enum led_color color) +{ + int rv = EC_SUCCESS; + int i; + + /* + * 1-wire communication can fail for timing reasons in the current + * system. We have a limited timing window to send/receive bits, but + * we can't disable interrupts for the rest of the system to guarantee + * we hit that window. Instead, simply retry the low-level command a + * few times. + */ + for (i = 0; i < ONEWIRE_RETRIES; i++) { + rv = led_set_mask(led_masks[color]); + if (rv == EC_SUCCESS) + break; + + /* + * Sleep for a bit between tries. This gives the 1-wire GPIO + * chip time to recover from the failed attempt, and allows + * lower-priority tasks a chance to run. + */ + usleep(100); + } + + return rv; +} + +/*****************************************************************************/ +/* Hooks */ + +static void onewire_led_tick(void) +{ + static enum led_color current_color = LED_COLOR_COUNT; + static int tick_count; + + enum led_color new_color = LED_OFF; + uint32_t chflags = charge_get_flags(); + + tick_count++; + + if (!(chflags & CHARGE_FLAG_EXTERNAL_POWER)) { + /* AC isn't present, so the power LED on the AC plug is off */ + current_color = LED_OFF; + return; + } + + /* Translate charge state to LED color */ + switch (charge_get_state()) { + case PWR_STATE_IDLE: + if (chflags & CHARGE_FLAG_FORCE_IDLE) + new_color = (tick_count & 1) ? LED_GREEN : LED_OFF; + else + new_color = LED_GREEN; + break; + case PWR_STATE_CHARGE: + new_color = LED_YELLOW; + break; + case PWR_STATE_CHARGE_NEAR_FULL: + new_color = LED_GREEN; + break; + case PWR_STATE_ERROR: + new_color = LED_RED; + break; + default: + /* Other states don't change LED color */ + break; + } + + /* + * The power adapter on link can partially unplug and lose its LED + * state. There's no way to detect this, so just assume it forgets its + * state every 10 seconds. + */ + if (!(tick_count % 10)) + current_color = LED_COLOR_COUNT; + + /* If current color is still correct, leave now */ + if (new_color == current_color) + return; + + /* Update LED */ + if (!led_set(new_color)) + current_color = new_color; +} +DECLARE_HOOK(HOOK_SECOND, onewire_led_tick, HOOK_PRIO_DEFAULT); + +/*****************************************************************************/ +/* Console commands */ + +static int command_powerled(int argc, char **argv) +{ + int i; + + /* Pick a color, any color... */ + for (i = 0; i < LED_COLOR_COUNT; i++) { + if (!strcasecmp(argv[1], color_names[i])) + return led_set(i); + } + return EC_ERROR_PARAM1; +} +DECLARE_CONSOLE_COMMAND(powerled, command_powerled, + "<off | red | yellow | green>", + "Set power LED color", + NULL); diff --git a/driver/led/lp5562.c b/driver/led/lp5562.c new file mode 100644 index 0000000000..fd92fae26f --- /dev/null +++ b/driver/led/lp5562.c @@ -0,0 +1,161 @@ +/* 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. + * + * TI LP5562 driver. + */ + +#include "console.h" +#include "i2c.h" +#include "lp5562.h" +#include "timer.h" +#include "uart.h" +#include "util.h" + +/* 8-bit I2C address */ +#define LP5562_I2C_ADDR (0x30 << 1) + +inline int lp5562_write(uint8_t reg, uint8_t val) +{ + return i2c_write8(I2C_PORT_HOST, LP5562_I2C_ADDR, reg, val); +} + +inline int lp5562_read(uint8_t reg, int *val) +{ + return i2c_read8(I2C_PORT_HOST, LP5562_I2C_ADDR, reg, val); +} + +int lp5562_set_color(uint32_t rgb) +{ + int ret = 0; + + ret |= lp5562_write(LP5562_REG_B_PWM, rgb & 0xff); + ret |= lp5562_write(LP5562_REG_G_PWM, (rgb >> 8) & 0xff); + ret |= lp5562_write(LP5562_REG_R_PWM, (rgb >> 16) & 0xff); + + return ret; +} + +int lp5562_set_engine(uint8_t r, uint8_t g, uint8_t b) +{ + return lp5562_write(LP5562_REG_LED_MAP, (r << 4) | (g << 2) | b); +} + +int lp5562_engine_load(int engine, const uint8_t *program, int size) +{ + int prog_addr = LP5562_REG_ENG_PROG(engine); + int i, ret, val; + int shift = 6 - engine * 2; + + ret = lp5562_read(LP5562_REG_OP_MODE, &val); + if (ret) + return ret; + val &= ~(0x3 << shift); + val |= 0x1 << shift; + ret = lp5562_write(LP5562_REG_OP_MODE, val); + if (ret) + return ret; + + for (i = 0; i < size; ++i) { + ret = lp5562_write(prog_addr + i, program[i]); + if (ret) + return ret; + } + + val &= ~(0x3 << shift); + val |= 0x2 << shift; + ret = lp5562_write(LP5562_REG_OP_MODE, val); + + return ret; +} + +int lp5562_engine_control(int eng1, int eng2, int eng3) +{ + int ret, val; + + ret = lp5562_read(LP5562_REG_ENABLE, &val); + if (ret) + return ret; + val &= 0xc0; + val |= (eng1 << 4) | (eng2 << 2) | eng3; + return lp5562_write(LP5562_REG_ENABLE, val); +} + +int lp5562_get_engine_state(int engine) +{ + int val; + + if (lp5562_read(LP5562_REG_ENABLE, &val)) + return 0xee; + return (val >> (6 - engine * 2)) & 0x3; +} + +int lp5562_poweron(void) +{ + int ret = 0; + + ret |= lp5562_write(LP5562_REG_ENABLE, 0x40); + udelay(500); /* start-up delay */ + + ret |= lp5562_write(LP5562_REG_CONFIG, 0x1); + ret |= lp5562_write(LP5562_REG_LED_MAP, 0x0); + + return ret; +} + +int lp5562_poweroff(void) +{ + return lp5562_write(LP5562_REG_ENABLE, 0x0); +} + +int lp5562_get_pc(int engine) +{ + int ret; + if (lp5562_read(LP5562_REG_ENG1_PC + engine - 1, &ret)) + return 0xee; + return ret; +} + +int lp5562_set_pc(int engine, int val) +{ + return lp5562_write(LP5562_REG_ENG1_PC + engine - 1, val); +} + +/*****************************************************************************/ +/* Console commands */ + +static int command_lp5562(int argc, char **argv) +{ + if (argc == 4) { + char *e; + uint8_t red, green, blue; + + red = strtoi(argv[1], &e, 0); + if (e && *e) + return EC_ERROR_PARAM1; + green = strtoi(argv[2], &e, 0); + if (e && *e) + return EC_ERROR_PARAM2; + blue = strtoi(argv[3], &e, 0); + if (e && *e) + return EC_ERROR_PARAM3; + + return lp5562_set_color((red << 16) | (green << 8) | blue); + } else if (argc == 2) { + int v; + + if (!parse_bool(argv[1], &v)) + return EC_ERROR_PARAM1; + + if (v) + return lp5562_poweron(); + else + return lp5562_poweroff(); + } + + return EC_ERROR_INVAL; +} +DECLARE_CONSOLE_COMMAND(lp5562, command_lp5562, + "on | off | <red> <green> <blue>", + "Set the color of the LED", + NULL); diff --git a/driver/regulator_ir357x.c b/driver/regulator_ir357x.c new file mode 100644 index 0000000000..c6966b7e31 --- /dev/null +++ b/driver/regulator_ir357x.c @@ -0,0 +1,247 @@ +/* Copyright (c) 2012 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. + * + * IR357x driver. + */ + +#include "common.h" +#include "console.h" +#include "hooks.h" +#include "i2c.h" +#include "timer.h" +#include "uart.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_CHIPSET, outstr) +#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args) + +/* 8-bit I2C address */ +#define IR357x_I2C_ADDR (0x8 << 1) + +struct ir_setting { + uint8_t reg; + uint8_t value; +}; + +static struct ir_setting ir3570_settings[] = { + {0x10, 0x22}, {0x11, 0x22}, {0x12, 0x88}, {0x13, 0x10}, + {0x14, 0x0d}, {0x15, 0x21}, {0x16, 0x21}, {0x17, 0x00}, + {0x18, 0x00}, {0x19, 0x00}, {0x1a, 0x00}, {0x1b, 0x00}, + {0x1c, 0x00}, {0x1d, 0x00}, {0x1e, 0x00}, {0x1f, 0x00}, + {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x60}, {0x23, 0x60}, + {0x24, 0x74}, {0x25, 0x4e}, {0x26, 0xff}, {0x27, 0x80}, + {0x28, 0x00}, {0x29, 0x20}, {0x2a, 0x15}, {0x2b, 0x26}, + {0x2c, 0xb6}, {0x2d, 0x21}, {0x2e, 0x11}, {0x2f, 0x20}, + {0x30, 0xab}, {0x31, 0x14}, {0x32, 0x90}, {0x33, 0x4d}, + {0x34, 0x75}, {0x35, 0x64}, {0x36, 0x64}, {0x37, 0x09}, + {0x38, 0xc4}, {0x39, 0x20}, {0x3a, 0x80}, {0x3b, 0x00}, + {0x3c, 0x00}, {0x3d, 0xaa}, {0x3e, 0x00}, {0x3f, 0x05}, + {0x40, 0x50}, {0x41, 0x40}, {0x42, 0x00}, {0x43, 0x00}, + {0x44, 0x00}, {0x45, 0x00}, {0x46, 0x00}, {0x47, 0x00}, + {0x48, 0x1c}, {0x49, 0x0c}, {0x4a, 0x0f}, {0x4b, 0x40}, + {0x4c, 0x80}, {0x4d, 0x40}, {0x4e, 0x80}, + {0x51, 0x00}, {0x52, 0x45}, {0x53, 0x59}, + {0x54, 0x23}, {0x55, 0xae}, {0x56, 0x68}, {0x57, 0x24}, + {0x58, 0x62}, {0x59, 0x42}, {0x5a, 0x34}, {0x5b, 0x00}, + {0x5c, 0x30}, {0x5d, 0x05}, {0x5e, 0x02}, {0x5f, 0x35}, + {0x60, 0x30}, {0x61, 0x00}, {0x62, 0xd8}, {0x63, 0x00}, + {0x64, 0x52}, {0x65, 0x28}, {0x66, 0x14}, {0x67, 0x87}, + {0x68, 0x80}, {0x69, 0x00}, {0x6a, 0x00}, {0x6b, 0x00}, + {0x6c, 0x00}, {0x6d, 0xff}, {0x6e, 0x06}, {0x6f, 0xff}, + {0x70, 0xff}, {0x71, 0x20}, {0x72, 0x00}, {0x73, 0x01}, + {0x74, 0x00}, {0x75, 0x00}, {0x76, 0x00}, {0x77, 0x00}, + {0x78, 0x00}, {0x79, 0x00}, {0x7a, 0x00}, {0x7b, 0x00}, + {0x7c, 0x15}, {0x7d, 0x15}, {0x7e, 0x00}, {0x7f, 0x00}, + {0x80, 0x00}, {0x81, 0x00}, {0x82, 0x00}, {0x83, 0x00}, + {0x84, 0x00}, {0x85, 0x00}, {0x86, 0x00}, {0x87, 0x00}, + {0x88, 0x88}, {0x89, 0x88}, {0x8a, 0x01}, {0x8b, 0x42}, + {0x8d, 0x00}, {0x8e, 0x00}, {0x8f, 0x1f}, + {0, 0} +}; + +static struct ir_setting ir3571_settings[] = { + {0x18, 0x22}, {0x19, 0x22}, {0x1a, 0x08}, {0x1b, 0x10}, + {0x1c, 0x06}, {0x1d, 0x21}, {0x1e, 0x21}, {0x1f, 0x83}, + {0x20, 0x83}, {0x21, 0x00}, {0x22, 0x00}, {0x23, 0x00}, + {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x34}, + {0x28, 0x34}, {0x29, 0x74}, {0x2a, 0x4e}, {0x2b, 0xff}, + {0x2c, 0x00}, {0x2d, 0x1d}, {0x2e, 0x14}, {0x2f, 0x1f}, + {0x30, 0x88}, {0x31, 0x9a}, {0x32, 0x1e}, {0x33, 0x19}, + {0x34, 0xe9}, {0x35, 0x40}, {0x36, 0x90}, {0x37, 0x6d}, + {0x38, 0x75}, {0x39, 0xa0}, {0x3a, 0x84}, {0x3b, 0x08}, + {0x3c, 0xc5}, {0x3d, 0xa0}, {0x3e, 0x80}, {0x3f, 0xaa}, + {0x40, 0x50}, {0x41, 0x4b}, {0x42, 0x02}, {0x43, 0x04}, + {0x44, 0x00}, {0x45, 0x00}, {0x46, 0x00}, {0x47, 0x78}, + {0x48, 0x56}, {0x49, 0x18}, {0x4a, 0x88}, {0x4b, 0x00}, + {0x4c, 0x80}, {0x4d, 0x60}, {0x4e, 0x60}, {0x4f, 0xff}, + {0x50, 0xff}, {0x51, 0x00}, {0x52, 0x9b}, {0x53, 0xaa}, + {0x54, 0xd8}, {0x55, 0x56}, {0x56, 0x31}, {0x57, 0x1a}, + {0x58, 0x12}, {0x59, 0x63}, {0x5a, 0x00}, {0x5b, 0x09}, + {0x5c, 0x02}, {0x5d, 0x00}, {0x5e, 0xea}, {0x5f, 0x00}, + {0x60, 0xb0}, {0x61, 0x1e}, {0x62, 0x00}, {0x63, 0x56}, + {0x64, 0x00}, {0x65, 0x00}, {0x66, 0x00}, {0x67, 0x00}, + {0x68, 0x28}, {0x69, 0x00}, {0x6a, 0x00}, {0x6b, 0x00}, + {0x6c, 0x00}, {0x6d, 0x00}, {0x6e, 0x00}, {0x6f, 0x00}, + {0x70, 0x80}, {0x71, 0x00}, {0x72, 0x00}, {0x73, 0x00}, + {0x74, 0x00}, {0x75, 0xbf}, {0x76, 0x06}, {0x77, 0xff}, + {0x78, 0xff}, {0x79, 0x04}, {0x7a, 0x00}, {0x7b, 0x1d}, + {0x7c, 0xa0}, {0x7d, 0x10}, {0x7e, 0x00}, {0x7f, 0x8a}, + {0x80, 0x1b}, {0x81, 0x11}, {0x82, 0x00}, {0x83, 0x00}, + {0x84, 0x00}, {0x85, 0x00}, {0x86, 0x00}, {0x87, 0x00}, + {0x88, 0x00}, {0x89, 0x00}, {0x8a, 0x00}, {0x8b, 0x00}, + {0x8c, 0x00}, {0x8d, 0x00}, {0x8e, 0x00}, {0x8f, 0x00}, + {0, 0} +}; + +static uint8_t ir357x_read(uint8_t reg) +{ + int res; + int val; + + res = i2c_read8(I2C_PORT_REGULATOR, IR357x_I2C_ADDR, reg, &val); + if (res) + return 0xee; + + return val; +} + +static void ir357x_write(uint8_t reg, uint8_t val) +{ + int res; + + res = i2c_write8(I2C_PORT_REGULATOR, IR357x_I2C_ADDR, reg, val); + if (res) + CPRINTF("[%T IR I2C write failed]\n"); +} + +static int ir357x_get_version(void) +{ + /* IR3571 on Link EVT */ + if ((ir357x_read(0xfc) == 'I') && (ir357x_read(0xfd) == 'R') && + ((ir357x_read(0x0a) & 0xe) == 0)) + return 3571; + + /* IR3570A on Link Proto 0/1 and Link DVT */ + if ((ir357x_read(0x92) == 'C') && (ir357x_read(0xcd) == 0x24)) + return 3570; + + /* Unknown and unsupported chip */ + return -1; +} + +struct ir_setting *ir357x_get_settings(void) +{ + int version = ir357x_get_version(); + + if (version == 3570) + return ir3570_settings; + else if (version == 3571) + return ir3571_settings; + else + return NULL; +} + +static void ir357x_prog(void) +{ + struct ir_setting *settings = ir357x_get_settings(); + + if (settings) { + for (; settings->reg; settings++) + ir357x_write(settings->reg, settings->value); + } else { + CPRINTF("[%T IR%d chip unsupported. Skip writing settings!\n", + ir357x_get_version()); + return; + } + + CPRINTF("[%T IR%d registers UPDATED]\n", ir357x_get_version()); +} + +static void ir357x_dump(void) +{ + int i; + + for (i = 0; i < 256; i++) { + if (!(i & 0xf)) { + ccprintf("\n%02x: ", i); + cflush(); + } + ccprintf("%02x ", ir357x_read(i)); + } + ccprintf("\n"); +} + +static int ir357x_check(void) +{ + uint8_t val; + int diff = 0; + struct ir_setting *settings = ir357x_get_settings(); + + if (!settings) { + ccprintf("no setting for chip IR%d !\n", ir357x_get_version()); + return 1; + } + + for (; settings->reg; settings++) { + val = ir357x_read(settings->reg); + if (val != settings->value) { + ccprintf("DIFF reg 0x%02x %02x->%02x\n", + settings->reg, settings->value, val); + cflush(); + diff++; + } + } + return !!diff; +} + +static int command_ir357x(int argc, char **argv) +{ + int reg, val; + char *rem; + + if (1 == argc) { /* dump all registers */ + ir357x_dump(); + return EC_SUCCESS; + } else if (2 == argc) { + if (!strcasecmp(argv[1], "check")) { + ir357x_check(); + } else { /* read one register */ + reg = strtoi(argv[1], &rem, 16); + if (*rem) { + ccprintf("Invalid register: %s\n", argv[1]); + return EC_ERROR_INVAL; + } + ccprintf("reg 0x%02x = 0x%02x\n", reg, + ir357x_read(reg)); + } + return EC_SUCCESS; + } else if (3 == argc) { /* write one register */ + reg = strtoi(argv[1], &rem, 16); + if (*rem) { + ccprintf("Invalid register: %s\n", argv[1]); + return EC_ERROR_INVAL; + } + val = strtoi(argv[2], &rem, 16); + if (*rem) { + ccprintf("Invalid value: %s\n", argv[2]); + return EC_ERROR_INVAL; + } + ir357x_write(reg, val); + return EC_SUCCESS; + } + + return EC_ERROR_INVAL; +} +DECLARE_CONSOLE_COMMAND(ir357x, command_ir357x, + "[check|write]", + "IR357x core regulator control", + NULL); + +static void ir357x_hot_settings(void) +{ + /* dynamically apply settings to workaround issue */ + ir357x_prog(); +} +DECLARE_HOOK(HOOK_CHIPSET_RESUME, ir357x_hot_settings, HOOK_PRIO_DEFAULT); diff --git a/driver/temp_sensor/g781.c b/driver/temp_sensor/g781.c new file mode 100644 index 0000000000..389b12629e --- /dev/null +++ b/driver/temp_sensor/g781.c @@ -0,0 +1,210 @@ +/* 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. + */ + +/* G781 temperature sensor module for Chrome EC */ + +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "i2c.h" +#include "hooks.h" +#include "temp_sensor_g781.h" +#include "util.h" + +static int g781_temp_val_local; +static int g781_temp_val_remote; + +/** + * Determine whether the sensor is powered. + * + * @return non-zero the g781 sensor is powered. + */ +static int g781_has_power(void) +{ +#ifdef CONFIG_TEMP_SENSOR_POWER_GPIO + return gpio_get_level(CONFIG_TEMP_SENSOR_POWER_GPIO); +#else + return 1; +#endif +} + +static int g781_read8(const int offset, int *data_ptr) +{ + return i2c_read8(I2C_PORT_THERMAL, G781_I2C_ADDR, offset, data_ptr); +} + +static int g781_write8(const int offset, int data) +{ + return i2c_write8(I2C_PORT_THERMAL, G781_I2C_ADDR, offset, data); +} + +static int g781_get_temp(const int offset, int *temp_ptr) +{ + int rv; + int temp_raw = 0; + + rv = g781_read8(offset, &temp_raw); + if (rv < 0) + return rv; + + *temp_ptr = (int)(int8_t)temp_raw; + return EC_SUCCESS; +} + +static int g781_set_temp(const int offset, int temp) +{ + if (temp < -127 || temp > 127) + return EC_ERROR_INVAL; + + return g781_write8(offset, (uint8_t)temp); +} + +int g781_get_val(int idx, int *temp_ptr) +{ + if (!g781_has_power()) + return EC_ERROR_NOT_POWERED; + + switch (idx) { + case G781_IDX_INTERNAL: + *temp_ptr = g781_temp_val_local; + break; + case G781_IDX_EXTERNAL: + *temp_ptr = g781_temp_val_remote; + break; + default: + return EC_ERROR_UNKNOWN; + } + + return EC_SUCCESS; +} + +static void g781_temp_sensor_poll(void) +{ + if (!g781_has_power()) + return; + + g781_get_temp(G781_TEMP_LOCAL, &g781_temp_val_local); + g781_temp_val_local = C_TO_K(g781_temp_val_local); + + g781_get_temp(G781_TEMP_REMOTE, &g781_temp_val_remote); + g781_temp_val_remote = C_TO_K(g781_temp_val_remote); +} +DECLARE_HOOK(HOOK_SECOND, g781_temp_sensor_poll, HOOK_PRIO_TEMP_SENSOR); + +static int g781_show_status(void) +{ + int value; + int rv; + + + rv = g781_get_temp(G781_TEMP_LOCAL, &value); + if (rv < 0) + return rv; + ccprintf("Local Temp: %3dC\n", value); + + rv = g781_get_temp(G781_LOCAL_TEMP_THERM_LIMIT, &value); + if (rv < 0) + return rv; + ccprintf(" Therm Trip: %3dC\n", value); + + rv = g781_get_temp(G781_LOCAL_TEMP_HIGH_LIMIT_R, &value); + if (rv < 0) + return rv; + ccprintf(" High Alarm: %3dC\n", value); + + rv = g781_get_temp(G781_LOCAL_TEMP_LOW_LIMIT_R, &value); + if (rv < 0) + return rv; + ccprintf(" Low Alarm: %3dC\n", value); + + rv = g781_get_temp(G781_TEMP_REMOTE, &value); + if (rv < 0) + return rv; + ccprintf("Remote Temp: %3dC\n", value); + + rv = g781_get_temp(G781_REMOTE_TEMP_THERM_LIMIT, &value); + if (rv < 0) + return rv; + ccprintf(" Therm Trip: %3dC\n", value); + + rv = g781_get_temp(G781_REMOTE_TEMP_HIGH_LIMIT_R, &value); + if (rv < 0) + return rv; + ccprintf(" High Alarm: %3dC\n", value); + + rv = g781_get_temp(G781_REMOTE_TEMP_LOW_LIMIT_R, &value); + if (rv < 0) + return rv; + ccprintf(" Low Alarm: %3dC\n", value); + + rv = g781_read8(G781_STATUS, &value); + if (rv < 0) + return rv; + ccprintf("\nSTATUS: %08b\n", value); + + rv = g781_read8(G781_CONFIGURATION_R, &value); + if (rv < 0) + return rv; + ccprintf("CONFIG: %08b\n", value); + + return EC_SUCCESS; +} + +static int command_g781(int argc, char **argv) +{ + char *command; + char *e; + int data; + int offset; + int rv; + + if (!g781_has_power()) { + ccprintf("ERROR: Temp sensor not powered.\n"); + return EC_ERROR_NOT_POWERED; + } + + /* If no args just print status */ + if (argc == 1) + return g781_show_status(); + + if (argc < 3) + return EC_ERROR_PARAM_COUNT; + + command = argv[1]; + offset = strtoi(argv[2], &e, 0); + if (*e || offset < 0 || offset > 255) + return EC_ERROR_PARAM2; + + if (!strcasecmp(command, "getbyte")) { + rv = g781_read8(offset, &data); + if (rv < 0) + return rv; + ccprintf("Byte at offset 0x%02x is %08b\n", offset, data); + return rv; + } + + /* Remaining commands are of the form "g781 set-command offset data" */ + if (argc != 4) + return EC_ERROR_PARAM_COUNT; + + data = strtoi(argv[3], &e, 0); + if (*e) + return EC_ERROR_PARAM3; + + if (!strcasecmp(command, "settemp")) { + ccprintf("Setting 0x%02x to %dC\n", offset, data); + rv = g781_set_temp(offset, data); + } else if (!strcasecmp(command, "setbyte")) { + ccprintf("Setting 0x%02x to 0x%02x\n", offset, data); + rv = g781_write8(offset, data); + } else + return EC_ERROR_PARAM1; + + return rv; +} +DECLARE_CONSOLE_COMMAND(g781, command_g781, + "[settemp|setbyte <offset> <value>] or [getbyte <offset>]. " + "Temps in Celsius.", + "Print g781 temp sensor status or set parameters.", NULL); diff --git a/driver/temp_sensor/tmp006.c b/driver/temp_sensor/tmp006.c new file mode 100644 index 0000000000..19d66d87aa --- /dev/null +++ b/driver/temp_sensor/tmp006.c @@ -0,0 +1,444 @@ +/* Copyright (c) 2012 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. + */ + +/* TMP006 temperature sensor module for Chrome EC */ + +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "host_command.h" +#include "i2c.h" +#include "math.h" +#include "task.h" +#include "temp_sensor.h" +#include "tmp006.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_THERMAL, outstr) +#define CPRINTF(format, args...) cprintf(CC_THERMAL, format, ## args) + +/* Constants for calculating target object temperatures */ +static const float A1 = 1.75e-3f; +static const float A2 = -1.678e-5f; +static const float B0 = -2.94e-5f; +static const float B1 = -5.7e-7f; +static const float B2 = 4.63e-9f; +static const float C2 = 13.4f; + +/* Defined in board_temp_sensor.c. */ +extern const struct tmp006_t tmp006_sensors[TMP006_COUNT]; + +/* Flags for tdata->fail */ +#define FAIL_INIT (1 << 0) /* Just initialized */ +#define FAIL_POWER (1 << 1) /* Sensor not powered */ +#define FAIL_I2C (1 << 2) /* I2C communication error */ +#define FAIL_NOT_READY (1 << 3) /* Data not ready */ + +struct tmp006_data_t { + int v; /* Object voltage */ + int t[4]; /* Circular buffer of last four die temperatures */ + int tidx; /* Index of the current value in t[] */ + int fail; /* Fail flags; non-zero if last read failed */ + float s0; /* Sensitivity factor */ + float b0, b1, b2; /* Coefficients for self-heating correction */ +}; + +static struct tmp006_data_t tmp006_data[TMP006_COUNT]; + +/** + * Check if sensor has power + * + * @param idx Sensor index + * + * @return non-zero if sensor has power. + */ +static int tmp006_has_power(int idx) +{ +#ifdef CONFIG_TEMP_SENSOR_POWER_GPIO + return gpio_get_level(CONFIG_TEMP_SENSOR_POWER_GPIO); +#else + return 1; +#endif +} + +static int tmp006_read_die_temp(const struct tmp006_data_t *tdata, + int *temp_ptr) +{ + if (tdata->fail) + return EC_ERROR_UNKNOWN; + + /* Return previous die temperature */ + *temp_ptr = tdata->t[(tdata->tidx - 1) & 0x3] / 100; + return EC_SUCCESS; +} + +/** + * Calculate the remote object temperature. + * + * @param Tdie_i Die temperature in 1/100 K. + * @param Vobj_i Voltage read from register 0. In nV. + * @param tdata TMP006 data for this sensor. + * + * @return Object temperature in 1/100 K. + */ +static int tmp006_calculate_object_temp(int Tdie_i, int Vobj_i, + const struct tmp006_data_t *tdata) +{ + float Tdie, Vobj; + float Tx, S, Vos, Vx, fv, Tobj, T4; + int Tobj_i; + + Tdie = (float)Tdie_i * 1e-2f; + Vobj = (float)Vobj_i * 1e-9f; + + /* Calculate according to TMP006 users guide. */ + Tx = Tdie - 298.15f; + /* S is the sensitivity */ + S = tdata->s0 * (1.0f + A1 * Tx + A2 * Tx * Tx); + /* Vos is the offset voltage */ + Vos = tdata->b0 + tdata->b1 * Tx + tdata->b2 * Tx * Tx; + Vx = Vobj - Vos; + /* fv is Seebeck coefficient f(Vobj) */ + fv = Vx + C2 * Vx * Vx; + + T4 = Tdie * Tdie * Tdie * Tdie + fv / S; + Tobj = sqrtf(sqrtf(T4)); + Tobj_i = (int32_t)(Tobj * 100.0f); + + return Tobj_i; +} + +/** + * Apply TMP006 temporal correction. + * + * @param T1-T4 Four die temperature readings separated by 1s in 1/100K. + * @param Vobj Voltage read from register 0, in nV. + * + * @return Corrected object voltage in 1/100K. + */ +static int tmp006_correct_object_voltage(int T1, int T2, int T3, int T4, + int Vobj) +{ + int Tslope = 3 * T1 + T2 - T3 - 3 * T4; + return Vobj + 296 * Tslope; +} + +static int tmp006_read_object_temp(const struct tmp006_data_t *tdata, + int *temp_ptr) +{ + int pidx = (tdata->tidx - 1) & 0x3; + int t = tdata->t[pidx]; + int v = tdata->v; + + if (tdata->fail) + return EC_ERROR_UNKNOWN; + + if (!tdata->s0) + return EC_ERROR_NOT_CALIBRATED; + + v = tmp006_correct_object_voltage( + t, + tdata->t[(pidx + 3) & 3], + tdata->t[(pidx + 2) & 3], + tdata->t[(pidx + 1) & 3], + v); + + *temp_ptr = tmp006_calculate_object_temp(t, v, tdata) / 100; + + return EC_SUCCESS; +} + +static int tmp006_poll_sensor(int sensor_id) +{ + struct tmp006_data_t *tdata = tmp006_data + sensor_id; + int traw, t; + int vraw, v; + int rv; + int addr = tmp006_sensors[sensor_id].addr; + int idx; + + if (!tmp006_has_power(sensor_id)) { + tdata->fail |= FAIL_POWER; + return EC_ERROR_UNKNOWN; + } + + /* + * If sensor has just initialized and/or has lost power, wait for + * data ready; otherwise, we read garbage data. + */ + if (tdata->fail && (FAIL_POWER | FAIL_INIT)) { + rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x02, &v); + if (rv) { + tdata->fail |= FAIL_I2C; + return EC_ERROR_UNKNOWN; + } else if (!(v & 0x80)) { + tdata->fail |= FAIL_NOT_READY; + return EC_ERROR_UNKNOWN; + } + } + + rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x01, &traw); + if (rv) { + tdata->fail |= FAIL_I2C; + return EC_ERROR_UNKNOWN; + } + + /* Convert temperature from raw to 1/100 K */ + t = ((int)(int16_t)traw * 100) / 128 + 27300; + + rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x00, &vraw); + if (rv) { + tdata->fail |= FAIL_I2C; + return EC_ERROR_UNKNOWN; + } + + /* Convert voltage from raw to nV */ + v = ((int)(int16_t)vraw * 15625) / 100; + + /* + * If last read failed, set the entire temperature history to the + * current temperature. This keeps us from making inaccurate temporal + * corrections based on stale data. + */ + if (tdata->fail) { + for (idx = 0; idx < 4; idx++) + tdata->t[idx] = t; + } else { + idx = tdata->tidx; + tdata->t[idx] = t; + tdata->tidx = (idx + 1) & 3; + } + + tdata->v = v; + tdata->fail = 0; + + return EC_SUCCESS; +} + +int tmp006_get_val(int idx, int *temp_ptr) +{ + /* + * Note: idx is a thermal sensor index, where the top N-1 bits are the + * TMP006 index and the bottom bit is (0=die, 1=remote). + */ + int tidx = idx >> 1; + const struct tmp006_data_t *tdata = tmp006_data + tidx; + + if (tdata->fail & FAIL_POWER) { + /* + * Sensor isn't powered, or hasn't successfully provided data + * since being powered. Keep reporting not-powered until + * we get good data (which will clear FAIL_POWER) or there is + * an I2C error. + */ + return (tdata->fail & FAIL_I2C) ? EC_ERROR_UNKNOWN : + EC_ERROR_NOT_POWERED; + } + + /* Check the low bit to determine which temperature to read. */ + if ((idx & 0x1) == 0) + return tmp006_read_die_temp(tdata, temp_ptr); + else + return tmp006_read_object_temp(tdata, temp_ptr); +} + +/*****************************************************************************/ +/* Hooks */ + +static void tmp006_poll(void) +{ + int i; + + for (i = 0; i < TMP006_COUNT; ++i) + tmp006_poll_sensor(i); +} +DECLARE_HOOK(HOOK_SECOND, tmp006_poll, HOOK_PRIO_TEMP_SENSOR); + +static void tmp006_init(void) +{ + int i; + + for (i = 0; i < TMP006_COUNT; ++i) { + struct tmp006_data_t *tdata = tmp006_data + i; + + /* Report error until we actually read the sensor */ + tdata->fail = FAIL_INIT; + + /* Use defaults for Bn params */ + tdata->b0 = B0; + tdata->b1 = B1; + tdata->b2 = B2; + } +} +DECLARE_HOOK(HOOK_INIT, tmp006_init, HOOK_PRIO_DEFAULT); + +/*****************************************************************************/ +/* Host commands */ + +int tmp006_get_calibration(struct host_cmd_handler_args *args) +{ + const struct ec_params_tmp006_get_calibration *p = args->params; + struct ec_response_tmp006_get_calibration *r = args->response; + const struct tmp006_data_t *tdata; + + if (p->index >= TMP006_COUNT) + return EC_RES_INVALID_PARAM; + + tdata = tmp006_data + p->index; + + r->s0 = tdata->s0; + r->b0 = tdata->b0; + r->b1 = tdata->b1; + r->b2 = tdata->b2; + + args->response_size = sizeof(*r); + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_TMP006_GET_CALIBRATION, + tmp006_get_calibration, + EC_VER_MASK(0)); + +int tmp006_set_calibration(struct host_cmd_handler_args *args) +{ + const struct ec_params_tmp006_set_calibration *p = args->params; + struct tmp006_data_t *tdata; + + if (p->index >= TMP006_COUNT) + return EC_RES_INVALID_PARAM; + + tdata = tmp006_data + p->index; + + tdata->s0 = p->s0; + tdata->b0 = p->b0; + tdata->b1 = p->b1; + tdata->b2 = p->b2; + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_TMP006_SET_CALIBRATION, + tmp006_set_calibration, + EC_VER_MASK(0)); + +/*****************************************************************************/ +/* Console commands */ + +/** + * Print temperature info for a sensor; used by console command. + */ +static int tmp006_print(int idx) +{ + int vraw, v; + int traw, t; + int rv; + int d; + int addr = tmp006_sensors[idx].addr; + + + ccprintf("Debug data from %s:\n", tmp006_sensors[idx].name); + + if (!tmp006_has_power(idx)) { + ccputs("Sensor powered off.\n"); + return EC_ERROR_UNKNOWN; + } + + rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0xfe, &d); + if (rv) + return rv; + ccprintf(" Manufacturer ID: 0x%04x\n", d); + + rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0xff, &d); + ccprintf(" Device ID: 0x%04x\n", d); + + rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x02, &d); + ccprintf(" Config: 0x%04x\n", d); + + rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x00, &vraw); + v = ((int)(int16_t)vraw * 15625) / 100; + ccprintf(" Voltage: 0x%04x = %d nV\n", vraw, v); + + rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x01, &traw); + t = ((int)(int16_t)traw * 100) / 128; + ccprintf(" Temperature: 0x%04x = %d.%02d C\n", + traw, t / 100, t > 0 ? t % 100 : 100 - (t % 100)); + + return EC_SUCCESS; +} + +static int command_sensor_info(int argc, char **argv) +{ + int i; + int rv, rv1; + + rv1 = EC_SUCCESS; + for (i = 0; i < TMP006_COUNT; i++) { + rv = tmp006_print(i); + if (rv != EC_SUCCESS) + rv1 = rv; + cflush(); + } + + return rv1; +} +DECLARE_CONSOLE_COMMAND(tmp006, command_sensor_info, + NULL, + "Print TMP006 sensors", + NULL); + +static int command_t6cal(int argc, char **argv) +{ + struct tmp006_data_t *tdata; + char *e; + int v; + int i; + + if (argc < 2) { + ccprintf("# Name S0 b0" + " b1 b2\n"); + for (i = 0; i < TMP006_COUNT; i++) { + tdata = tmp006_data + i; + ccprintf("%d %-11s" + "%7de-17 %7de-8 %7de-10 %7de-12\n", + i, tmp006_sensors[i].name, + (int)(tdata->s0 * 1e17f), + (int)(tdata->b0 * 1e8f), + (int)(tdata->b1 * 1e10f), + (int)(tdata->b2 * 1e12f)); + } + + return EC_SUCCESS; + } + + if (argc != 4) + return EC_ERROR_PARAM_COUNT; + + i = strtoi(argv[1], &e, 0); + if (*e || i < 0 || i >= TMP006_COUNT) + return EC_ERROR_PARAM1; + tdata = tmp006_data + i; + + v = strtoi(argv[3], &e, 0); + if (*e) + return EC_ERROR_PARAM3; + + if (!strcasecmp(argv[2], "s0")) + tdata->s0 = (float)v * 1e-17f; + else if (!strcasecmp(argv[2], "b0")) + tdata->b0 = (float)v * 1e-8f; + else if (!strcasecmp(argv[2], "b1")) + tdata->b1 = (float)v * 1e-10f; + else if (!strcasecmp(argv[2], "b2")) + tdata->b2 = (float)v * 1e-12f; + else + return EC_ERROR_PARAM2; + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(t6cal, command_t6cal, + "[<index> <coeff_name> <radix>]", + "Set/print TMP006 calibration", + NULL); diff --git a/driver/usb_switch_tsu6721.c b/driver/usb_switch_tsu6721.c new file mode 100644 index 0000000000..1eaa74de55 --- /dev/null +++ b/driver/usb_switch_tsu6721.c @@ -0,0 +1,252 @@ +/* Copyright (c) 2012 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. + * + * TI TSU6721 USB port switch driver. + */ + +#include "console.h" +#include "ec_commands.h" +#include "hooks.h" +#include "host_command.h" +#include "i2c.h" +#include "system.h" +#include "timer.h" +#include "tsu6721.h" +#include "uart.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_USBCHARGE, outstr) +#define CPRINTF(format, args...) cprintf(CC_USBCHARGE, format, ## args) + +/* 8-bit I2C address */ +#define TSU6721_I2C_ADDR (0x25 << 1) + +/* Delay values */ +#define TSU6721_SW_RESET_DELAY 15 + +/* Number of retries when reset fails */ +#define TSU6721_SW_RESET_RETRY 3 + +static int saved_interrupts; + +uint8_t tsu6721_read(uint8_t reg) +{ + int res; + int val; + + res = i2c_read8(I2C_PORT_HOST, TSU6721_I2C_ADDR, reg, &val); + if (res) + return 0xee; + + return val; +} + +int tsu6721_write(uint8_t reg, uint8_t val) +{ + int res; + + res = i2c_write8(I2C_PORT_HOST, TSU6721_I2C_ADDR, reg, val); + if (res) + CPRINTF("[%T TSU6721 I2C write failed]\n"); + return res; +} + +int tsu6721_enable_interrupts(void) +{ + int ctrl = tsu6721_read(TSU6721_REG_CONTROL); + return tsu6721_write(TSU6721_REG_CONTROL, ctrl & 0x1e); +} + +int tsu6721_disable_interrupts(void) +{ + int ctrl = tsu6721_read(TSU6721_REG_CONTROL); + int rv; + + rv = tsu6721_write(TSU6721_REG_CONTROL, ctrl | 0x1); + tsu6721_get_interrupts(); + return rv; +} + +int tsu6721_set_interrupt_mask(uint16_t mask) +{ + return tsu6721_write(TSU6721_REG_INT_MASK1, (~mask) & 0xff) | + tsu6721_write(TSU6721_REG_INT_MASK2, ((~mask) >> 8) & 0xff); +} + +int tsu6721_get_interrupts(void) +{ + int ret = tsu6721_peek_interrupts(); + saved_interrupts = 0; + return ret; +} + +int tsu6721_peek_interrupts(void) +{ + saved_interrupts |= (tsu6721_read(TSU6721_REG_INT2) << 8) | + (tsu6721_read(TSU6721_REG_INT1)); + return saved_interrupts; +} + +int tsu6721_get_device_type(void) +{ + return (tsu6721_read(TSU6721_REG_DEV_TYPE3) << 16) | + (tsu6721_read(TSU6721_REG_DEV_TYPE2) << 8) | + (tsu6721_read(TSU6721_REG_DEV_TYPE1)); +} + +void tsu6721_reset(void) +{ + int i; + + for (i = 0; i < TSU6721_SW_RESET_RETRY; ++i) { + if (i != 0) { + CPRINTF("[%T TSU6721 init failed. Retrying.]\n"); + msleep(500); + } + if (tsu6721_write(TSU6721_REG_RESET, 0x1)) + continue; + /* TSU6721 reset takes ~10ms. Let's wait for 15ms to be safe. */ + msleep(TSU6721_SW_RESET_DELAY); + if (tsu6721_init() == EC_SUCCESS) + break; + } +} + +int tsu6721_mux(enum tsu6721_mux sel) +{ + uint8_t id = tsu6721_read(TSU6721_REG_ADC); + uint8_t vbus1 = tsu6721_read(TSU6721_REG_DEV_TYPE1) & 0x74; + uint8_t vbus3 = tsu6721_read(TSU6721_REG_DEV_TYPE3) & 0x74; + uint8_t ctrl = tsu6721_read(TSU6721_REG_CONTROL); + + /* + * silicon limitation: the chip stays in low power mode and cannot + * activate manual mode if it is not detecting either a VBUS or + * something known on the ID pin + */ + if (sel != TSU6721_MUX_AUTO && (id == 0x1f) && !vbus1 && !vbus3) { + CPRINTF("TSU6721 cannot use manual mode: no VBUS or ID\n"); + return EC_ERROR_INVAL; + } + + if (sel == TSU6721_MUX_AUTO) { + tsu6721_write(TSU6721_REG_CONTROL, ctrl | TSU6721_CTRL_AUTO); + } else { + tsu6721_write(TSU6721_REG_MANUAL1, sel); + tsu6721_write(TSU6721_REG_CONTROL, ctrl & ~TSU6721_CTRL_AUTO); + } + + return EC_SUCCESS; +} + +int tsu6721_init(void) +{ + uint8_t settings; + uint8_t dev_id = tsu6721_read(TSU6721_REG_DEV_ID); + int res = 0; + + if ((dev_id != 0x0a) && (dev_id != 0x12)) { + CPRINTF("TSU6721 invalid device ID 0x%02x\n", dev_id); + return EC_ERROR_UNKNOWN; + } + + /* set USB charger detection timeout to 600ms */ + settings = tsu6721_read(TSU6721_REG_TIMER); + if (settings == 0xee) + return EC_ERROR_UNKNOWN; + settings = (settings & ~0x38); + res |= tsu6721_write(TSU6721_REG_TIMER, settings); + + res |= tsu6721_set_interrupt_mask(TSU6721_INT_ATTACH | + TSU6721_INT_DETACH | + TSU6721_INT_ADC_CHANGE | + TSU6721_INT_VBUS); + res |= tsu6721_enable_interrupts(); + + return res ? EC_ERROR_UNKNOWN : EC_SUCCESS; +} +/* + * TODO(vpalatin): using the I2C early in the HOOK_INIT + * currently triggers all sort of badness, I need to debug + * this before re-activatin this initialization. + */ +#if 0 +DECLARE_HOOK(HOOK_INIT, tsu6721_init, HOOK_PRIO_DEFAULT); +#endif + +static void tsu6721_dump(void) +{ + int i; + uint8_t id = tsu6721_read(TSU6721_REG_ADC); + uint8_t ctrl = tsu6721_read(TSU6721_REG_CONTROL); + + if (ctrl & TSU6721_CTRL_AUTO) + ccprintf("Auto: %02x %02x %02x\n", + tsu6721_read(TSU6721_REG_DEV_TYPE1), + tsu6721_read(TSU6721_REG_DEV_TYPE2), + tsu6721_read(TSU6721_REG_DEV_TYPE3)); + else + ccprintf("Manual: %02x %02x\n", + tsu6721_read(TSU6721_REG_MANUAL1), + tsu6721_read(TSU6721_REG_MANUAL2)); + ccprintf("ID: 0x%02x\n", id); + for (i = 1; i < 0x24; i++) + ccprintf("%02x ", tsu6721_read(i)); + ccprintf("\n"); +} + +/*****************************************************************************/ +/* Console commands */ + +static int command_usbmux(int argc, char **argv) +{ + if (1 == argc) { /* dump all registers */ + tsu6721_dump(); + return EC_SUCCESS; + } else if (2 == argc) { + if (!strcasecmp(argv[1], "usb")) { + tsu6721_mux(TSU6721_MUX_USB); + } else if (!strcasecmp(argv[1], "uart1")) { + tsu6721_mux(TSU6721_MUX_UART); + } else if (!strcasecmp(argv[1], "uart2")) { + tsu6721_mux(TSU6721_MUX_AUDIO); + } else if (!strcasecmp(argv[1], "auto")) { + tsu6721_mux(TSU6721_MUX_AUTO); + } else { /* read one register */ + ccprintf("Invalid mux value: %s\n", argv[1]); + return EC_ERROR_INVAL; + } + return EC_SUCCESS; + } + + return EC_ERROR_INVAL; +} +DECLARE_CONSOLE_COMMAND(usbmux, command_usbmux, + "[usb|uart1|uart2|auto]", + "TSU6721 USB mux control", + NULL); + +/*****************************************************************************/ +/* Host commands */ + +static int usb_command_mux(struct host_cmd_handler_args *args) +{ + const struct ec_params_usb_mux *p = args->params; + + if (system_is_locked()) + return EC_RES_ACCESS_DENIED; + + /* Safety check */ + if (p->mux != TSU6721_MUX_AUTO && + p->mux != TSU6721_MUX_USB && + p->mux != TSU6721_MUX_UART && + p->mux != TSU6721_MUX_AUDIO) + return EC_RES_ERROR; + + if (tsu6721_mux(p->mux)) + return EC_RES_ERROR; + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_USB_MUX, usb_command_mux, EC_VER_MASK(0)); |