509 lines
13 KiB
C
509 lines
13 KiB
C
/*
|
|
* drivers/power/axp/axp803/axp803.c
|
|
* (C) Copyright 2010-2016
|
|
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
* Pannan <pannan@allwinnertech.com>
|
|
*
|
|
* driver of axp803
|
|
*
|
|
* 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 "axp803.h"
|
|
#include "axp803-regulator.h"
|
|
|
|
static struct axp_dev *axp803_pm_power;
|
|
struct axp_config_info axp803_config;
|
|
struct wakeup_source *axp803_ws;
|
|
static int axp803_pmu_num;
|
|
|
|
static const struct axp_compatible_name_mapping axp803_cn_mapping[] = {
|
|
{
|
|
.device_name = "axp803",
|
|
.mfd_name = {
|
|
.powerkey_name = "axp803-powerkey",
|
|
.charger_name = "axp803-charger",
|
|
.regulator_name = "axp803-regulator",
|
|
.gpio_name = "axp803-gpio",
|
|
},
|
|
},
|
|
};
|
|
|
|
static struct axp_regmap_irq_chip axp803_regmap_irq_chip = {
|
|
.name = "axp803_irq_chip",
|
|
.status_base = AXP803_INTSTS1,
|
|
.enable_base = AXP803_INTEN1,
|
|
.num_regs = 6,
|
|
};
|
|
|
|
static struct resource axp803_pek_resources[] = {
|
|
{AXP803_IRQ_PEKRE, AXP803_IRQ_PEKRE, "PEK_DBR", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_PEKFE, AXP803_IRQ_PEKFE, "PEK_DBF", IORESOURCE_IRQ,},
|
|
};
|
|
|
|
static struct resource axp803_charger_resources[] = {
|
|
{AXP803_IRQ_USBIN, AXP803_IRQ_USBIN, "usb in", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_USBRE, AXP803_IRQ_USBRE, "usb out", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_ACIN, AXP803_IRQ_ACIN, "ac in", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_ACRE, AXP803_IRQ_ACRE, "ac out", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_BATIN, AXP803_IRQ_BATIN, "bat in", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_BATRE, AXP803_IRQ_BATRE, "bat out", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_BATINWORK, AXP803_IRQ_BATINWORK, "bat untemp work", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_BATOVWORK, AXP803_IRQ_BATOVWORK, "bat ovtemp work", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_QBATINCHG, AXP803_IRQ_QBATINCHG, "quit bat untemp chg", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_BATINCHG, AXP803_IRQ_BATINCHG, "bat untemp chg", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_QBATOVCHG, AXP803_IRQ_QBATOVCHG, "quit bat ovtemp chg", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_BATOVCHG, AXP803_IRQ_BATOVCHG, "bat ovtemp chg", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_CHAST, AXP803_IRQ_CHAST, "charging", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_CHAOV, AXP803_IRQ_CHAOV, "charge over", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_LOWN1, AXP803_IRQ_LOWN1, "low warning1", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_LOWN2, AXP803_IRQ_LOWN2, "low warning2", IORESOURCE_IRQ,},
|
|
};
|
|
|
|
static struct resource axp803_gpio_resources[] = {
|
|
{AXP803_IRQ_GPIO0, AXP803_IRQ_GPIO0, "gpio0", IORESOURCE_IRQ,},
|
|
{AXP803_IRQ_GPIO1, AXP803_IRQ_GPIO1, "gpio1", IORESOURCE_IRQ,},
|
|
};
|
|
|
|
static struct mfd_cell axp803_cells[] = {
|
|
{
|
|
.name = "axp803-powerkey",
|
|
.num_resources = ARRAY_SIZE(axp803_pek_resources),
|
|
.resources = axp803_pek_resources,
|
|
},
|
|
{
|
|
.name = "axp803-regulator",
|
|
},
|
|
{
|
|
.name = "axp803-charger",
|
|
.num_resources = ARRAY_SIZE(axp803_charger_resources),
|
|
.resources = axp803_charger_resources,
|
|
},
|
|
{
|
|
.name = "axp803-gpio",
|
|
.num_resources = ARRAY_SIZE(axp803_gpio_resources),
|
|
.resources = axp803_gpio_resources,
|
|
},
|
|
};
|
|
|
|
void axp803_power_off(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
pr_info("[axp] send power-off command!\n");
|
|
mdelay(20);
|
|
|
|
if (axp803_config.power_start != 1) {
|
|
axp_regmap_read(axp803_pm_power->regmap, AXP803_STATUS, &val);
|
|
if (val & 0xF0) {
|
|
axp_regmap_read(axp803_pm_power->regmap,
|
|
AXP803_MODE_CHGSTATUS, &val);
|
|
if ((axp803_config.pmu_bat_unused == 0)
|
|
&& (val & 0x20)
|
|
&& (val & 0x10)) {
|
|
pr_info("[axp] set flag!\n");
|
|
axp_regmap_read(axp803_pm_power->regmap,
|
|
AXP803_BUFFERC, &val);
|
|
if (0x0d != val)
|
|
axp_regmap_write(axp803_pm_power->regmap,
|
|
AXP803_BUFFERC, 0x0f);
|
|
|
|
mdelay(20);
|
|
pr_info("[axp] reboot!\n");
|
|
machine_restart(NULL);
|
|
pr_warn("[axp] warning!!! arch can't reboot,\"\
|
|
\" maybe some error happened!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
axp_regmap_read(axp803_pm_power->regmap, AXP803_BUFFERC, &val);
|
|
if (val != 0x0d)
|
|
axp_regmap_write(axp803_pm_power->regmap, AXP803_BUFFERC, 0x00);
|
|
mdelay(20);
|
|
|
|
axp_regmap_set_bits(axp803_pm_power->regmap, AXP803_OFF_CTL, 0x80);
|
|
mdelay(20);
|
|
|
|
pr_warn("[axp] warning!!! arch can't reboot,\"\
|
|
\" maybe some error happened!\n");
|
|
}
|
|
|
|
static int axp803_init_chip(struct axp_dev *axp803)
|
|
{
|
|
uint8_t chip_id, dcdc2_ctl;
|
|
int err;
|
|
|
|
err = axp_regmap_read(axp803->regmap, AXP803_IC_TYPE, &chip_id);
|
|
if (err) {
|
|
pr_err("[%s] try to read chip id failed!\n",
|
|
axp_name[axp803_pmu_num]);
|
|
return err;
|
|
}
|
|
|
|
if (((chip_id & 0xc0) == 0x40) && ((chip_id & 0x0f) == 0x01))
|
|
pr_info("[%s] chip id detect 0x%x !\n",
|
|
axp_name[axp803_pmu_num], chip_id);
|
|
else
|
|
pr_info("[%s] chip id not detect 0x%x !\n",
|
|
axp_name[axp803_pmu_num], chip_id);
|
|
|
|
/* enable dcdc2 dvm */
|
|
err = axp_regmap_read(axp803->regmap, AXP803_DCDC_DVM_CTL, &dcdc2_ctl);
|
|
if (err) {
|
|
pr_err("[%s] try to read dcdc dvm failed!\n",
|
|
axp_name[axp803_pmu_num]);
|
|
return err;
|
|
}
|
|
|
|
dcdc2_ctl |= (0x1<<2);
|
|
err = axp_regmap_write(axp803->regmap, AXP803_DCDC_DVM_CTL, dcdc2_ctl);
|
|
if (err) {
|
|
pr_err("[%s] try to enable dcdc2 dvm failed!\n",
|
|
axp_name[axp803_pmu_num]);
|
|
return err;
|
|
}
|
|
pr_info("[%s] enable dcdc2 dvm.\n", axp_name[axp803_pmu_num]);
|
|
|
|
/* init N_VBUSEN status */
|
|
if (axp803_config.pmu_vbusen_func)
|
|
axp_regmap_set_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x10);
|
|
else
|
|
axp_regmap_clr_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x10);
|
|
|
|
/* init 16's reset pmu en */
|
|
if (axp803_config.pmu_reset)
|
|
axp_regmap_set_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x08);
|
|
else
|
|
axp_regmap_clr_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x08);
|
|
|
|
/* init irq wakeup en */
|
|
if (axp803_config.pmu_irq_wakeup)
|
|
axp_regmap_set_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x80);
|
|
else
|
|
axp_regmap_clr_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x80);
|
|
|
|
/* init pmu over temperature protection */
|
|
if (axp803_config.pmu_hot_shutdown)
|
|
axp_regmap_set_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x04);
|
|
else
|
|
axp_regmap_clr_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x04);
|
|
|
|
/* init inshort status */
|
|
if (axp803_config.pmu_inshort)
|
|
axp_regmap_set_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x60);
|
|
else
|
|
axp_regmap_clr_bits(axp803->regmap, AXP803_HOTOVER_CTL, 0x60);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void axp803_wakeup_event(void)
|
|
{
|
|
__pm_wakeup_event(axp803_ws, 0);
|
|
}
|
|
|
|
static s32 axp803_usb_det(void)
|
|
{
|
|
u8 value = 0;
|
|
int ret = 0;
|
|
|
|
axp_regmap_read(axp803_pm_power->regmap, AXP803_STATUS, &value);
|
|
|
|
if (value & 0x10) {
|
|
axp_usb_connect = 1;
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static s32 axp803_usb_vbus_output(int high)
|
|
{
|
|
u8 ret = 0;
|
|
|
|
ret = axp_regmap_clr_bits_sync(axp803_pm_power->regmap,
|
|
AXP803_HOTOVER_CTL, 0x10);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (high)
|
|
ret = axp_regmap_set_bits_sync(axp803_pm_power->regmap,
|
|
AXP803_IPS_SET, 0x04);
|
|
else
|
|
ret = axp_regmap_clr_bits_sync(axp803_pm_power->regmap,
|
|
AXP803_IPS_SET, 0x04);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp803_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 (np == NULL) {
|
|
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 = axp803_pm_power->regmap->client->adapter->nr;
|
|
api->pmu_arg.dev_addr = axp803_pm_power->regmap->client->addr;
|
|
}
|
|
#endif
|
|
*pmu_id = axp803_config.pmu_id;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *axp803_get_pmu_name(void)
|
|
{
|
|
return axp_name[axp803_pmu_num];
|
|
}
|
|
|
|
static struct axp_dev *axp803_get_pmu_dev(void)
|
|
{
|
|
return axp803_pm_power;
|
|
}
|
|
|
|
struct axp_platform_ops axp803_platform_ops = {
|
|
.usb_det = axp803_usb_det,
|
|
.usb_vbus_output = axp803_usb_vbus_output,
|
|
.cfg_pmux_para = axp803_cfg_pmux_para,
|
|
.get_pmu_name = axp803_get_pmu_name,
|
|
.get_pmu_dev = axp803_get_pmu_dev,
|
|
};
|
|
|
|
static const struct of_device_id axp803_dt_ids[] = {
|
|
{ .compatible = "axp803", },
|
|
{ .compatible = "axp288", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, axp803_dt_ids);
|
|
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
static int axp803_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
#else
|
|
static int axp803_probe(struct platform_device *pdev)
|
|
#endif
|
|
{
|
|
int ret;
|
|
struct axp_dev *axp803;
|
|
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
|
|
|
|
axp803_pmu_num = axp_get_pmu_num(axp803_cn_mapping,
|
|
ARRAY_SIZE(axp803_cn_mapping));
|
|
if (axp803_pmu_num < 0) {
|
|
pr_err("%s get pmu num failed\n", __func__);
|
|
return axp803_pmu_num;
|
|
}
|
|
|
|
if (node) {
|
|
/* get dt and sysconfig */
|
|
if (!of_device_is_available(node)) {
|
|
axp803_config.pmu_used = 0;
|
|
pr_err("%s: pmu_used = %u\n", __func__,
|
|
axp803_config.pmu_used);
|
|
return -EPERM;
|
|
} else {
|
|
axp803_config.pmu_used = 1;
|
|
ret = axp_dt_parse(node, axp803_pmu_num,
|
|
&axp803_config);
|
|
if (ret) {
|
|
pr_err("%s parse device tree err\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
} else {
|
|
pr_err("axp803x device tree err!\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
axp803 = devm_kzalloc(device, sizeof(*axp803), GFP_KERNEL);
|
|
if (!axp803)
|
|
return -ENOMEM;
|
|
|
|
axp803->dev = device;
|
|
axp803->nr_cells = ARRAY_SIZE(axp803_cells);
|
|
axp803->cells = axp803_cells;
|
|
axp803->pmu_num = axp803_pmu_num;
|
|
|
|
ret = axp_mfd_cell_name_init(axp803_cn_mapping,
|
|
ARRAY_SIZE(axp803_cn_mapping), axp803->pmu_num,
|
|
axp803->nr_cells, axp803->cells);
|
|
if (ret)
|
|
return ret;
|
|
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
axp803->regmap = axp_regmap_init_i2c(device);
|
|
#else
|
|
axp803->regmap = axp_regmap_init_arisc_rsb(device, AXP803_RSB_RTSADDR);
|
|
#endif
|
|
if (IS_ERR(axp803->regmap)) {
|
|
ret = PTR_ERR(axp803->regmap);
|
|
dev_err(device, "regmap init failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
i2c_set_clientdata(client, axp803);
|
|
#else
|
|
platform_set_drvdata(pdev, axp803);
|
|
#endif
|
|
ret = axp803_init_chip(axp803);
|
|
if (ret)
|
|
return ret;
|
|
|
|
axp803_pm_power = axp803;
|
|
|
|
axp_platform_ops_set(axp803->pmu_num, &axp803_platform_ops);
|
|
|
|
ret = axp_mfd_add_devices(axp803);
|
|
if (ret) {
|
|
dev_err(axp803->dev, "failed to add MFD devices: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
axp803->irq = client->irq;
|
|
#else
|
|
axp803->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
|
|
#endif
|
|
axp803->irq_data = axp_irq_chip_register(axp803->regmap, axp803->irq,
|
|
IRQF_SHARED
|
|
| IRQF_NO_SUSPEND,
|
|
&axp803_regmap_irq_chip,
|
|
axp803_wakeup_event);
|
|
if (IS_ERR(axp803->irq_data)) {
|
|
ret = PTR_ERR(axp803->irq_data);
|
|
dev_err(device, "axp init irq failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (!pm_power_off)
|
|
pm_power_off = axp803_power_off;
|
|
|
|
axp803_ws = wakeup_source_register("axp803_wakeup_source");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
static int axp803_remove(struct i2c_client *client)
|
|
#else
|
|
static int axp803_remove(struct platform_device *pdev)
|
|
#endif
|
|
{
|
|
struct axp_dev *axp803;
|
|
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
axp803 = i2c_get_clientdata(client);
|
|
#else
|
|
axp803 = platform_get_drvdata(pdev);
|
|
#endif
|
|
|
|
if (axp803 == axp803_pm_power) {
|
|
axp803_pm_power = NULL;
|
|
pm_power_off = NULL;
|
|
}
|
|
|
|
axp_mfd_remove_devices(axp803);
|
|
axp_irq_chip_unregister(axp803->irq, axp803->irq_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id axp803_id_table[] = {
|
|
{ "axp803", 0 },
|
|
{ "axp288", 0 },
|
|
{}
|
|
};
|
|
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
static struct i2c_driver axp803_driver = {
|
|
#else
|
|
static struct platform_driver axp803_driver = {
|
|
#endif
|
|
.driver = {
|
|
.name = "axp803",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = axp803_dt_ids,
|
|
},
|
|
.probe = axp803_probe,
|
|
.remove = axp803_remove,
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
.id_table = axp803_id_table,
|
|
#endif
|
|
};
|
|
|
|
static int __init axp803_init(void)
|
|
{
|
|
int ret;
|
|
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
ret = i2c_add_driver(&axp803_driver);
|
|
#else
|
|
ret = platform_driver_register(&axp803_driver);
|
|
#endif
|
|
if (ret != 0)
|
|
pr_err("Failed to register axp803x driver: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
subsys_initcall_sync(axp803_init);
|
|
|
|
static void __exit axp803_exit(void)
|
|
{
|
|
#ifdef CONFIG_AXP_TWI_USED
|
|
i2c_del_driver(&axp803_driver);
|
|
#else
|
|
platform_driver_unregister(&axp803_driver);
|
|
#endif
|
|
}
|
|
module_exit(axp803_exit);
|
|
|
|
MODULE_DESCRIPTION("Driver of AXP803");
|
|
MODULE_AUTHOR("pannan");
|
|
MODULE_LICENSE("GPL");
|