blob: 619c94aef3baa3d8b0ba1439295575183cf61b0d [file] [log] [blame]
#include "88pm8xx-config.h"
#define PM800_BASE_PAGE 0x0
#define PM800_POWER_PAGE 0x1
#define PM800_GPADC_PAGE 0x2
/*
* board specfic configurations also parameters from customers,
* this parameters are passed form board dts file
*/
static void pmic_board_config(struct pm80x_chip *chip, struct device_node *np)
{
unsigned int page, reg, mask, data;
const __be32 *values;
int size, rows, index;
values = of_get_property(np, "marvell,pmic-board-cfg", &size);
if (!values) {
dev_warn(chip->dev, "no valid property for %s\n", np->name);
return;
}
/* number of elements in array */
size /= sizeof(*values);
rows = size / 4;
dev_info(chip->dev, "Proceed PMIC board specific configuration (%d items)\n", rows);
index = 0;
while (rows--) {
page = be32_to_cpup(values + index++);
reg = be32_to_cpup(values + index++);
mask = be32_to_cpup(values + index++);
data = be32_to_cpup(values + index++);
switch (page) {
case PM800_BASE_PAGE:
dev_info(chip->dev, "base [0x%02x] <- 0x%02x, mask = [0x%2x]\n", reg, data, mask);
regmap_update_bits(chip->regmap, reg, mask, data);
break;
case PM800_POWER_PAGE:
dev_info(chip->dev, "power [0x%02x] <- 0x%02x, mask = [0x%2x]\n", reg, data, mask);
regmap_update_bits(chip->subchip->regmap_power, reg, mask, data);
break;
case PM800_GPADC_PAGE:
dev_info(chip->dev, "gpadc [0x%02x] <- 0x%02x, mask = [0x%2x]\n", reg, data, mask);
regmap_update_bits(chip->subchip->regmap_gpadc, reg, mask, data);
break;
default:
dev_warn(chip->dev, "Unsupported page for %d\n", page);
break;
}
}
return;
}
void parse_powerup_down_log(struct pm80x_chip *chip)
{
int up_log, down1_log, down2_log, bit;
static const char *powerup_name[7] = {
"ONKEY_WAKEUP ",
"CHG_WAKEUP ",
"EXTON_WAKEUP ",
"RESERVED ",
"RTC_ALARM_WAKEUP",
"FAULT_WAKEUP ",
"BAT_WAKEUP "
};
static const char *powerd1_name[8] = {
"OVER_TEMP ",
"UV_VSYS1 ",
"SW_PDOWN ",
"FL_ALARM ",
"WDT ",
"LONG_ONKEY",
"OV_VSYS ",
"RTC_RESET "
};
static const char *powerd2_name[5] = {
"HYB_DONE ",
"UV_VSYS2 ",
"HW_RESET ",
"PGOOD_PDOWN",
"LONKEY_RTC "
};
/*power up log*/
regmap_read(chip->regmap, PM800_POWER_UP_LOG, &up_log);
dev_info(chip->dev, "powerup log 0x%x: 0x%x\n", PM800_POWER_UP_LOG, up_log);
dev_info(chip->dev, " -------------------------------\n");
dev_info(chip->dev, "| name(power up) | status |\n");
dev_info(chip->dev, "|--------------------|----------|\n");
for (bit = 0; bit < ARRAY_SIZE(powerup_name); bit++)
dev_info(chip->dev, "| %s | %x |\n",
powerup_name[bit], (up_log >> bit) & 1);
dev_info(chip->dev, " -------------------------------\n");
/*power down log1*/
regmap_read(chip->regmap, PM800_POWER_DOWN_LOG1, &down1_log);
dev_info(chip->dev, "powerdown log1 0x%x: 0x%x\n", PM800_POWER_DOWN_LOG1, down1_log);
dev_info(chip->dev, " -------------------------------\n");
dev_info(chip->dev, "| name(power down1) | status |\n");
dev_info(chip->dev, "|--------------------|----------|\n");
for (bit = 0; bit < ARRAY_SIZE(powerd1_name); bit++)
dev_info(chip->dev, "| %s | %x |\n",
powerd1_name[bit], (down1_log >> bit) & 1);
dev_info(chip->dev, " -------------------------------\n");
/*power down log2*/
regmap_read(chip->regmap, PM800_POWER_DOWN_LOG2, &down2_log);
dev_info(chip->dev, "powerdown log2 0x%x: 0x%x\n", PM800_POWER_DOWN_LOG2, down2_log);
dev_info(chip->dev, " -------------------------------\n");
dev_info(chip->dev, "| name(power down2) | status |\n");
dev_info(chip->dev, "|--------------------|----------|\n");
for (bit = 0; bit < ARRAY_SIZE(powerd2_name); bit++)
dev_info(chip->dev, "| %s | %x |\n",
powerd2_name[bit], (down2_log>> bit) & 1);
dev_info(chip->dev, " -------------------------------\n");
/* write to clear */
regmap_write(chip->regmap, PM800_POWER_DOWN_LOG1, 0xff);
regmap_write(chip->regmap, PM800_POWER_DOWN_LOG2, 0xff);
/* mask reserved bits and sleep indication */
down2_log &= 0x1e;
/* keep globals for external usage */
chip->powerup = up_log;
chip->powerdown1 = down1_log;
chip->powerdown2 = down2_log;
}
int pm800_init_config(struct pm80x_chip *chip, struct device_node *np)
{
int data;
if (!chip || !chip->regmap || !chip->subchip
|| !chip->subchip->regmap_power) {
pr_err("%s:chip is not availiable!\n", __func__);
return -EINVAL;
}
/*base page:reg 0xd0.7 = 1 32kHZ generated from XO */
regmap_read(chip->regmap, PM800_RTC_CONTROL, &data);
data |= (1 << 7);
regmap_write(chip->regmap, PM800_RTC_CONTROL, data);
/* Set internal digital sleep voltage as 1.2V */
regmap_read(chip->regmap, PM800_LOW_POWER1, &data);
data &= ~(0xf << 4);
regmap_write(chip->regmap, PM800_LOW_POWER1, data);
/*
* enable 32Khz-out-3 low jitter XO_LJ = 1 in pm800
* enable 32Khz-out-2 low jitter XO_LJ = 1 in pm822
* they are the same bit
*/
regmap_read(chip->regmap, PM800_LOW_POWER2, &data);
data |= (1 << 5);
regmap_write(chip->regmap, PM800_LOW_POWER2, data);
switch (chip->type) {
case CHIP_PM800:
/* enable 32Khz-out-from XO 1, 2, 3 all enabled */
regmap_write(chip->regmap, PM800_RTC_MISC2, 0x2a);
break;
case CHIP_PM822:
/* select 22pF internal capacitance on XTAL1 and XTAL2*/
regmap_read(chip->regmap, PM800_RTC_MISC6, &data);
data |= (0x7 << 4);
regmap_write(chip->regmap, PM800_RTC_MISC6, data);
/* Enable 32Khz-out-from XO 1, 2 all enabled */
regmap_write(chip->regmap, PM800_RTC_MISC2, 0xa);
/* gps use the LDO13 set the current 170mA */
regmap_read(chip->subchip->regmap_power,
PM822_LDO13_CTRL, &data);
data &= ~(0x3);
data |= (0x2);
regmap_write(chip->subchip->regmap_power,
PM822_LDO13_CTRL, data);
/* low power config
* 1. base_page 0x21, BK_CKSLP_DIS is gated 1ms after sleep mode entry.
* 2. base_page 0x23, REF_SLP_EN reference group enter low power mode.
* REF_UVL_SEL set to be 5.6V
* 3. base_page 0x50, 0x55 OSC_CKLCK buck FLL is locked
* 4. gpadc_page 0x06, GP_SLEEP_MODE MEANS_OFF scale set to be 8
* MEANS_EN_SLP set to 1, GPADC operates in sleep duty cycle mode.
*/
regmap_read(chip->regmap, PM800_LOW_POWER2, &data);
data |= (1 << 4);
regmap_write(chip->regmap, PM800_LOW_POWER2, data);
regmap_read(chip->regmap, PM800_LOW_POWER_CONFIG4, &data);
data |= (1 << 4);
data &= ~(0x3 < 2);
regmap_write(chip->regmap, PM800_LOW_POWER_CONFIG4, data);
regmap_read(chip->regmap, PM800_OSC_CNTRL1, &data);
data |= (1 << 0);
regmap_write(chip->regmap, PM800_OSC_CNTRL1, data);
regmap_read(chip->regmap, PM800_OSC_CNTRL6, &data);
data &= ~(1 << 0);
regmap_write(chip->regmap, PM800_OSC_CNTRL6, data);
regmap_read(chip->subchip->regmap_gpadc, PM800_GPADC_MISC_CONFIG2, &data);
data |= (0x7 << 4);
regmap_write(chip->subchip->regmap_gpadc, PM800_GPADC_MISC_CONFIG2, data);
/*
* enable LDO sleep mode
* TODO: GPS and RF module need to test after enable
* ldo3 sleep mode may make emmc not work when resume, disable it
*/
regmap_write(chip->subchip->regmap_power, PM800_LDO_SLP1, 0xba);
regmap_write(chip->subchip->regmap_power, PM800_LDO_SLP2, 0xaa);
regmap_write(chip->subchip->regmap_power, PM800_LDO_SLP3, 0xaa);
regmap_write(chip->subchip->regmap_power, PM800_LDO_SLP4, 0x0a);
break;
case CHIP_PM86X:
/* enable buck1 dual phase mode */
regmap_read(chip->subchip->regmap_power, PM860_BUCK1_MISC,
&data);
data |= BUCK1_DUAL_PHASE_SEL;
regmap_write(chip->subchip->regmap_power, PM860_BUCK1_MISC,
data);
/* xo_cap sel bit(4~6)= 100 12pf register:0xe8 */
regmap_read(chip->regmap, PM860_MISC_RTC3, &data);
data |= (0x4 << 4);
regmap_write(chip->regmap, PM860_MISC_RTC3, data);
switch (chip->chip_id) {
case CHIP_PM86X_ID_A0:
/* set gpio4 and gpio5 to be DVC mode */
regmap_read(chip->regmap, PM860_GPIO_4_5_CNTRL, &data);
data |= PM860_GPIO4_GPIO_MODE(7) | PM860_GPIO5_GPIO_MODE(7);
regmap_write(chip->regmap, PM860_GPIO_4_5_CNTRL, data);
/* enable SLP_CNT_HD for 88pm860 A0 chip */
regmap_update_bits(chip->regmap, PM860_A0_SLP_CNT2,
PM860_A0_SLP_CNT_HD, PM860_A0_SLP_CNT_HD);
break;
case CHIP_PM86X_ID_Z3:
default:
/* set gpio3 and gpio4 to be DVC mode */
regmap_read(chip->regmap, PM860_GPIO_2_3_CNTRL, &data);
data |= PM860_GPIO3_GPIO_MODE(7);
regmap_write(chip->regmap, PM860_GPIO_2_3_CNTRL, data);
regmap_read(chip->regmap, PM860_GPIO_4_5_CNTRL, &data);
data |= PM860_GPIO4_GPIO_MODE(7);
regmap_write(chip->regmap, PM860_GPIO_4_5_CNTRL, data);
break;
}
break;
default:
dev_err(chip->dev, "Unknown device type: %d\n", chip->type);
break;
}
/*
* block wakeup attempts when VSYS rises above
* VSYS_UNDER_RISE_TH1, or power off may fail.
* it is set to prevent contimuous attempt to power up
* incase the VSYS is above the VSYS_LOW_TH threshold.
*/
regmap_read(chip->regmap, PM800_RTC_MISC5, &data);
data |= 0x1;
regmap_write(chip->regmap, PM800_RTC_MISC5, data);
/* enabele LDO and BUCK clock gating in lpm */
regmap_read(chip->regmap, PM800_LOW_POWER_CONFIG3, &data);
data |= (1 << 7);
regmap_write(chip->regmap, PM800_LOW_POWER_CONFIG3, data);
/*
* disable reference group sleep mode
* - to reduce power fluctuation in suspend
*/
regmap_read(chip->regmap, PM800_LOW_POWER_CONFIG4, &data);
data &= ~(1 << 7);
regmap_write(chip->regmap, PM800_LOW_POWER_CONFIG4, data);
/* enable voltage change in pmic, POWER_HOLD = 1 */
regmap_read(chip->regmap, PM800_WAKEUP1, &data);
data |= (1 << 7);
regmap_write(chip->regmap, PM800_WAKEUP1, data);
/* enable buck sleep mode */
regmap_write(chip->subchip->regmap_power, PM800_BUCK_SLP1, 0xaa);
regmap_write(chip->subchip->regmap_power, PM800_BUCK_SLP2, 0x2);
/*
* set buck2 and buck4 driver selection to be full.
* this bit is now reserved and default value is 0,
* for full drive, set to 1.
*/
regmap_read(chip->subchip->regmap_power, 0x7c, &data);
data |= (1 << 2);
regmap_write(chip->subchip->regmap_power, 0x7c, data);
regmap_read(chip->subchip->regmap_power, 0x82, &data);
data |= (1 << 2);
regmap_write(chip->subchip->regmap_power, 0x82, data);
/* need to config board specific parameters */
if (np)
pmic_board_config(chip, np);
return 0;
}
static int pmic_rw_reg(u8 reg, u8 value)
{
int ret;
u8 data, buf[2];
struct i2c_client *client = chip_g->client;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = buf,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = 1,
.buf = &data,
},
};
/*
* I2C pins may be in non-AP pinstate, and __i2c_transfer
* won't change it back to AP pinstate like i2c_transfer,
* so change i2c pins to AP pinstate explicitly here.
*/
i2c_pxa_set_pinstate(client->adapter, "default");
/*
* set i2c to pio mode
* for in power off sequence, irq has been disabled
*/
i2c_set_pio_mode(client->adapter, 1);
buf[0] = reg;
ret = __i2c_transfer(client->adapter, msgs, 2);
if (ret < 0) {
pr_err("%s read register fails...\n", __func__);
WARN_ON(1);
}
/* issue SW power down */
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf[0] = reg;
msgs[0].buf[1] = data | value;
ret = __i2c_transfer(client->adapter, msgs, 1);
if (ret < 0) {
pr_err("%s write data fails: ret = %d\n", __func__, ret);
WARN_ON(1);
}
return ret;
}
void sw_poweroff(void)
{
int ret;
pr_info("turning off power....\n");
ret = pmic_rw_reg(PM800_WAKEUP1, PM800_SW_PDOWN);
if (ret < 0)
pr_err("%s, turn off power fail", __func__);
}
void pmic_reset(void)
{
int ret;
/* pmic reset contain two parts: fault wake up and sw_powerdown */
pr_info("pmic power down/up reset....\n");
ret = pmic_rw_reg(PM800_RTC_MISC5, PM800_FAULT_WAKEUP_EN | PM800_FAULT_WAKEUP);
if (ret < 0) {
pr_err("%s, enbale pmic fault wakeup fail!", __func__);
return;
}
ret = pmic_rw_reg(PM800_WAKEUP1, PM800_SW_PDOWN);
if (ret < 0)
pr_err("%s, turn off power fail", __func__);
}
void extern_set_buck1_slp_volt(int on)
{
int data;
static int data_old;
static bool get_data_old;
/*
* needs to set buck1 sleep voltage as 0.95v if gps is powered on,
* and set it back when gps is powered off;
* This function provide such an interface to satisfy it.
*/
if (!get_data_old) {
regmap_read(chip_g->subchip->regmap_power, PM800_BUCK1_SLEEP, &data_old);
get_data_old = true;
}
data = data_old;
if (on) {
/*
* this means gps power on
* buck1 sleep voltage is set to be 0.95v
*/
data &= ~PM800_BUCK1_SLP_MASK;
data |= PM800_BUCK1_SLP_V095;
regmap_write(chip_g->subchip->regmap_power, PM800_BUCK1_SLEEP, data);
} else {
regmap_write(chip_g->subchip->regmap_power, PM800_BUCK1_SLEEP, data);
}
}
EXPORT_SYMBOL(extern_set_buck1_slp_volt);
/* return gpadc voltage */
int get_gpadc_volt(struct pm80x_chip *chip, int gpadc_id)
{
int ret, volt;
unsigned char buf[2];
int gp_meas;
switch (gpadc_id) {
case PM800_GPADC0:
gp_meas = PM800_GPADC0_MEAS1;
break;
case PM800_GPADC1:
gp_meas = PM800_GPADC1_MEAS1;
break;
case PM800_GPADC2:
gp_meas = PM800_GPADC2_MEAS1;
break;
case PM800_GPADC3:
gp_meas = PM800_GPADC3_MEAS1;
break;
default:
dev_err(chip->dev, "get GPADC failed!\n");
return -EINVAL;
}
ret = regmap_bulk_read(chip->subchip->regmap_gpadc,
gp_meas, buf, 2);
if (ret < 0) {
dev_err(chip->dev, "Attention: failed to get volt!\n");
return -EINVAL;
}
volt = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f);
dev_dbg(chip->dev, "%s: volt value = 0x%x\n", __func__, volt);
/* volt = value * 1.4 * 1000 / (2^12) */
volt = ((volt & 0xfff) * 7 * 100) >> 11;
dev_dbg(chip->dev, "%s: voltage = %dmV\n", __func__, volt);
return volt;
}
/* return voltage via bias current from GPADC */
int get_gpadc_bias_volt(struct pm80x_chip *chip, int gpadc_id, int bias)
{
int volt, data, gp_bias;
switch (gpadc_id) {
case PM800_GPADC0:
gp_bias = PM800_GPADC_BIAS1;
break;
case PM800_GPADC1:
gp_bias = PM800_GPADC_BIAS2;
break;
case PM800_GPADC2:
gp_bias = PM800_GPADC_BIAS3;
break;
case PM800_GPADC3:
gp_bias = PM800_GPADC_BIAS4;
break;
default:
dev_err(chip->dev, "get GPADC failed!\n");
return -EINVAL;
}
/* get the register value */
if (bias > 76)
bias = 76;
if (bias < 1)
bias = 1;
bias = (bias - 1) / 5;
regmap_read(chip->subchip->regmap_gpadc, gp_bias, &data);
data &= 0xf0;
data |= bias;
regmap_write(chip->subchip->regmap_gpadc, gp_bias, data);
volt = get_gpadc_volt(chip, gpadc_id);
if ((volt < 0) || (volt > 1400)) {
dev_err(chip->dev, "%s return %dmV\n", __func__, volt);
return -EINVAL;
}
return volt;
}
/*
* used by non-pmic driver
* TODO: remove later
*/
int extern_get_gpadc_bias_volt(int gpadc_id, int bias)
{
return get_gpadc_bias_volt(chip_g, gpadc_id, bias);
}
EXPORT_SYMBOL(extern_get_gpadc_bias_volt);