blob: 53a63389810d16a62f35f2e450fa2744d731d983 [file] [log] [blame] [edit]
/* Power support for Samsung Tuna Board.
*
* Copyright (C) 2011 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/max17040_battery.h>
#include <linux/moduleparam.h>
#include <linux/pda_power.h>
#include <linux/platform_device.h>
#include <linux/i2c/twl6030-madc.h>
#include <linux/delay.h>
#include <plat/cpu.h>
#include <plat/omap-pm.h>
#include "board-tuna.h"
#include "mux.h"
#include "pm.h"
/* These will be different on pre-lunchbox, lunchbox, and final */
#define GPIO_CHARGING_N 83
#define GPIO_TA_NCONNECTED 142
#define GPIO_CHARGE_N 13
#define GPIO_CHG_CUR_ADJ 102
#define GPIO_FUEL_ALERT 44
#define TPS62361_GPIO 7
#define ADC_NUM_SAMPLES 5
#define ADC_LIMIT_ERR_COUNT 5
#define ISET_ADC_CHANNEL 3
#define TEMP_ADC_CHANNEL 1
#define CHARGE_FULL_ADC 150
#define HIGH_BLOCK_TEMP_MAGURO 500
#define HIGH_RECOVER_TEMP_MAGURO 420
#define LOW_BLOCK_TEMP_MAGURO (-50)
#define LOW_RECOVER_TEMP_MAGURO 0
#define HIGH_BLOCK_TEMP_TORO 490
#define HIGH_RECOVER_TEMP_TORO 420
#define LOW_BLOCK_TEMP_TORO (-50)
#define LOW_RECOVER_TEMP_TORO 0
/**
** temp_adc_table_data
** @adc_value : thermistor adc value
** @temperature : temperature(C) * 10
**/
struct temp_adc_table_data {
int adc_value;
int temperature;
};
static DEFINE_SPINLOCK(charge_en_lock);
static int charger_state;
static bool is_charging_mode;
static struct temp_adc_table_data temper_table_maguro[] = {
/* ADC, Temperature (C/10) */
{ 75, 700 },
{ 78, 690 },
{ 82, 680 },
{ 84, 670 },
{ 87, 660 },
{ 89, 650 },
{ 92, 640 },
{ 95, 630 },
{ 99, 620 },
{ 102, 610 },
{ 105, 600 },
{ 109, 590 },
{ 113, 580 },
{ 117, 570 },
{ 121, 560 },
{ 124, 550 },
{ 127, 540 },
{ 135, 530 },
{ 139, 520 },
{ 143, 510 },
{ 147, 500 },
{ 153, 490 },
{ 158, 480 },
{ 163, 470 },
{ 169, 460 },
{ 175, 450 },
{ 181, 440 },
{ 187, 430 },
{ 193, 420 },
{ 199, 410 },
{ 205, 400 },
{ 212, 390 },
{ 218, 380 },
{ 227, 370 },
{ 233, 360 },
{ 240, 350 },
{ 249, 340 },
{ 258, 330 },
{ 267, 320 },
{ 276, 310 },
{ 285, 300 },
{ 299, 290 },
{ 308, 280 },
{ 313, 270 },
{ 322, 260 },
{ 331, 250 },
{ 342, 240 },
{ 355, 230 },
{ 363, 220 },
{ 373, 210 },
{ 383, 200 },
{ 394, 190 },
{ 407, 180 },
{ 417, 170 },
{ 427, 160 },
{ 437, 150 },
{ 450, 140 },
{ 465, 130 },
{ 475, 120 },
{ 487, 110 },
{ 500, 100 },
{ 514, 90 },
{ 526, 80 },
{ 540, 70 },
{ 552, 60 },
{ 565, 50 },
{ 577, 40 },
{ 589, 30 },
{ 603, 20 },
{ 614, 10 },
{ 628, 0 },
{ 639, (-10) },
{ 664, (-20) },
{ 689, (-30) },
{ 717, (-40) },
{ 744, (-50) },
{ 754, (-60) },
{ 765, (-70) },
{ 776, (-80) },
{ 787, (-90) },
{ 798, (-100) },
};
static struct temp_adc_table_data temper_table_toro[] = {
/* ADC, Temperature (C/10) */
{ 70, 700 },
{ 72, 690 },
{ 75, 680 },
{ 77, 670 },
{ 80, 660 },
{ 82, 650 },
{ 85, 640 },
{ 88, 630 },
{ 91, 620 },
{ 94, 610 },
{ 97, 600 },
{ 101, 590 },
{ 104, 580 },
{ 108, 570 },
{ 111, 560 },
{ 115, 550 },
{ 119, 540 },
{ 124, 530 },
{ 129, 520 },
{ 133, 510 },
{ 137, 500 },
{ 141, 490 },
{ 146, 480 },
{ 150, 470 },
{ 155, 460 },
{ 160, 450 },
{ 166, 440 },
{ 171, 430 },
{ 177, 420 },
{ 184, 410 },
{ 190, 400 },
{ 196, 390 },
{ 203, 380 },
{ 212, 370 },
{ 219, 360 },
{ 226, 350 },
{ 234, 340 },
{ 242, 330 },
{ 250, 320 },
{ 258, 310 },
{ 266, 300 },
{ 275, 290 },
{ 284, 280 },
{ 294, 270 },
{ 303, 260 },
{ 312, 250 },
{ 322, 240 },
{ 333, 230 },
{ 344, 220 },
{ 354, 210 },
{ 364, 200 },
{ 375, 190 },
{ 387, 180 },
{ 399, 170 },
{ 410, 160 },
{ 422, 150 },
{ 431, 140 },
{ 443, 130 },
{ 456, 120 },
{ 468, 110 },
{ 480, 100 },
{ 493, 90 },
{ 506, 80 },
{ 519, 70 },
{ 532, 60 },
{ 545, 50 },
{ 558, 40 },
{ 571, 30 },
{ 582, 20 },
{ 595, 10 },
{ 608, 0 },
{ 620, (-10) },
{ 632, (-20) },
{ 645, (-30) },
{ 658, (-40) },
{ 670, (-50) },
{ 681, (-60) },
{ 696, (-70) },
{ 708, (-80) },
{ 720, (-90) },
{ 732, (-100) },
};
static struct temp_adc_table_data *temper_table = temper_table_maguro;
static int temper_table_size = ARRAY_SIZE(temper_table_maguro);
static bool enable_sr = true;
module_param(enable_sr, bool, S_IRUSR | S_IRGRP | S_IROTH);
static struct gpio charger_gpios[] = {
{ .gpio = GPIO_CHARGING_N, .flags = GPIOF_IN, .label = "charging_n" },
{ .gpio = GPIO_TA_NCONNECTED, .flags = GPIOF_IN, .label = "charger_n" },
{ .gpio = GPIO_CHARGE_N, .flags = GPIOF_OUT_INIT_HIGH, .label = "charge_n" },
{ .gpio = GPIO_CHG_CUR_ADJ, .flags = GPIOF_OUT_INIT_LOW, .label = "charge_cur_adj" },
};
static int twl6030_get_adc_data(int ch)
{
int adc_data;
int adc_max = -1;
int adc_min = 1 << 11;
int adc_total = 0;
int i, j;
for (i = 0; i < ADC_NUM_SAMPLES; i++) {
adc_data = twl6030_get_madc_conversion(ch);
if (adc_data == -EAGAIN) {
for (j = 0; j < ADC_LIMIT_ERR_COUNT; j++) {
msleep(20);
adc_data = twl6030_get_madc_conversion(ch);
if (adc_data > 0)
break;
}
if (j >= ADC_LIMIT_ERR_COUNT) {
pr_err("%s: Retry count exceeded[ch:%d]\n",
__func__, ch);
return adc_data;
}
} else if (adc_data < 0) {
pr_err("%s: Failed read adc value : %d [ch:%d]\n",
__func__, adc_data, ch);
return adc_data;
}
if (adc_data > adc_max)
adc_max = adc_data;
if (adc_data < adc_min)
adc_min = adc_data;
adc_total += adc_data;
}
return (adc_total - adc_max - adc_min) / (ADC_NUM_SAMPLES - 2);
}
static int iset_adc_value(void)
{
return twl6030_get_adc_data(ISET_ADC_CHANNEL);
}
static int temp_adc_value(void)
{
return twl6030_get_adc_data(TEMP_ADC_CHANNEL);
}
static bool check_charge_full(void)
{
int ret;
ret = iset_adc_value();
if (ret < 0) {
pr_err("%s: invalid iset adc value [%d]\n",
__func__, ret);
return false;
}
pr_debug("%s : iset adc value : %d\n", __func__, ret);
return ret < CHARGE_FULL_ADC;
}
static int get_bat_temp_by_adc(int *batt_temp)
{
int array_size = temper_table_size;
int temp_adc = temp_adc_value();
int mid;
int left_side = 0;
int right_side = array_size - 1;
int temp = 0;
if (temp_adc < 0) {
pr_err("%s : Invalid temperature adc value [%d]\n",
__func__, temp_adc);
return temp_adc;
}
while (left_side <= right_side) {
mid = (left_side + right_side) / 2;
if (mid == 0 || mid == array_size - 1 ||
(temper_table[mid].adc_value <= temp_adc &&
temper_table[mid+1].adc_value > temp_adc)) {
temp = temper_table[mid].temperature;
break;
} else if (temp_adc - temper_table[mid].adc_value > 0) {
left_side = mid + 1;
} else {
right_side = mid - 1;
}
}
pr_debug("%s: temp adc : %d, temp : %d\n", __func__, temp_adc, temp);
*batt_temp = temp;
return 0;
}
static int charger_init(struct device *dev)
{
return gpio_request_array(charger_gpios, ARRAY_SIZE(charger_gpios));
}
static void charger_exit(struct device *dev)
{
gpio_free_array(charger_gpios, ARRAY_SIZE(charger_gpios));
}
static void set_charge_en(int state)
{
gpio_set_value(GPIO_CHARGE_N, !state);
}
static void charger_set_charge(int state)
{
unsigned long flags;
spin_lock_irqsave(&charge_en_lock, flags);
gpio_set_value(GPIO_CHG_CUR_ADJ, !!(state & PDA_POWER_CHARGE_AC));
charger_state = state;
set_charge_en(state);
spin_unlock_irqrestore(&charge_en_lock, flags);
}
static void charger_set_only_charge(int state)
{
unsigned long flags;
spin_lock_irqsave(&charge_en_lock, flags);
if (charger_state)
set_charge_en(state);
spin_unlock_irqrestore(&charge_en_lock, flags);
/* CHG_ING_N level changed after set charge_en and 150ms */
msleep(150);
}
static int charger_is_online(void)
{
return !gpio_get_value(GPIO_TA_NCONNECTED);
}
static int charger_is_charging(void)
{
return !gpio_get_value(GPIO_CHARGING_N);
}
static char *tuna_charger_supplied_to[] = {
"battery",
};
static const __initdata struct pda_power_pdata charger_pdata = {
.init = charger_init,
.exit = charger_exit,
.set_charge = charger_set_charge,
.wait_for_status = 500,
.wait_for_charger = 500,
.supplied_to = tuna_charger_supplied_to,
.num_supplicants = ARRAY_SIZE(tuna_charger_supplied_to),
.use_otg_notifier = true,
};
static struct max17040_platform_data max17043_pdata = {
.charger_online = charger_is_online,
.charger_enable = charger_is_charging,
.allow_charging = charger_set_only_charge,
.skip_reset = true,
.min_capacity = 3,
.is_full_charge = check_charge_full,
.get_bat_temp = get_bat_temp_by_adc,
.high_block_temp = HIGH_BLOCK_TEMP_MAGURO,
.high_recover_temp = HIGH_RECOVER_TEMP_MAGURO,
.low_block_temp = LOW_BLOCK_TEMP_MAGURO,
.low_recover_temp = LOW_RECOVER_TEMP_MAGURO,
.fully_charged_vol = 4150000,
.recharge_vol = 4140000,
.limit_charging_time = 21600, /* 6 hours */
.limit_recharging_time = 5400, /* 90 min */
};
static const __initdata struct i2c_board_info max17043_i2c[] = {
{
I2C_BOARD_INFO("max17040", (0x6C >> 1)),
.platform_data = &max17043_pdata,
.irq = OMAP_GPIO_IRQ(GPIO_FUEL_ALERT),
}
};
static int __init tuna_charger_mode_setup(char *str)
{
if (!str) /* No mode string */
return 0;
is_charging_mode = !strcmp(str, "charger");
pr_debug("Charge mode string = \"%s\" charger mode = %d\n", str,
is_charging_mode);
return 1;
}
__setup("androidboot.mode=", tuna_charger_mode_setup);
void __init omap4_tuna_power_init(void)
{
struct platform_device *pdev;
int status;
/* Vsel0 = gpio, vsel1 = gnd */
status = omap_tps6236x_board_setup(true, TPS62361_GPIO, -1,
OMAP_PIN_OFF_OUTPUT_HIGH, -1);
if (status)
pr_err("TPS62361 initialization failed: %d\n", status);
/*
* Some Tuna devices have a 4430 chip on a 4460 board, manually
* tweak the power tree to the 4460 style with the TPS regulator.
*/
if (cpu_is_omap443x()) {
/* Disable 4430 mapping */
omap_twl_pmic_update("mpu", CHIP_IS_OMAP443X, 0x0);
omap_twl_pmic_update("core", CHIP_IS_OMAP443X, 0x0);
/* make 4460 map usable for 4430 */
omap_twl_pmic_update("core", CHIP_IS_OMAP446X, CHIP_IS_OMAP443X);
omap_tps6236x_update("mpu", CHIP_IS_OMAP446X, CHIP_IS_OMAP443X);
}
/* Update temperature data from board type */
if (omap4_tuna_get_type() == TUNA_TYPE_TORO) {
temper_table = temper_table_toro;
temper_table_size = ARRAY_SIZE(temper_table_toro);
max17043_pdata.high_block_temp = HIGH_BLOCK_TEMP_TORO;
max17043_pdata.high_recover_temp = HIGH_RECOVER_TEMP_TORO;
max17043_pdata.low_block_temp = LOW_BLOCK_TEMP_TORO;
max17043_pdata.low_recover_temp = LOW_RECOVER_TEMP_TORO;
}
/* Update oscillator information */
if (omap4_tuna_get_revision() <= 0x3) {
/*
* until sample 4 (Toro and Maguro), we used KC2520B38:
* ST = 10ms
* Output Disable time = 100ns
* Output enable time = 5ms
* tstart = 10ms + 5ms = 15ms.
* tshut = 1us (rounded)
*/
omap_pm_set_osc_lp_time(15000, 1);
} else {
/*
* sample 5 onwards (Toro and Maguro), we use SQ200384:
* ST = 10ms
* Output Disable time = 100ns
* Output enable time = 10ms
* tstart = 10ms + 10ms = 20ms.
* tshut = 1us (rounded)
*/
omap_pm_set_osc_lp_time(20000, 1);
}
omap_mux_init_gpio(charger_gpios[0].gpio, OMAP_PIN_INPUT);
omap_mux_init_gpio(charger_gpios[1].gpio, OMAP_PIN_INPUT);
omap_mux_init_gpio(charger_gpios[2].gpio, OMAP_PIN_OUTPUT);
omap_mux_init_gpio(charger_gpios[3].gpio, OMAP_PIN_OUTPUT);
omap_mux_init_gpio(GPIO_FUEL_ALERT, OMAP_PIN_INPUT);
pdev = platform_device_register_resndata(NULL, "pda-power", -1,
NULL, 0, &charger_pdata, sizeof(charger_pdata));
if (IS_ERR_OR_NULL(pdev))
pr_err("cannot register pda-power\n");
max17043_pdata.use_fuel_alert = !is_charging_mode;
i2c_register_board_info(4, max17043_i2c, ARRAY_SIZE(max17043_i2c));
if (enable_sr)
omap_enable_smartreflex_on_init();
omap_pm_enable_off_mode();
}