SmartAudio/lichee/linux-4.9/drivers/power/supply/axp/axp22x/axp22x.c

557 lines
14 KiB
C
Raw Normal View History

2018-07-13 01:31:50 +00:00
/*
* drivers/power/axp/axp22x/axp22x.c
* (C) Copyright 2010-2016
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Pannan <pannan@allwinnertech.com>
*
* driver of axp22x
*
* 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.
*
*/
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/reboot.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/mfd/core.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/err.h>
#include <linux/power/aw_pm.h>
#include "../axp-core.h"
#include "../axp-charger.h"
#include "../axp-regulator.h"
#include "axp22x.h"
#include "axp22x-regulator.h"
static struct axp_dev *axp22x_pm_power;
struct axp_config_info axp22x_config;
struct wakeup_source *axp22x_ws;
static int axp22x_pmu_num;
static const struct axp_compatible_name_mapping axp22x_cn_mapping[] = {
{
.device_name = "axp221s",
.mfd_name = {
.powerkey_name = "axp221s-powerkey",
.charger_name = "axp221s-charger",
.regulator_name = "axp221s-regulator",
.gpio_name = "axp221s-gpio",
},
},
2018-12-13 10:48:25 +00:00
{
.device_name = "axp223",
.mfd_name = {
.powerkey_name = "axp223-powerkey",
.charger_name = "axp223-charger",
.regulator_name = "axp223-regulator",
.gpio_name = "axp223-gpio",
},
},
2018-07-13 01:31:50 +00:00
{
.device_name = "axp227",
.mfd_name = {
.powerkey_name = "axp227-powerkey",
.charger_name = "axp227-charger",
.regulator_name = "axp227-regulator",
.gpio_name = "axp227-gpio",
},
},
{
.device_name = "axp233",
.mfd_name = {
.powerkey_name = "axp233-powerkey",
.charger_name = "axp233-charger",
.regulator_name = "axp233-regulator",
.gpio_name = "axp233-gpio",
},
}
};
static struct axp_regmap_irq_chip axp22x_regmap_irq_chip = {
.name = "axp22x_irq_chip",
.status_base = AXP22X_INTSTS1,
.enable_base = AXP22X_INTEN1,
.num_regs = 5,
};
static struct resource axp22x_pek_resources[] = {
{AXP22X_IRQ_PEKRE, AXP22X_IRQ_PEKRE, "PEK_DBR", IORESOURCE_IRQ,},
{AXP22X_IRQ_PEKFE, AXP22X_IRQ_PEKFE, "PEK_DBF", IORESOURCE_IRQ,},
};
static struct resource axp22x_charger_resources[] = {
{AXP22X_IRQ_USBIN, AXP22X_IRQ_USBIN, "usb in", IORESOURCE_IRQ,},
{AXP22X_IRQ_USBRE, AXP22X_IRQ_USBRE, "usb out", IORESOURCE_IRQ,},
{AXP22X_IRQ_ACIN, AXP22X_IRQ_ACIN, "ac in", IORESOURCE_IRQ,},
{AXP22X_IRQ_ACRE, AXP22X_IRQ_ACRE, "ac out", IORESOURCE_IRQ,},
{AXP22X_IRQ_BATIN, AXP22X_IRQ_BATIN, "bat in", IORESOURCE_IRQ,},
{AXP22X_IRQ_BATRE, AXP22X_IRQ_BATRE, "bat out", IORESOURCE_IRQ,},
#ifdef CONFIG_AW_AXP233
{AXP22X_IRQ_QBWUT, AXP22X_IRQ_QBWUT, "bat QWtemplow", IORESOURCE_IRQ,},
{AXP22X_IRQ_BWUT, AXP22X_IRQ_BWUT, "bat Wtemplow", IORESOURCE_IRQ,},
{AXP22X_IRQ_QBWOT, AXP22X_IRQ_QBWOT, "bat QWtempover", IORESOURCE_IRQ,},
{AXP22X_IRQ_BWUT, AXP22X_IRQ_BWUT, "bat Wtempover", IORESOURCE_IRQ,},
{AXP22X_IRQ_QBCUT, AXP22X_IRQ_QBCUT, "bat QCtemplow", IORESOURCE_IRQ,},
{AXP22X_IRQ_BCUT, AXP22X_IRQ_BCUT, "bat Ctemplow", IORESOURCE_IRQ,},
{AXP22X_IRQ_QBCOT, AXP22X_IRQ_QBCOT, "bat QCtempover", IORESOURCE_IRQ,},
{AXP22X_IRQ_BCOT, AXP22X_IRQ_BCOT, "bat Ctempover", IORESOURCE_IRQ,},
#else
{AXP22X_IRQ_TEMLO, AXP22X_IRQ_TEMLO, "bat templow", IORESOURCE_IRQ,},
{AXP22X_IRQ_TEMOV, AXP22X_IRQ_TEMOV, "bat tempover", IORESOURCE_IRQ,},
#endif
{AXP22X_IRQ_CHAST, AXP22X_IRQ_CHAST, "charging", IORESOURCE_IRQ,},
{AXP22X_IRQ_CHAOV, AXP22X_IRQ_CHAOV, "charge over", IORESOURCE_IRQ,},
{AXP22X_IRQ_LOWN1, AXP22X_IRQ_LOWN1, "low warning1", IORESOURCE_IRQ,},
{AXP22X_IRQ_LOWN2, AXP22X_IRQ_LOWN2, "low warning2", IORESOURCE_IRQ,},
};
static struct resource axp22x_gpio_resources[] = {
{AXP22X_IRQ_GPIO0, AXP22X_IRQ_GPIO0, "gpio0", IORESOURCE_IRQ,},
{AXP22X_IRQ_GPIO1, AXP22X_IRQ_GPIO1, "gpio1", IORESOURCE_IRQ,},
};
static struct mfd_cell axp22x_cells[] = {
{
.name = "axp22x-powerkey",
.num_resources = ARRAY_SIZE(axp22x_pek_resources),
.resources = axp22x_pek_resources,
},
{
.name = "axp22x-regulator",
},
{
.name = "axp22x-charger",
.num_resources = ARRAY_SIZE(axp22x_charger_resources),
.resources = axp22x_charger_resources,
},
{
.name = "axp22x-gpio",
.num_resources = ARRAY_SIZE(axp22x_gpio_resources),
.resources = axp22x_gpio_resources,
},
};
void axp22x_power_off(void)
{
uint8_t val;
pr_info("[axp] send power-off command!\n");
mdelay(20);
if (axp22x_config.power_start != 1) {
axp_regmap_read(axp22x_pm_power->regmap, AXP22X_STATUS, &val);
if (val & 0xF0) {
axp_regmap_read(axp22x_pm_power->regmap,
AXP22X_MODE_CHGSTATUS, &val);
if ((axp22x_config.pmu_bat_unused == 0)
&& (val & 0x20)
&& (val & 0x10)) {
pr_info("[axp] set flag!\n");
axp_regmap_read(axp22x_pm_power->regmap,
AXP22X_BUFFERC, &val);
if (0x0d != val)
axp_regmap_write(
axp22x_pm_power->regmap,
AXP22X_BUFFERC, 0x0f);
mdelay(20);
pr_info("[axp] reboot!\n");
machine_restart(NULL);
pr_warn("[axp] warning!!! arch can't reboot,\"\
\" maybe some error happend!\n");
}
}
}
axp_regmap_read(axp22x_pm_power->regmap, AXP22X_BUFFERC, &val);
if (0x0d != val)
axp_regmap_write(axp22x_pm_power->regmap, AXP22X_BUFFERC, 0x00);
mdelay(20);
axp_regmap_set_bits(axp22x_pm_power->regmap, AXP22X_OFF_CTL, 0x80);
mdelay(20);
pr_warn("[axp] warning!!! axp can't power-off,\"\
\" maybe some error happend!\n");
}
static int axp22x_init_chip(struct axp_dev *axp22x)
{
uint8_t chip_id, dcdc2_ctl;
int err;
err = axp_regmap_read(axp22x->regmap, AXP22X_IC_TYPE, &chip_id);
if (err) {
pr_err("[%s] try to read chip id failed!\n",
axp_name[axp22x_pmu_num]);
return err;
}
if (((chip_id & 0xc0) == 0x00) &&
(((chip_id & 0x0f) == 0x06)
|| ((chip_id & 0x0f) == 0x07)
|| ((chip_id & 0xff) == 0x42))
) {
if ((chip_id & 0x0f) == 0x07)
axp22x_need_save_regulator = 1;
pr_info("[%s] chip id detect 0x%x !\n",
axp_name[axp22x_pmu_num], chip_id);
} else {
pr_info("[%s] chip id not detect 0x%x !\n",
axp_name[axp22x_pmu_num], chip_id);
}
/* enable dcdc2 dvm */
err = axp_regmap_read(axp22x->regmap, AXP22X_DC23_DVM_CTL, &dcdc2_ctl);
if (err) {
pr_err("[%s] try to read dcdc2 dvm failed!\n",
axp_name[axp22x_pmu_num]);
return err;
}
dcdc2_ctl |= (0x1<<2);
err = axp_regmap_write(axp22x->regmap, AXP22X_DC23_DVM_CTL, dcdc2_ctl);
if (err) {
pr_err("[%s] try to enable dcdc2 dvm failed!\n",
axp_name[axp22x_pmu_num]);
return err;
}
pr_info("[%s] enable dcdc2 dvm.\n",
axp_name[axp22x_pmu_num]);
/*init N_VBUSEN status*/
if (axp22x_config.pmu_vbusen_func)
axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x10);
else
axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x10);
/*init 16's reset pmu en */
if (axp22x_config.pmu_reset)
axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x08);
else
axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x08);
/*init irq wakeup en*/
if (axp22x_config.pmu_irq_wakeup)
axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x80);
else
axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x80);
/*init pmu over temperature protection*/
if (axp22x_config.pmu_hot_shutdown)
axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x04);
else
axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x04);
/*init inshort status*/
if (axp22x_config.pmu_inshort)
axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x60);
else
axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x60);
return 0;
}
static void axp22x_wakeup_event(void)
{
__pm_wakeup_event(axp22x_ws, 0);
}
static s32 axp22x_usb_det(void)
{
u8 value = 0;
int ret = 0;
axp_regmap_read(axp22x_pm_power->regmap, AXP22X_STATUS, &value);
if (value & 0x10) {
axp_usb_connect = 1;
ret = 1;
}
return ret;
}
static s32 axp22x_usb_vbus_output(int high)
{
u8 ret = 0;
ret = axp_regmap_clr_bits_sync(axp22x_pm_power->regmap,
AXP22X_HOTOVER_CTL, 0x10);
if (ret)
return ret;
if (high)
ret = axp_regmap_set_bits_sync(axp22x_pm_power->regmap,
AXP22X_IPS_SET, 0x04);
else
ret = axp_regmap_clr_bits_sync(axp22x_pm_power->regmap,
AXP22X_IPS_SET, 0x04);
return ret;
}
static int axp22x_cfg_pmux_para(int num, struct aw_pm_info *api, int *pmu_id)
{
char name[8];
struct device_node *np;
sprintf(name, "pmu%d", num);
np = of_find_node_by_type(NULL, name);
if (NULL == np) {
pr_err("can not find device_type for %s\n", name);
return -1;
}
if (!of_device_is_available(np)) {
pr_err("can not find node for %s\n", name);
return -1;
}
#ifdef CONFIG_AXP_TWI_USED
if (api != NULL) {
api->pmu_arg.twi_port = axp22x_pm_power->regmap->client->adapter->nr;
api->pmu_arg.dev_addr = axp22x_pm_power->regmap->client->addr;
}
#endif
*pmu_id = axp22x_config.pmu_id;
return 0;
}
static const char *axp22x_get_pmu_name(void)
{
return axp_name[axp22x_pmu_num];
}
static struct axp_dev *axp22x_get_pmu_dev(void)
{
return axp22x_pm_power;
}
struct axp_platform_ops axp22x_platform_ops = {
.usb_det = axp22x_usb_det,
.usb_vbus_output = axp22x_usb_vbus_output,
.cfg_pmux_para = axp22x_cfg_pmux_para,
.get_pmu_name = axp22x_get_pmu_name,
.get_pmu_dev = axp22x_get_pmu_dev,
.pmu_regulator_save = axp22x_regulator_save,
.pmu_regulator_restore = axp22x_regulator_restore,
};
static const struct i2c_device_id axp22x_id_table[] = {
{ "axp221s", 0 },
2018-12-13 10:48:25 +00:00
{ "axp223", 0 },
2018-07-13 01:31:50 +00:00
{ "axp227", 0 },
{ "axp233", 0 },
{}
};
static const struct of_device_id axp22x_dt_ids[] = {
{ .compatible = "axp221s", },
2018-12-13 10:48:25 +00:00
{ .compatible = "axp223", },
2018-07-13 01:31:50 +00:00
{ .compatible = "axp227", },
{ .compatible = "axp233", },
{},
};
MODULE_DEVICE_TABLE(of, axp22x_dt_ids);
#ifdef CONFIG_AXP_TWI_USED
static int axp22x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
#else
static int axp22x_probe(struct platform_device *pdev)
#endif
{
int ret;
struct axp_dev *axp22x;
struct device_node *node;
struct device *device;
#ifdef CONFIG_AXP_TWI_USED
node = client->dev.of_node;
device = &client->dev;
#else
node = pdev->dev.of_node;
device = &pdev->dev;
#endif
axp22x_pmu_num = axp_get_pmu_num(axp22x_cn_mapping,
ARRAY_SIZE(axp22x_cn_mapping));
if (axp22x_pmu_num < 0) {
pr_err("%s get pmu num failed\n", __func__);
return axp22x_pmu_num;
}
if (node) {
/* get dt and sysconfig */
if (!of_device_is_available(node)) {
axp22x_config.pmu_used = 0;
pr_err("%s: pmu_used = %u\n", __func__,
axp22x_config.pmu_used);
return -EPERM;
} else {
axp22x_config.pmu_used = 1;
ret = axp_dt_parse(node, axp22x_pmu_num,
&axp22x_config);
if (ret) {
pr_err("%s parse device tree err\n", __func__);
return -EINVAL;
}
}
} else {
pr_err("AXP22x device tree err!\n");
return -EBUSY;
}
axp22x = devm_kzalloc(device, sizeof(*axp22x), GFP_KERNEL);
if (!axp22x)
return -ENOMEM;
axp22x->dev = device;
axp22x->nr_cells = ARRAY_SIZE(axp22x_cells);
axp22x->cells = axp22x_cells;
axp22x->pmu_num = axp22x_pmu_num;
ret = axp_mfd_cell_name_init(axp22x_cn_mapping,
ARRAY_SIZE(axp22x_cn_mapping), axp22x->pmu_num,
axp22x->nr_cells, axp22x->cells);
if (ret)
return ret;
#ifdef CONFIG_AXP_TWI_USED
axp22x->regmap = axp_regmap_init_i2c(device);
#else
axp22x->regmap = axp_regmap_init_arisc_rsb(device, AXP22X_RSB_RTSADDR);
#endif
if (IS_ERR(axp22x->regmap)) {
ret = PTR_ERR(axp22x->regmap);
dev_err(device, "regmap init failed: %d\n", ret);
return ret;
}
#ifdef CONFIG_AXP_TWI_USED
i2c_set_clientdata(client, axp22x);
#else
platform_set_drvdata(pdev, axp22x);
#endif
ret = axp22x_init_chip(axp22x);
if (ret)
return ret;
axp22x_pm_power = axp22x;
axp_platform_ops_set(axp22x->pmu_num, &axp22x_platform_ops);
ret = axp_mfd_add_devices(axp22x);
if (ret) {
dev_err(axp22x->dev, "failed to add MFD devices: %d\n", ret);
return ret;
}
#ifdef CONFIG_AXP_TWI_USED
axp22x->irq = client->irq;
#else
axp22x->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
#endif
axp22x->irq_data = axp_irq_chip_register(axp22x->regmap, axp22x->irq,
IRQF_SHARED
| IRQF_NO_SUSPEND,
&axp22x_regmap_irq_chip,
axp22x_wakeup_event);
if (IS_ERR(axp22x->irq_data)) {
ret = PTR_ERR(axp22x->irq_data);
dev_err(device, "axp init irq failed: %d\n", ret);
return ret;
}
if (!pm_power_off)
pm_power_off = axp22x_power_off;
axp22x_ws = wakeup_source_register("axp22_wakeup_source");
return 0;
}
#ifdef CONFIG_AXP_TWI_USED
static int axp22x_remove(struct i2c_client *client)
#else
static int axp22x_remove(struct platform_device *pdev)
#endif
{
struct axp_dev *axp22x;
#ifdef CONFIG_AXP_TWI_USED
axp22x = i2c_get_clientdata(client);
#else
axp22x = platform_get_drvdata(pdev);
#endif
if (axp22x == axp22x_pm_power) {
axp22x_pm_power = NULL;
pm_power_off = NULL;
}
axp_mfd_remove_devices(axp22x);
axp_irq_chip_unregister(axp22x->irq, axp22x->irq_data);
return 0;
}
#ifdef CONFIG_AXP_TWI_USED
static struct i2c_driver axp22x_driver = {
#else
static struct platform_driver axp22x_driver = {
#endif
.driver = {
.name = "axp22x",
.owner = THIS_MODULE,
.of_match_table = axp22x_dt_ids,
},
.probe = axp22x_probe,
.remove = axp22x_remove,
#ifdef CONFIG_AXP_TWI_USED
.id_table = axp22x_id_table,
#endif
};
static int __init axp22x_init(void)
{
int ret;
#ifdef CONFIG_AXP_TWI_USED
ret = i2c_add_driver(&axp22x_driver);
#else
ret = platform_driver_register(&axp22x_driver);
#endif
if (ret != 0)
pr_err("Failed to register axp22x I2C driver: %d\n", ret);
return ret;
}
subsys_initcall_sync(axp22x_init);
static void __exit axp22x_exit(void)
{
#ifdef CONFIG_AXP_TWI_USED
i2c_del_driver(&axp22x_driver);
#else
platform_driver_unregister(&axp22x_driver);
#endif
}
module_exit(axp22x_exit);
MODULE_DESCRIPTION("Driver of AXP22X");
MODULE_AUTHOR("pannan");
MODULE_LICENSE("GPL");