diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 8af7fd72592e..09b513866f03 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -741,6 +741,12 @@ config GPIO_ADNP enough to represent all pins, but the driver will assume a register layout for 64 pins (8 registers). +config GPIO_BD7181X + tristate "BD7181X GPO" + depends on MFD_BD7181X + help + Say yes here to access the GPO signals of bd71815/bd71817 chip from ROHM. + config GPIO_MAX7300 tristate "Maxim MAX7300 GPIO expander" select GPIO_MAX730X diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index df630457f5ba..3ab14028e263 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o obj-$(CONFIG_GPIO_AXP209) += gpio-axp209.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o +obj-$(CONFIG_GPIO_BD7181X) += gpio-bd7181x.o obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o diff --git a/drivers/gpio/gpio-bd7181x.c b/drivers/gpio/gpio-bd7181x.c new file mode 100644 index 000000000000..9c66b79d27fd --- /dev/null +++ b/drivers/gpio/gpio-bd7181x.c @@ -0,0 +1,201 @@ +/* + * gpio-bd7181x.c + * @file Access to GPOs on ROHM BD7181XMWV chip + * + * Copyright 2014 Embest Technology Co. Ltd. Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#define DEBUG +#include +#include +#include +#include +#include +#include +#include + +#include + +/** @brief bd7181x gpio chip core data */ +static struct gpio_chip bd7181xgpo_chip; + +/** @brief get gpo output value + * @param chip pointer to core data + * @param offset gpo number, start from 0 + * @retval 0 success + * @retval negative error number + */ +static int bd7181xgpo_get(struct gpio_chip *chip, unsigned offset) +{ + struct bd7181x *bd7181x = dev_get_drvdata(chip->dev->parent); + int ret = 0; + + ret = bd7181x_reg_read(bd7181x, BD7181X_REG_GPO); + if (ret < 0) + return ret; + + return (ret >> offset) & 1; +} + +/** @brief set gpo direction as output + * @param chip pointer to core data + * @param offset gpo number, start from 0 + * @param value output value when set direction out + * @retval 0 success + */ +static int bd7181xgpo_direction_out(struct gpio_chip *chip, unsigned offset, + int value) +{ + /* This only drives GPOs, and can't change direction */ + return 0; +} + +/** @brief set gpo output value + * @param chip pointer to core data + * @param offset gpo number, start from 0 + * @param value output value, not zero as high level, 0 as low level + * @retval 0 success + * @retval negative error number + */ +static void bd7181xgpo_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct bd7181x *bd7181x = dev_get_drvdata(chip->dev->parent); + int ret; + u8 gpoctl; + + ret = bd7181x_reg_read(bd7181x, BD7181X_REG_GPO); + if (ret < 0) + return; + + if (value) + gpoctl = ret | (1 << offset); + else + gpoctl = ret & ~(1 << offset); + + bd7181x_reg_write(bd7181x, BD7181X_REG_GPO, gpoctl); +} + +/** @brief bd7181x gpio chip core data */ +static struct gpio_chip bd7181xgpo_chip = { + .label = "bd7181x", ///< gpio chip name + .owner = THIS_MODULE, + .get = bd7181xgpo_get, + .direction_output = bd7181xgpo_direction_out, + .set = bd7181xgpo_set, + .can_sleep = 1, +}; + +/*----------------------------------------------------------------------*/ +#ifdef CONFIG_OF +/** @brief retrive gpo platform data from device tree + * @param pdev platfrom device pointer + * @return pointer to platform data + * @retval NULL error + */ +static struct bd7181x_gpo_plat_data *of_gpio_bd7181x( + struct platform_device *pdev) +{ + struct bd7181x_gpo_plat_data *platform_data; + struct device_node *np, *gpio_np; + + platform_data = devm_kzalloc(&pdev->dev, sizeof(*platform_data), GFP_KERNEL); + if (!platform_data) { + return NULL; + } + + np = of_node_get(pdev->dev.parent->of_node); + gpio_np = of_find_node_by_name(np, "gpo"); + if (!gpio_np) { + dev_err(&pdev->dev, "gpio node not found\n"); + return NULL; + } + + pdev->dev.of_node = gpio_np; + + if (of_property_read_u32(gpio_np, "rohm,mode", &platform_data->mode)) { + platform_data->mode = -1; + } + + return platform_data; +} +#endif + +/** @brief probe bd7181x gpo device + * @param pdev platfrom device pointer + * @retval 0 success + * @retval negative error number + */ +static int gpo_bd7181x_probe(struct platform_device *pdev) +{ + struct bd7181x_gpo_plat_data *pdata = pdev->dev.platform_data; + struct device *mfd_dev = pdev->dev.parent; + struct bd7181x *bd7181x = dev_get_drvdata(mfd_dev); + int ret; + +#ifdef CONFIG_OF + pdata = of_gpio_bd7181x(pdev); +#endif + if (pdata && pdata->gpio_base > 0) + bd7181xgpo_chip.base = pdata->gpio_base; + else + bd7181xgpo_chip.base = -1; + + bd7181xgpo_chip.ngpio = 2; /* bd71815/bd71817 have 2 GPO */ + + bd7181xgpo_chip.dev = &pdev->dev; + + ret = gpiochip_add(&bd7181xgpo_chip); + if (ret < 0) { + dev_err(&pdev->dev, "could not register gpiochip, %d\n", ret); + bd7181xgpo_chip.ngpio = 0; + return ret; + } + + if (pdata && pdata->mode != -1UL) { + bd7181x_update_bits(bd7181x, BD7181X_REG_GPO, 0x70, pdata->mode); + } + + return ret; +} + +/** @brief remove bd7181x gpo device + * @param pdev platfrom device pointer + * @retval 0 success + * @retval negative error number + */ +static int gpo_bd7181x_remove(struct platform_device *pdev) +{ + return gpiochip_remove(&bd7181xgpo_chip); +} + +/* Note: this hardware lives inside an I2C-based multi-function device. */ +MODULE_ALIAS("platform:bd7181x-gpo"); + +/** @brief bd7181x gpo driver core data */ +static struct platform_driver gpo_bd7181x_driver = { + .driver = { + .name = "bd7181x-gpo", + .owner = THIS_MODULE, + }, + .probe = gpo_bd7181x_probe, + .remove = gpo_bd7181x_remove, +}; + +module_platform_driver(gpo_bd7181x_driver); + +MODULE_AUTHOR("Peter Yang "); +MODULE_DESCRIPTION("GPO interface for BD71815/BD71817"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 997a6172735e..73c4e07a1170 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1810,6 +1810,14 @@ config MFD_STM32_TIMERS for PWM and IIO Timer. This driver allow to share the registers between the others drivers. +config MFD_BD7181X + bool "BD71815/BD71817 Power Management chip" + depends on I2C=y + select MFD_CORE + help + if you say yes here you get support for the BD71815/BD71817 + Power Management chips. + config MFD_BD71837 bool "BD71837 Power Management chip" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index c6755df735ba..45421cc8a5e3 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -231,4 +231,5 @@ obj-$(CONFIG_MFD_SUN4I_GPADC) += sun4i-gpadc.o obj-$(CONFIG_MFD_STM32_LPTIMER) += stm32-lptimer.o obj-$(CONFIG_MFD_STM32_TIMERS) += stm32-timers.o obj-$(CONFIG_MFD_MXS_LRADC) += mxs-lradc.o +obj-$(CONFIG_MFD_BD7181X) += bd7181x.o obj-$(CONFIG_MFD_BD71837) += bd71837.o diff --git a/drivers/mfd/bd7181x.c b/drivers/mfd/bd7181x.c new file mode 100644 index 000000000000..bd82d430ddd1 --- /dev/null +++ b/drivers/mfd/bd7181x.c @@ -0,0 +1,389 @@ +/* + * @file bd7181x.c -- RoHM BD7181X/BD71817 mfd driver + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + * @author: Tony Luo + * Copyright 2014 Embest Technology Co. Ltd. Inc. + */ +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @brief bd7181x irq resource */ +static struct resource rtc_resources[] = { + { + .start = BD7181X_IRQ_ALARM_12, + .end = BD7181X_IRQ_ALARM_12, + .flags = IORESOURCE_IRQ, + } +}; + +static struct resource power_resources[] = { + // irq# 0 + { + .start = BD7181X_IRQ_DCIN_03, + .end = BD7181X_IRQ_DCIN_03, + .flags = IORESOURCE_IRQ, + }, + // irq# 1 + { + .start = BD7181X_IRQ_BAT_MON_08, + .end = BD7181X_IRQ_BAT_MON_08, + .flags = IORESOURCE_IRQ, + }, + // irq# 2 + { + .start = BD7181X_IRQ_TEMPERATURE_11, + .end = BD7181X_IRQ_TEMPERATURE_11, + .flags = IORESOURCE_IRQ, + } +}; + +/** @brief bd7181x multi function cells */ +static struct mfd_cell bd7181x_mfd_cells[] = { + { + .name = "bd7181x-pmic", + }, + { + .name = "bd7181x-power", + .num_resources = ARRAY_SIZE(power_resources), + .resources = &power_resources[0], + }, + { + .name = "bd7181x-gpo", + }, + { + .name = "bd7181x-rtc", + .num_resources = ARRAY_SIZE(rtc_resources), + .resources = &rtc_resources[0], + }, +}; + +/** @brief bd7181x irqs */ +static const struct regmap_irq bd7181x_irqs[] = { + [BD7181X_IRQ_BUCK_01] = { + .mask = BD7181X_INT_EN_01_BUCKAST_MASK, + .reg_offset = 1, + }, + [BD7181X_IRQ_DCIN_02] = { + .mask = BD7181X_INT_EN_02_DCINAST_MASK, + .reg_offset = 2, + }, + [BD7181X_IRQ_DCIN_03] = { + .mask = BD7181X_INT_EN_03_DCINAST_MASK, + .reg_offset = 3, + }, + [BD7181X_IRQ_VSYS_04] = { + .mask = BD7181X_INT_EN_04_VSYSAST_MASK, + .reg_offset = 4, + }, + [BD7181X_IRQ_CHARGE_05] = { + .mask = BD7181X_INT_EN_05_CHGAST_MASK, + .reg_offset = 5, + }, + [BD7181X_IRQ_BAT_06] = { + .mask = BD7181X_INT_EN_06_BATAST_MASK, + .reg_offset = 6, + }, + [BD7181X_IRQ_BAT_MON_07] = { + .mask = BD7181X_INT_EN_07_BMONAST_MASK, + .reg_offset = 7, + }, + [BD7181X_IRQ_BAT_MON_08] = { + .mask = BD7181X_INT_EN_08_BMONAST_MASK, + .reg_offset = 8, + }, + [BD7181X_IRQ_BAT_MON_09] = { + .mask = BD7181X_INT_EN_09_BMONAST_MASK, + .reg_offset = 9, + }, + [BD7181X_IRQ_BAT_MON_10] = { + .mask = BD7181X_INT_EN_10_BMONAST_MASK, + .reg_offset = 10, + }, + [BD7181X_IRQ_TEMPERATURE_11] = { + .mask = BD7181X_INT_EN_11_TMPAST_MASK, + .reg_offset = 11, + }, + [BD7181X_IRQ_ALARM_12] = { + .mask = BD7181X_INT_EN_12_ALMAST_MASK, + .reg_offset = 12, + }, +}; + +/** @brief bd7181x irq chip definition */ +static struct regmap_irq_chip bd7181x_irq_chip = { + .name = "bd7181x", + .irqs = bd7181x_irqs, + .num_irqs = ARRAY_SIZE(bd7181x_irqs), + .num_regs = 13, + .irq_reg_stride = 1, + .status_base = BD7181X_REG_INT_STAT, + .mask_base = BD7181X_REG_INT_EN_01 - 1, + .mask_invert = true, + // .ack_base = BD7181X_REG_INT_STAT_00, +}; + +/** @brief bd7181x irq initialize + * @param bd7181x bd7181x device to init + * @param bdinfo platform init data + * @retval 0 probe success + * @retval negative error number + */ +static int bd7181x_irq_init(struct bd7181x *bd7181x, struct bd7181x_board* bdinfo) { + int irq; + int ret = 0; + + if (!bdinfo) { + dev_warn(bd7181x->dev, "No interrupt support, no pdata\n"); + return -EINVAL; + } + + irq = gpio_to_irq(bdinfo->gpio_intr); + + bd7181x->chip_irq = irq; + printk("bd7181x->chip_irq=%d \n", bd7181x->chip_irq); + ret = regmap_add_irq_chip(bd7181x->regmap, bd7181x->chip_irq, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, bdinfo->irq_base, + &bd7181x_irq_chip, &bd7181x->irq_data); + if (ret < 0) { + dev_warn(bd7181x->dev, "Failed to add irq_chip %d\n", ret); + } + return ret; +} + +/** @brief bd7181x irq initialize + * @param bd7181x bd7181x device to init + * @retval 0 probe success + * @retval negative error number + */ +static int bd7181x_irq_exit(struct bd7181x *bd7181x) +{ + if (bd7181x->chip_irq > 0) + regmap_del_irq_chip(bd7181x->chip_irq, bd7181x->irq_data); + return 0; +} + +/** @brief check whether volatile register + * @param dev kernel device pointer + * @param reg register index + */ +static bool is_volatile_reg(struct device *dev, unsigned int reg) +{ + // struct bd7181x *bd7181x = dev_get_drvdata(dev); + + /* + * Caching all regulator registers. + */ + return true; +} + +/** @brief regmap configures */ +static const struct regmap_config bd7181x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = is_volatile_reg, + .max_register = BD7181X_MAX_REGISTER - 1, + .cache_type = REGCACHE_RBTREE, +}; + +#ifdef CONFIG_OF +static struct of_device_id bd7181x_of_match[] = { + { .compatible = "rohm,bd71815", .data = (void *)0}, + { .compatible = "rohm,bd71817", .data = (void *)1}, + { }, +}; +MODULE_DEVICE_TABLE(of, bd7181x_of_match); + + +/** @brief parse device tree data of bd7181x + * @param client client object provided by system + * @param chip_id return chip id back to caller + * @return board initialize data + */ +static struct bd7181x_board *bd7181x_parse_dt(struct i2c_client *client, + int *chip_id) +{ + struct device_node *np = client->dev.of_node; + struct bd7181x_board *board_info; + unsigned int prop; + const struct of_device_id *match; + int r = 0; + + match = of_match_device(bd7181x_of_match, &client->dev); + if (!match) { + dev_err(&client->dev, "Failed to find matching dt id\n"); + return NULL; + } + + *chip_id = (int)match->data; + + board_info = devm_kzalloc(&client->dev, sizeof(*board_info), + GFP_KERNEL); + if (!board_info) { + dev_err(&client->dev, "Failed to allocate pdata\n"); + return NULL; + } + + board_info->gpio_intr = of_get_named_gpio(np, "gpio_intr", 0); + if (!gpio_is_valid(board_info->gpio_intr)) { + dev_err(&client->dev, "no pmic intr pin available\n"); + goto err_intr; + } + + r = of_property_read_u32(np, "irq_base", &prop); + if (!r) { + board_info->irq_base = prop; + } else { + board_info->irq_base = -1; + } + + return board_info; + +err_intr: + devm_kfree(&client->dev, board_info); + return NULL; +} +#else +static inline +struct bd7181x_board *bd7181x_parse_dt(struct i2c_client *client, + int *chip_id) +{ + return NULL; +} +#endif + +/** @brief probe bd7181x device + * @param i2c client object provided by system + * @param id chip id + * @retval 0 probe success + * @retval negative error number + */ +static int bd7181x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct bd7181x *bd7181x; + struct bd7181x_board *pmic_plat_data; + struct bd7181x_board *of_pmic_plat_data = NULL; + int chip_id = id->driver_data; + int ret = 0; + + pmic_plat_data = dev_get_platdata(&i2c->dev); + + if (!pmic_plat_data && i2c->dev.of_node) { + pmic_plat_data = bd7181x_parse_dt(i2c, &chip_id); + of_pmic_plat_data = pmic_plat_data; + } + + if (!pmic_plat_data) + return -EINVAL; + + bd7181x = kzalloc(sizeof(struct bd7181x), GFP_KERNEL); + if (bd7181x == NULL) + return -ENOMEM; + + bd7181x->of_plat_data = of_pmic_plat_data; + i2c_set_clientdata(i2c, bd7181x); + bd7181x->dev = &i2c->dev; + bd7181x->i2c_client = i2c; + bd7181x->id = chip_id; + mutex_init(&bd7181x->io_mutex); + + bd7181x->regmap = devm_regmap_init_i2c(i2c, &bd7181x_regmap_config); + if (IS_ERR(bd7181x->regmap)) { + ret = PTR_ERR(bd7181x->regmap); + dev_err(&i2c->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + ret = bd7181x_reg_read(bd7181x, BD7181X_REG_DEVICE); + if(ret < 0) { + dev_err(bd7181x->dev, "%s(): Read BD7181X_REG_DEVICE failed!\n", __func__); + goto err; + } + dev_info(bd7181x->dev, "BD7181x: Device ID=0x%X\n", ret); + + bd7181x_irq_init(bd7181x, of_pmic_plat_data); + + ret = mfd_add_devices(bd7181x->dev, -1, + bd7181x_mfd_cells, ARRAY_SIZE(bd7181x_mfd_cells), + NULL, 0, + regmap_irq_get_domain(bd7181x->irq_data)); + if (ret < 0) + goto err; + + return ret; + +err: + mfd_remove_devices(bd7181x->dev); + kfree(bd7181x); + return ret; +} + +/** @brief remove bd7181x device + * @param i2c client object provided by system + * @return 0 + */ +static int bd7181x_i2c_remove(struct i2c_client *i2c) +{ + struct bd7181x *bd7181x = i2c_get_clientdata(i2c); + + bd7181x_irq_exit(bd7181x); + mfd_remove_devices(bd7181x->dev); + kfree(bd7181x); + + return 0; +} + +static const struct i2c_device_id bd7181x_i2c_id[] = { + { "bd71815", 0 }, + { "bd71817", 1 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bd7181x_i2c_id); + + +static struct i2c_driver bd7181x_i2c_driver = { + .driver = { + .name = "bd7181x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(bd7181x_of_match), + }, + .probe = bd7181x_i2c_probe, + .remove = bd7181x_i2c_remove, + .id_table = bd7181x_i2c_id, +}; + +static int __init bd7181x_i2c_init(void) +{ + return i2c_add_driver(&bd7181x_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(bd7181x_i2c_init); + +static void __exit bd7181x_i2c_exit(void) +{ + i2c_del_driver(&bd7181x_i2c_driver); +} +module_exit(bd7181x_i2c_exit); + +MODULE_AUTHOR("Tony Luo "); +MODULE_AUTHOR("Peter Yang "); +MODULE_DESCRIPTION("BD71815/BD71817 chip multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index ea17cca4fb90..2da02322fc37 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -29,6 +29,12 @@ config APM_POWER Say Y here to enable support APM status emulation using battery class devices. +config BD7181X_POWER + tristate "ROHM BD71815/BD71817 Charger for Battery and Adapter Power" + depends on MFD_BD7181X + help + Say Y to enable support for the BD71815/BD71817 charger. + config GENERIC_ADC_BATTERY tristate "Generic battery support using IIO" depends on IIO diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index c3f368601a54..5bbb5c2358b2 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o obj-$(CONFIG_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o +obj-$(CONFIG_BD7181X_POWER) += bd7181x-power.o obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o obj-$(CONFIG_MAX8925_POWER) += max8925_power.o obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o diff --git a/drivers/power/supply/bd7181x-power.c b/drivers/power/supply/bd7181x-power.c new file mode 100644 index 000000000000..ba32181b288f --- /dev/null +++ b/drivers/power/supply/bd7181x-power.c @@ -0,0 +1,2243 @@ +/* + * bd7181x-power.c + * @file ROHM BD71815/BD71817 Charger driver + * + * Copyright 2014 Embest Technology Co. Ltd. Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include + +#define JITTER_DEFAULT 10000 /* hope 3s is enough */ +#define JITTER_REPORT_CAP 10000 /* 10 seconds */ +#define CALIB_CURRENT_A2A3 0xCE9E +#define BD7181X_BATTERY_CAP_MAH 1200 +#define BD7181X_BATTERY_CAP mAh_A10s(BD7181X_BATTERY_CAP_MAH) +#define MAX_VOLTAGE ocv_table[0] +#define MIN_VOLTAGE 3200000 +#define THR_VOLTAGE 4100000 +#define MAX_CURRENT 1500000 /* uA */ +#define AC_NAME "bd7181x_ac" +#define BAT_NAME "bd7181x_bat" +#define BD7181X_BATTERY_FULL 100 + +#define BY_BAT_VOLT 0 +#define BY_VBATLOAD_REG 1 +#define INIT_COULOMB BY_VBATLOAD_REG + +#define CALIB_CURRENT_A2A3 0xCE9E + +//VBAT Low voltage detection Threshold +#define VBAT_LOW_TH 0x00D4 // 0x00D4*16mV = 212*0.016 = 3.392v + + +#define RS_30mOHM /* This is for 30mOhm sense resistance */ + +#ifdef RS_30mOHM +#define A10s_mAh(s) ((s) * 1000 / (360 * 3)) +#define mAh_A10s(m) ((m) * (360 * 3) / 1000) +#else +#define A10s_mAh(s) ((s) * 1000 / 360) +#define mAh_A10s(m) ((m) * 360 / 1000) +#endif + +#define THR_RELAX_CURRENT 5 /* mA */ +#define THR_RELAX_TIME ((60 * 60) - 10) /* sec. */ + +#define BD7181X_DGRD_CYC_CAP 15 /* 1 micro Ah unit */ + +#define BD7181X_DGRD_TEMP_M 25 /* 1 degrees C unit */ +#define BD7181X_DGRD_TEMP_L 5 /* 1 degrees C unit */ +#define BD7181X_DGRD_TEMP_CAP_H (0) /* 1 micro Ah unit */ +#define BD7181X_DGRD_TEMP_CAP_M (0) /* 1 micro Ah unit */ +#define BD7181X_DGRD_TEMP_CAP_L (0) /* 1 micro Ah unit */ + +#define CANCEL_ADJ_COULOMB_SOC_H_1 700 /* unit 0.1% */ +#define CANCEL_ADJ_COULOMB_SOC_L_1 550 /* unit 0.1% */ +#define CANCEL_ADJ_COULOMB_SOC_H_2 350 /* unit 0.1% */ +#define CANCEL_ADJ_COULOMB_SOC_L_2 0 /* unit 0.1% */ + +#define FORCE_ADJ_COULOMB_TEMP_H 35 /* 1 degrees C unit */ +#define FORCE_ADJ_COULOMB_TEMP_L 15 /* 1 degrees C unit */ + +unsigned int battery_cycle; + + +static int ocv_table[] = { + 4200000, + 4160000, + 4111638, + 4081966, + 4016618, + 3974931, + 3953040, + 3925422, + 3896285, + 3859529, + 3835339, + 3815914, + 3803244, + 3793558, + 3783886, + 3780000, + 3769083, + 3749738, + 3725590, + 3705524, + 3646077, + 3400000, + 2682635 +}; /* unit 1 micro V */ + +static int soc_table[] = { + 1000, + 1000, + 950, + 900, + 850, + 800, + 750, + 700, + 650, + 600, + 550, + 500, + 450, + 400, + 350, + 300, + 250, + 200, + 150, + 100, + 50, + 0, + -50 + /* unit 0.1% */ +}; + + +/** @brief power deivce */ +struct bd7181x_power { + struct device *dev; + struct bd7181x *mfd; /**< parent for access register */ + struct power_supply ac; /**< alternating current power */ + struct power_supply bat; /**< battery power */ + struct delayed_work bd_work; /**< delayed work for timed work */ + + int reg_index; /**< register address saved for sysfs */ + + int vbus_status; /**< last vbus status */ + int charge_status; /**< last charge status */ + int bat_status; /**< last bat status */ + + int hw_ocv1; /**< HW ocv1 */ + int hw_ocv2; /**< HW ocv2 */ + int bat_online; /**< battery connect */ + int charger_online; /**< charger connect */ + int vcell; /**< battery voltage */ + int vsys; /**< system voltage */ + int vcell_min; /**< minimum battery voltage */ + int vsys_min; /**< minimum system voltage */ + int rpt_status; /**< battery status report */ + int prev_rpt_status; /**< previous battery status report */ + int bat_health; /**< battery health */ + int designed_cap; /**< battery designed capacity */ + int full_cap; /**< battery capacity */ + int curr; /**< battery current from DS-ADC */ + int curr_sar; /**< battery current from VM_IBAT */ + int temp; /**< battery tempature */ + u32 coulomb_cnt; /**< Coulomb Counter */ + int state_machine; /**< initial-procedure state machine */ + + u32 soc_org; /**< State Of Charge using designed capacity without by load */ + u32 soc_norm; /**< State Of Charge using full capacity without by load */ + u32 soc; /**< State Of Charge using full capacity with by load */ + u32 clamp_soc; /**< Clamped State Of Charge using full capacity with by load */ + + int relax_time; /**< Relax Time */ + + u32 cycle; /**< Charging and Discharging cycle number */ + volatile int calib_current; /**< calibration current */ +}; + + +#define CALIB_NORM 0 +#define CALIB_START 1 +#define CALIB_GO 2 + +enum { + STAT_POWER_ON, + STAT_INITIALIZED, +}; + +static int bd7181x_calc_soc_org(struct bd7181x_power* pwr); + +/** @brief read a register group once + * @param mfd bd7181x device + * @param reg register address of lower register + * @return register value + */ +#ifdef __BD7181X_REGMAP_H__ +static u16 bd7181x_reg_read16(struct bd7181x* mfd, int reg) { + u16 v; + + v = (u16)bd7181x_reg_read(mfd, reg) << 8; + v |= (u16)bd7181x_reg_read(mfd, reg + 1) << 0; + return v; +} +#else +static u16 bd7181x_reg_read16(struct bd7181x* mfd, int reg) { + union { + u16 long_type; + char chars[2]; + } u; + int r; + + r = regmap_bulk_read(mfd->regmap, reg, u.chars, sizeof u.chars); + if (r) { + return -1; + } + return be16_to_cpu(u.long_type); +} +#endif + +/** @brief write a register group once + * @param mfd bd7181x device + * @param reg register address of lower register + * @param val value to write + * @retval 0 success + * @retval -1 fail + */ +static int bd7181x_reg_write16(struct bd7181x *mfd, int reg, u16 val) { + union { + u16 long_type; + char chars[2]; + } u; + int r; + + u.long_type = cpu_to_be16(val); + // printk("write16 0x%.4X 0x%.4X\n", val, u.long_type); +#ifdef __BD7181X_REGMAP_H__ + r = mfd->write(mfd, reg, sizeof u.chars, u.chars); +#else + r = regmap_bulk_write(mfd->regmap, reg, u.chars, sizeof u.chars); +#endif + if (r) { + return -1; + } + return 0; +} + +/** @brief read quad register once + * @param mfd bd7181x device + * @param reg register address of lower register + * @return register value + */ +static int bd7181x_reg_read32(struct bd7181x *mfd, int reg) { + union { + u32 long_type; + char chars[4]; + } u; + int r; + +#ifdef __BD7181X_REGMAP_H__ + r = mfd->read(mfd, reg, sizeof u.chars, u.chars); +#else + r = regmap_bulk_read(mfd->regmap, reg, u.chars, sizeof u.chars); +#endif + if (r) { + return -1; + } + return be32_to_cpu(u.long_type); +} + +#if 0 +/** @brief write quad register once + * @param mfd bd7181x device + * @param reg register address of lower register + * @param val value to write + * @retval 0 success + * @retval -1 fail + */ +static int bd7181x_reg_write32(struct bd7181x *mfd, int reg, unsigned val) { + union { + u32 long_type; + char chars[4]; + } u; + int r; + + u.long_type = cpu_to_be32(val); + r = regmap_bulk_write(mfd->regmap, reg, u.chars, sizeof u.chars); + if (r) { + return -1; + } + return 0; +} +#endif + +#if INIT_COULOMB == BY_VBATLOAD_REG +/** @brief get initial battery voltage and current + * @param pwr power device + * @return 0 + */ +static int bd7181x_get_init_bat_stat(struct bd7181x_power *pwr) +{ + struct bd7181x *mfd = pwr->mfd; + int vcell; + + vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_OCV_PRE_U) * 1000; + dev_dbg(pwr->dev, "VM_OCV_PRE = %d\n", vcell); + pwr->hw_ocv1 = vcell; + + vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_OCV_PST_U) * 1000; + dev_dbg(pwr->dev, "VM_OCV_PST = %d\n", vcell); + pwr->hw_ocv2 = vcell; + + return 0; +} +#endif + +/** @brief get battery average voltage and current + * @param pwr power device + * @param vcell pointer to return back voltage in unit uV. + * @param curr pointer to return back current in unit uA. + * @return 0 + */ +static int bd7181x_get_vbat_curr(struct bd7181x_power *pwr, int *vcell, int *curr) { + struct bd7181x* mfd = pwr->mfd; + int tmp_vcell, tmp_curr; + + tmp_vcell = 0; + tmp_curr = 0; + + tmp_vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_VBAT_U); + tmp_curr = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_IBAT_U); + if (tmp_curr & IBAT_SA_DIR_Discharging) { + tmp_curr = -(tmp_curr & ~IBAT_SA_DIR_Discharging); + } + + *vcell = tmp_vcell * 1000; +#ifdef RS_30mOHM + *curr = tmp_curr * 1000 / 3; +#else + *curr = tmp_curr * 1000; +#endif + return 0; +} + +/** @brief get battery current from DS-ADC + * @param pwr power device + * @return current in unit uA + */ +static int bd7181x_get_current_ds_adc(struct bd7181x_power *pwr) { + int r; + + r = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_CC_CURCD_U); + if (r < 0) { + return 0; + } + if (r & CURDIR_Discharging) { + r = -(r & ~CURDIR_Discharging); + } +#ifdef RS_30mOHM + return r * 1000 / 3; +#else + return r * 1000; +#endif +} + +/** @brief get system average voltage + * @param pwr power device + * @param vcell pointer to return back voltage in unit uV. + * @return 0 + */ +static int bd7181x_get_vsys(struct bd7181x_power *pwr, int *vsys) { + struct bd7181x* mfd = pwr->mfd; + int tmp_vsys; + + tmp_vsys = 0; + + tmp_vsys = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_VSYS_U); + + *vsys = tmp_vsys * 1000; + + return 0; +} + +/** @brief get battery minimum average voltage + * @param pwr power device + * @param vcell pointer to return back voltage in unit uV. + * @return 0 + */ +static int bd7181x_get_vbat_min(struct bd7181x_power *pwr, int *vcell) { + struct bd7181x* mfd = pwr->mfd; + int tmp_vcell; + + tmp_vcell = 0; + + tmp_vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_VBAT_MIN_U); + bd7181x_set_bits(pwr->mfd, BD7181X_REG_VM_SA_MINMAX_CLR, VBAT_SA_MIN_CLR); + + *vcell = tmp_vcell * 1000; + + return 0; +} + +/** @brief get system minimum average voltage + * @param pwr power device + * @param vcell pointer to return back voltage in unit uV. + * @return 0 + */ +static int bd7181x_get_vsys_min(struct bd7181x_power *pwr, int *vcell) { + struct bd7181x* mfd = pwr->mfd; + int tmp_vcell; + + tmp_vcell = 0; + + tmp_vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_VSYS_MIN_U); + bd7181x_set_bits(pwr->mfd, BD7181X_REG_VM_SA_MINMAX_CLR, VSYS_SA_MIN_CLR); + + *vcell = tmp_vcell * 1000; + + return 0; +} + +/** @brief get battery capacity + * @param ocv open circuit voltage + * @return capcity in unit 0.1 percent + */ +static int bd7181x_voltage_to_capacity(int ocv) { + int i = 0; + int soc; + + if (ocv > ocv_table[0]) { + soc = soc_table[0]; + } else { + i = 0; + while (soc_table[i] != -50) { + if ((ocv <= ocv_table[i]) && (ocv > ocv_table[i+1])) { + soc = (soc_table[i] - soc_table[i+1]) * (ocv - ocv_table[i+1]) / (ocv_table[i] - ocv_table[i+1]); + soc += soc_table[i+1]; + break; + } + i++; + } + if (soc_table[i] == -50) + soc = soc_table[i]; + } + return soc; +} + +/** @brief get battery temperature + * @param pwr power device + * @return temperature in unit deg.Celsius + */ +static int bd7181x_get_temp(struct bd7181x_power *pwr) { + struct bd7181x* mfd = pwr->mfd; + int t; + + t = 200 - (int)bd7181x_reg_read(mfd, BD7181X_REG_VM_BTMP); + + // battery temperature error + t = (t > 200)? 200: t; + + return t; +} + +static int bd7181x_reset_coulomb_count(struct bd7181x_power* pwr); + +/** @brief get battery charge status + * @param pwr power device + * @return temperature in unit deg.Celsius + */ +static int bd7181x_charge_status(struct bd7181x_power *pwr) +{ + u8 state; + int ret = 1; + + state = bd7181x_reg_read(pwr->mfd, BD7181X_REG_CHG_STATE); + dev_dbg(pwr->dev, "%s(): CHG_STATE %d\n", __func__, state); + + switch (state) { + case 0x00: + ret = 0; + pwr->rpt_status = POWER_SUPPLY_STATUS_DISCHARGING; + pwr->bat_health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x01: + case 0x02: + case 0x03: + case 0x0E: + pwr->rpt_status = POWER_SUPPLY_STATUS_CHARGING; + pwr->bat_health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x0F: + ret = 0; + pwr->rpt_status = POWER_SUPPLY_STATUS_FULL; + pwr->bat_health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + ret = 0; + pwr->rpt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pwr->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case 0x30: + case 0x31: + case 0x32: + case 0x40: + ret = 0; + pwr->rpt_status = POWER_SUPPLY_STATUS_DISCHARGING; + pwr->bat_health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x7f: + default: + ret = 0; + pwr->rpt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pwr->bat_health = POWER_SUPPLY_HEALTH_DEAD; + break; + } + + bd7181x_reset_coulomb_count(pwr); + + pwr->prev_rpt_status = pwr->rpt_status; + + return ret; +} + +#if INIT_COULOMB == BY_BAT_VOLT +static int bd7181x_calib_voltage(struct bd7181x_power* pwr, int* ocv) { + int r, curr, volt; + + bd7181x_get_vbat_curr(pwr, &volt, &curr); + + r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_CHG_STATE); + if (r >= 0 && curr > 0) { + // voltage increment caused by battery inner resistor + if (r == 3) volt -= 100 * 1000; + else if (r == 2) volt -= 50 * 1000; + } + *ocv = volt; + + return 0; +} +#endif + +/** @brief set initial coulomb counter value from battery voltage + * @param pwr power device + * @return 0 + */ +static int calibration_coulomb_counter(struct bd7181x_power* pwr) +{ + u32 bcap; + int soc, ocv; + +#if INIT_COULOMB == BY_VBATLOAD_REG + /* Get init OCV by HW */ + bd7181x_get_init_bat_stat(pwr); + + ocv = (pwr->hw_ocv1 >= pwr->hw_ocv2)? pwr->hw_ocv1: pwr->hw_ocv2; + dev_dbg(pwr->dev, "ocv %d\n", ocv); +#elif INIT_COULOMB == BY_BAT_VOLT + bd7181x_calib_voltage(pwr, &ocv); +#endif + + /* Get init soc from ocv/soc table */ + soc = bd7181x_voltage_to_capacity(ocv); + dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc); + if (soc < 0) + soc = 0; + bcap = pwr->designed_cap * soc / 1000; + + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0); + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((bcap + pwr->designed_cap / 200) & 0x1FFFUL)); + + pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL; + dev_dbg(pwr->dev, "%s() CC_CCNTD = %d\n", __func__, pwr->coulomb_cnt); + + /* Start canceling offset of the DS ADC. This needs 1 second at least */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCCALIB); + + return 0; +} + +/** @brief adjust coulomb counter values at relaxed state + * @param pwr power device + * @return 0 + */ +static int bd7181x_adjust_coulomb_count(struct bd7181x_power* pwr) +{ + u32 relaxed_coulomb_cnt; + + relaxed_coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_REX_CCNTD_3) & 0x1FFFFFFFUL; + dev_dbg(pwr->dev, "%s(): relaxed_coulomb_cnt = 0x%x\n", __func__, relaxed_coulomb_cnt); + if (relaxed_coulomb_cnt != 0) { + u32 bcap; + int soc, ocv; + int diff_coulomb_cnt; + + /* Get OCV at relaxed state by HW */ + ocv = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_REX_SA_VBAT_U) * 1000; + dev_dbg(pwr->dev, "ocv %d\n", ocv); + + /* Clear Relaxed Coulomb Counter */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_REX_CTRL_1, REX_CLR); + + diff_coulomb_cnt = relaxed_coulomb_cnt - (bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL); + diff_coulomb_cnt = diff_coulomb_cnt >> 16; + dev_dbg(pwr->dev, "diff_coulomb_cnt = %d\n", diff_coulomb_cnt); + + /* Get soc at relaxed state from ocv/soc table */ + soc = bd7181x_voltage_to_capacity(ocv); + dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc); + if (soc < 0) + soc = 0; + + if ((soc > CANCEL_ADJ_COULOMB_SOC_H_1) || ((soc < CANCEL_ADJ_COULOMB_SOC_L_1) && (soc > CANCEL_ADJ_COULOMB_SOC_H_2)) || (soc < CANCEL_ADJ_COULOMB_SOC_L_2) || + ((pwr->temp <= FORCE_ADJ_COULOMB_TEMP_H) && (pwr->temp >= FORCE_ADJ_COULOMB_TEMP_L))) { + bcap = pwr->designed_cap * soc / 1000; + + /* Stop Coulomb Counter */ + bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0); + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((bcap + pwr->designed_cap / 200) & 0x1FFFUL) + diff_coulomb_cnt); + + pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL; + dev_dbg(pwr->dev, "Adjust Coulomb Counter at Relaxed State\n"); + dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt); + + /* Start Coulomb Counter */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + + /* If the following commented out code is enabled, the SOC is not clamped at the relax time. */ + /* Reset SOCs */ + /* bd7181x_calc_soc_org(pwr); */ + /* pwr->soc_norm = pwr->soc_org; */ + /* pwr->soc = pwr->soc_norm; */ + /* pwr->clamp_soc = pwr->soc; */ + } + } + + return 0; +} + +/** @brief reset coulomb counter values at full charged state + * @param pwr power device + * @return 0 + */ +static int bd7181x_reset_coulomb_count(struct bd7181x_power* pwr) +{ + u32 full_charged_coulomb_cnt; + + full_charged_coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_FULL_CCNTD_3) & 0x1FFFFFFFUL; + dev_dbg(pwr->dev, "%s(): full_charged_coulomb_cnt=0x%x\n", __func__, full_charged_coulomb_cnt); + if (full_charged_coulomb_cnt != 0) { + int diff_coulomb_cnt; + + /* Clear Full Charged Coulomb Counter */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_FULL_CTRL, FULL_CLR); + + diff_coulomb_cnt = full_charged_coulomb_cnt - (bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL); + diff_coulomb_cnt = diff_coulomb_cnt >> 16; + if (diff_coulomb_cnt > 0) { + diff_coulomb_cnt = 0; + } + dev_dbg(pwr->dev, "diff_coulomb_cnt = %d\n", diff_coulomb_cnt); + + /* Stop Coulomb Counter */ + bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0); + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((pwr->designed_cap + pwr->designed_cap / 200) & 0x1FFFUL) + diff_coulomb_cnt); + + pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL; + dev_dbg(pwr->dev, "Reset Coulomb Counter at POWER_SUPPLY_STATUS_FULL\n"); + dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt); + + /* Start Coulomb Counter */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + } + + return 0; +} + +/** @brief get battery parameters, such as voltages, currents, temperatures. + * @param pwr power device + * @return 0 + */ +static int bd7181x_get_voltage_current(struct bd7181x_power* pwr) +{ + + /* Read detailed vcell and current */ + bd7181x_get_vbat_curr(pwr, &pwr->vcell, &pwr->curr_sar); + dev_dbg(pwr->dev, "VM_VBAT = %d\n", pwr->vcell); + dev_dbg(pwr->dev, "VM_IBAT = %d\n", pwr->curr_sar); + + pwr->curr = bd7181x_get_current_ds_adc(pwr); + dev_dbg(pwr->dev, "CC_CURCD = %d\n", pwr->curr); + + /* Read detailed vsys */ + bd7181x_get_vsys(pwr, &pwr->vsys); + dev_dbg(pwr->dev, "VM_VSYS = %d\n", pwr->vsys); + + /* Read detailed vbat_min */ + bd7181x_get_vbat_min(pwr, &pwr->vcell_min); + dev_dbg(pwr->dev, "VM_VBAT_MIN = %d\n", pwr->vcell_min); + + /* Read detailed vsys_min */ + bd7181x_get_vsys_min(pwr, &pwr->vsys_min); + dev_dbg(pwr->dev, "VM_VSYS_MIN = %d\n", pwr->vsys_min); + + /* Get tempature */ + pwr->temp = bd7181x_get_temp(pwr); + // dev_dbg(pwr->dev, "Temperature %d degrees C\n", pwr->temp); + + return 0; +} + +/** @brief adjust coulomb counter values at relaxed state by SW + * @param pwr power device + * @return 0 + */ +static int bd7181x_adjust_coulomb_count_sw(struct bd7181x_power* pwr) +{ + int tmp_curr_mA; + + tmp_curr_mA = pwr->curr / 1000; + if ((tmp_curr_mA * tmp_curr_mA) <= (THR_RELAX_CURRENT * THR_RELAX_CURRENT)) { /* No load */ + pwr->relax_time += (JITTER_DEFAULT / 1000); + } + else { + pwr->relax_time = 0; + } + dev_dbg(pwr->dev, "%s(): pwr->relax_time = 0x%x\n", __func__, pwr->relax_time); + if (pwr->relax_time >= THR_RELAX_TIME) { /* Battery is relaxed. */ + u32 bcap; + int soc, ocv; + + pwr->relax_time = 0; + + /* Get OCV */ + ocv = pwr->vcell; + + /* Get soc at relaxed state from ocv/soc table */ + soc = bd7181x_voltage_to_capacity(ocv); + dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc); + if (soc < 0) + soc = 0; + + if ((soc > CANCEL_ADJ_COULOMB_SOC_H_1) || ((soc < CANCEL_ADJ_COULOMB_SOC_L_1) && (soc > CANCEL_ADJ_COULOMB_SOC_H_2)) || (soc < CANCEL_ADJ_COULOMB_SOC_L_2) || + ((pwr->temp <= FORCE_ADJ_COULOMB_TEMP_H) && (pwr->temp >= FORCE_ADJ_COULOMB_TEMP_L))) { + bcap = pwr->designed_cap * soc / 1000; + + /* Stop Coulomb Counter */ + bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0); + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((bcap + pwr->designed_cap / 200) & 0x1FFFUL)); + + pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL; + dev_dbg(pwr->dev, "Adjust Coulomb Counter by SW at Relaxed State\n"); + dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt); + + /* Start Coulomb Counter */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + + /* If the following commented out code is enabled, the SOC is not clamped at the relax time. */ + /* Reset SOCs */ + /* bd7181x_calc_soc_org(pwr); */ + /* pwr->soc_norm = pwr->soc_org; */ + /* pwr->soc = pwr->soc_norm; */ + /* pwr->clamp_soc = pwr->soc; */ + } + + } + + return 0; +} + +/** @brief get coulomb counter values + * @param pwr power device + * @return 0 + */ +static int bd7181x_coulomb_count(struct bd7181x_power* pwr) +{ + dev_dbg(pwr->dev, "%s(): pwr->state_machine = 0x%x\n", __func__, pwr->state_machine); + if (pwr->state_machine == STAT_POWER_ON) { + pwr->state_machine = STAT_INITIALIZED; + /* Start Coulomb Counter */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + } else if (pwr->state_machine == STAT_INITIALIZED) { + pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL; + // dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt); + } + return 0; +} + +/** @brief calc cycle + * @param pwr power device + * @return 0 + */ +static int bd7181x_update_cycle(struct bd7181x_power* pwr) +{ + int charged_coulomb_cnt; + + charged_coulomb_cnt = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_CCNTD_CHG_3); + dev_dbg(pwr->dev, "%s(): charged_coulomb_cnt = 0x%x\n", __func__, charged_coulomb_cnt); + if (charged_coulomb_cnt >= pwr->designed_cap) { + pwr->cycle++; + dev_dbg(pwr->dev, "Update cycle = %d\n", pwr->cycle); + battery_cycle = pwr->cycle; + charged_coulomb_cnt -= pwr->designed_cap; + /* Stop Coulomb Counter */ + bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CCNTD_CHG_3, charged_coulomb_cnt); + + /* Start Coulomb Counter */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + } + return 0; +} + +/** @brief calc full capacity value by Cycle and Temperature + * @param pwr power device + * @return 0 + */ +static int bd7181x_calc_full_cap(struct bd7181x_power* pwr) +{ + u32 designed_cap_uAh; + u32 full_cap_uAh; + + /* Calculate full capacity by cycle */ + designed_cap_uAh = A10s_mAh(pwr->designed_cap) * 1000; + full_cap_uAh = designed_cap_uAh - BD7181X_DGRD_CYC_CAP * pwr->cycle; + pwr->full_cap = mAh_A10s(full_cap_uAh / 1000); + dev_dbg(pwr->dev, "Calculate full capacity by cycle\n"); + dev_dbg(pwr->dev, "%s() pwr->full_cap = %d\n", __func__, pwr->full_cap); + + /* Calculate full capacity by temperature */ + dev_dbg(pwr->dev, "Temperature = %d\n", pwr->temp); + if (pwr->temp >= BD7181X_DGRD_TEMP_M) { + full_cap_uAh += (pwr->temp - BD7181X_DGRD_TEMP_M) * BD7181X_DGRD_TEMP_CAP_H; + pwr->full_cap = mAh_A10s(full_cap_uAh / 1000); + } + else if (pwr->temp >= BD7181X_DGRD_TEMP_L) { + full_cap_uAh += (pwr->temp - BD7181X_DGRD_TEMP_M) * BD7181X_DGRD_TEMP_CAP_M; + pwr->full_cap = mAh_A10s(full_cap_uAh / 1000); + } + else { + full_cap_uAh += (BD7181X_DGRD_TEMP_L - BD7181X_DGRD_TEMP_M) * BD7181X_DGRD_TEMP_CAP_M; + full_cap_uAh += (pwr->temp - BD7181X_DGRD_TEMP_L) * BD7181X_DGRD_TEMP_CAP_L; + pwr->full_cap = mAh_A10s(full_cap_uAh / 1000); + } + dev_dbg(pwr->dev, "Calculate full capacity by cycle and temperature\n"); + dev_dbg(pwr->dev, "%s() pwr->full_cap = %d\n", __func__, pwr->full_cap); + + return 0; +} + +/** @brief calculate SOC values by designed capacity + * @param pwr power device + * @return 0 + */ +static int bd7181x_calc_soc_org(struct bd7181x_power* pwr) +{ + pwr->soc_org = (pwr->coulomb_cnt >> 16) * 100 / pwr->designed_cap; + if (pwr->soc_org > 100) { + pwr->soc_org = 100; + /* Stop Coulomb Counter */ + bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0); + bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((pwr->designed_cap + pwr->designed_cap / 200) & 0x1FFFUL)); + + pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL; + dev_dbg(pwr->dev, "Limit Coulomb Counter\n"); + dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt); + + /* Start Coulomb Counter */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB); + } + dev_dbg(pwr->dev, "%s(): pwr->soc_org = %d\n", __func__, pwr->soc_org); + return 0; +} + +/** @brief calculate SOC values by full capacity + * @param pwr power device + * @return 0 + */ +static int bd7181x_calc_soc_norm(struct bd7181x_power* pwr) +{ + int lost_cap; + int mod_coulomb_cnt; + + lost_cap = pwr->designed_cap - pwr->full_cap; + dev_dbg(pwr->dev, "%s() lost_cap = %d\n", __func__, lost_cap); + mod_coulomb_cnt = (pwr->coulomb_cnt >> 16) - lost_cap; + if ((mod_coulomb_cnt > 0) && (pwr->full_cap > 0)) { + pwr->soc_norm = mod_coulomb_cnt * 100 / pwr->full_cap; + } + else { + pwr->soc_norm = 0; + } + if (pwr->soc_norm > 100) { + pwr->soc_norm = 100; + } + dev_dbg(pwr->dev, "%s() pwr->soc_norm = %d\n", __func__, pwr->soc_norm); + return 0; +} + +/** @brief get OCV value by SOC + * @param pwr power device + * @return 0 + */ +int bd7181x_get_ocv(struct bd7181x_power* pwr, int dsoc) { + int i = 0; + int ocv = 0; + + if (dsoc > soc_table[0]) { + ocv = MAX_VOLTAGE; + } + else if (dsoc == 0) { + ocv = ocv_table[21]; + } + else { + i = 0; + while (i < 22) { + if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) { + ocv = (ocv_table[i] - ocv_table[i+1]) * (dsoc - soc_table[i+1]) / (soc_table[i] - soc_table[i+1]) + ocv_table[i+1]; + break; + } + i++; + } + if (i == 22) + ocv = ocv_table[22]; + } + dev_dbg(pwr->dev, "%s() ocv = %d\n", __func__, ocv); + return ocv; +} + +/** @brief calculate SOC value by full_capacity and load + * @param pwr power device + * @return OCV + */ +static int bd7181x_calc_soc(struct bd7181x_power* pwr) { + int ocv_table_load[23]; + + pwr->soc = pwr->soc_norm; + + switch (pwr->rpt_status) { /* Adjust for 0% between THR_VOLTAGE and MIN_VOLTAGE */ + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (pwr->vsys_min <= THR_VOLTAGE) { + int i; + int ocv; + int lost_cap; + int mod_coulomb_cnt; + int dsoc; + + lost_cap = pwr->designed_cap - pwr->full_cap; + mod_coulomb_cnt = (pwr->coulomb_cnt >> 16) - lost_cap; + dsoc = mod_coulomb_cnt * 1000 / pwr->full_cap; + dev_dbg(pwr->dev, "%s() dsoc = %d\n", __func__, dsoc); + ocv = bd7181x_get_ocv(pwr, dsoc); + for (i = 1; i < 23; i++) { + ocv_table_load[i] = ocv_table[i] - (ocv - pwr->vsys_min); + if (ocv_table_load[i] <= MIN_VOLTAGE) { + dev_dbg(pwr->dev, "%s() ocv_table_load[%d] = %d\n", __func__, i, ocv_table_load[i]); + break; + } + } + if (i < 23) { + int j; + int dv = (ocv_table_load[i-1] - ocv_table_load[i]) / 5; + int lost_cap2; + int mod_coulomb_cnt2, mod_full_cap; + for (j = 1; j < 5; j++){ + if ((ocv_table_load[i] + dv * j) > MIN_VOLTAGE) { + break; + } + } + lost_cap2 = ((21 - i) * 5 + (j - 1)) * pwr->full_cap / 100; + dev_dbg(pwr->dev, "%s() lost_cap2 = %d\n", __func__, lost_cap2); + mod_coulomb_cnt2 = mod_coulomb_cnt - lost_cap2; + mod_full_cap = pwr->full_cap - lost_cap2; + if ((mod_coulomb_cnt2 > 0) && (mod_full_cap > 0)) { + pwr->soc = mod_coulomb_cnt2 * 100 / mod_full_cap; + } + else { + pwr->soc = 0; + } + dev_dbg(pwr->dev, "%s() pwr->soc(by load) = %d\n", __func__, pwr->soc); + } + } + break; + default: + break; + } + + switch (pwr->rpt_status) {/* Adjust for 0% and 100% */ + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (pwr->vsys_min <= MIN_VOLTAGE) { + pwr->soc = 0; + } + else { + if (pwr->soc == 0) { + pwr->soc = 1; + } + } + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (pwr->soc == 100) { + pwr->soc = 99; + } + break; + default: + break; + } + dev_dbg(pwr->dev, "%s() pwr->soc = %d\n", __func__, pwr->soc); + return 0; +} + +/** @brief calculate Clamped SOC value by full_capacity and load + * @param pwr power device + * @return OCV + */ +static int bd7181x_calc_soc_clamp(struct bd7181x_power* pwr) { + switch (pwr->rpt_status) {/* Adjust for 0% and 100% */ + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (pwr->soc <= pwr->clamp_soc) { + pwr->clamp_soc = pwr->soc; + } + break; + default: + pwr->clamp_soc = pwr->soc; + break; + } + dev_dbg(pwr->dev, "%s() pwr->clamp_soc = %d\n", __func__, pwr->clamp_soc); + return 0; +} + +/** @brief get battery and DC online status + * @param pwr power device + * @return 0 + */ +static int bd7181x_get_online(struct bd7181x_power* pwr) +{ + int r; + +#if 0 +#define TS_THRESHOLD_VOLT 0xD9 + r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_VM_VTH); + pwr->bat_online = (r > TS_THRESHOLD_VOLT); +#endif +#if 0 + r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_STAT); + if (r >= 0 && (r & BAT_DET_DONE)) { + pwr->bat_online = (r & BAT_DET) != 0; + } +#endif +#if 1 +#define BAT_OPEN 0x7 + r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_TEMP); + pwr->bat_online = (r != BAT_OPEN); +#endif + r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_DCIN_STAT); + if (r >= 0) { + pwr->charger_online = (r & VBUS_DET) != 0; + } + dev_dbg(pwr->dev, "%s(): pwr->bat_online = %d, pwr->charger_online = %d\n", __func__, pwr->bat_online, pwr->charger_online); + return 0; +} + +/** @brief init bd7181x sub module charger + * @param pwr power device + * @return 0 + */ +static int bd7181x_init_hardware(struct bd7181x_power *pwr) +{ + struct bd7181x *mfd = pwr->mfd; + int r; + + r = bd7181x_reg_write(mfd, BD7181X_REG_DCIN_CLPS, 0x36); + +#define XSTB 0x02 + r = bd7181x_reg_read(mfd, BD7181X_REG_CONF); + +#if 0 + for (i = 0; i < 300; i++) { + r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_STAT); + if (r >= 0 && (r & BAT_DET_DONE)) { + break; + } + msleep(5); + } +#endif + if ((r & XSTB) == 0x00) { + //if (r & BAT_DET) { + /* Init HW, when the battery is inserted. */ + + bd7181x_reg_write(mfd, BD7181X_REG_CONF, r | XSTB); + +#define TEST_SEQ_00 0x00 +#define TEST_SEQ_01 0x76 +#define TEST_SEQ_02 0x66 +#define TEST_SEQ_03 0x56 +#if 0 + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_01); + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_02); + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_03); + bd7181x_reg_write16(pwr->mfd, 0xA2, CALIB_CURRENT_A2A3); + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_00); +#endif + + /* Stop Coulomb Counter */ + bd7181x_clear_bits(mfd, BD7181X_REG_CC_CTRL, CCNTENB); + + /* Set Coulomb Counter Reset bit*/ + bd7181x_set_bits(mfd, BD7181X_REG_CC_CTRL, CCNTRST); + + /* Clear Coulomb Counter Reset bit*/ + bd7181x_clear_bits(mfd, BD7181X_REG_CC_CTRL, CCNTRST); + + /* Set default Battery Capacity */ + pwr->designed_cap = BD7181X_BATTERY_CAP; + pwr->full_cap = BD7181X_BATTERY_CAP; + + /* Set initial Coulomb Counter by HW OCV */ + calibration_coulomb_counter(pwr); + + /* WDT_FST auto set */ + bd7181x_set_bits(mfd, BD7181X_REG_CHG_SET1, WDT_AUTO); + + /* VBAT Low voltage detection Setting, added by John Zhang*/ + bd7181x_reg_write16(mfd, BD7181X_REG_ALM_VBAT_TH_U, VBAT_LOW_TH); + + /* Mask Relax decision by PMU STATE */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_REX_CTRL_1, REX_PMU_STATE_MASK); + + /* Set Battery Capacity Monitor threshold1 as 90% */ + bd7181x_reg_write16(mfd, BD7181X_REG_CC_BATCAP1_TH_U, (BD7181X_BATTERY_CAP * 9 / 10)); + dev_dbg(pwr->dev, "BD7181X_REG_CC_BATCAP1_TH = %d\n", (BD7181X_BATTERY_CAP * 9 / 10)); + + /* Enable LED ON when charging */ + bd7181x_set_bits(pwr->mfd, BD7181X_REG_LED_CTRL, CHGDONE_LED_EN); + + pwr->state_machine = STAT_POWER_ON; + } else { + pwr->designed_cap = BD7181X_BATTERY_CAP; + pwr->full_cap = BD7181X_BATTERY_CAP; // bd7181x_reg_read16(pwr->mfd, BD7181X_REG_CC_BATCAP_U); + pwr->state_machine = STAT_INITIALIZED; // STAT_INITIALIZED + } + + pwr->temp = bd7181x_get_temp(pwr); + dev_dbg(pwr->dev, "Temperature = %d\n", pwr->temp); + bd7181x_adjust_coulomb_count(pwr); + bd7181x_reset_coulomb_count(pwr); + pwr->coulomb_cnt = bd7181x_reg_read32(mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL; + bd7181x_calc_soc_org(pwr); + pwr->soc_norm = pwr->soc_org; + pwr->soc = pwr->soc_norm; + pwr->clamp_soc = pwr->soc; + dev_dbg(pwr->dev, "%s() CC_CCNTD = %d\n", __func__, pwr->coulomb_cnt); + dev_dbg(pwr->dev, "%s() pwr->soc = %d\n", __func__, pwr->soc); + dev_dbg(pwr->dev, "%s() pwr->clamp_soc = %d\n", __func__, pwr->clamp_soc); + + pwr->cycle = battery_cycle; + pwr->curr = 0; + pwr->curr_sar = 0; + pwr->relax_time = 0; + + return 0; +} + +/**@brief timed work function called by system + * read battery capacity, + * sense change of charge status, etc. + * @param work work struct + * @return void + */ + +static void bd_work_callback(struct work_struct *work) +{ + struct bd7181x_power *pwr; + struct delayed_work *delayed_work; + int status, changed = 0; + static int cap_counter = 0; + + delayed_work = container_of(work, struct delayed_work, work); + pwr = container_of(delayed_work, struct bd7181x_power, bd_work); + + dev_dbg(pwr->dev, "%s(): in\n", __func__); + status = bd7181x_reg_read(pwr->mfd, BD7181X_REG_DCIN_STAT); + if (status != pwr->vbus_status) { + //printk("DCIN_STAT CHANGED from 0x%X to 0x%X\n", pwr->vbus_status, status); + pwr->vbus_status = status; + changed = 1; + } + + status = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_STAT); + status &= ~BAT_DET_DONE; + if (status != pwr->bat_status) { + dev_dbg(pwr->dev, "BAT_STAT CHANGED from 0x%X to 0x%X\n", pwr->bat_status, status); + pwr->bat_status = status; + changed = 1; + } + + status = bd7181x_reg_read(pwr->mfd, BD7181X_REG_CHG_STATE); + if (status != pwr->charge_status) { + dev_dbg(pwr->dev, "CHG_STATE CHANGED from 0x%X to 0x%X\n", pwr->charge_status, status); + pwr->charge_status = status; + //changed = 1; + } + + bd7181x_get_voltage_current(pwr); + bd7181x_adjust_coulomb_count(pwr); + bd7181x_reset_coulomb_count(pwr); + bd7181x_adjust_coulomb_count_sw(pwr); + bd7181x_coulomb_count(pwr); + bd7181x_update_cycle(pwr); + bd7181x_calc_full_cap(pwr); + bd7181x_calc_soc_org(pwr); + bd7181x_calc_soc_norm(pwr); + bd7181x_calc_soc(pwr); + bd7181x_calc_soc_clamp(pwr); + bd7181x_get_online(pwr); + bd7181x_charge_status(pwr); + + if (changed || cap_counter++ > JITTER_REPORT_CAP / JITTER_DEFAULT) { + power_supply_changed(&pwr->ac); + power_supply_changed(&pwr->bat); + cap_counter = 0; + } + + if (pwr->calib_current == CALIB_NORM) { + schedule_delayed_work(&pwr->bd_work, msecs_to_jiffies(JITTER_DEFAULT)); + } else if (pwr->calib_current == CALIB_START) { + pwr->calib_current = CALIB_GO; + } +} + +/**@brief bd7181x power interrupt + * @param irq system irq + * @param pwrsys bd7181x power device of system + * @retval IRQ_HANDLED success + * @retval IRQ_NONE error + */ +static irqreturn_t bd7181x_power_interrupt(int irq, void *pwrsys) +{ + struct device *dev = pwrsys; + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + // struct bd7181x_power *pwr = dev_get_drvdata(dev); + int reg, r; + + dev_info(mfd->dev, "bd7181x_power_interrupt() in.\n"); + + reg = bd7181x_reg_read(mfd, BD7181X_REG_INT_STAT_03); + if (reg < 0) + return IRQ_NONE; + + dev_info(mfd->dev, "INT_STAT_03 = 0x%.2X\n", reg); + if(reg & POWERON_PRESS) + { + kobject_uevent(&(mfd->dev->kobj), KOBJ_ONLINE); + dev_info(mfd->dev, "POWERON_PRESS\n"); + } + if(reg & POWERON_SHORT) + { + kobject_uevent(&(mfd->dev->kobj), KOBJ_OFFLINE); + dev_info(mfd->dev, "POWERON_SHORT\n"); + } + if(reg & POWERON_MID) + { + kobject_uevent(&(mfd->dev->kobj), KOBJ_OFFLINE); + dev_info(mfd->dev, "POWERON_MID\n"); + } + if(reg & POWERON_LONG) + { + kobject_uevent(&(mfd->dev->kobj), KOBJ_OFFLINE); + dev_info(mfd->dev, "POWERON_LONG\n"); + } + + r = bd7181x_reg_write(mfd, BD7181X_REG_INT_STAT_03, reg); + if (r) + return IRQ_NONE; + + if (reg & DCIN_MON_DET) { + dev_info(mfd->dev, "\n~~~DCIN removed\n"); + } else if (reg & DCIN_MON_RES) { + dev_info(mfd->dev, "\n~~~DCIN inserted\n"); + } + + return IRQ_HANDLED; +} + +/**@brief bd7181x vbat low voltage detection interrupt + * @param irq system irq + * @param pwrsys bd7181x power device of system + * @retval IRQ_HANDLED success + * @retval IRQ_NONE error + * added by John Zhang at 2015-07-22 + */ +static irqreturn_t bd7181x_vbat_interrupt(int irq, void *pwrsys) +{ + struct device *dev = pwrsys; + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + // struct bd7181x_power *pwr = dev_get_drvdata(dev); + int reg, r; + + dev_info(mfd->dev, "bd7181x_vbat_interrupt() in.\n"); + + reg = bd7181x_reg_read(mfd, BD7181X_REG_INT_STAT_08); + if (reg < 0) + return IRQ_NONE; + + dev_info(mfd->dev, "INT_STAT_08 = 0x%.2X\n", reg); + + r = bd7181x_reg_write(mfd, BD7181X_REG_INT_STAT_08, reg); + if (r) + return IRQ_NONE; + + if (reg & VBAT_MON_DET) { + dev_info(mfd->dev, "\n~~~ VBAT LOW Detected ... \n"); + + } else if (reg & VBAT_MON_RES) { + dev_info(mfd->dev, "\n~~~ VBAT LOW Resumed ... \n"); + } + + return IRQ_HANDLED; + +} + +/**@brief bd7181x int_stat_11 detection interrupt + * @param irq system irq + * @param pwrsys bd7181x power device of system + * @retval IRQ_HANDLED success + * @retval IRQ_NONE error + * added 2015-12-26 + */ +static irqreturn_t bd7181x_int_11_interrupt(int irq, void *pwrsys) +{ + struct device *dev = pwrsys; + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + // struct bd7181x_power *pwr = dev_get_drvdata(dev); + int reg, r; + + dev_info(mfd->dev, "bd7181x_int_11_interrupt() in.\n"); + + reg = bd7181x_reg_read(mfd, BD7181X_REG_INT_STAT_11); + if (reg < 0) + return IRQ_NONE; + + dev_info(mfd->dev, "INT_STAT_11 = 0x%.2X\n", reg); + + r = bd7181x_reg_write(mfd, BD7181X_REG_INT_STAT_11, reg); + if (r) { + return IRQ_NONE; + } + + if (reg & INT_STAT_11_VF_DET) { + dev_info(mfd->dev, "\n~~~ VF Detected ... \n"); + } else if (reg & INT_STAT_11_VF_RES) { + dev_info(mfd->dev, "\n~~~ VF Resumed ... \n"); + } else if (reg & INT_STAT_11_VF125_DET) { + dev_info(mfd->dev, "\n~~~ VF125 Detected ... \n"); + } else if (reg & INT_STAT_11_VF125_RES) { + dev_info(mfd->dev, "\n~~~ VF125 Resumed ... \n"); + } else if (reg & INT_STAT_11_OVTMP_DET) { + dev_info(mfd->dev, "\n~~~ Overtemp Detected ... \n"); + } else if (reg & INT_STAT_11_OVTMP_RES) { + dev_info(mfd->dev, "\n~~~ Overtemp Detected ... \n"); + } else if (reg & INT_STAT_11_LOTMP_DET) { + dev_info(mfd->dev, "\n~~~ Lowtemp Detected ... \n"); + } else if (reg & INT_STAT_11_LOTMP_RES) { + dev_info(mfd->dev, "\n~~~ Lowtemp Detected ... \n"); + } + + return IRQ_HANDLED; + +} + +/** @brief get property of power supply ac + * @param psy power supply deivce + * @param psp property to get + * @param val property value to return + * @retval 0 success + * @retval negative fail + */ +static int bd7181x_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct bd7181x_power *pwr = dev_get_drvdata(psy->dev->parent); + u32 vot; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = pwr->charger_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + vot = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_VM_DCIN_U); + val->intval = 5000 * vot; // 5 milli volt steps + break; + default: + return -EINVAL; + } + + return 0; +} + +/** @brief get property of power supply bat + * @param psy power supply deivce + * @param psp property to get + * @param val property value to return + * @retval 0 success + * @retval negative fail + */ + +static int bd7181x_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct bd7181x_power *pwr = dev_get_drvdata(psy->dev->parent); + // u32 cap, vot, r; + // u8 ret; + + switch (psp) { + /* + case POWER_SUPPLY_PROP_STATUS: + r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_CHG_STATE); + // printk("CHG_STATE = 0x%.2X\n", r); + switch(r) { + case CHG_STATE_SUSPEND: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHG_STATE_TRICKLE_CHARGE: + case CHG_STATE_PRE_CHARGE: + case CHG_STATE_FAST_CHARGE: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case CHG_STATE_TOP_OFF: + case CHG_STATE_DONE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + } + break; + case POWER_SUPPLY_PROP_PRESENT: + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_STAT); + if (ret & DBAT_DET) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (ret & VBAT_OV) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_CAPACITY: + cap = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_CC_BATCAP_U); + // printk("CC_BATCAP = 0x%.4X\n", cap); + val->intval = cap * 100 / 0x1FFF; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + vot = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_VM_VBAT_U) * 1000; + val->intval = vot; + break; + */ + case POWER_SUPPLY_PROP_STATUS: + val->intval = pwr->rpt_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = pwr->bat_health; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (pwr->rpt_status == POWER_SUPPLY_STATUS_CHARGING) + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = pwr->bat_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = pwr->vcell; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = pwr->clamp_soc; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + { + u32 t; + + t = pwr->coulomb_cnt >> 16; + t = A10s_mAh(t); + if (t > A10s_mAh(pwr->designed_cap)) t = A10s_mAh(pwr->designed_cap); + val->intval = t * 1000; /* uA to report */ + } + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = pwr->bat_online; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = BD7181X_BATTERY_FULL * A10s_mAh(pwr->designed_cap) * 10; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = BD7181X_BATTERY_FULL * A10s_mAh(pwr->full_cap) * 10; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = pwr->curr_sar; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = pwr->curr; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = pwr->temp * 10; /* 0.1 degrees C unit */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = MAX_VOLTAGE; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + val->intval = MIN_VOLTAGE; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = MAX_CURRENT; + break; + default: + return -EINVAL; + } + + return 0; +} + +/** @brief ac properties */ +static enum power_supply_property bd7181x_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/** @brief bat properies */ +static enum power_supply_property bd7181x_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +/** @brief directly set raw value to chip register, format: 'register value' */ +static ssize_t bd7181x_sysfs_set_registers(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bd7181x_power *pwr = container_of(psy, struct bd7181x_power, bat); + ssize_t ret = 0; + unsigned int reg; + unsigned int val; + + ret = sscanf(buf, "%x %x", ®, &val); + if (ret < 1) { + pwr->reg_index = -1; + dev_err(pwr->dev, "registers set: \n"); + return count; + } + + if (ret == 1 && reg <= BD7181X_MAX_REGISTER) { + pwr->reg_index = reg; + dev_info(pwr->dev, "registers set: reg=0x%x\n", reg); + return count; + } + + if (reg > BD7181X_MAX_REGISTER || val > 255) + return -EINVAL; + + dev_info(pwr->dev, "registers set: reg=0x%x, val=0x%x\n", reg, val); + ret = bd7181x_reg_write(pwr->mfd, reg, val); + if (ret < 0) + return ret; + return count; +} + +/** @brief print value of chip register, format: 'register=value' */ +static ssize_t bd7181x_sysfs_print_reg(struct bd7181x_power *pwr, + u8 reg, + char *buf) +{ + int ret = bd7181x_reg_read(pwr->mfd, reg); + + if (ret < 0) + return sprintf(buf, "%#.2x=error %d\n", reg, ret); + return sprintf(buf, "[0x%.2X] = %.2X\n", reg, ret); +} + +/** @brief show all raw values of chip register, format per line: 'register=value' */ +static ssize_t bd7181x_sysfs_show_registers(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bd7181x_power *pwr = container_of(psy, struct bd7181x_power, bat); + ssize_t ret = 0; + int i; + + dev_info(pwr->dev, "register: index[%d]\n", pwr->reg_index); + if (pwr->reg_index >= 0) { + ret += bd7181x_sysfs_print_reg(pwr, pwr->reg_index, buf + ret); + } else { + for (i = 0; i <= BD7181X_MAX_REGISTER; i++) { + ret += bd7181x_sysfs_print_reg(pwr, i, buf + ret); + } + } + return ret; +} + +static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO, + bd7181x_sysfs_show_registers, bd7181x_sysfs_set_registers); + +static int first_offset(struct bd7181x_power *pwr) +{ + unsigned char ra2, ra3, ra6, ra7; + unsigned char ra2_temp; + struct bd7181x *mfd = pwr->mfd; + + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_01); + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_02); + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_03); + + + ra2 = bd7181x_reg_read(mfd, 0xA2); // I want to know initial A2 & A3. + ra3 = bd7181x_reg_read(mfd, 0xA3); // I want to know initial A2 & A3. + ra6 = bd7181x_reg_read(mfd, 0xA6); + ra7 = bd7181x_reg_read(mfd, 0xA7); + + bd7181x_reg_write(mfd, 0xA2, 0x00); + bd7181x_reg_write(mfd, 0xA3, 0x00); + + dev_info(pwr->dev, "TEST[A2] = 0x%.2X\n", ra2); + dev_info(pwr->dev, "TEST[A3] = 0x%.2X\n", ra3); + dev_info(pwr->dev, "TEST[A6] = 0x%.2X\n", ra6); + dev_info(pwr->dev, "TEST[A7] = 0x%.2X\n", ra7); + + //-------------- First Step ------------------- + dev_info(pwr->dev, "Frist Step begginning \n"); + + // delay some time , Make a state of IBAT=0mA + // mdelay(1000 * 10); + + ra2_temp = ra2; + + if (ra7 != 0) { + //if 0<0xA7<20 decrease the Test register 0xA2[7:3] until 0xA7 becomes 0x00. + if ((ra7 > 0) && (ra7 < 20)) { + do { + ra2 = bd7181x_reg_read(mfd, 0xA2); + ra2_temp = ra2 >> 3; + ra2_temp -= 1; + ra2_temp <<= 3; + bd7181x_reg_write(mfd, 0xA2, ra2_temp); + dev_info(pwr->dev, "TEST[A2] = 0x%.2X\n", ra2_temp); + + ra7 = bd7181x_reg_read(mfd, 0xA7); + dev_info(pwr->dev, "TEST[A7] = 0x%.2X\n", ra7); + mdelay(1000); // 1sec? + } while (ra7); + + dev_info(pwr->dev, "A7 becomes 0 . \n"); + + } // end if((ra7 > 0)&&(ra7 < 20)) + else if ((ra7 > 0xDF) && (ra7 < 0xFF)) + //if DF<0xA7> 3; + ra2_temp += 1; + ra2_temp <<= 3; + + bd7181x_reg_write(mfd, 0xA2, ra2_temp); + dev_info(pwr->dev, "TEST[A2] = 0x%.2X\n", ra2_temp); + + ra7 = bd7181x_reg_read(mfd, 0xA7); + dev_info(pwr->dev, "TEST[A7] = 0x%.2X\n", ra7); + mdelay(1000); // 1sec? + } while (ra7); + + dev_info(pwr->dev, "A7 becomes 0 . \n"); + } + } + + // please use "ra2_temp" at step2. + return ra2_temp; +} + +static int second_step(struct bd7181x_power *pwr, u8 ra2_temp) +{ + u16 ra6, ra7; + u8 aft_ra2, aft_ra3; + u8 r79, r7a; + unsigned int LNRDSA_FUSE; + long ADC_SIGN; + long DSADGAIN1_INI; + struct bd7181x *mfd = pwr->mfd; + + //-------------- Second Step ------------------- + dev_info(pwr->dev, "Second Step begginning \n"); + + // need to change boad setting ( input 1A tio 10mohm) + // delay some time , Make a state of IBAT=1000mA + // mdelay(1000 * 10); + +// rough adjust + dev_info(pwr->dev, "ra2_temp = 0x%.2X\n", ra2_temp); + + ra6 = bd7181x_reg_read(mfd, 0xA6); + ra7 = bd7181x_reg_read(mfd, 0xA7); + ra6 <<= 8; + ra6 |= ra7; // [0xA6 0xA7] + dev_info(pwr->dev, "TEST[A6,A7] = 0x%.4X\n", ra6); + + bd7181x_reg_write(mfd, 0xA2, ra2_temp); // this value from step1 + bd7181x_reg_write(mfd, 0xA3, 0x00); + + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_00); + + r79 = bd7181x_reg_read(mfd, 0x79); + r7a = bd7181x_reg_read(mfd, 0x7A); + + ADC_SIGN = r79 >> 7; + ADC_SIGN = 1 - (2 * ADC_SIGN); + DSADGAIN1_INI = r79 << 8; + DSADGAIN1_INI = DSADGAIN1_INI + r7a; + DSADGAIN1_INI = DSADGAIN1_INI & 0x7FFF; + DSADGAIN1_INI = DSADGAIN1_INI * ADC_SIGN; // unit 0.001 + + // unit 0.000001 + DSADGAIN1_INI *= 1000; + { + if (DSADGAIN1_INI > 1000001) { + DSADGAIN1_INI = 2048000000UL - (DSADGAIN1_INI - 1000000) * 8187; + } else if (DSADGAIN1_INI < 999999) { + DSADGAIN1_INI = -(DSADGAIN1_INI - 1000000) * 8187; + } else { + DSADGAIN1_INI = 0; + } + } + + LNRDSA_FUSE = (int) DSADGAIN1_INI / 1000000; + + dev_info(pwr->dev, "LNRDSA_FUSE = 0x%.8X\n", LNRDSA_FUSE); + + aft_ra2 = (LNRDSA_FUSE >> 8) & 255; + aft_ra3 = (LNRDSA_FUSE) & 255; + + aft_ra2 = aft_ra2 + ra2_temp; + + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_01); + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_02); + bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_03); + + bd7181x_reg_write(mfd, 0xA2, aft_ra2); + bd7181x_reg_write(mfd, 0xA3, aft_ra3); + + return 0; +} + +static int third_step(struct bd7181x_power *pwr, unsigned thr) { + u16 ra2_a3, ra6, ra7; + u8 ra2, ra3; + u8 aft_ra2, aft_ra3; + struct bd7181x *mfd = pwr->mfd; + +// fine adjust + ra2 = bd7181x_reg_read(mfd, 0xA2); // + ra3 = bd7181x_reg_read(mfd, 0xA3); // + + ra6 = bd7181x_reg_read(mfd, 0xA6); + ra7 = bd7181x_reg_read(mfd, 0xA7); + ra6 <<= 8; + ra6 |= ra7; // [0xA6 0xA7] + dev_info(pwr->dev, "TEST[A6,A7] = 0x%.4X\n", ra6); + + + if (ra6 > thr) { + do { + ra2_a3 = bd7181x_reg_read(mfd, 0xA2); + ra2_a3 <<= 8; + ra3 = bd7181x_reg_read(mfd, 0xA3); + ra2_a3 |= ra3; + //ra2_a3 >>= 3; // ? 0xA3[7:3] , or 0xA3[7:0] + + ra2_a3 -= 1; + //ra2_a3 <<= 3; + ra3 = ra2_a3; + bd7181x_reg_write(mfd, 0xA3, ra3); + + ra2_a3 >>= 8; + ra2 = ra2_a3; + bd7181x_reg_write(mfd, 0xA2, ra2); + + dev_info(pwr->dev, "TEST[A2] = 0x%.2X , TEST[A3] = 0x%.2X \n", ra2, ra3); + + mdelay(1000); // 1sec? + + ra6 = bd7181x_reg_read(mfd, 0xA6); + ra7 = bd7181x_reg_read(mfd, 0xA7); + ra6 <<= 8; + ra6 |= ra7; // [0xA6 0xA7] + dev_info(pwr->dev, "TEST[A6,A7] = 0x%.4X\n", ra6); + } while (ra6 > thr); + } else if (ra6 < thr) { + do { + ra2_a3 = bd7181x_reg_read(mfd, 0xA2); + ra2_a3 <<= 8; + ra3 = bd7181x_reg_read(mfd, 0xA3); + ra2_a3 |= ra3; + //ra2_a3 >>= 3; // ? 0xA3[7:3] , or 0xA3[7:0] + + ra2_a3 += 1; + //ra2_a3 <<= 3; + ra3 = ra2_a3; + bd7181x_reg_write(mfd, 0xA3, ra3); + + ra2_a3 >>= 8; + ra2 = ra2_a3; + bd7181x_reg_write(mfd, 0xA2, ra2); + + dev_info(pwr->dev, "TEST[A2] = 0x%.2X , TEST[A3] = 0x%.2X \n", ra2, ra3); + + mdelay(1000); // 1sec? + + ra6 = bd7181x_reg_read(mfd, 0xA6); + ra7 = bd7181x_reg_read(mfd, 0xA7); + ra6 <<= 8; + ra6 |= ra7; // [0xA6 0xA7] + dev_info(pwr->dev, "TEST[A6,A7] = 0x%.4X\n", ra6); + + } while (ra6 < thr); + } + + dev_info(pwr->dev, "[0xA6 0xA7] becomes [0x%.4X] . \n", thr); + dev_info(pwr->dev, " Calibation finished ... \n\n"); + + aft_ra2 = bd7181x_reg_read(mfd, 0xA2); // + aft_ra3 = bd7181x_reg_read(mfd, 0xA3); // I want to know initial A2 & A3. + + dev_info(pwr->dev, "TEST[A2,A3] = 0x%.2X%.2X\n", aft_ra2, aft_ra3); + + // bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_00); + + return 0; +} + +static ssize_t bd7181x_sysfs_set_calibrate(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bd7181x_power *pwr = container_of(psy, struct bd7181x_power, bat); + ssize_t ret = 0; + unsigned int val, mA; + static u8 rA2; + + ret = sscanf(buf, "%d %d", &val, &mA); + if (ret < 1) { + dev_err(pwr->dev, "error: write a integer string"); + return count; + } + + if (val == 1) { + pwr->calib_current = CALIB_START; + while (pwr->calib_current != CALIB_GO) { + msleep(500); + } + rA2 = first_offset(pwr); + } + if (val == 2) { + second_step(pwr, rA2); + } + if (val == 3) { + if (ret <= 1) { + dev_err(pwr->dev, "error: Fine adjust need a mA argument!"); + } else { + unsigned int ra6_thr; + + ra6_thr = mA * 0xFFFF / 20000; + dev_info(pwr->dev, "Fine adjust at %d mA, ra6 threshold %d(0x%X)\n", mA, ra6_thr, ra6_thr); + third_step(pwr, ra6_thr); + } + } + if (val == 4) { + bd7181x_reg_write(pwr->mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_00); + pwr->calib_current = CALIB_NORM; + schedule_delayed_work(&pwr->bd_work, msecs_to_jiffies(0)); + } + + return count; +} + +static ssize_t bd7181x_sysfs_show_calibrate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + // struct power_supply *psy = dev_get_drvdata(dev); + // struct bd7181x_power *pwr = container_of(psy, struct bd7181x_power, bat); + ssize_t ret = 0; + + ret = 0; + ret += sprintf(buf + ret, "write string value\n" + "\t1 0 mA for step one\n" + "\t2 1000 mA for rough adjust\n" + "\t3 for fine adjust\n" + "\t4 exit current calibration\n"); + return ret; +} + +static DEVICE_ATTR(calibrate, S_IWUSR | S_IRUGO, + bd7181x_sysfs_show_calibrate, bd7181x_sysfs_set_calibrate); + +static struct attribute *bd7181x_sysfs_attributes[] = { + /* + * TODO: some (appropriate) of these attrs should be switched to + * use pwr supply class props. + */ + &dev_attr_registers.attr, + &dev_attr_calibrate.attr, + NULL, +}; + +static const struct attribute_group bd7181x_sysfs_attr_group = { + .attrs = bd7181x_sysfs_attributes, +}; + +/** @brief powers supplied by bd7181x_ac */ +static char *bd7181x_ac_supplied_to[] = { + BAT_NAME, +}; + +/** @brief probe pwr device + * @param pdev platform deivce of bd7181x_power + * @retval 0 success + * @retval negative fail + */ +static int __init bd7181x_power_probe(struct platform_device *pdev) +{ + struct bd7181x *bd7181x = dev_get_drvdata(pdev->dev.parent); + struct bd7181x_power *pwr; + int irq, ret, reg; + + pwr = kzalloc(sizeof(*pwr), GFP_KERNEL); + if (pwr == NULL) + return -ENOMEM; + + pwr->dev = &pdev->dev; + pwr->mfd = bd7181x; + + platform_set_drvdata(pdev, pwr); + + if (battery_cycle <= 0) { + battery_cycle = 0; + } + dev_err(pwr->dev, "battery_cycle = %d\n", battery_cycle); + + /* If the product often power up/down and the power down time is long, the Coulomb Counter may have a drift. */ + /* If so, it may be better accuracy to enable Coulomb Counter using following commented out code */ + /* for counting Coulomb when the product is power up(including sleep). */ + /* The condition */ + /* (1) Product often power up and down, the power down time is long and there is no power consumed in power down time. */ + /* (2) Kernel must call this routin at power up time. */ + /* (3) Kernel must call this routin at charging time. */ + /* (4) Must use this code with "Stop Coulomb Counter" code in bd7181x_power_remove() function */ + /* Start Coulomb Counter */ + /* bd7181x_set_bits(pwr->mfd, BD7181x_REG_CC_CTRL, CCNTENB); */ + + bd7181x_init_hardware(pwr); + + pwr->bat.name = BAT_NAME; + pwr->bat.type = POWER_SUPPLY_TYPE_BATTERY; + pwr->bat.properties = bd7181x_battery_props; + pwr->bat.num_properties = ARRAY_SIZE(bd7181x_battery_props); + pwr->bat.get_property = bd7181x_battery_get_property; + + ret = power_supply_register(&pdev->dev, &pwr->bat); + if (ret) { + dev_err(&pdev->dev, "failed to register usb: %d\n", ret); + goto fail_register_bat; + } + + pwr->ac.name = AC_NAME; + pwr->ac.type = POWER_SUPPLY_TYPE_MAINS; + pwr->ac.properties = bd7181x_charger_props; + pwr->ac.supplied_to = bd7181x_ac_supplied_to; + pwr->ac.num_supplicants = ARRAY_SIZE(bd7181x_ac_supplied_to); + pwr->ac.num_properties = ARRAY_SIZE(bd7181x_charger_props); + pwr->ac.get_property = bd7181x_charger_get_property; + + ret = power_supply_register(&pdev->dev, &pwr->ac); + if (ret) { + dev_err(&pdev->dev, "failed to register ac: %d\n", ret); + goto fail_register_ac; + } + + /*Add DC_IN Inserted and Remove ISR */ + irq = platform_get_irq(pdev, 0); // get irq number +#ifdef __BD7181X_REGMAP_H__ + irq += bd7181x->irq_base; +#endif + if (irq <= 0) { + dev_warn(&pdev->dev, "platform irq error # %d\n", irq); + return -ENXIO; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + bd7181x_power_interrupt, IRQF_TRIGGER_LOW | IRQF_EARLY_RESUME, + dev_name(&pdev->dev), &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "IRQ %d is not free.\n", irq); + } + + /*add VBAT Low Voltage detection, John Zhang*/ + irq = platform_get_irq(pdev, 1); +#ifdef __BD7181X_REGMAP_H__ + irq += bd7181x->irq_base; +#endif + if (irq <= 0) { + dev_warn(&pdev->dev, "platform irq error # %d\n", irq); + return -ENXIO; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + bd7181x_vbat_interrupt, IRQF_TRIGGER_LOW | IRQF_EARLY_RESUME, + dev_name(&pdev->dev), &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "IRQ %d is not free.\n", irq); + } + + /* add INT_STAT_11 */ + irq = platform_get_irq(pdev, 2); +#ifdef __BD7181X_REGMAP_H__ + irq += bd7181x->irq_base; +#endif + if (irq <= 0) { + dev_warn(&pdev->dev, "platform irq error # %d\n", irq); + return -ENXIO; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + bd7181x_int_11_interrupt, IRQF_TRIGGER_LOW | IRQF_EARLY_RESUME, + dev_name(&pdev->dev), &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "IRQ %d is not free.\n", irq); + } + + /* Enable INT_11 */ + ret = bd7181x_reg_write(bd7181x, BD7181X_REG_INT_EN_11, 0xFF); + if (ret < 0) { + dev_warn(&pdev->dev, "Write BD7181X_REG_INT_EN_11 failed\n"); + } + reg = bd7181x_reg_read(bd7181x, BD7181X_REG_INT_EN_11); + if (reg < 0) { + dev_warn(&pdev->dev, "Read BD7181X_REG_INT_EN_11 failed\n"); + } + dev_info(&pdev->dev, "BD7181X_REG_INT_EN_11=0x%x\n", reg); + + + ret = sysfs_create_group(&pwr->bat.dev->kobj, &bd7181x_sysfs_attr_group); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register sysfs interface\n"); + } + + pwr->reg_index = -1; + + INIT_DELAYED_WORK(&pwr->bd_work, bd_work_callback); + + /* Schedule timer to check current status */ + pwr->calib_current = CALIB_NORM; + schedule_delayed_work(&pwr->bd_work, msecs_to_jiffies(0)); + + return 0; + +//error_exit: + power_supply_unregister(&pwr->ac); +fail_register_ac: + power_supply_unregister(&pwr->bat); +fail_register_bat: + platform_set_drvdata(pdev, NULL); + kfree(pwr); + + return ret; +} + +/** @brief remove pwr device + * @param pdev platform deivce of bd7181x_power + * @return 0 + */ + +static int __exit bd7181x_power_remove(struct platform_device *pdev) +{ + struct bd7181x_power *pwr = platform_get_drvdata(pdev); + + /* If the product often power up/down and the power down time is long, the Coulomb Counter may have a drift. */ + /* If so, it may be better accuracy to disable Coulomb Counter using following commented out code */ + /* for stopping counting Coulomb when the product is power down(without sleep). */ + /* The condition */ + /* (1) Product often power up and down, the power down time is long and there is no power consumed in power down time. */ + /* (2) Kernel must call this routin at power down time. */ + /* (3) Must use this code with "Start Coulomb Counter" code in bd7181x_power_probe() function */ + /* Stop Coulomb Counter */ + /* bd7181x_clear_bits(pwr->mfd, BD7181x_REG_CC_CTRL, CCNTENB); */ + + sysfs_remove_group(&pwr->bat.dev->kobj, &bd7181x_sysfs_attr_group); + + cancel_delayed_work(&pwr->bd_work); + + power_supply_unregister(&pwr->bat); + power_supply_unregister(&pwr->ac); + platform_set_drvdata(pdev, NULL); + kfree(pwr); + + return 0; +} + +static struct platform_driver bd7181x_power_driver = { + .driver = { + .name = "bd7181x-power", + .owner = THIS_MODULE, + }, + .remove = __exit_p(bd7181x_power_remove), +}; + +/** @brief module initialize function */ +static int __init bd7181x_power_init(void) +{ + return platform_driver_probe(&bd7181x_power_driver, bd7181x_power_probe); +} + +module_init(bd7181x_power_init); + +/** @brief module deinitialize function */ +static void __exit bd7181x_power_exit(void) +{ + platform_driver_unregister(&bd7181x_power_driver); +} + +module_exit(bd7181x_power_exit); + +module_param(battery_cycle, uint, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(battery_parameters, "battery_cycle:battery charge/discharge cycles"); + +MODULE_AUTHOR("Tony Luo "); +MODULE_AUTHOR("Peter Yang "); +MODULE_DESCRIPTION("BD71815/BD71817 Battery Charger Power driver"); +MODULE_LICENSE("GPL"); + + +/*-------------------------------------------------------*/ +#include +#include +#include +#include + +#define PROCFS_NAME "bd7181x_rev" +#define BD7181X_REV "BD7181x Driver: Rev008\n" + +#define BD7181X_BUF_SIZE 1024 +static char procfs_buffer[BD7181X_BUF_SIZE]; +/** + * This function is called then the /proc file is read + * + */ +static int onetime = 0; +static ssize_t bd7181x_proc_read (struct file *file, char __user *buffer, size_t count, loff_t *data) +{ + int ret = 0, error = 0; + if(onetime==0) { + onetime = 1; + memset( procfs_buffer, 0, BD7181X_BUF_SIZE); + sprintf(procfs_buffer, "%s", BD7181X_REV); + ret = strlen(procfs_buffer); + error = copy_to_user(buffer, procfs_buffer, strlen(procfs_buffer)); + } else { + //Clear for next time + onetime = 0; + } + return (error!=0)?0:ret; +} + +#if 0 +int bd7181x_debug_mask = 0; +static ssize_t bd7181x_proc_write (struct file *file, const char __user *buffer, size_t count, loff_t *data) +{ + sscanf(buffer, "0x%x", &bd7181x_debug_mask); + printk("BD7181x: bd7181x_debug_mask=0x%08x\n", bd7181x_debug_mask); + return count; +} +#endif + +static const struct file_operations bd7181x_proc_fops = { + .owner = THIS_MODULE, + .read = bd7181x_proc_read, + //.write = bd7181x_proc_write, +}; + +/** + *This function is called when the module is loaded + * + */ +int bd7181x_revision_init(void) +{ + struct proc_dir_entry *bd7181x_proc_entry; + + /* create the /proc/bd7181x_rev */ + bd7181x_proc_entry = proc_create(PROCFS_NAME, 0644, NULL, &bd7181x_proc_fops); + if (bd7181x_proc_entry == NULL) { + printk("Error: Could not initialize /proc/%s\n", PROCFS_NAME); + return -ENOMEM; + } + + return 0; +} +module_init(bd7181x_revision_init); +/*-------------------------------------------------------*/ diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 5361947ea726..c6cc25406834 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -171,6 +171,12 @@ config REGULATOR_BCM590XX BCM590xx PMUs. This will enable support for the software controllable LDO/Switching regulators. +config REGULATOR_BD7181X + tristate "RoHM BD71815/BD71817 Power Regulator" + depends on MFD_BD7181X + help + This driver supports BD71815/BD71817 voltage regulator chips. + config REGULATOR_BD9571MWV tristate "ROHM BD9571MWV Regulators" depends on MFD_BD9571MWV diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 1bddbefbc8e7..8d4f2e881a97 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_REGULATOR_AS3711) += as3711-regulator.o obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o obj-$(CONFIG_REGULATOR_AXP20X) += axp20x-regulator.o obj-$(CONFIG_REGULATOR_BCM590XX) += bcm590xx-regulator.o +obj-$(CONFIG_REGULATOR_BD7181X) += bd7181x-regulator.o obj-$(CONFIG_REGULATOR_BD9571MWV) += bd9571mwv-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o diff --git a/drivers/regulator/bd7181x-regulator.c b/drivers/regulator/bd7181x-regulator.c new file mode 100644 index 000000000000..49756aa9774f --- /dev/null +++ b/drivers/regulator/bd7181x-regulator.c @@ -0,0 +1,791 @@ +/* + * @file bd7181x-regulator.c RoHM BD71815/BD71817 regulator driver + * + * Copyright 2014 Embest Technology Co. Ltd. Inc. + * + * @author Tony Luo + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BD7181X_VOL_OFFSET 0 +#define BD7181X_STANDBY_OFFSET 0 +#define BD7181X_DVS_BUCK_NUM 2 +#define BD7181X_DVS_HIGH_LOW 2 + +struct bd7181x_buck_dvs { + int i2c_dvs_enable; + u32 voltage[BD7181X_DVS_HIGH_LOW]; +}; + +struct bd7181x_regulator { + struct regulator_desc desc; + unsigned char stby_reg; + unsigned char stby_mask; +}; + +/** @brief bd7181x regulator type */ +struct bd7181x_pmic { + struct bd7181x_regulator descs[BD7181X_REGULATOR_CNT]; /**< regulator description to system */ + struct bd7181x *mfd; /**< parent device */ + struct device *dev; /**< regulator kernel device */ + struct regulator_dev *rdev[BD7181X_REGULATOR_CNT]; /**< regulator device of system */ + struct bd7181x_buck_dvs buck_dvs[BD7181X_DVS_BUCK_NUM]; /**< buck1/2 dvs */ +}; + +static const int bd7181x_wled_currents[] = { + // 0x00 + 10, 20, 30, 50, + 70, 100, 200, 300, + 500, 700, 1000, 2000, + 3000, 4000, 5000, 6000, + // 0x10 + 7000, 8000, 9000, 10000, + 11000, 12000, 13000, 14000, + 15000, 16000, 17000, 18000, + 19000, 20000, 21000, 22000, + // 0x20 + 23000, 24000, 25000, +}; + +/* + * BUCK1/2 + * BUCK1RAMPRATE[1:0] BUCK1 DVS ramp rate setting + * 00: 10.00mV/usec 10mV 1uS + * 01: 5.00mV/usec 10mV 2uS + * 10: 2.50mV/usec 10mV 4uS + * 11: 1.25mV/usec 10mV 8uS + */ +static int bd7181x_buck12_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) +{ + struct bd7181x_pmic *pmic = rdev_get_drvdata(rdev); + struct bd7181x *mfd = pmic->mfd; + int id = rdev->desc->id; + unsigned int ramp_value = BUCK1_RAMPRATE_10P00MV; + + switch (ramp_delay) { + case 1 ... 1250: + ramp_value = BUCK1_RAMPRATE_1P25MV; + break; + case 1251 ... 2500: + ramp_value = BUCK1_RAMPRATE_2P50MV; + break; + case 2501 ... 5000: + ramp_value = BUCK1_RAMPRATE_5P00MV; + break; + case 5001 ... 10000: + ramp_value = BUCK1_RAMPRATE_10P00MV; + break; + default: + ramp_value = BUCK1_RAMPRATE_10P00MV; + dev_err(pmic->dev, "%s: ramp_delay: %d not supported, setting 10000mV//us\n", + rdev->desc->name, ramp_delay); + } + + return regmap_update_bits(mfd->regmap, BD7181X_REG_BUCK1_MODE + id*0x1, + BUCK1_RAMPRATE_MASK, ramp_value << 6); +} + +static int bd7181x_led_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct bd7181x_pmic* pmic = rdev_get_drvdata(rdev); + struct bd7181x* mfd = pmic->mfd; + u8 addr; + // int id = rdev_get_id(rdev); + int i; + + addr = BD7181X_REG_LED_DIMM; + + for (i = ARRAY_SIZE(bd7181x_wled_currents) - 1 ; i >= 0; i--) { + if (bd7181x_wled_currents[i] >= min_uA && + bd7181x_wled_currents[i] <= max_uA) + return bd7181x_update_bits(mfd, addr, 0x3F, i); + } + + return -EINVAL; +} + +static int bd7181x_led_get_current_limit(struct regulator_dev *rdev) +{ + struct bd7181x_pmic* pmic = rdev_get_drvdata(rdev); + struct bd7181x* mfd = pmic->mfd; + // int id = rdev_get_id(rdev); + u8 addr; + int r; + + addr = BD7181X_REG_LED_DIMM; + + r = bd7181x_reg_read(mfd, addr); + if (r < 0) { + return r; + } + + r = r & 0x3F; + + return (r < ARRAY_SIZE(bd7181x_wled_currents)) ? + bd7181x_wled_currents[r] : -EINVAL; +} + +static int bd7181x_buck12_get_voltage_sel(struct regulator_dev *rdev) +{ + struct bd7181x_pmic *pmic = rdev_get_drvdata(rdev); + int rid = rdev_get_id(rdev); + struct bd7181x *bd7181x = pmic->mfd; + int ret, val; + u8 regh = BD7181X_REG_BUCK1_VOLT_H + rid*0x2, + regl = BD7181X_REG_BUCK1_VOLT_L + rid*0x2; + + ret = bd7181x_reg_read(bd7181x, regh); + if (ret < 0) { + return ret; + } + val = ret; + if((!(val & BUCK1_STBY_DVS)) && (!(val & BUCK1_DVSSEL))) { + ret = bd7181x_reg_read(bd7181x, regl); + if (ret < 0) { + return ret; + } + val = ret & BUCK1_L_MASK; + } else { + val &= BUCK1_H_MASK; + } + return val; +} + +/* + * For Buck 1/2. + * + */ +static int bd7181x_buck12_set_voltage_sel(struct regulator_dev *rdev, unsigned sel) +{ + struct bd7181x_pmic *pmic = rdev_get_drvdata(rdev); + int rid = rdev_get_id(rdev); + struct bd7181x *bd7181x = pmic->mfd; + int ret, val; + u8 regh = BD7181X_REG_BUCK1_VOLT_H + rid*0x2, + regl = BD7181X_REG_BUCK1_VOLT_L + rid*0x2; + + ret = bd7181x_reg_read(bd7181x, regh); + if (ret < 0) { + return ret; + } + val = ret; + if((!(val & BUCK1_STBY_DVS)) && (!(val & BUCK1_DVSSEL))) { + ret = bd7181x_reg_write(bd7181x, regl, sel & BUCK1_L_MASK); + } else { + val = (val & 0xC0) | (sel & BUCK1_H_MASK); + ret = bd7181x_reg_write(bd7181x, regh, val); + } + + return ret; +} + +static struct regulator_ops bd7181x_ldo_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +static struct regulator_ops bd7181x_fixed_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +static struct regulator_ops bd7181x_buck_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +static struct regulator_ops bd7181x_buck12_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = bd7181x_buck12_set_voltage_sel, + .get_voltage_sel = bd7181x_buck12_get_voltage_sel, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = bd7181x_buck12_set_ramp_delay, +}; + +static struct regulator_ops bd7181x_led_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_current_limit = bd7181x_led_set_current_limit, + .get_current_limit = bd7181x_led_get_current_limit, +}; + +#define BD7181X_FIXED_REG(_name, ereg, emsk, voltage) \ + [BD7181X_ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = 1, \ + .ops = &bd7181x_fixed_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = BD7181X_ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (voltage), \ + .enable_reg = (ereg), \ + .enable_mask = (emsk), \ + }, \ + } + +#define BD7181X_BUCK_REG(_name, base, ereg, min, max, step) \ + [BD7181X_ ## _name] = { \ + .desc = { \ + .name = #_name,\ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &bd7181x_buck_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = BD7181X_ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base) + BD7181X_VOL_OFFSET, \ + .vsel_mask = 0x3f, \ + .enable_reg = (ereg), \ + .enable_mask = 0x04, \ + }, \ + .stby_reg = (base) + BD7181X_STANDBY_OFFSET, \ + .stby_mask = 0x3f, \ + } + +#define BD7181X_BUCK12_REG(_name, base, ereg, min, max, step) \ + [BD7181X_ ## _name] = { \ + .desc = { \ + .name = #_name,\ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &bd7181x_buck12_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = BD7181X_ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base) + BD7181X_VOL_OFFSET, \ + .vsel_mask = 0x3f, \ + .enable_reg = (ereg), \ + .enable_mask = 0x04, \ + }, \ + .stby_reg = (base) + BD7181X_STANDBY_OFFSET, \ + .stby_mask = 0x3f, \ + } + +#define BD7181X_LED_REG(_name, base, mask, ereg, emsk, voltages) \ + [BD7181X_ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(voltages), \ + .ops = &bd7181x_led_regulator_ops, \ + .type = REGULATOR_CURRENT, \ + .id = BD7181X_ ## _name, \ + .owner = THIS_MODULE, \ + .volt_table = voltages, \ + .vsel_reg = (base), \ + .vsel_mask = (mask), \ + .enable_reg = (ereg), \ + .enable_mask = (emsk), \ + }, \ + } + +#define BD7181X_LDO_REG(_name, base, ereg, emsk, min, max, step) \ + [BD7181X_ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &bd7181x_ldo_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = BD7181X_ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base), \ + .vsel_mask = 0x3f, \ + .enable_reg = (ereg), \ + .enable_mask = (emsk), \ + }, \ + .stby_reg = (base), \ + .stby_mask = 0x20, \ + } + +static struct bd7181x_regulator bd7181x_regulators[] = { + BD7181X_BUCK12_REG(BUCK1, BD7181X_REG_BUCK1_VOLT_H, BD7181X_REG_BUCK1_MODE, 800000, 2000000, 25000), + BD7181X_BUCK12_REG(BUCK2, BD7181X_REG_BUCK2_VOLT_H, BD7181X_REG_BUCK2_MODE, 800000, 2000000, 25000), + BD7181X_BUCK_REG(BUCK3, BD7181X_REG_BUCK3_VOLT, BD7181X_REG_BUCK3_MODE, 1200000, 2700000, 50000), + BD7181X_BUCK_REG(BUCK4, BD7181X_REG_BUCK4_VOLT, BD7181X_REG_BUCK4_MODE, 1100000, 1850000, 25000), + BD7181X_BUCK_REG(BUCK5, BD7181X_REG_BUCK5_VOLT, BD7181X_REG_BUCK5_MODE, 1800000, 3300000, 50000), + BD7181X_LDO_REG(LDO1, BD7181X_REG_LDO1_VOLT, BD7181X_REG_LDO_MODE1, 0x40, 800000, 3300000, 50000), + BD7181X_LDO_REG(LDO2, BD7181X_REG_LDO2_VOLT, BD7181X_REG_LDO_MODE2, 0x04, 800000, 3300000, 50000), + BD7181X_LDO_REG(LDO3, BD7181X_REG_LDO3_VOLT, BD7181X_REG_LDO_MODE2, 0x40, 800000, 3300000, 50000), + BD7181X_LDO_REG(LDO4, BD7181X_REG_LDO4_VOLT, BD7181X_REG_LDO_MODE3, 0x04, 800000, 3300000, 50000), + BD7181X_LDO_REG(LDO5, BD7181X_REG_LDO5_VOLT_H,BD7181X_REG_LDO_MODE3,0x40, 800000, 3300000, 50000), + BD7181X_FIXED_REG(LDODVREF, BD7181X_REG_LDO_MODE4, 0x40, 3000000), + BD7181X_FIXED_REG(LDOLPSR, BD7181X_REG_LDO_MODE4, 0x04, 1800000), + BD7181X_LED_REG(WLED, BD7181X_REG_LED_DIMM, 0x3F, BD7181X_REG_LED_CTRL, 0x04, bd7181x_wled_currents), +}; + +#ifdef CONFIG_OF + +static struct of_regulator_match bd7181x_matches[] = { + { .name = "buck1", }, + { .name = "buck2", }, + { .name = "buck3", }, + { .name = "buck4", }, + { .name = "buck5", }, + { .name = "ldo1", }, + { .name = "ldo2", }, + { .name = "ldo3", }, + { .name = "ldo4", }, + { .name = "ldo5", }, + { .name = "dvref", }, + { .name = "lpsr", }, + { .name = "wled", }, +}; + +/**@brief parse bd7181x regulator device tree + * @param pdev platform device of bd7181x regulator + * @param bd7181x_reg_matches return regualtor matches + * @retval 0 parse success + * @retval NULL parse fail + */ +static int bd7181x_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **reg_matches) +{ + // struct bd7181x *bd7181x = dev_get_drvdata(pdev->dev.parent); + struct device_node *np, *regulators; + struct of_regulator_match *matches; + int ret, count; + + np = of_node_get(pdev->dev.parent->of_node); + regulators = of_find_node_by_name(np, "regulators"); + if (!regulators) { + dev_err(&pdev->dev, "regulator node not found\n"); + return -EINVAL; + } + + count = ARRAY_SIZE(bd7181x_matches); + matches = bd7181x_matches; + + ret = of_regulator_match(&pdev->dev, regulators, matches, count); + of_node_put(regulators); + if (ret < 0) { + dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", + ret); + return ret; + } + + *reg_matches = matches; + + return 0; +} +#else +static inline int bd7181x_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **reg_matches) +{ + *reg_matches = NULL; + return 0; +} +#endif + +/** @brief out32k mode constants */ +static const char* out32k_modes[] = {"open_drain", "cmos"}; + +/** @brief retrive out32k output mode */ +static ssize_t show_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct bd7181x_pmic *pmic = dev_get_drvdata(dev); + int o; + + o = bd7181x_reg_read(pmic->mfd, BD7181X_REG_OUT32K); + o = (o & OUT32K_MODE) != 0; + + return sprintf(buf, "%s\n", out32k_modes[o]); +} + +/** @brief set out32k output mode */ +static ssize_t set_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bd7181x_pmic *pmic = dev_get_drvdata(dev); + int o, r; + + if (strncmp(buf, out32k_modes[0], strlen(out32k_modes[0])) == 0) { + o = 0; + } else { + o = OUT32K_MODE; + } + + r = bd7181x_update_bits(pmic->mfd, BD7181X_REG_OUT32K, OUT32K_MODE, o); + if (r < 0) { + return r; + } + return count; +} + +/** @brief retrive out32k output value */ +static ssize_t show_value(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct bd7181x_pmic *pmic = dev_get_drvdata(dev); + int o; + + o = bd7181x_reg_read(pmic->mfd, BD7181X_REG_OUT32K); + o = (o & OUT32K_EN) != 0; + + return sprintf(buf, "%d\n", o); +} + +/** @brief set o output value */ +static ssize_t set_value(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bd7181x_pmic *pmic = dev_get_drvdata(dev); + int o, r; + + if (sscanf(buf, "%d", &o) < 1) { + return -EINVAL; + } + + if (o != 0) { + o = OUT32K_EN; + } + r = bd7181x_update_bits(pmic->mfd, BD7181X_REG_OUT32K, OUT32K_EN, o); + if (r < 0) { + return r; + } + return count; +} + +/** @brief list all supported modes */ +static ssize_t available_modes(struct device *dev, struct device_attribute *attr, char *buf) +{ + int i, r; + + r = 0; + for (i = 0; i < ARRAY_SIZE(out32k_modes) && r >= 0; i++) { + r += sprintf(buf + r, "%s ", out32k_modes[i]); + } + r += sprintf(buf + r, "\n"); + + return r; +} + +/** @brief list all supported values */ +static ssize_t available_values(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0 1 \n"); +} + + +/** @brief retrive dvssel output value */ +static ssize_t show_dvssel(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct bd7181x_pmic *pmic = dev_get_drvdata(dev); + int value, index = 0, i; + + for (i = 0; i < BD7181X_DVS_BUCK_NUM; i++) { + value = bd7181x_reg_read(pmic->mfd, BD7181X_REG_BUCK1_VOLT_H + i*0x2); + if(value < 0) + return value; + value = (value & BUCK1_DVSSEL) != 0; + index += sprintf(buf+index, "BUCK%i: %d\n", i, value); + } + return index; +} + +/** @brief set o output value */ +static ssize_t set_dvssel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bd7181x_pmic *pmic = dev_get_drvdata(dev); + int devsel1, devsel2, ret; + + if (sscanf(buf, "%d %d", &devsel1, &devsel2) < 1) { + return -EINVAL; + } + + ret = bd7181x_update_bits(pmic->mfd, BD7181X_REG_BUCK1_VOLT_H, BUCK1_DVSSEL, devsel1<<7); + if(ret < 0) + return ret; + ret = bd7181x_update_bits(pmic->mfd, BD7181X_REG_BUCK2_VOLT_H, BUCK2_DVSSEL, devsel2<<7); + if(ret < 0) + return ret; + return count; +} + +static DEVICE_ATTR(out32k_mode, S_IWUSR | S_IRUGO, show_mode, set_mode); +static DEVICE_ATTR(out32k_value, S_IWUSR | S_IRUGO, show_value, set_value); +static DEVICE_ATTR(available_mode, S_IWUSR | S_IRUGO, available_modes, NULL); +static DEVICE_ATTR(available_value, S_IWUSR | S_IRUGO, available_values, NULL); +static DEVICE_ATTR(dvssel, S_IWUSR | S_IRUGO, show_dvssel, set_dvssel); + +/** @brief device sysfs attribute table, about o */ +static struct attribute *gpo_attributes[] = { + &dev_attr_out32k_mode.attr, + &dev_attr_out32k_value.attr, + &dev_attr_available_mode.attr, + &dev_attr_available_value.attr, + &dev_attr_dvssel.attr, + NULL +}; + +static const struct attribute_group gpo_attr_group = { + .attrs = gpo_attributes, +}; + +/*----------------------------------------------------------------------*/ +#ifdef CONFIG_OF +/** @brief buck1/2 dvs enable/voltage from device tree + * @param pdev platfrom device pointer + * @param buck_dvs pointer + * @return void + */ +static void of_bd7181x_buck_dvs(struct platform_device *pdev, struct bd7181x_buck_dvs *buck_dvs) +{ + struct device_node *pmic_np; + + pmic_np = of_node_get(pdev->dev.parent->of_node); + if (!pmic_np) { + dev_err(&pdev->dev, "could not find pmic sub-node\n"); + return; + } + + if (of_get_property(pmic_np, "bd7181x,pmic-buck1-uses-i2c-dvs", NULL)) { + buck_dvs[0].i2c_dvs_enable = 1; + if (of_property_read_u32_array(pmic_np, + "bd7181x,pmic-buck1-dvs-voltage", + &buck_dvs[0].voltage[0], 2)) { + dev_err(&pdev->dev, "buck1 voltages not specified\n"); + } + } + + if (of_get_property(pmic_np, "bd7181x,pmic-buck2-uses-i2c-dvs", NULL)) { + buck_dvs[1].i2c_dvs_enable = 1; + if (of_property_read_u32_array(pmic_np, + "bd7181x,pmic-buck2-dvs-voltage", + &buck_dvs[1].voltage[0], 2)) { + dev_err(&pdev->dev, "buck2 voltages not specified\n"); + } + } +} +#else +static void of_bd7181x_buck_dvs(struct platform_device *pdev, struct bd7181x_buck_dvs *buck_dvs) +{ + buck_dvs[0].i2c_dvs_enable = 0; + buck_dvs[0].voltage[0] = BUCK1_H_DEFAULT; + buck_dvs[0].voltage[1] = BUCK1_L_DEFAULT; + buck_dvs[1].i2c_dvs_enable = 0; + buck_dvs[1].voltage[0] = BUCK1_H_DEFAULT; + buck_dvs[1].voltage[1] = BUCK1_L_DEFAULT; +} +#endif + +static int bd7181x_buck12_dvs_init(struct bd7181x_pmic *pmic) +{ + struct bd7181x *bd7181x = pmic->mfd; + struct bd7181x_buck_dvs *buck_dvs = &pmic->buck_dvs[0]; + int i, ret, val, selector = 0; + u8 regh, regl; + + for(i = 0; i < BD7181X_DVS_BUCK_NUM; i++, buck_dvs++) { + regh = BD7181X_REG_BUCK1_VOLT_H + i*0x2; + regl = BD7181X_REG_BUCK1_VOLT_L + i*0x2; + val = BUCK1_DVSSEL; + if(buck_dvs->i2c_dvs_enable) { + dev_info(pmic->dev, "Buck%d: I2C DVS Enabled !\n", i); + val &= ~BUCK1_STBY_DVS; + } + dev_info(pmic->dev, "Buck%d: DVS High-Low[%d - %d].\n", i, buck_dvs->voltage[0], buck_dvs->voltage[1]); + selector = regulator_map_voltage_iterate(pmic->rdev[i], buck_dvs->voltage[0], buck_dvs->voltage[0]); + if(selector < 0) { + dev_err(pmic->dev, "%s(): not found selector for voltage [%d]\n", __func__, buck_dvs->voltage[0]); + } else { + ret = bd7181x_reg_write(bd7181x, regh, val | (selector & BUCK1_H_MASK)); + if(ret < 0) + return ret; + } + selector = regulator_map_voltage_iterate(pmic->rdev[i], buck_dvs->voltage[1], buck_dvs->voltage[1]); + if(selector < 0) { + dev_err(pmic->dev, "%s(): not found selector for voltage [%d]\n", __func__, buck_dvs->voltage[1]); + } else { + ret = bd7181x_reg_write(bd7181x, regl, val | (selector & BUCK1_L_MASK)); + if(ret < 0) + return ret; + } + } + return 0; +} + +/**@brief probe bd7181x regulator device + @param pdev bd7181x regulator platform device + @retval 0 success + @retval negative fail +*/ +static __init int bd7181x_probe(struct platform_device *pdev) +{ + struct bd7181x_pmic *pmic; + struct bd7181x_board *pdata; + struct regulator_config config = {}; + struct bd7181x *bd7181x = dev_get_drvdata(pdev->dev.parent); + struct of_regulator_match *matches = NULL; + int i, err; + + pmic = kzalloc(sizeof(*pmic), GFP_KERNEL); + if (!pmic) { + dev_err(&pdev->dev, "Memory allocation failed for pmic\n"); + return -ENOMEM; + } + + memcpy(pmic->descs, bd7181x_regulators, sizeof(pmic->descs)); + + pmic->dev = &pdev->dev; + pmic->mfd = bd7181x; + platform_set_drvdata(pdev, pmic); + + bd7181x_clear_bits(pmic->mfd, BD7181X_REG_PWRCTRL, RESTARTEN); // Disable to go to ship-mode + bd7181x_clear_bits(pmic->mfd, BD7181X_REG_GPO, RESTARTEN); // Turn OFF the green LED + bd7181x_set_bits(pmic->mfd, BD7181X_REG_CHG_SET1, CHG_EN); // Enable charger + + pdata = dev_get_platdata(bd7181x->dev); + if (!pdata && bd7181x->dev->of_node) { + bd7181x_parse_dt_reg_data(pdev, &matches); + if (matches == NULL) { + dev_err(&pdev->dev, "Platform data not found\n"); + return -EINVAL; + } + } + + /* Get buck dvs parameters */ + of_bd7181x_buck_dvs(pdev, &pmic->buck_dvs[0]); + + for (i = 0; i < BD7181X_REGULATOR_CNT; i++) { + struct regulator_init_data *init_data; + struct regulator_desc *desc; + struct regulator_dev *rdev; + + desc = &pmic->descs[i].desc; + desc->name = bd7181x_matches[i].name; + + if (pdata) { + init_data = pdata->init_data[i]; + } else { + init_data = matches[i].init_data; + } + + config.dev = pmic->dev; + config.init_data = init_data; + config.driver_data = pmic; + config.regmap = bd7181x->regmap; + config.of_node = matches[i].of_node; + + rdev = regulator_register(desc, &config); + if (IS_ERR(rdev)) { + dev_err(bd7181x->dev, + "failed to register %s regulator\n", + desc->name); + err = PTR_ERR(rdev); + goto err; + } + pmic->rdev[i] = rdev; + } + + err = sysfs_create_group(&pdev->dev.kobj, &gpo_attr_group); + if (err != 0) { + dev_err(&pdev->dev, "Failed to create attribute group: %d\n", err); + goto err; + } + + /* Init buck12 dvs */ + err = bd7181x_buck12_dvs_init(pmic); + if (err != 0) { + dev_err(&pdev->dev, "Failed to buck12 dvs: %d\n", err); + goto err; + } + + return 0; + +err: + while (--i >= 0) + regulator_unregister(pmic->rdev[i]); + + kfree(pmic); + return err; +} + +/**@brief remove bd7181x regulator device + @param pdev bd7181x regulator platform device + @return 0 +*/ +static int __exit bd7181x_remove(struct platform_device *pdev) +{ + struct bd7181x_pmic *pmic = platform_get_drvdata(pdev); + int i; + + sysfs_remove_group(&pdev->dev.kobj, &gpo_attr_group); + + for (i = 0; i < BD7181X_REGULATOR_CNT; i++) + regulator_unregister(pmic->rdev[i]); + + kfree(pmic); + return 0; +} + +static struct platform_driver bd7181x_driver = { + .driver = { + .name = "bd7181x-pmic", + .owner = THIS_MODULE, + }, + .probe = bd7181x_probe, + .remove = bd7181x_remove, +}; + +/**@brief module initialize function */ +static int __init bd7181x_init(void) +{ + return platform_driver_register(&bd7181x_driver); +} +subsys_initcall(bd7181x_init); + +/**@brief module deinitialize function */ +static void __exit bd7181x_cleanup(void) +{ + platform_driver_unregister(&bd7181x_driver); +} +module_exit(bd7181x_cleanup); + +MODULE_AUTHOR("Tony Luo "); +MODULE_DESCRIPTION("BD71815/BD71817 voltage regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bd7181x-pmic"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index f90aca3f9353..c88898512cf3 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -631,6 +631,15 @@ config RTC_DRV_S5M This driver can also be built as a module. If so, the module will be called rtc-s5m. +config RTC_DRV_BD7181X + tristate "BD71815/BD71817 RTC" + depends on RTC_CLASS && MFD_BD7181X + help + If you say yes here you get support for the RTC on the + RoHM BD71815/BD71817 chips. + + This driver can also be built as a module. + endif # I2C comment "SPI RTC drivers" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 23a9d6d60784..b15b6b188f7c 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o obj-$(CONFIG_RTC_DRV_AU1XXX) += rtc-au1xxx.o +obj-$(CONFIG_RTC_DRV_BD7181X) += rtc-bd7181x.o obj-$(CONFIG_RTC_DRV_BFIN) += rtc-bfin.o obj-$(CONFIG_RTC_DRV_BRCMSTB) += rtc-brcmstb-waketimer.o obj-$(CONFIG_RTC_DRV_BQ32K) += rtc-bq32k.o diff --git a/drivers/rtc/rtc-bd7181x.c b/drivers/rtc/rtc-bd7181x.c new file mode 100644 index 000000000000..afbe0fbe6e78 --- /dev/null +++ b/drivers/rtc/rtc-bd7181x.c @@ -0,0 +1,417 @@ +/* + * @file RoHM BD71815/BD71817 Real Time Clock interface + * + * Copyright (C) 2014 Embest Technology Co. Ltd. Inc. + * + * @author Peter Yang + * + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @brief bd7181x rtc struct */ +struct bd7181x_rtc { + struct rtc_device *rtc; /**< system rtc device */ + int irq; /**< rtc irq */ +}; + +/* @brief Total number of RTC registers needed to set time*/ +// #define NUM_TIME_REGS (BD7181X_REG_YEAR - BD7181X_REG_SEC + 1) + +/**@brief enable or disable rtc alarm irq + * @param dev rtc device of system + * @param enabled enable if non-zero + * @retval 0 success + * @retval negative error number + */ +static int bd7181x_rtc_alarm_irq_enable(struct device *dev, unsigned enabled) +{ + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + u8 val = 0; + + if (enabled) + val = ALM0; + + return regmap_write(mfd->regmap, BD7181X_REG_INT_EN_12, val); +} + +/**@brief bd7181x rtc time convert to linux time + * @param tm linux rtc time + * @param hw_rtc bd7181x rtc time + * @return argument tm + */ +static struct rtc_time* hw_to_rtc_time(struct rtc_time* tm, const struct bd7181x_rtc_alarm* hw_rtc) { + u8 hour; + + tm->tm_sec = bcd2bin(hw_rtc->sec); + tm->tm_min = bcd2bin(hw_rtc->min); + hour = hw_rtc->hour & ~HOUR_24HOUR; + tm->tm_hour = bcd2bin(hour); + tm->tm_mday = bcd2bin(hw_rtc->day); + tm->tm_mon = bcd2bin(hw_rtc->month) - 1; + tm->tm_year = bcd2bin(hw_rtc->year) + 100; + return tm; +} + +/**@brief linux time convert bd7181x rtc time + * @param hw_rtc bd7181x rtc time + * @param tm linux rtc time + * @return argument hw_rtc + */ +static struct bd7181x_rtc_alarm* rtc_time_to_hw(struct bd7181x_rtc_alarm* hw_rtc, const struct rtc_time* tm) { + hw_rtc->sec = bin2bcd(tm->tm_sec); + hw_rtc->min = bin2bcd(tm->tm_min); + hw_rtc->hour = HOUR_24HOUR | bin2bcd(tm->tm_hour); + hw_rtc->day = bin2bcd(tm->tm_mday); + hw_rtc->month = bin2bcd(tm->tm_mon + 1); + hw_rtc->year = bin2bcd(tm->tm_year - 100); + + return hw_rtc; +} + +/* + * Gets current bd7181x RTC time and date parameters. + * + * The RTC's time/alarm representation is not what gmtime(3) requires + * Linux to use: + * + * - Months are 1..12 vs Linux 0-11 + * - Years are 0..99 vs Linux 1900..N (we assume 21st century) + */ +/**@brief read date/time from bd7181x rtc + * @param dev rtc device of system + * @param tm date/time store target + * @retval 0 success + * @retval negative error number + */ +static int bd7181x_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct bd7181x_rtc_alarm rtc_data[1]; + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + int ret; + + ret = regmap_bulk_read(mfd->regmap, BD7181X_REG_SEC, rtc_data, sizeof rtc_data); + if (ret < 0) { + dev_err(dev, "reading from RTC failed with err:%d\n", ret); + return ret; + } + + hw_to_rtc_time(tm, rtc_data); + + return ret; +} + +/**@brief write date/time to bd7181x rtc + * @param dev rtc device of system + * @param tm date/time source + * @retval 0 success + * @retval negative error number + */ +static int bd7181x_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct bd7181x_rtc_alarm rtc_data[1]; + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + int ret; + + rtc_time_to_hw(rtc_data, tm); + + /* update all the time registers in one shot */ + ret = regmap_bulk_write(mfd->regmap, BD7181X_REG_SEC, rtc_data, sizeof rtc_data); + if (ret < 0) { + dev_err(dev, "rtc_set_time error %d\n", ret); + return ret; + } + + return ret; +} + +/**@brief Gets current bd7181x RTC alarm time. + * @param dev rtc device of system + * @param alm alarm date/time store target + * @retval 0 success + * @retval negative error number + */ +static int bd7181x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + struct bd7181x_rtc_alarm rtc_data[1]; + u32 int_val; + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + int ret; + + ret = regmap_bulk_read(mfd->regmap, BD7181X_REG_ALM0_SEC, rtc_data, sizeof rtc_data); + if (ret < 0) { + dev_err(dev, "rtc_read_alarm error %d\n", ret); + return ret; + } + + hw_to_rtc_time(&alm->time, rtc_data); + + ret = regmap_read(mfd->regmap, BD7181X_REG_INT_EN_12, &int_val); + if (ret < 0) + return ret; + + if (int_val & ALM0) + alm->enabled = 1; + + return ret; +} + +/**@brief Set current bd7181x RTC alarm time + * @param dev rtc device of system + * @param alm alarm date/time to set + * @retval 0 success + * @retval negative error number + */ +static int bd7181x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + struct bd7181x_rtc_alarm rtc_data[1]; + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + int ret; + + // printk("%s() L%d\n", __func__, __LINE__); + + ret = bd7181x_rtc_alarm_irq_enable(dev, 0); + if (ret) + return ret; + + rtc_time_to_hw(rtc_data, &alm->time); + + /* update all the alarm registers in one shot */ + ret = regmap_bulk_write(mfd->regmap, BD7181X_REG_ALM0_SEC, rtc_data, sizeof rtc_data); + if (ret) { + dev_err(dev, "rtc_set_alarm error %d\n", ret); + return ret; + } + + if (alm->enabled) + ret = bd7181x_rtc_alarm_irq_enable(dev, 1); + + return ret; +} + +/**@brief bd7181x rtc alarm interrupt + * @param irq system irq + * @param rtc rtc device of system + * @retval IRQ_HANDLED success + * @retval IRQ_NONE error + */ +static irqreturn_t bd7181x_rtc_interrupt(int irq, void *rtc) +{ + struct device *dev = rtc; + unsigned long events = 0; + struct bd7181x *mfd = dev_get_drvdata(dev->parent); + struct bd7181x_rtc *bd_rtc = dev_get_drvdata(dev); + int ret; + u32 rtc_reg; + + dev_info(mfd->dev, "bd7181x_rtc_interrupt() in.\n"); + + ret = regmap_read(mfd->regmap, BD7181X_REG_INT_STAT_12, &rtc_reg); + if (ret) + return IRQ_NONE; + + dev_info(mfd->dev, "BD7181X_REG_INT_STAT_12=0x%x\n", rtc_reg); + + if (rtc_reg & ALM0) + events = RTC_IRQF | RTC_AF; + + ret = regmap_write(mfd->regmap, BD7181X_REG_INT_STAT_12, rtc_reg); + if (ret) + return IRQ_NONE; + + dev_info(mfd->dev, "\n~~~IRQ ALARM.\n"); + + /* Notify RTC core on event */ + rtc_update_irq(bd_rtc->rtc, 1, events); + + return IRQ_HANDLED; +} + +/** @brief function operations definition */ +static struct rtc_class_ops bd7181x_rtc_ops = { + .read_time = bd7181x_rtc_read_time, + .set_time = bd7181x_rtc_set_time, + .read_alarm = bd7181x_rtc_read_alarm, + .set_alarm = bd7181x_rtc_set_alarm, + .alarm_irq_enable = bd7181x_rtc_alarm_irq_enable, +}; + +/**@brief probe bd7181x rtc device + @param pdev bd7181x rtc platform device + @retval 0 success + @retval negative fail +*/ +static int bd7181x_rtc_probe(struct platform_device *pdev) +{ + struct bd7181x *bd7181x = NULL; + struct bd7181x_rtc *bd_rtc = NULL; + int ret; + int irq; + u32 rtc_reg; + + bd7181x = dev_get_drvdata(pdev->dev.parent); + + bd_rtc = devm_kzalloc(&pdev->dev, sizeof(struct bd7181x_rtc), + GFP_KERNEL); + if (!bd_rtc) + return -ENOMEM; + + /* Clear pending interrupts */ + ret = regmap_read(bd7181x->regmap, BD7181X_REG_INT_STAT_12, &rtc_reg); + if (ret < 0) + return ret; + + ret = regmap_write(bd7181x->regmap, BD7181X_REG_INT_STAT_12, rtc_reg); + if (ret < 0) + return ret; + + dev_dbg(&pdev->dev, "Enabling rtc-bd7181x.\n"); + + #if 0 + /* Enable RTC alarm interrupt */ + ret = regmap_update_bits(bd7181x->regmap, BD7181X_REG_INT_EN_00, ALMALE, ALMALE); + if (ret < 0) + return ret; + #endif + /* Enable ALM0_EN mask */ + ret = regmap_write(bd7181x->regmap, BD7181X_REG_ALM0_MASK, ALM0); + if (ret < 0) + return ret; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_warn(&pdev->dev, "Wake up is not possible as irq = %d\n", irq); + return -ENXIO; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + bd7181x_rtc_interrupt, IRQF_TRIGGER_LOW | IRQF_EARLY_RESUME, + dev_name(&pdev->dev), &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "IRQ is not free.\n"); + return ret; + } + bd_rtc->irq = irq; + device_set_wakeup_capable(&pdev->dev, 1); + + bd_rtc->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, + &bd7181x_rtc_ops, THIS_MODULE); + if (IS_ERR(bd_rtc->rtc)) { + ret = PTR_ERR(bd_rtc->rtc); + dev_err(&pdev->dev, "RTC device register: err %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, bd_rtc); + + return 0; +} + +/* + * Disable bd7181x RTC interrupts. + * Sets status flag to free. + */ +/**@brief remove bd7181x rtc device + @param pdev bd7181x rtc platform device + @return 0 +*/ +static int bd7181x_rtc_remove(struct platform_device *pdev) +{ + bd7181x_rtc_alarm_irq_enable(&pdev->dev, 0); + + return 0; +} + +/**@brief shutdown bd7181x rtc device + @param pdev bd7181x rtc platform device + @return void +*/ +static void bd7181x_rtc_shutdown(struct platform_device *pdev) +{ + /* mask timer interrupts, but leave alarm interrupts on to enable + power-on when alarm is triggered */ + bd7181x_rtc_alarm_irq_enable(&pdev->dev, 0); +} + +#ifdef CONFIG_PM_SLEEP +/**@brief suspend bd7181x rtc device + * @param dev rtc device of system + * @retval 0 + */ +static int bd7181x_rtc_suspend(struct device *dev) +{ + struct bd7181x_rtc *bd_rtc = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(bd_rtc->irq); + return 0; +} + +/**@brief resume bd7181x rtc device + * @param dev rtc device of system + * @retval 0 + */ +static int bd7181x_rtc_resume(struct device *dev) +{ + struct bd7181x_rtc *bd_rtc = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(bd_rtc->irq); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(bd7181x_rtc_pm_ops, bd7181x_rtc_suspend, bd7181x_rtc_resume); + +#ifdef CONFIG_OF +static const struct of_device_id bd7181x_rtc_of_match[] = { + {.compatible = "ti,bd7181x-rtc", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bd7181x_rtc_of_match); +#endif + +static struct platform_driver bd7181xrtc_driver = { + .probe = bd7181x_rtc_probe, + .remove = bd7181x_rtc_remove, + .shutdown = bd7181x_rtc_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "bd7181x-rtc", + .pm = &bd7181x_rtc_pm_ops, + .of_match_table = of_match_ptr(bd7181x_rtc_of_match), + }, +}; + +/**@brief module initialize function */ +static int __init bd7181x_rtc_init(void) +{ + return platform_driver_register(&bd7181xrtc_driver); +} +module_init(bd7181x_rtc_init); + +/**@brief module deinitialize function */ +static void __exit bd7181x_rtc_exit(void) +{ + platform_driver_unregister(&bd7181xrtc_driver); +} +module_exit(bd7181x_rtc_exit); + +MODULE_AUTHOR("Peter Yang "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bd7181x-rtc"); diff --git a/include/linux/mfd/bd7181x.h b/include/linux/mfd/bd7181x.h new file mode 100644 index 000000000000..7afec81c4334 --- /dev/null +++ b/include/linux/mfd/bd7181x.h @@ -0,0 +1,601 @@ +/** + * @file bd7181x.h ROHM BD71815GW/BD71817GW header file + * + * Copyright 2014 Embest Technology Co. Ltd. Inc. + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + * @author yanglsh@embest-tech.com + */ + +#ifndef __LINUX_MFD_BD7181X_H +#define __LINUX_MFD_BD7181X_H + +#include + +// LDO5VSEL_EQ_H +// define to 1 when LDO5VSEL connect to High +// define to 0 when LDO5VSEL connect to Low +#define LDO5VSEL_EQ_H 1 + +#ifndef LDO5VSEL_EQ_H + #error define LDO5VSEL_EQ_H to 1 when connect to High, to 0 when connect to Low +#else + #if LDO5VSEL_EQ_H == 1 + #define BD7181X_REG_LDO5_VOLT BD7181X_REG_LDO5_VOLT_H + #elif LDO5VSEL_EQ_H == 0 + #define BD7181X_REG_LDO5_VOLT BD7181X_REG_LDO5_VOLT_L + #else + #error Define LDO5VSEL_EQ_H only to 0 or 1 + #endif +#endif + +enum { + BD7181X_BUCK1 = 0, + BD7181X_BUCK2, + BD7181X_BUCK3, + BD7181X_BUCK4, + BD7181X_BUCK5, + // General Purpose + BD7181X_LDO1, + BD7181X_LDO2, + BD7181X_LDO3, + // LDOs for SD Card and SD Card Interface + BD7181X_LDO4, + BD7181X_LDO5, + // LDO for DDR Reference Voltage + BD7181X_LDODVREF, + // LDO for Secure Non-Volatile Storage + // BD7181X_LDOSNVS, + // LDO for Low-Power State Retention + BD7181X_LDOLPSR, + BD7181X_WLED, + BD7181X_REGULATOR_CNT, +}; + +#define BD7181X_SUPPLY_STATE_ENABLED 0x1 + +enum { + BD7181X_REG_DEVICE = 0, + BD7181X_REG_PWRCTRL, + BD7181X_REG_BUCK1_MODE, + BD7181X_REG_BUCK2_MODE, + BD7181X_REG_BUCK3_MODE, + BD7181X_REG_BUCK4_MODE, + BD7181X_REG_BUCK5_MODE, + BD7181X_REG_BUCK1_VOLT_H, + // 0x08 + BD7181X_REG_BUCK1_VOLT_L, + BD7181X_REG_BUCK2_VOLT_H, + BD7181X_REG_BUCK2_VOLT_L, + BD7181X_REG_BUCK3_VOLT, + BD7181X_REG_BUCK4_VOLT, + BD7181X_REG_BUCK5_VOLT, + BD7181X_REG_LED_CTRL, + BD7181X_REG_LED_DIMM, + // 0x10 + BD7181X_REG_LDO_MODE1, + BD7181X_REG_LDO_MODE2, + BD7181X_REG_LDO_MODE3, + BD7181X_REG_LDO_MODE4, + BD7181X_REG_LDO1_VOLT, + BD7181X_REG_LDO2_VOLT, + BD7181X_REG_LDO3_VOLT, + BD7181X_REG_LDO4_VOLT, + // 0x18 + BD7181X_REG_LDO5_VOLT_H, + BD7181X_REG_LDO5_VOLT_L, + BD7181X_REG_BUCK_PD_DIS, + BD7181X_REG_LDO_PD_DIS, + BD7181X_REG_GPO, + BD7181X_REG_OUT32K, + BD7181X_REG_SEC, + BD7181X_REG_MIN, + // 0x20 + BD7181X_REG_HOUR, + BD7181X_REG_WEEK, + BD7181X_REG_DAY, + BD7181X_REG_MONTH, + BD7181X_REG_YEAR, + BD7181X_REG_ALM0_SEC, + + // 0x2C + BD7181X_REG_ALM1_SEC = 0x2C, + + // 0x33 + BD7181X_REG_ALM0_MASK = 0x33, + BD7181X_REG_ALM1_MASK, + BD7181X_REG_ALM2, + BD7181X_REG_TRIM, + BD7181X_REG_CONF, + // 0x38 + BD7181X_REG_SYS_INIT, + BD7181X_REG_CHG_STATE, + BD7181X_REG_CHG_LAST_STATE, + BD7181X_REG_BAT_STAT, + BD7181X_REG_DCIN_STAT, + BD7181X_REG_VSYS_STAT, + BD7181X_REG_CHG_STAT, + BD7181X_REG_CHG_WDT_STAT, + // 0x40 + BD7181X_REG_BAT_TEMP, + BD7181X_REG_IGNORE_0, + BD7181X_REG_INHIBIT_0, + BD7181X_REG_DCIN_CLPS, + BD7181X_REG_VSYS_REG, + BD7181X_REG_VSYS_MAX, + BD7181X_REG_VSYS_MIN, + BD7181X_REG_CHG_SET1, + // 0x48 + BD7181X_REG_CHG_SET2, + BD7181X_REG_CHG_WDT_PRE, + BD7181X_REG_CHG_WDT_FST, + BD7181X_REG_CHG_IPRE, + BD7181X_REG_CHG_IFST, + BD7181X_REG_CHG_IFST_TERM, + BD7181X_REG_CHG_VPRE, + BD7181X_REG_CHG_VBAT_1, + // 0x50 + BD7181X_REG_CHG_VBAT_2, + BD7181X_REG_CHG_VBAT_3, + BD7181X_REG_CHG_LED_1, + BD7181X_REG_VF_TH, + BD7181X_REG_BAT_SET_1, + BD7181X_REG_BAT_SET_2, + BD7181X_REG_BAT_SET_3, + BD7181X_REG_ALM_VBAT_TH_U, + // 0x58 + BD7181X_REG_ALM_VBAT_TH_L, + BD7181X_REG_ALM_DCIN_TH, + BD7181X_REG_ALM_VSYS_TH, + BD7181X_REG_VM_IBAT_U, + BD7181X_REG_VM_IBAT_L, + BD7181X_REG_VM_VBAT_U, + BD7181X_REG_VM_VBAT_L, + BD7181X_REG_VM_BTMP, + // 0x60 + BD7181X_REG_VM_VTH, + BD7181X_REG_VM_DCIN_U, + BD7181X_REG_VM_DCIN_L, + BD7181X_REG_VM_VSYS, + BD7181X_REG_VM_VF, + BD7181X_REG_VM_OCI_PRE_U, + BD7181X_REG_VM_OCI_PRE_L, + BD7181X_REG_VM_OCV_PRE_U, + // 0x68 + BD7181X_REG_VM_OCV_PRE_L, + BD7181X_REG_VM_OCI_PST_U, + BD7181X_REG_VM_OCI_PST_L, + BD7181X_REG_VM_OCV_PST_U, + BD7181X_REG_VM_OCV_PST_L, + BD7181X_REG_VM_SA_VBAT_U, + BD7181X_REG_VM_SA_VBAT_L, + BD7181X_REG_VM_SA_IBAT_U, + // 0x70 + BD7181X_REG_VM_SA_IBAT_L, + BD7181X_REG_CC_CTRL, + BD7181X_REG_CC_BATCAP1_TH_U, + BD7181X_REG_CC_BATCAP1_TH_L, + BD7181X_REG_CC_BATCAP2_TH_U, + BD7181X_REG_CC_BATCAP2_TH_L, + BD7181X_REG_CC_BATCAP3_TH_U, + BD7181X_REG_CC_BATCAP3_TH_L, + // 0x78 + BD7181X_REG_CC_STAT, + BD7181X_REG_CC_CCNTD_3, + BD7181X_REG_CC_CCNTD_2, + BD7181X_REG_CC_CCNTD_1, + BD7181X_REG_CC_CCNTD_0, + BD7181X_REG_CC_CURCD_U, + BD7181X_REG_CC_CURCD_L, + BD7181X_REG_VM_OCUR_THR_1, + // 0x80 + BD7181X_REG_VM_OCUR_DUR_1, + BD7181X_REG_VM_OCUR_THR_2, + BD7181X_REG_VM_OCUR_DUR_2, + BD7181X_REG_VM_OCUR_THR_3, + BD7181X_REG_VM_OCUR_DUR_3, + BD7181X_REG_VM_OCUR_MON, + BD7181X_REG_VM_BTMP_OV_THR, + BD7181X_REG_VM_BTMP_OV_DUR, + // 0x88 + BD7181X_REG_VM_BTMP_LO_THR, + BD7181X_REG_VM_BTMP_LO_DUR, + BD7181X_REG_VM_BTMP_MON, + BD7181X_REG_INT_EN_01, + // 0x95 + BD7181X_REG_INT_EN_11 = 0x95, + // 0x96 + BD7181X_REG_INT_EN_12 = 0x96, + BD7181X_REG_INT_STAT, + + // 0x98 + BD7181X_REG_INT_STAT_01, + BD7181X_REG_INT_STAT_02, + BD7181X_REG_INT_STAT_03, + BD7181X_REG_INT_STAT_04, + BD7181X_REG_INT_STAT_05, + BD7181X_REG_INT_STAT_06, + BD7181X_REG_INT_STAT_07, + BD7181X_REG_INT_STAT_08, + + // 0xA0 + BD7181X_REG_INT_STAT_09, + BD7181X_REG_INT_STAT_10, + BD7181X_REG_INT_STAT_11, + BD7181X_REG_INT_STAT_12, + BD7181X_REG_INT_UPDATE, + + // 0xC0 + BD7181X_REG_VM_VSYS_U = 0xC0, + BD7181X_REG_VM_VSYS_L, + BD7181X_REG_VM_SA_VSYS_U, + BD7181X_REG_VM_SA_VSYS_L, + + // 0xD0 + BD7181X_REG_VM_SA_IBAT_MIN_U = 0xD0, + BD7181X_REG_VM_SA_IBAT_MIN_L, + BD7181X_REG_VM_SA_IBAT_MAX_U, + BD7181X_REG_VM_SA_IBAT_MAX_L, + BD7181X_REG_VM_SA_VBAT_MIN_U, + BD7181X_REG_VM_SA_VBAT_MIN_L, + BD7181X_REG_VM_SA_VBAT_MAX_U, + BD7181X_REG_VM_SA_VBAT_MAX_L, + BD7181X_REG_VM_SA_VSYS_MIN_U, + BD7181X_REG_VM_SA_VSYS_MIN_L, + BD7181X_REG_VM_SA_VSYS_MAX_U, + BD7181X_REG_VM_SA_VSYS_MAX_L, + BD7181X_REG_VM_SA_MINMAX_CLR, + + // 0xE0 + BD7181X_REG_REX_CCNTD_3 = 0xE0, + BD7181X_REG_REX_CCNTD_2, + BD7181X_REG_REX_CCNTD_1, + BD7181X_REG_REX_CCNTD_0, + BD7181X_REG_REX_SA_VBAT_U, + BD7181X_REG_REX_SA_VBAT_L, + BD7181X_REG_REX_CTRL_1, + BD7181X_REG_REX_CTRL_2, + BD7181X_REG_FULL_CCNTD_3, + BD7181X_REG_FULL_CCNTD_2, + BD7181X_REG_FULL_CCNTD_1, + BD7181X_REG_FULL_CCNTD_0, + BD7181X_REG_FULL_CTRL, + + // 0xF0 + BD7181X_REG_CCNTD_CHG_3 = 0xF0, + BD7181X_REG_CCNTD_CHG_2, + + // 0xFE + BD7181X_REG_TEST_MODE = 0xFE, + BD7181X_MAX_REGISTER, +}; + +/* BD7181X_REG_BUCK1_MODE bits */ +#define BUCK1_RAMPRATE_MASK 0xC0 +#define BUCK1_RAMPRATE_10P00MV 0x0 +#define BUCK1_RAMPRATE_5P00MV 0x1 +#define BUCK1_RAMPRATE_2P50MV 0x2 +#define BUCK1_RAMPRATE_1P25MV 0x3 + +/* BD7181X_REG_BUCK1_VOLT_H bits */ +#define BUCK1_DVSSEL 0x80 +#define BUCK1_STBY_DVS 0x40 +#define BUCK1_H_MASK 0x3F +#define BUCK1_H_DEFAULT 0x14 + +/* BD7181X_REG_BUCK1_VOLT_L bits */ +#define BUCK1_L_MASK 0x3F +#define BUCK1_L_DEFAULT 0x14 + +/* BD7181X_REG_BUCK2_VOLT_H bits */ +#define BUCK2_DVSSEL 0x80 +#define BUCK2_STBY_DVS 0x40 +#define BUCK2_H_MASK 0x3F +#define BUCK2_H_DEFAULT 0x14 + +/* BD7181X_REG_BUCK2_VOLT_L bits */ +#define BUCK2_L_MASK 0x3F +#define BUCK2_L_DEFAULT 0x14 + +/* BD7181X_REG_LDO1_CTRL bits */ +#define LDO1_EN 0x01 +#define LDO2_EN 0x02 +#define LDO3_EN 0x04 +#define DVREF_EN 0x08 +#define VOSNVS_SW_EN 0x10 +#define VOLT_MASK 0x3F + +/* BD7181X_REG_OUT32K bits */ +#define OUT32K_EN 0x01 +#define OUT32K_MODE 0x02 + +/* BD7181X_REG_BAT_STAT bits */ +#define BAT_DET 0x20 +#define BAT_DET_OFFSET 5 +#define BAT_DET_DONE 0x10 +#define VBAT_OV 0x08 +#define DBAT_DET 0x01 + +/* BD7181X_REG_VBUS_STAT bits */ +#define VBUS_DET 0x01 + +#define BUCK1_RAMPRATE_10MV_US 0x0 +#define BUCK1_RAMPRATE_5MV_US 0x1 +#define BUCK1_RAMPRATE_2P5MV_US 0x2 +#define BUCK1_RAMPRATE_1P25MV_US 0x3a + +/* BD7181X_REG_ALM0_MASK bits */ +#define A0_ONESEC 0x80 + +/* BD7181X_REG_INT_EN_00 bits */ +#define ALMALE 0x1 + +/* BD7181X_REG_INT_STAT_03 bits */ +#define DCIN_MON_DET 0x02 +#define DCIN_MON_RES 0x01 +#define POWERON_LONG 0x04 +#define POWERON_MID 0x08 +#define POWERON_SHORT 0x10 +#define POWERON_PRESS 0x20 + + +/* BD71805_REG_INT_STAT_08 bits */ +#define VBAT_MON_DET 0x02 +#define VBAT_MON_RES 0x01 + +/* BD71805_REG_INT_STAT_11 bits */ +#define INT_STAT_11_VF_DET 0x80 +#define INT_STAT_11_VF_RES 0x40 +#define INT_STAT_11_VF125_DET 0x20 +#define INT_STAT_11_VF125_RES 0x10 +#define INT_STAT_11_OVTMP_DET 0x08 +#define INT_STAT_11_OVTMP_RES 0x04 +#define INT_STAT_11_LOTMP_DET 0x02 +#define INT_STAT_11_LOTMP_RES 0x01 + +#define VBAT_MON_DET 0x02 +#define VBAT_MON_RES 0x01 + +/* BD7181X_REG_PWRCTRL bits */ +#define RESTARTEN 0x01 + +/* BD7181X_REG_GPO bits */ +#define READY_FORCE_LOW 0x04 + +/* BD7181X_REG_CHG_SET1 bits */ +#define CHG_EN 0x01 + +/* BD7181X interrupt masks */ +enum { + BD7181X_INT_EN_01_BUCKAST_MASK = 0x0F, + BD7181X_INT_EN_02_DCINAST_MASK = 0x3E, + BD7181X_INT_EN_03_DCINAST_MASK = 0x3F, + BD7181X_INT_EN_04_VSYSAST_MASK = 0xCF, + BD7181X_INT_EN_05_CHGAST_MASK = 0xFC, + BD7181X_INT_EN_06_BATAST_MASK = 0xF3, + BD7181X_INT_EN_07_BMONAST_MASK = 0xFE, + BD7181X_INT_EN_08_BMONAST_MASK = 0x03, + BD7181X_INT_EN_09_BMONAST_MASK = 0x07, + BD7181X_INT_EN_10_BMONAST_MASK = 0x3F, + BD7181X_INT_EN_11_TMPAST_MASK = 0xFF, + BD7181X_INT_EN_12_ALMAST_MASK = 0x07, +}; +/* BD7181X interrupt irqs */ +enum { + BD7181X_IRQ_BUCK_01 = 0x0, + BD7181X_IRQ_DCIN_02, + BD7181X_IRQ_DCIN_03, + BD7181X_IRQ_VSYS_04, + BD7181X_IRQ_CHARGE_05, + BD7181X_IRQ_BAT_06, + BD7181X_IRQ_BAT_MON_07, + BD7181X_IRQ_BAT_MON_08, + BD7181X_IRQ_BAT_MON_09, + BD7181X_IRQ_BAT_MON_10, + BD7181X_IRQ_TEMPERATURE_11, + BD7181X_IRQ_ALARM_12, +}; + +/* BD7181X_REG_INT_EN_12 bits */ +#define ALM0 0x1 + +/* BD7181X_REG_HOUR bits */ +#define HOUR_24HOUR 0x80 + +/* BD7181X_REG_CC_CTRL bits */ +#define CCNTRST 0x80 +#define CCNTENB 0x40 +#define CCCALIB 0x20 + +/* BD7181X_REG_CHG_SET1 bits */ +#define WDT_AUTO 0x40 + +/* BD7181X_REG_CC_CURCD */ +#define CURDIR_Discharging 0x8000 + +/* BD7181X_REG_VM_SA_IBAT */ +#define IBAT_SA_DIR_Discharging 0x8000 + +/* BD7181X_REG_VM_SA_MINMAX_CLR bits */ +#define VSYS_SA_MIN_CLR 0x10 +#define VBAT_SA_MIN_CLR 0x01 + +/* BD7181X_REG_REX_CTRL_1 bits */ +#define REX_CLR 0x10 + +/* BD7181X_REG_REX_CTRL_1 bits */ +#define REX_PMU_STATE_MASK 0x04 + +/* BD7181X_REG_FULL_CTRL bits */ +#define FULL_CLR 0x10 + +/* BD7181X_REG_LED_CTRL bits */ +#define CHGDONE_LED_EN 0x10 + +/** @brief charge state enumuration */ +enum CHG_STATE { + CHG_STATE_SUSPEND = 0x0, /**< suspend state */ + CHG_STATE_TRICKLE_CHARGE, /**< trickle charge state */ + CHG_STATE_PRE_CHARGE, /**< precharge state */ + CHG_STATE_FAST_CHARGE, /**< fast charge state */ + CHG_STATE_TOP_OFF, /**< top off state */ + CHG_STATE_DONE, /**< charge complete */ +}; + +/** @brief rtc or alarm registers structure */ +struct bd7181x_rtc_alarm { + u8 sec; + u8 min; + u8 hour; + u8 week; + u8 day; + u8 month; + u8 year; +}; + +struct bd7181x; + +/** + * @brief Board platform data may be used to initialize regulators. + */ + +struct bd7181x_board { + struct regulator_init_data *init_data[BD7181X_REGULATOR_CNT]; + /**< regulator initialize data */ + int gpio_intr; /**< gpio connected to bd7181x INTB */ + int irq_base; /**< bd7181x sub irqs base # */ +}; + +/** + * @brief bd7181x sub-driver chip access routines + */ + +struct bd7181x { + struct device *dev; + struct i2c_client *i2c_client; + struct regmap *regmap; + struct mutex io_mutex; + unsigned int id; + + /* IRQ Handling */ + int chip_irq; /**< bd7181x irq to host cpu */ + struct regmap_irq_chip_data *irq_data; + + /* Client devices */ + struct bd7181x_pmic *pmic; /**< client device regulator */ + struct bd7181x_power *power; /**< client device battery */ + + struct bd7181x_board *of_plat_data; + /**< Device node parsed board data */ +}; + +static inline int bd7181x_chip_id(struct bd7181x *bd7181x) +{ + return bd7181x->id; +} + + +/** + * @brief bd7181x_reg_read + * read single register's value of bd7181x + * @param bd7181x device to read + * @param reg register address + * @return register value if success + * error number if fail + */ +static inline int bd7181x_reg_read(struct bd7181x *bd7181x, u8 reg) +{ + int r, val; + + r = regmap_read(bd7181x->regmap, reg, &val); + if (r < 0) { + return r; + } + return val; +} + +/** + * @brief bd7181x_reg_write + * write single register of bd7181x + * @param bd7181x device to write + * @param reg register address + * @param val value to write + * @retval 0 if success + * @retval negative error number if fail + */ + +static inline int bd7181x_reg_write(struct bd7181x *bd7181x, u8 reg, + unsigned int val) +{ + return regmap_write(bd7181x->regmap, reg, val); +} + +/** + * @brief bd7181x_set_bits + * set bits in one register of bd7181x + * @param bd7181x device to read + * @param reg register address + * @param mask mask bits + * @retval 0 if success + * @retval negative error number if fail + */ +static inline int bd7181x_set_bits(struct bd7181x *bd7181x, u8 reg, + u8 mask) +{ + return regmap_update_bits(bd7181x->regmap, reg, mask, mask); +} + +/** + * @brief bd7181x_clear_bits + * clear bits in one register of bd7181x + * @param bd7181x device to read + * @param reg register address + * @param mask mask bits + * @retval 0 if success + * @retval negative error number if fail + */ + +static inline int bd7181x_clear_bits(struct bd7181x *bd7181x, u8 reg, + u8 mask) +{ + return regmap_update_bits(bd7181x->regmap, reg, mask, 0); +} + +/** + * @brief bd7181x_update_bits + * update bits in one register of bd7181x + * @param bd7181x device to read + * @param reg register address + * @param mask mask bits + * @param val value to update + * @retval 0 if success + * @retval negative error number if fail + */ + +static inline int bd7181x_update_bits(struct bd7181x *bd7181x, u8 reg, + u8 mask, u8 val) +{ + return regmap_update_bits(bd7181x->regmap, reg, mask, val); +} + +/** + * @brief bd7181x platform data type + */ +struct bd7181x_gpo_plat_data { + u32 mode; ///< gpo output mode + int gpio_base; ///< base gpio number in system +}; + +#define BD7181X_DBG0 0x0001 +#define BD7181X_DBG1 0x0002 +#define BD7181X_DBG2 0x0004 +#define BD7181X_DBG3 0x0008 + +extern unsigned int bd7181x_debug_mask; +#define bd7181x_debug(debug, fmt, arg...) do { if(debug & bd7181x_debug_mask) printk("BD7181x:" fmt, ##arg);} while(0) + +#endif /* __LINUX_MFD_BD7181X_H */