blob: 39f8b7df80fcc0508416c742d1564d038f8cd748 [file] [log] [blame]
/*
* Base driver for Marvell 88PM886
*
* Copyright (C) 2014 Marvell International Ltd.
* Yi Zhang <yizhang@marvell.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/mfd/core.h>
#include <linux/mfd/88pm88x.h>
#include <linux/mfd/88pm886.h>
#include <linux/regulator/machine.h>
#include "88pm88x.h"
#define PM88X_POWER_UP_LOG (0x17)
#define PM88X_POWER_DOWN_LOG1 (0xe5)
#define PM88X_POWER_DOWN_LOG2 (0xe6)
#define PM88X_SW_PDOWN (1 << 5)
#define PM88X_BK_OSE_CTRL3 (0x52)
#define PM88X_CHGBK_CONFIG6 (0x50)
/* don't export it at present */
static struct pm88x_chip *pm88x_chip_priv;
extern struct regmap *get_88pm860_codec_regmap(void);
struct regmap *companion_base_page;
struct regmap *get_companion(void)
{
return companion_base_page;
}
EXPORT_SYMBOL(get_companion);
struct regmap *get_codec_companion(void)
{
return get_88pm860_codec_regmap();
}
EXPORT_SYMBOL(get_codec_companion);
static const struct resource onkey_resources[] = {
CELL_IRQ_RESOURCE(PM88X_ONKEY_NAME, PM88X_IRQ_ONKEY),
};
static const struct resource rtc_resources[] = {
CELL_IRQ_RESOURCE(PM88X_RTC_NAME, PM88X_IRQ_RTC),
};
static const struct resource charger_resources[] = {
CELL_IRQ_RESOURCE("88pm88x-chg-fail", PM88X_IRQ_CHG_FAIL),
CELL_IRQ_RESOURCE("88pm88x-chg-done", PM88X_IRQ_CHG_DONE),
CELL_IRQ_RESOURCE("88pm88x-chg-good", PM88X_IRQ_CHG_GOOD),
};
static const struct resource battery_resources[] = {
CELL_IRQ_RESOURCE("88pm88x-bat-cc", PM88X_IRQ_CC),
CELL_IRQ_RESOURCE("88pm88x-bat-volt", PM88X_IRQ_VBAT),
};
static const struct resource headset_resources[] = {
CELL_IRQ_RESOURCE("88pm88x-headset-det", PM88X_IRQ_HS_DET),
CELL_IRQ_RESOURCE("88pm88x-mic-det", PM88X_IRQ_MIC_DET),
};
static const struct resource vbus_resources[] = {
CELL_IRQ_RESOURCE("88pm88x-vbus-det", PM88X_IRQ_VBUS),
CELL_IRQ_RESOURCE("88pm88x-gpadc0", PM88X_IRQ_GPADC0),
CELL_IRQ_RESOURCE("88pm88x-gpadc1", PM88X_IRQ_GPADC1),
CELL_IRQ_RESOURCE("88pm88x-gpadc2", PM88X_IRQ_GPADC2),
CELL_IRQ_RESOURCE("88pm88x-gpadc3", PM88X_IRQ_GPADC3),
CELL_IRQ_RESOURCE("88pm88x-otg-fail", PM88X_IRQ_OTG_FAIL),
};
static const struct resource leds_resources[] = {
CELL_IRQ_RESOURCE("88pm88x-cfd-fail", PM88X_IRQ_CFD_FAIL),
};
static const struct resource dvc_resources[] = {
{
.name = PM88X_DVC_NAME,
},
};
static const struct resource rgb_resources[] = {
{
.name = PM88X_RGB_NAME,
},
};
static const struct resource debugfs_resources[] = {
{
.name = PM88X_DEBUGFS_NAME,
},
};
static const struct resource gpadc_resources[] = {
{
.name = PM88X_GPADC_NAME,
},
};
static const struct resource vr_resources[] = {
{
.name = PM88X_VIRTUAL_REGULATOR_NAME,
},
};
static const struct mfd_cell common_cell_devs[] = {
CELL_DEV(PM88X_RTC_NAME, rtc_resources, "marvell,88pm88x-rtc", -1),
CELL_DEV(PM88X_ONKEY_NAME, onkey_resources, "marvell,88pm88x-onkey", -1),
CELL_DEV(PM88X_CHARGER_NAME, charger_resources, "marvell,88pm88x-charger", -1),
CELL_DEV(PM88X_BATTERY_NAME, battery_resources, "marvell,88pm88x-battery", -1),
CELL_DEV(PM88X_HEADSET_NAME, headset_resources, "marvell,88pm88x-headset", -1),
CELL_DEV(PM88X_VBUS_NAME, vbus_resources, "marvell,88pm88x-vbus", -1),
CELL_DEV(PM88X_CFD_NAME, leds_resources, "marvell,88pm88x-leds", PM88X_FLASH_LED),
CELL_DEV(PM88X_CFD_NAME, leds_resources, "marvell,88pm88x-leds", PM88X_TORCH_LED),
CELL_DEV(PM88X_DVC_NAME, dvc_resources, "marvell,88pm88x-dvc", -1),
CELL_DEV(PM88X_RGB_NAME, rgb_resources, "marvell,88pm88x-rgb0", PM88X_RGB_LED0),
CELL_DEV(PM88X_RGB_NAME, rgb_resources, "marvell,88pm88x-rgb1", PM88X_RGB_LED1),
CELL_DEV(PM88X_RGB_NAME, rgb_resources, "marvell,88pm88x-rgb2", PM88X_RGB_LED2),
CELL_DEV(PM88X_DEBUGFS_NAME, debugfs_resources, "marvell,88pm88x-debugfs", -1),
CELL_DEV(PM88X_GPADC_NAME, gpadc_resources, "marvell,88pm88x-gpadc", -1),
CELL_DEV(PM88X_VIRTUAL_REGULATOR_NAME, vr_resources, "marvell,88pm88x-votg", -1),
};
const struct of_device_id pm88x_of_match[] = {
{ .compatible = "marvell,88pm886", .data = (void *)PM886 },
{ .compatible = "marvell,88pm880", .data = (void *)PM880 },
{},
};
EXPORT_SYMBOL_GPL(pm88x_of_match);
struct pm88x_chip *pm88x_init_chip(struct i2c_client *client)
{
struct pm88x_chip *chip;
chip = devm_kzalloc(&client->dev, sizeof(struct pm88x_chip), GFP_KERNEL);
if (!chip)
return ERR_PTR(-ENOMEM);
chip->client = client;
chip->irq = client->irq;
chip->dev = &client->dev;
chip->ldo_page_addr = client->addr + 1;
chip->power_page_addr = client->addr + 1;
chip->gpadc_page_addr = client->addr + 2;
chip->battery_page_addr = client->addr + 3;
chip->buck_page_addr = client->addr + 4;
chip->test_page_addr = client->addr + 7;
dev_set_drvdata(chip->dev, chip);
i2c_set_clientdata(chip->client, chip);
device_init_wakeup(&client->dev, 1);
return chip;
}
int pm88x_parse_dt(struct device_node *np, struct pm88x_chip *chip)
{
if (!chip)
return -EINVAL;
chip->irq_mode =
!of_property_read_bool(np, "marvell,88pm88x-irq-write-clear");
return 0;
}
static void parse_powerup_down_log(struct pm88x_chip *chip)
{
int powerup, powerdown1, powerdown2, bit;
int powerup_bits, powerdown1_bits, powerdown2_bits;
static const char * const powerup_name[] = {
"ONKEY_WAKEUP ",
"CHG_WAKEUP ",
"EXTON_WAKEUP ",
"SMPL_WAKEUP ",
"ALARM_WAKEUP ",
"FAULT_WAKEUP ",
"BAT_WAKEUP ",
"WLCHG_WAKEUP ",
};
static const char * const powerdown1_name[] = {
"OVER_TEMP ",
"UV_VANA5 ",
"SW_PDOWN ",
"FL_ALARM ",
"WD ",
"LONG_ONKEY",
"OV_VSYS ",
"RTC_RESET "
};
static const char * const powerdown2_name[] = {
"HYB_DONE ",
"UV_VBAT ",
"HW_RESET2 ",
"PGOOD_PDOWN",
"LONKEY_RTC ",
"HW_RESET1 ",
};
regmap_read(chip->base_regmap, PM88X_POWER_UP_LOG, &powerup);
regmap_read(chip->base_regmap, PM88X_POWER_DOWN_LOG1, &powerdown1);
regmap_read(chip->base_regmap, PM88X_POWER_DOWN_LOG2, &powerdown2);
/*
* mask reserved bits
*
* note: HYB_DONE and HW_RESET1 are kept,
* but should not be considered as power down events
*/
switch (chip->type) {
case PM886:
powerup &= 0x7f;
powerdown2 &= 0x1f;
powerup_bits = 7;
powerdown1_bits = 8;
powerdown2_bits = 5;
break;
case PM880:
powerdown2 &= 0x3f;
powerup_bits = 8;
powerdown1_bits = 8;
powerdown2_bits = 6;
break;
default:
return;
}
/* keep globals for external usage */
chip->powerup = powerup;
chip->powerdown1 = powerdown1;
chip->powerdown2 = powerdown2;
#ifdef CONFIG_DEBUG
/* power up log */
dev_info(chip->dev, "powerup log 0x%x: 0x%x\n",
PM88X_POWER_UP_LOG, powerup);
dev_info(chip->dev, " ----------------------------\n");
dev_info(chip->dev, "| name(power up) | status |\n");
dev_info(chip->dev, "|-----------------|----------|\n");
for (bit = 0; bit < powerup_bits; bit++)
dev_info(chip->dev, "| %s | %x |\n",
powerup_name[bit], (powerup >> bit) & 1);
dev_info(chip->dev, " ----------------------------\n");
/* power down log1 */
dev_info(chip->dev, "PowerDW Log1 0x%x: 0x%x\n",
PM88X_POWER_DOWN_LOG1, powerdown1);
dev_info(chip->dev, " -------------------------------\n");
dev_info(chip->dev, "| name(power down1) | status |\n");
dev_info(chip->dev, "|--------------------|----------|\n");
for (bit = 0; bit < powerdown1_bits; bit++)
dev_info(chip->dev, "| %s | %x |\n",
powerdown1_name[bit], (powerdown1 >> bit) & 1);
dev_info(chip->dev, " -------------------------------\n");
/* power down log2 */
dev_info(chip->dev, "PowerDW Log2 0x%x: 0x%x\n",
PM88X_POWER_DOWN_LOG2, powerdown2);
dev_info(chip->dev, " -------------------------------\n");
dev_info(chip->dev, "| name(power down2) | status |\n");
dev_info(chip->dev, "|--------------------|----------|\n");
for (bit = 0; bit < powerdown2_bits; bit++)
dev_info(chip->dev, "| %s | %x |\n",
powerdown2_name[bit], (powerdown2 >> bit) & 1);
dev_info(chip->dev, " -------------------------------\n");
#endif
/* write to clear power down log */
regmap_write(chip->base_regmap, PM88X_POWER_DOWN_LOG1, 0xff);
regmap_write(chip->base_regmap, PM88X_POWER_DOWN_LOG2, 0xff);
}
static const char *chip_stepping_to_string(unsigned int id)
{
switch (id) {
case 0x00:
return "88pm886 A0";
case 0xa1:
return "88pm886 A1";
case 0xb0:
return "88pm880 A0";
case 0xb1:
return "88pm880 A1";
default:
break;
}
return "Unknown";
}
static void pm88x_verify_is_trimmed(struct pm88x_chip *chip)
{
int val, trimming_reg, trimming_mask;
switch (chip->type) {
case PM886:
/* bit [7] of reg 0x8A indicates trimming */
trimming_reg = PM886_TEST_OTP5COPY7;
trimming_mask = PM886_OTP_OOL_TEMP_DIS;
break;
case PM880:
/* bits [4:0] of reg 0x92 indicates trimming */
trimming_reg = PM880_TEST_OTP6COPY7;
trimming_mask = PM880_VSYS_CHECK;
break;
default:
return;
}
/* unlock test page */
regmap_write(chip->base_regmap, 0x1f, 0x1);
/* read trimming status */
regmap_read(chip->test_regmap, trimming_reg, &val);
/* lock test page */
regmap_write(chip->base_regmap, 0x1f, 0x0);
/* trimming value should not be zero */
if ((val & trimming_mask) == 0) {
dev_err(chip->dev, "WARNING!!! PMIC is not trimmed, reg[0x%x]=0x%x\n",
trimming_reg, val);
chip->trimming_status = false;
} else {
dev_info(chip->dev, "PMIC is trimmed, reg[0x%x]=0x%x\n", trimming_reg, val);
chip->trimming_status = true;
}
}
/*
* fuel gauge initialization,
* stored CC for later use before GPADC enable.
*/
int pm88x_pre_fg_init(struct pm88x_chip *chip)
{
int ret;
unsigned int val, mask;
u8 buf[5];
/*
* set SD_PWRUP to enable sigma-delta
* set CC_CLR_ON_RD to clear coulomb counter on read
* set CC_EN to enable coulomb counter
*/
val = mask = PM88X_SD_PWRUP | PM88X_CC_CLR_ON_RD | PM88X_CC_EN;
ret = regmap_update_bits(chip->battery_regmap, PM88X_CC_CONFIG1,
mask, val);
/* read columb counter to get the original SoC value */
regmap_read(chip->battery_regmap, PM88X_CC_CONFIG2, &val);
/*
* set PM88X_CC_READ_REQ to read Qbat_cc,
* if it has been set, then it means the data not ready
*/
if (!(val & PM88X_CC_READ_REQ))
regmap_update_bits(chip->battery_regmap, PM88X_CC_CONFIG2,
PM88X_CC_READ_REQ, PM88X_CC_READ_REQ);
/* wait until Qbat_cc is ready */
do {
regmap_read(chip->battery_regmap, PM88X_CC_CONFIG2,
&val);
} while ((val & PM88X_CC_READ_REQ));
ret = regmap_bulk_read(chip->battery_regmap, PM88X_CC_VAL1,
buf, 5);
if (ret < 0)
return ret;
chip->pre_ccnt_uc = (s64) (((s64)(buf[4]) << 32)
| (u64)(buf[3] << 24) | (u64)(buf[2] << 16)
| (u64)(buf[1] << 8) | (u64)buf[0]);
dev_info(chip->dev, "buf[0 ~ 4] = 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n",
buf[0], buf[1], buf[2], buf[3], buf[4]);
return 0;
}
int pm88x_post_init_chip(struct pm88x_chip *chip)
{
int ret;
unsigned int val;
if (!chip || !chip->base_regmap || !chip->power_regmap ||
!chip->gpadc_regmap || !chip->battery_regmap)
return -EINVAL;
/* save chip stepping */
ret = regmap_read(chip->base_regmap, PM88X_ID_REG, &val);
if (ret < 0) {
dev_err(chip->dev, "Failed to read chip ID: %d\n", ret);
return ret;
}
chip->chip_id = val;
dev_info(chip->dev, "PM88X chip ID = 0x%x(%s)\n", val,
chip_stepping_to_string(chip->chip_id));
/* verify chip is trimmed */
pm88x_verify_is_trimmed(chip);
/* read before alarm wake up bit before initialize interrupt */
ret = regmap_read(chip->base_regmap, PM88X_RTC_ALARM_CTRL1, &val);
if (ret < 0) {
dev_err(chip->dev, "Failed to read RTC register: %d\n", ret);
return ret;
}
chip->rtc_wakeup = !!(val & PM88X_ALARM_WAKEUP);
ret = pm88x_pre_fg_init(chip);
if (ret < 0) {
dev_err(chip->dev, "Failed to initial fuel gauge: %d\n", ret);
return ret;
}
parse_powerup_down_log(chip);
return 0;
}
int pm88x_init_pages(struct pm88x_chip *chip)
{
struct i2c_client *client = chip->client;
const struct regmap_config *base_regmap_config;
const struct regmap_config *power_regmap_config;
const struct regmap_config *gpadc_regmap_config;
const struct regmap_config *battery_regmap_config;
const struct regmap_config *test_regmap_config;
int ret = 0;
if (!chip || !chip->power_page_addr ||
!chip->gpadc_page_addr || !chip->battery_page_addr)
return -ENODEV;
chip->type = pm88x_of_get_type(&client->dev);
switch (chip->type) {
case PM886:
base_regmap_config = &pm886_base_i2c_regmap;
power_regmap_config = &pm886_power_i2c_regmap;
gpadc_regmap_config = &pm886_gpadc_i2c_regmap;
battery_regmap_config = &pm886_battery_i2c_regmap;
test_regmap_config = &pm886_test_i2c_regmap;
break;
case PM880:
base_regmap_config = &pm880_base_i2c_regmap;
power_regmap_config = &pm880_power_i2c_regmap;
gpadc_regmap_config = &pm880_gpadc_i2c_regmap;
battery_regmap_config = &pm880_battery_i2c_regmap;
test_regmap_config = &pm880_test_i2c_regmap;
break;
default:
return -ENODEV;
}
/* base page */
chip->base_regmap = devm_regmap_init_i2c(client, base_regmap_config);
if (IS_ERR(chip->base_regmap)) {
dev_err(chip->dev, "Failed to init base_regmap: %d\n", ret);
ret = PTR_ERR(chip->base_regmap);
goto out;
}
companion_base_page = chip->base_regmap;
/* power page */
chip->power_page = i2c_new_dummy(client->adapter, chip->power_page_addr);
if (!chip->power_page) {
dev_err(chip->dev, "Failed to new power_page: %d\n", ret);
ret = -ENODEV;
goto out;
}
chip->power_regmap = devm_regmap_init_i2c(chip->power_page,
power_regmap_config);
if (IS_ERR(chip->power_regmap)) {
dev_err(chip->dev, "Failed to init power_regmap: %d\n", ret);
ret = PTR_ERR(chip->power_regmap);
goto out;
}
/* gpadc page */
chip->gpadc_page = i2c_new_dummy(client->adapter, chip->gpadc_page_addr);
if (!chip->gpadc_page) {
dev_err(chip->dev, "Failed to new gpadc_page: %d\n", ret);
ret = -ENODEV;
goto out;
}
chip->gpadc_regmap = devm_regmap_init_i2c(chip->gpadc_page,
gpadc_regmap_config);
if (IS_ERR(chip->gpadc_regmap)) {
dev_err(chip->dev, "Failed to init gpadc_regmap: %d\n", ret);
ret = PTR_ERR(chip->gpadc_regmap);
goto out;
}
/* battery page */
chip->battery_page = i2c_new_dummy(client->adapter, chip->battery_page_addr);
if (!chip->battery_page) {
dev_err(chip->dev, "Failed to new gpadc_page: %d\n", ret);
ret = -ENODEV;
goto out;
}
chip->battery_regmap = devm_regmap_init_i2c(chip->battery_page,
battery_regmap_config);
if (IS_ERR(chip->battery_regmap)) {
dev_err(chip->dev, "Failed to init battery_regmap: %d\n", ret);
ret = PTR_ERR(chip->battery_regmap);
goto out;
}
/* test page */
chip->test_page = i2c_new_dummy(client->adapter, chip->test_page_addr);
if (!chip->test_page) {
dev_err(chip->dev, "Failed to new test_page: %d\n", ret);
ret = -ENODEV;
goto out;
}
chip->test_regmap = devm_regmap_init_i2c(chip->test_page,
test_regmap_config);
if (IS_ERR(chip->test_regmap)) {
dev_err(chip->dev, "Failed to init test_regmap: %d\n", ret);
ret = PTR_ERR(chip->test_regmap);
goto out;
}
chip->type = pm88x_of_get_type(&client->dev);
switch (chip->type) {
case PM886:
/* ldo page */
chip->ldo_page = chip->power_page;
chip->ldo_regmap = chip->power_regmap;
/* buck page */
chip->buck_regmap = chip->power_regmap;
break;
case PM880:
/* ldo page */
chip->ldo_page = chip->power_page;
chip->ldo_regmap = chip->power_regmap;
/* buck page */
chip->buck_page = i2c_new_dummy(client->adapter,
chip->buck_page_addr);
if (!chip->buck_page) {
dev_err(chip->dev, "Failed to new buck_page: %d\n", ret);
ret = -ENODEV;
goto out;
}
chip->buck_regmap = devm_regmap_init_i2c(chip->buck_page,
power_regmap_config);
if (IS_ERR(chip->buck_regmap)) {
dev_err(chip->dev, "Failed to init buck_regmap: %d\n", ret);
ret = PTR_ERR(chip->buck_regmap);
goto out;
}
break;
default:
ret = -EINVAL;
break;
}
out:
return ret;
}
void pm800_exit_pages(struct pm88x_chip *chip)
{
if (!chip)
return;
if (chip->ldo_page)
i2c_unregister_device(chip->ldo_page);
if (chip->gpadc_page)
i2c_unregister_device(chip->gpadc_page);
if (chip->test_page)
i2c_unregister_device(chip->test_page);
/* no need to unregister ldo_page */
switch (chip->type) {
case PM886:
break;
case PM880:
if (chip->buck_page)
i2c_unregister_device(chip->buck_page);
break;
default:
break;
}
}
int pm88x_init_subdev(struct pm88x_chip *chip)
{
int ret;
if (!chip)
return -EINVAL;
ret = mfd_add_devices(chip->dev, 0, common_cell_devs,
ARRAY_SIZE(common_cell_devs), NULL, 0,
regmap_irq_get_domain(chip->irq_data));
if (ret < 0)
return ret;
switch (chip->type) {
case PM886:
ret = mfd_add_devices(chip->dev, 0, pm886_cell_info.cells,
pm886_cell_info.cell_nr, NULL, 0,
regmap_irq_get_domain(chip->irq_data));
break;
case PM880:
ret = mfd_add_devices(chip->dev, 0, pm880_cell_info.cells,
pm880_cell_info.cell_nr, NULL, 0,
regmap_irq_get_domain(chip->irq_data));
break;
default:
break;
}
return ret;
}
static int (*apply_to_chip)(struct pm88x_chip *chip);
/* PMIC chip itself related */
int pm88x_apply_patch(struct pm88x_chip *chip)
{
if (!chip || !chip->client)
return -EINVAL;
chip->type = pm88x_of_get_type(&chip->client->dev);
switch (chip->type) {
case PM886:
apply_to_chip = pm886_apply_patch;
break;
case PM880:
apply_to_chip = pm880_apply_patch;
break;
default:
break;
}
if (apply_to_chip)
apply_to_chip(chip);
return 0;
}
int pm88x_stepping_fixup(struct pm88x_chip *chip)
{
if (!chip || !chip->client)
return -EINVAL;
chip->type = pm88x_of_get_type(&chip->client->dev);
switch (chip->type) {
case PM886:
if (chip->chip_id == PM886_A1) {
/* set HPFM bit for buck1 */
regmap_update_bits(chip->power_regmap, 0xa0, 1 << 7, 1 << 7);
/* clear LPFM bit for buck1 */
regmap_update_bits(chip->power_regmap, 0x9f, 1 << 3, 0 << 3);
}
/* set USE_XO */
regmap_update_bits(chip->base_regmap, PM88X_RTC_ALARM_CTRL1,
PM88X_USE_XO, PM88X_USE_XO);
/* to fix fault charger remove detection */
regmap_write(chip->base_regmap, PM88X_BK_OSE_CTRL3, 0xc0);
regmap_write(chip->battery_regmap, PM88X_CHGBK_CONFIG6, 0xe1);
break;
case PM880:
/* to fix fault charger remove detection */
regmap_write(chip->base_regmap, PM88X_BK_OSE_CTRL3, 0xc0);
regmap_write(chip->battery_regmap, PM88X_CHGBK_CONFIG6, 0xe1);
break;
default:
break;
}
return 0;
}
int pm88x_apply_bd_patch(struct pm88x_chip *chip, struct device_node *np)
{
unsigned int page, reg, mask, data;
const __be32 *values;
int size, rows, index;
if (!chip || !chip->base_regmap ||
!chip->power_regmap || !chip->gpadc_regmap ||
!chip->battery_regmap ||
!chip->test_regmap)
return -EINVAL;
values = of_get_property(np, "marvell,pmic-bd-cfg", &size);
if (!values) {
dev_warn(chip->dev, "No valid property for %s\n", np->name);
/* exit SUCCESS */
return 0;
}
/* number of elements in array */
size /= sizeof(*values);
rows = size / 4;
dev_info(chip->dev, "pmic board specific configuration.\n");
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 PM88X_BASE_PAGE:
regmap_update_bits(chip->base_regmap, reg, mask, data);
break;
case PM88X_LDO_PAGE:
regmap_update_bits(chip->ldo_regmap, reg, mask, data);
break;
case PM88X_GPADC_PAGE:
regmap_update_bits(chip->gpadc_regmap, reg, mask, data);
break;
case PM88X_BATTERY_PAGE:
regmap_update_bits(chip->battery_regmap, reg, mask, data);
break;
case PM88X_BUCK_PAGE:
regmap_update_bits(chip->buck_regmap, reg, mask, data);
break;
case PM88X_TEST_PAGE:
regmap_update_bits(chip->test_regmap, reg, mask, data);
break;
default:
dev_err(chip->dev, "wrong page index for %d\n", page);
break;
}
}
return 0;
}
long pm88x_of_get_type(struct device *dev)
{
const struct of_device_id *id = of_match_device(pm88x_of_match, dev);
if (id)
return (long)id->data;
else
return 0;
}
void pm88x_dev_exit(struct pm88x_chip *chip)
{
mfd_remove_devices(chip->dev);
pm88x_irq_exit(chip);
}
void pm88x_set_chip(struct pm88x_chip *chip)
{
pm88x_chip_priv = chip;
}
struct pm88x_chip *pm88x_get_chip(void)
{
return pm88x_chip_priv;
}
static int i2c_raw_update_bits(u8 reg, u8 value)
{
int ret;
u8 data, buf[2];
/* only for base page */
struct i2c_client *client = pm88x_get_chip()->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);
/* 1. read the original value */
buf[0] = reg;
ret = __i2c_transfer(client->adapter, msgs, 2);
if (ret != 2) {
pr_err("%s read register fails, ret = %d...\n", __func__, ret);
WARN_ON(1);
goto out;
}
/* 2. update value */
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 != 1) {
pr_err("%s write data fails: ret = %d\n", __func__, ret);
WARN_ON(1);
}
out:
return ret;
}
static void pm88x_get_status(void)
{
u8 data, buf[2];
int ret;
/* only for base page */
struct i2c_client *client = pm88x_get_chip()->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_pxa_set_pinstate(client->adapter, "default");
i2c_set_pio_mode(client->adapter, 1);
buf[0] = PM88X_MISC_CONFIG1;
ret = __i2c_transfer(client->adapter, msgs, 2);
if (ret != 2)
pr_err("%s read register fails..., error code = %d\n", __func__, ret);
pr_info("1: update value: msgs->buf[0] = 0x%x, msgs->buf[1] = 0x%x\n",
msgs->buf[0], msgs->buf[1]);
buf[0] = PM88X_MISC_CONFIG1;
ret = __i2c_transfer(client->adapter, msgs, 2);
if (ret != 2)
pr_err("%s read register fails..., error code = %d\n", __func__, ret);
pr_info("2: update value: msgs->buf[0] = 0x%x, msgs->buf[1] = 0x%x\n",
msgs->buf[0], msgs->buf[1]);
}
void pm88x_power_off(void)
{
int ret;
pr_info("begin to power off system.");
ret = i2c_raw_update_bits(PM88X_MISC_CONFIG1, PM88X_SW_PDOWN);
if (ret < 0)
pr_err("%s power off fails", __func__);
pr_info("finish powering off system: this line shouldn't appear.");
pm88x_get_status();
/* wait for power off */
for (;;)
cpu_relax();
}
int pm88x_reboot_notifier_callback(struct notifier_block *this,
unsigned long code, void *cmd)
{
struct pm88x_chip *chip;
pr_info("%s: code = %ld, cmd = '%s'\n", __func__, code, (char *)cmd);
chip = container_of(this, struct pm88x_chip, reboot_notifier);
if (cmd && (0 == strncmp(cmd, "recovery", 8))) {
pr_info("%s: --> handle recovery mode\n", __func__);
regmap_update_bits(chip->base_regmap, PM88X_RTC_SPARE6,
1 << 0, 1 << 0);
} else {
/* clear the recovery indication bit */
regmap_update_bits(chip->base_regmap,
PM88X_RTC_SPARE6, 1 << 0, 0);
}
/*
* the uboot recognize the "reboot" case via power down log,
* which is 0 in this case
*/
return 0;
}