/* 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 #include #include #include #include #include #include #include #include #include #if defined(CONFIG_TWL6030_GPADC) #include #elif defined(CONFIG_TWL4030_MADC) #include #else /* CONFIG_TWL6030_MADC */ #include #endif #include #include #include #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++) { #if defined(CONFIG_TWL6030_GPADC) adc_data = twl6030_get_gpadc_conversion(ch); #elif defined(CONFIG_TWL4030_MADC) adc_data = twl4030_get_madc_conversion(ch); #else /* CONFIG_TWL6030_MADC */ adc_data = twl6030_get_madc_conversion(ch); #endif if (adc_data == -EAGAIN) { for (j = 0; j < ADC_LIMIT_ERR_COUNT; j++) { msleep(20); #if defined(CONFIG_TWL6030_GPADC) adc_data = twl6030_get_gpadc_conversion(ch); #elif defined(CONFIG_TWL4030_MADC) adc_data = twl4030_get_madc_conversion(ch); #else /* CONFIG_TWL6030_MADC */ adc_data = twl6030_get_madc_conversion(ch); #endif 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(); }