blob: 5cc80012babf99d174c5cc909149cc386c65934e [file] [log] [blame]
/*
* Dollar Cove -- Device access for Intel PMIC for CR
*
* Copyright (c) 2014, Intel Corporation.
*
* Author: Yang Bin <bin.yang@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/mfd/core.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/acpi.h>
#include <linux/version.h>
#include <linux/power/dc_xpwr_battery.h>
#include <linux/power/dc_xpwr_charger.h>
#include <linux/regulator/intel_dollar_cove_pmic.h>
#include <asm/intel_em_config.h>
#include <linux/extcon/extcon-dc-pwrsrc.h>
#include <linux/power/battery_id.h>
#include "intel_soc_pmic_core.h"
#define XPOWER_DEFAULT_TEMP_MAX 45
#define GPIO_USB_MUX_INDEX 1
#define GPIO_OTG_VBUS_INDEX 2
enum {
VBUS_FALLING_IRQ = 2,
VBUS_RISING_IRQ,
VBUS_OV_IRQ,
VBUS_FALLING_ALT_IRQ,
VBUS_RISING_ALT_IRQ,
VBUS_OV_ALT_IRQ,
CHARGE_DONE_IRQ = 10,
CHARGE_CHARGING_IRQ,
BAT_SAFE_QUIT_IRQ,
BAT_SAFE_ENTER_IRQ,
BAT_ABSENT_IRQ,
BAT_APPEND_IRQ,
QWBTU_IRQ = 16,
WBTU_IRQ,
QWBTO_IRQ,
WBTO_IRQ,
QCBTU_IRQ,
CBTU_IRQ,
QCBTO_IRQ,
CBTO_IRQ,
WL2_IRQ = 24,
WL1_IRQ,
GPADC_IRQ,
OT_IRQ = 31,
GPIO0_IRQ = 32,
GPIO1_IRQ,
POKO_IRQ,
POKL_IRQ,
POKS_IRQ,
POKN_IRQ,
POKP_IRQ,
EVENT_IRQ,
MV_CHNG_IRQ = 40,
BC_USB_CHNG_IRQ,
};
static struct resource power_button_resources[] = {
{
.start = POKN_IRQ,
.end = POKN_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = POKP_IRQ,
.end = POKP_IRQ,
.flags = IORESOURCE_IRQ,
},
};
static struct resource gpio_resources[] = {
{
.start = GPIO0_IRQ,
.end = GPIO1_IRQ,
.flags = IORESOURCE_IRQ,
},
};
static struct resource adc_resources[] = {
{
.start = GPADC_IRQ,
.end = GPADC_IRQ,
.flags = IORESOURCE_IRQ,
},
};
static struct resource pwrsrc_resources[] = {
{
.start = VBUS_FALLING_IRQ,
.end = VBUS_FALLING_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = VBUS_RISING_IRQ,
.end = VBUS_RISING_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = MV_CHNG_IRQ,
.end = MV_CHNG_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = BC_USB_CHNG_IRQ,
.end = BC_USB_CHNG_IRQ,
.flags = IORESOURCE_IRQ,
},
};
static struct resource charger_resources[] = {
{
.start = VBUS_OV_IRQ,
.end = VBUS_OV_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = CHARGE_DONE_IRQ,
.end = CHARGE_DONE_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = CHARGE_CHARGING_IRQ,
.end = CHARGE_CHARGING_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = BAT_SAFE_QUIT_IRQ,
.end = BAT_SAFE_QUIT_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = BAT_SAFE_ENTER_IRQ,
.end = BAT_SAFE_ENTER_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = QCBTU_IRQ,
.end = QCBTU_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = CBTU_IRQ,
.end = CBTU_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = QCBTO_IRQ,
.end = QCBTO_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = CBTO_IRQ,
.end = CBTO_IRQ,
.flags = IORESOURCE_IRQ,
},
};
static struct resource battery_resources[] = {
{
.start = QWBTU_IRQ,
.end = QWBTU_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = WBTU_IRQ,
.end = WBTU_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = QWBTO_IRQ,
.end = QWBTO_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = WBTO_IRQ,
.end = WBTO_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = WL2_IRQ,
.end = WL2_IRQ,
.flags = IORESOURCE_IRQ,
},
{
.start = WL1_IRQ,
.end = WL1_IRQ,
.flags = IORESOURCE_IRQ,
},
};
static struct mfd_cell dollar_cove_dev[] = {
{
.name = "dollar_cove_adc",
.id = 0,
.num_resources = ARRAY_SIZE(adc_resources),
.resources = adc_resources,
},
{
.name = "dollar_cove_gpio",
.id = 0,
.num_resources = ARRAY_SIZE(gpio_resources),
.resources = gpio_resources,
},
{
.name = "dollar_cove_power_button",
.id = 0,
.num_resources = ARRAY_SIZE(power_button_resources),
.resources = power_button_resources,
},
{
.name = "dollar_cove_pwrsrc",
.id = 0,
.num_resources = ARRAY_SIZE(pwrsrc_resources),
.resources = pwrsrc_resources,
},
{
.name = "dollar_cove_charger",
.id = 0,
.num_resources = ARRAY_SIZE(charger_resources),
.resources = charger_resources,
},
{
.name = "dollar_cove_battery",
.id = 0,
.num_resources = ARRAY_SIZE(battery_resources),
.resources = battery_resources,
},
{
.name = "dollar_cove_region",
},
{
.name = "dcovex_regulator",
.id = DCOVEX_ID_LDO2 + 1,
.num_resources = 0,
.resources = NULL,
},
{
.name = "dcovex_regulator",
.id = DCOVEX_ID_LDO3 + 1,
.num_resources = 0,
.resources = NULL,
},
{NULL, },
};
#define DOLLAR_COVE_IRQREGMAP(irq) \
[irq] = { \
{(0x40 + (irq / 8)), (irq % 8), 1, INTEL_PMIC_REG_INV},\
{(0x48 + (irq / 8)), (irq % 8), 1, INTEL_PMIC_REG_W1C},\
{(0x48 + (irq / 8)), (irq % 8), 1, INTEL_PMIC_REG_W1C},\
}
struct intel_pmic_irqregmap dollar_cove_irqregmap[] = {
DOLLAR_COVE_IRQREGMAP(VBUS_FALLING_IRQ),
DOLLAR_COVE_IRQREGMAP(VBUS_RISING_IRQ),
DOLLAR_COVE_IRQREGMAP(VBUS_OV_IRQ),
DOLLAR_COVE_IRQREGMAP(VBUS_FALLING_ALT_IRQ),
DOLLAR_COVE_IRQREGMAP(VBUS_RISING_ALT_IRQ),
DOLLAR_COVE_IRQREGMAP(VBUS_OV_ALT_IRQ),
DOLLAR_COVE_IRQREGMAP(CHARGE_DONE_IRQ),
DOLLAR_COVE_IRQREGMAP(CHARGE_CHARGING_IRQ),
DOLLAR_COVE_IRQREGMAP(BAT_SAFE_QUIT_IRQ),
DOLLAR_COVE_IRQREGMAP(BAT_SAFE_ENTER_IRQ),
DOLLAR_COVE_IRQREGMAP(BAT_ABSENT_IRQ),
DOLLAR_COVE_IRQREGMAP(BAT_APPEND_IRQ),
DOLLAR_COVE_IRQREGMAP(QWBTU_IRQ),
DOLLAR_COVE_IRQREGMAP(WBTU_IRQ),
DOLLAR_COVE_IRQREGMAP(QWBTO_IRQ),
DOLLAR_COVE_IRQREGMAP(WBTO_IRQ),
DOLLAR_COVE_IRQREGMAP(QCBTU_IRQ),
DOLLAR_COVE_IRQREGMAP(CBTU_IRQ),
DOLLAR_COVE_IRQREGMAP(QCBTO_IRQ),
DOLLAR_COVE_IRQREGMAP(CBTO_IRQ),
DOLLAR_COVE_IRQREGMAP(WL2_IRQ),
DOLLAR_COVE_IRQREGMAP(WL1_IRQ),
DOLLAR_COVE_IRQREGMAP(GPADC_IRQ),
DOLLAR_COVE_IRQREGMAP(OT_IRQ),
DOLLAR_COVE_IRQREGMAP(GPIO0_IRQ),
DOLLAR_COVE_IRQREGMAP(GPIO1_IRQ),
DOLLAR_COVE_IRQREGMAP(POKO_IRQ),
DOLLAR_COVE_IRQREGMAP(POKL_IRQ),
DOLLAR_COVE_IRQREGMAP(POKS_IRQ),
DOLLAR_COVE_IRQREGMAP(POKN_IRQ),
DOLLAR_COVE_IRQREGMAP(POKP_IRQ),
DOLLAR_COVE_IRQREGMAP(EVENT_IRQ),
DOLLAR_COVE_IRQREGMAP(MV_CHNG_IRQ),
DOLLAR_COVE_IRQREGMAP(BC_USB_CHNG_IRQ),
};
static struct ps_batt_chg_prof ps_batt_chrg_prof;
static struct ps_pse_mod_prof *pse_mod_prof;
#ifdef CONFIG_POWER_SUPPLY_CHARGER
#define DC_CHRG_CHRG_CUR_NOLIMIT 1800
#define DC_CHRG_CHRG_CUR_MEDIUM 1400
#define DC_CHRG_CHRG_CUR_LOW 1000
static struct power_supply_throttle dc_chrg_throttle_states[] = {
{
.throttle_action = PSY_THROTTLE_CC_LIMIT,
.throttle_val = DC_CHRG_CHRG_CUR_NOLIMIT,
},
{
.throttle_action = PSY_THROTTLE_CC_LIMIT,
.throttle_val = DC_CHRG_CHRG_CUR_MEDIUM,
},
{
.throttle_action = PSY_THROTTLE_CC_LIMIT,
.throttle_val = DC_CHRG_CHRG_CUR_LOW,
},
{
.throttle_action = PSY_THROTTLE_DISABLE_CHARGING,
},
};
static char *dc_chrg_supplied_to[] = {
"dollar_cove_battery"
};
static void *platform_get_batt_charge_profile(void)
{
int ret;
ret = get_batt_prop(&ps_batt_chrg_prof);
pse_mod_prof = (struct ps_pse_mod_prof *)
ps_batt_chrg_prof.batt_prof;
if ((ret < 0 || ps_batt_chrg_prof.chrg_prof_type == CHRG_PROF_NONE)
&& pse_mod_prof)
snprintf(pse_mod_prof->batt_id, (BATTID_STR_LEN + 1),
"%s", BATTID_UNKNOWN);
return &ps_batt_chrg_prof;
}
static void platform_init_chrg_params(struct dollarcove_chrg_pdata *pdata)
{
pdata->throttle_states = dc_chrg_throttle_states;
pdata->supplied_to = dc_chrg_supplied_to;
pdata->num_throttle_states = ARRAY_SIZE(dc_chrg_throttle_states);
pdata->num_supplicants = ARRAY_SIZE(dc_chrg_supplied_to);
pdata->supported_cables = POWER_SUPPLY_CHARGER_TYPE_USB;
pdata->chg_profile = (struct ps_batt_chg_prof *)
platform_get_batt_charge_profile();
}
static void platform_set_battery_data(struct dollarcove_fg_pdata *pdata,
struct ps_batt_chg_prof *chg_prof)
{
struct ps_pse_mod_prof *prof = (struct ps_pse_mod_prof *)
chg_prof->batt_prof;
/*
* Get the data from ACPI Table OEM0 if it is available
* Also make sure we are not setting value of 0 as acpi table may
* sometimes incorrectly set them to 0.
*/
if (chg_prof->chrg_prof_type == PSE_MOD_CHRG_PROF) {
pdata->design_cap = prof->capacity ? prof->capacity : 4045;
pdata->design_max_volt = prof->voltage_max ? prof->voltage_max : 4350;
pdata->design_min_volt = prof->low_batt_mV ? prof->low_batt_mV : 3400;
} else {
pdata->design_cap = 4045;
pdata->design_max_volt = 4350;
pdata->design_min_volt = 3400;
}
}
#else
static void platform_init_chrg_params(struct dollarcove_chrg_pdata *pdata)
{
}
static void platform_set_battery_data(struct dollarcove_fg_pdata *pdata,
struct ps_batt_chg_prof *chg_prof)
{
pdata->design_cap = 4045;
pdata->design_max_volt = 4350;
pdata->design_min_volt = 3400;
}
#endif
static void dc_xpwr_chrg_pdata(void)
{
static struct dollarcove_chrg_pdata pdata;
struct gpio_desc *gpio_desc;
struct ps_pse_mod_prof *prof;
int i, diff;
struct ps_temp_chg_table *entry;
/* Default parameters */
pdata.max_cc = 2000;
pdata.max_cv = 4350;
pdata.def_cc = 500;
pdata.def_cv = 4350;
pdata.def_ilim = 900;
pdata.def_iterm = 300;
pdata.def_max_temp = 55;
pdata.def_min_temp = 0;
gpio_desc = devm_gpiod_get_index(dollar_cove_pmic.dev,
"pmic_res", GPIO_OTG_VBUS_INDEX);
if (IS_ERR(gpio_desc))
pdata.otg_gpio = -1;
else {
pdata.otg_gpio = desc_to_gpio(gpio_desc);
gpiod_put(gpio_desc);
}
platform_init_chrg_params(&pdata);
/* Do some sanity checks */
prof = (struct ps_pse_mod_prof *)pdata.chg_profile->batt_prof;
pdata.max_cv = min_t(u16, pdata.max_cv, prof->voltage_max);
pdata.def_cv = min_t(u16, pdata.def_cv, prof->voltage_max);
for (i = 0; i < prof->temp_mon_ranges; i++) {
entry = &prof->temp_mon_range[i];
if (entry->full_chrg_vol > pdata.max_cv)
entry->full_chrg_vol = pdata.max_cv;
if (entry->maint_chrg_vol_ul > pdata.max_cv) {
diff = entry->maint_chrg_vol_ul
- entry->maint_chrg_vol_ll;
entry->maint_chrg_vol_ul = pdata.max_cv;
entry->maint_chrg_vol_ll = pdata.max_cv - diff;
}
}
intel_soc_pmic_set_pdata("dollar_cove_charger",
(void *)&pdata, sizeof(pdata), 0);
}
static u8 fg_bat_curve_4350mv[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x02, 0x03, 0x05, 0x09, 0x0f, 0x18, 0x24, 0x29,
0x2e, 0x32, 0x35, 0x3b, 0x40, 0x45, 0x49, 0x4c,
0x50, 0x53, 0x55, 0x57, 0x5a, 0x5d, 0x61, 0x64,
};
static u8 fg_bat_curve_4200mv[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02,
0x02, 0x02, 0x04, 0x0b, 0x12, 0x1b, 0x27, 0x2e,
0x32, 0x36, 0x3a, 0x40, 0x45, 0x4a, 0x4e, 0x52,
0x57, 0x5b, 0x5e, 0x61, 0x64, 0x64, 0x64, 0x64,
};
#ifdef CONFIG_ACPI
#define FGCONFIG_ACPI_TABLE_NAME "BCFG"
#define XPWR_FGCONFIG_NAME "XPOWER-0"
static int
dc_xpwr_get_acpi_cdata(struct dollarcove_fg_pdata *pdata)
{
struct dc_xpwr_acpi_fg_config *acpi_tbl = NULL;
char *name = FGCONFIG_ACPI_TABLE_NAME;
acpi_size tbl_size;
acpi_status status;
/* read the fg config table from acpi */
status = acpi_get_table_with_size(name , 0,
(struct acpi_table_header **)&acpi_tbl, &tbl_size);
if (ACPI_FAILURE(status)) {
pr_err("%s:%s table not found!!\n", __func__, name);
return status;
}
pr_info("%s: %s table found, size=%d\n",
__func__, name, (int)tbl_size);
/* validate the table size */
if (tbl_size < sizeof(struct dc_xpwr_acpi_fg_config)) {
pr_err("%s:%s table incomplete!!\n", __func__, name);
pr_info("%s: table_size=%d, structure_size=%lu\n",
__func__, (int)tbl_size,
sizeof(struct dc_xpwr_acpi_fg_config));
return -ENXIO;
}
if (strncmp(acpi_tbl->cdata.fg_name, XPWR_FGCONFIG_NAME,
ACPI_FG_CONF_NAME_LEN)
|| strncmp(acpi_tbl->cdata.battid, pdata->battid,
BATTID_STR_LEN)) {
pr_err("%s: battid and fg_name mismatch!!!\n", __func__);
return -EINVAL;
}
memcpy(&pdata->cdata, &acpi_tbl->cdata,
sizeof(struct dc_xpwr_fg_config_data));
return 0;
}
#endif /* CONFIG_ACPI */
static void dc_xpwr_get_fg_config_data(struct dollarcove_fg_pdata *pdata)
{
int scaled_capacity;
int i;
u8 *fg_bat_curve;
#ifdef CONFIG_ACPI
if (!dc_xpwr_get_acpi_cdata(pdata)) {
pr_info("%s: Loading fg config from acpi table\n", __func__);
pdata->fg_save_restore_enabled = 1;
return;
}
#endif /* CONFIG_ACPI */
pr_info("%s: Loading default fg config.\n", __func__);
/*
* Calculate cap1 and cap0. The value of a LSB is 1.456mAh.
* Using 1.5 as math friendly and close enough.
*/
scaled_capacity = (pdata->design_cap >> 1) +
(pdata->design_cap >> 3) +
(pdata->design_cap >> 4);
/*
* bit 7 of cap1 register is set to indicate battery maximum
* capacity is valid
*/
pdata->cdata.cap0 = scaled_capacity & 0xFF;
pdata->cdata.cap1 = (scaled_capacity >> 8) | 0x80;
pdata->cdata.rdc1 = 0xc0;
pdata->cdata.rdc0 = 0x97;
/* Donot update the entire fg data on every boot*/
pdata->cdata.fco = 0x00;
/* copy curve data */
if (pdata->design_max_volt == 4200)
fg_bat_curve = fg_bat_curve_4200mv;
else
fg_bat_curve = fg_bat_curve_4350mv;
for (i = 0; i < XPWR_BAT_CURVE_SIZE; i++)
pdata->cdata.bat_curve[i] = fg_bat_curve[i];
pdata->fg_save_restore_enabled = 0;
return;
}
static void dc_xpwr_fg_pdata(void)
{
static struct dollarcove_fg_pdata pdata;
int i, temp_mon_ranges;
int scaled_capacity;
if (ps_batt_chrg_prof.chrg_prof_type == CHRG_PROF_NONE
|| (!pse_mod_prof))
snprintf(pdata.battid, (BATTID_STR_LEN + 1),
"%s", BATTID_UNKNOWN);
else {
memcpy(pdata.battid, pse_mod_prof->batt_id,
strlen(pse_mod_prof->batt_id));
pdata.battid[BATTID_STR_LEN] = '\0';
}
platform_set_battery_data(&pdata, &ps_batt_chrg_prof);
pse_mod_prof = (struct ps_pse_mod_prof *)
ps_batt_chrg_prof.batt_prof;
temp_mon_ranges = min_t(u16, pse_mod_prof->temp_mon_ranges,
BATT_TEMP_NR_RNG);
for (i = 0; i < temp_mon_ranges; ++i) {
if (pse_mod_prof->temp_mon_range[i].full_chrg_cur)
break;
}
if (i < temp_mon_ranges)
pdata.max_temp = pse_mod_prof->temp_mon_range[i].temp_up_lim;
else
pdata.max_temp = XPOWER_DEFAULT_TEMP_MAX;
pdata.min_temp = pse_mod_prof->temp_low_lim;
/* Load FG config data to pdata */
dc_xpwr_get_fg_config_data(&pdata);
intel_soc_pmic_set_pdata("dollar_cove_battery",
(void *)&pdata, sizeof(pdata), 0);
}
static void dc_xpwr_pwrsrc_pdata(void)
{
static struct dc_xpwr_pwrsrc_pdata pdata;
/*
* set en_chrg_det to true if the
* D+/D- lines are connected to
* PMIC itself.
*/
pdata.en_chrg_det = true;
/* Get the gpio based uab mux which will be used to switch usb D+/D-
* data line between SOC for data communication and PMIC for charger
* detection functionality.
*/
pdata.gpio_mux_cntl = devm_gpiod_get_index(dollar_cove_pmic.dev,
"pmic_res", GPIO_USB_MUX_INDEX);
if (IS_ERR(pdata.gpio_mux_cntl))
pdata.gpio_mux_cntl = NULL;
else
gpiod_put(pdata.gpio_mux_cntl);
intel_soc_pmic_set_pdata("dollar_cove_pwrsrc",
(void *)&pdata, sizeof(pdata), 0);
}
static struct pmic_gpio_data dollar_cove_gpio_data = {
.type = DOLLAR_COVE,
.num_gpio = 2,
.num_vgpio = 0x5e,
};
static void dc_set_gpio_pdata(void)
{
intel_soc_pmic_set_pdata("dollar_cove_gpio",
(void *)&dollar_cove_gpio_data,
sizeof(dollar_cove_gpio_data), 0);
}
static int dollar_cove_init(void)
{
pr_info("Dollar Cove: IC_TYPE 0x%02X\n", intel_soc_pmic_readb(0x03));
dc_xpwr_chrg_pdata();
dc_xpwr_pwrsrc_pdata();
dc_xpwr_fg_pdata();
dc_set_gpio_pdata();
return 0;
}
struct intel_soc_pmic dollar_cove_pmic = {
.label = "dollar cove",
.irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT
| IRQF_NO_SUSPEND,
.init = dollar_cove_init,
.cell_dev = dollar_cove_dev,
.irq_regmap = dollar_cove_irqregmap,
.irq_num = 48,
};
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yang Bin <bin.yang@intel.com");