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

330 lines
7.5 KiB
C
Raw Normal View History

2018-07-13 01:31:50 +00:00
/*
* drivers/power/axp/axp80x/axp80x.c
* (C) Copyright 2010-2016
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Pannan <pannan@allwinnertech.com>
*
* driver of axp80x
*
* 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/of_irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mfd/core.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/err.h>
#include <linux/power/aw_pm.h>
#include "../axp-core.h"
#include "axp80x.h"
static struct axp_dev *axp80x_pm_power;
struct axp_config_info axp80x_config;
struct wakeup_source *axp80x_ws;
static int axp80x_pmu_num;
static const struct axp_compatible_name_mapping axp80x_cn_mapping[] = {
{
.device_name = "axp806",
.mfd_name = {
.powerkey_name = "axp806-powerkey",
.regulator_name = "axp806-regulator",
.gpio_name = "axp806-gpio",
},
},
{
.device_name = "axp808",
.mfd_name = {
.powerkey_name = "axp808-powerkey",
.regulator_name = "axp808-regulator",
.gpio_name = "axp808-gpio",
},
},
};
static struct axp_regmap_irq_chip axp80x_regmap_irq_chip = {
.name = "axp80x_irq_chip",
.status_base = AXP80X_INTSTS1,
.enable_base = AXP80X_INTEN1,
.num_regs = 2,
};
static struct resource axp80x_pek_resources[] = {
{AXP80X_IRQ_PEKRE, AXP80X_IRQ_PEKRE, "PEK_DBR", IORESOURCE_IRQ,},
{AXP80X_IRQ_PEKFE, AXP80X_IRQ_PEKFE, "PEK_DBF", IORESOURCE_IRQ,},
};
static struct mfd_cell axp80x_cells[] = {
{
.name = "axp80x-powerkey",
.num_resources = ARRAY_SIZE(axp80x_pek_resources),
.resources = axp80x_pek_resources,
},
{
.name = "axp80x-regulator",
},
};
void axp80x_power_off(void)
{
pr_info("[axp] send power-off command!\n");
axp_regmap_set_bits(axp80x_pm_power->regmap, AXP80X_OFF_CTL,
0x80);
mdelay(20);
pr_warn("[axp] warning!!! axp can't power-off!\n");
}
static int axp80x_init_chip(struct axp_dev *axp80x)
{
uint8_t chip_id;
int err;
err = axp_regmap_read(axp80x->regmap, AXP80X_IC_TYPE, &chip_id);
if (err) {
pr_err("[%s] try to read chip id failed!\n",
axp_name[axp80x_pmu_num]);
return err;
}
if (((((chip_id & 0xc0) >> 6) << 4) | (chip_id & 0xf)) == 0x10)
pr_info("[%s] chip id detect 0x%x !\n",
axp_name[axp80x_pmu_num], chip_id);
else
pr_info("[%s] chip id not detect 0x%x !\n",
axp_name[axp80x_pmu_num], chip_id);
/*init irq wakeup en*/
if (axp80x_config.pmu_irq_wakeup)
axp_regmap_set_bits(axp80x->regmap,
AXP80X_IFQ_PWROK_SET, 0x80);
else
axp_regmap_clr_bits(axp80x->regmap,
AXP80X_IFQ_PWROK_SET, 0x80);
/*init pmu over temperature protection*/
if (axp80x_config.pmu_hot_shutdown)
axp_regmap_set_bits(axp80x->regmap, AXP80X_OFF_CTL, 0x02);
else
axp_regmap_clr_bits(axp80x->regmap, AXP80X_OFF_CTL, 0x02);
return 0;
}
static void axp80x_wakeup_event(void)
{
__pm_wakeup_event(axp80x_ws, 0);
}
static s32 axp80x_usb_det(void)
{
return 0;
}
static s32 axp80x_usb_vbus_output(int high)
{
return 0;
}
static int axp80x_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;
}
if (api != NULL) {
#ifdef CONFIG_AXP_TWI_USED
api->pmu_arg.twi_port = axp80x_pm_power->regmap->client->adapter->nr;
api->pmu_arg.dev_addr = axp80x_pm_power->regmap->client->addr;
#else
api->pmu_arg.twi_port = 0;
api->pmu_arg.dev_addr = 0x3;
#endif
}
*pmu_id = axp80x_config.pmu_id;
return 0;
}
static const char *axp80x_get_pmu_name(void)
{
return axp_name[axp80x_pmu_num];
}
static struct axp_dev *axp80x_get_pmu_dev(void)
{
return axp80x_pm_power;
}
static struct axp_platform_ops axp80x_platform_ops = {
.usb_det = axp80x_usb_det,
.usb_vbus_output = axp80x_usb_vbus_output,
.cfg_pmux_para = axp80x_cfg_pmux_para,
.get_pmu_name = axp80x_get_pmu_name,
.get_pmu_dev = axp80x_get_pmu_dev,
};
static int axp80x_probe(struct platform_device *pdev)
{
int ret;
struct axp_dev *axp80x;
struct device_node *node = pdev->dev.of_node;
axp80x_pmu_num = axp_get_pmu_num(axp80x_cn_mapping,
ARRAY_SIZE(axp80x_cn_mapping));
if (axp80x_pmu_num < 0) {
pr_err("%s get pmu num failed\n", __func__);
return axp80x_pmu_num;
}
if (!node) {
pr_err("AXP80x device tree err!\n");
return -EBUSY;
}
/* get dt and sysconfig */
if (!of_device_is_available(node)) {
axp80x_config.pmu_used = 0;
pr_err("%s: pmu_used = %u\n", __func__, axp80x_config.pmu_used);
return -EPERM;
}
axp80x_config.pmu_used = 1;
ret = axp_dt_parse(node,
axp80x_pmu_num, &axp80x_config);
if (ret) {
pr_err("%s parse device tree err\n", __func__);
return -EINVAL;
}
axp80x = devm_kzalloc(&pdev->dev, sizeof(*axp80x), GFP_KERNEL);
if (!axp80x)
return -ENOMEM;
axp80x->dev = &pdev->dev;
axp80x->nr_cells = ARRAY_SIZE(axp80x_cells);
axp80x->cells = axp80x_cells;
axp80x->pmu_num = axp80x_pmu_num;
ret = axp_mfd_cell_name_init(axp80x_cn_mapping,
ARRAY_SIZE(axp80x_cn_mapping), axp80x->pmu_num,
axp80x->nr_cells, axp80x->cells);
if (ret)
return ret;
axp80x->regmap = axp_regmap_init_arisc_twi(&pdev->dev);
if (IS_ERR(axp80x->regmap)) {
ret = PTR_ERR(axp80x->regmap);
dev_err(&pdev->dev, "regmap init failed: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, axp80x);
ret = axp80x_init_chip(axp80x);
if (ret)
return ret;
ret = axp_mfd_add_devices(axp80x);
if (ret) {
dev_err(axp80x->dev, "failed to add MFD devices: %d\n", ret);
return ret;
}
axp80x->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
axp80x->irq_data = axp_irq_chip_register(axp80x->regmap, axp80x->irq,
IRQF_SHARED
| IRQF_NO_SUSPEND,
&axp80x_regmap_irq_chip,
axp80x_wakeup_event);
if (IS_ERR(axp80x->irq_data)) {
ret = PTR_ERR(axp80x->irq_data);
dev_err(&pdev->dev, "axp init irq failed: %d\n", ret);
return ret;
}
axp80x_pm_power = axp80x;
if (!pm_power_off)
pm_power_off = axp80x_power_off;
axp_platform_ops_set(axp80x->pmu_num, &axp80x_platform_ops);
axp80x_ws = wakeup_source_register("axp80x_wakeup_source");
return 0;
}
static int axp80x_remove(struct platform_device *pdev)
{
struct axp_dev *axp80x = platform_get_drvdata(pdev);
if (axp80x == axp80x_pm_power) {
axp80x_pm_power = NULL;
pm_power_off = NULL;
}
axp_mfd_remove_devices(axp80x);
axp_irq_chip_unregister(axp80x->irq, axp80x->irq_data);
return 0;
}
static const struct of_device_id axp80x_dt_ids[] = {
{ .compatible = "axp806", },
{ .compatible = "axp808", },
{},
};
MODULE_DEVICE_TABLE(of, axp80x_dt_ids);
static struct platform_driver axp80x_driver = {
.driver = {
.name = "axp80x",
.owner = THIS_MODULE,
.of_match_table = axp80x_dt_ids,
},
.probe = axp80x_probe,
.remove = axp80x_remove,
};
static int __init axp80x_init(void)
{
int ret;
ret = platform_driver_register(&axp80x_driver);
if (ret != 0)
pr_err("Failed to register axp80x driver: %d\n", ret);
return ret;
}
subsys_initcall(axp80x_init);
static void __exit axp80x_exit(void)
{
platform_driver_unregister(&axp80x_driver);
}
module_exit(axp80x_exit);
MODULE_DESCRIPTION("Driver of AXP80X");
MODULE_AUTHOR("pannan");
MODULE_LICENSE("GPL");