397 lines
11 KiB
C
397 lines
11 KiB
C
|
#define pr_fmt(x) KBUILD_MODNAME ": " x "\n"
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/init.h>
|
||
|
#include "linux/irq.h"
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/param.h>
|
||
|
#include <linux/jiffies.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/power_supply.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/ktime.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/timekeeping.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <asm/irq.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/pm_runtime.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/freezer.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/sunxi-gpio.h>
|
||
|
#include <linux/pinctrl/pinctrl.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include "linux/mfd/axp2101.h"
|
||
|
|
||
|
#include <linux/err.h>
|
||
|
//#include "../drivers/gpio/gpiolib.h"
|
||
|
#include "axp2202_charger.h"
|
||
|
|
||
|
struct axp2202_gpio_power {
|
||
|
char *name;
|
||
|
struct device *dev;
|
||
|
struct regmap *regmap;
|
||
|
struct power_supply *gpio_supply;
|
||
|
struct power_supply *usb_psy;
|
||
|
struct axp_config_info dts_info;
|
||
|
struct delayed_work gpio_supply_mon;
|
||
|
struct delayed_work gpio_chg_state;
|
||
|
struct gpio_config axp_acin_det;
|
||
|
};
|
||
|
|
||
|
|
||
|
static enum power_supply_property axp2202_gpio_props[] = {
|
||
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
||
|
POWER_SUPPLY_PROP_ONLINE,
|
||
|
};
|
||
|
|
||
|
static int axp2202_gpio_get_property(struct power_supply *psy,
|
||
|
enum power_supply_property psp,
|
||
|
union power_supply_propval *val)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power = power_supply_get_drvdata(psy);
|
||
|
|
||
|
int ret = 0;
|
||
|
|
||
|
switch (psp) {
|
||
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||
|
val->strval = psy->desc->name;
|
||
|
break;
|
||
|
case POWER_SUPPLY_PROP_ONLINE:
|
||
|
val->intval = __gpio_get_value(gpio_power->axp_acin_det.gpio);
|
||
|
break;
|
||
|
default:
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static const struct power_supply_desc axp2202_gpio_desc = {
|
||
|
.name = "axp2202-gpio",
|
||
|
.type = POWER_SUPPLY_TYPE_MAINS,
|
||
|
.get_property = axp2202_gpio_get_property,
|
||
|
.properties = axp2202_gpio_props,
|
||
|
.num_properties = ARRAY_SIZE(axp2202_gpio_props),
|
||
|
};
|
||
|
|
||
|
int axp2202_irq_handler_gpio_in(void *data)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power = data;
|
||
|
struct device_node *np = NULL;
|
||
|
union power_supply_propval temp;
|
||
|
|
||
|
pr_info("[acin_irq]axp2202_gpio_in\n");
|
||
|
power_supply_changed(gpio_power->gpio_supply);
|
||
|
if (gpio_power->usb_psy && (!IS_ERR(gpio_power->usb_psy))) {
|
||
|
temp.intval = 2500;
|
||
|
np = of_parse_phandle(gpio_power->dev->of_node, "det_usb_supply", 0);
|
||
|
if (np)
|
||
|
of_property_read_u32(np, "pmu_usbad_cur", &temp.intval);
|
||
|
|
||
|
pr_info("[acin_irq] ad_current_limit = %d\n", temp.intval);
|
||
|
power_supply_set_property(gpio_power->usb_psy,
|
||
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &temp);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int axp2202_irq_handler_gpio_out(void *data)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power = data;
|
||
|
struct device_node *np = NULL;
|
||
|
union power_supply_propval temp;
|
||
|
|
||
|
pr_info("[acout_irq]axp2202_gpio_out\n");
|
||
|
power_supply_changed(gpio_power->gpio_supply);
|
||
|
if (gpio_power->usb_psy && (!IS_ERR(gpio_power->usb_psy))) {
|
||
|
power_supply_get_property(gpio_power->usb_psy, POWER_SUPPLY_PROP_ONLINE, &temp);
|
||
|
if (temp.intval) {
|
||
|
power_supply_get_property(gpio_power->usb_psy, POWER_SUPPLY_PROP_USB_TYPE, &temp);
|
||
|
if (temp.intval == POWER_SUPPLY_USB_TYPE_SDP) {
|
||
|
temp.intval = 500;
|
||
|
np = of_parse_phandle(gpio_power->dev->of_node, "det_usb_supply", 0);
|
||
|
if (np)
|
||
|
of_property_read_u32(np, "pmu_usbpc_cur", &temp.intval);
|
||
|
|
||
|
pr_info("[acout_irq] pc_current_limit = %d\n", temp.intval);
|
||
|
power_supply_set_property(gpio_power->usb_psy,
|
||
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &temp);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static irqreturn_t axp_acin_gpio_isr(int irq, void *data)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power = data;
|
||
|
|
||
|
cancel_delayed_work_sync(&gpio_power->gpio_chg_state);
|
||
|
schedule_delayed_work(&gpio_power->gpio_chg_state, 0);
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
enum axp2202_gpio_virq_index {
|
||
|
AXP2202_VIRQ_GPIO,
|
||
|
};
|
||
|
|
||
|
static struct axp_interrupts axp_gpio_irq[] = {
|
||
|
[AXP2202_VIRQ_GPIO] = { "pmu_acin_det_gpio", axp_acin_gpio_isr },
|
||
|
};
|
||
|
|
||
|
static int axp_acin_gpio_init(struct axp2202_gpio_power *gpio_power)
|
||
|
{
|
||
|
unsigned long int config_set;
|
||
|
int pull = 0;
|
||
|
char pin_name[SUNXI_PIN_NAME_MAX_LEN];
|
||
|
|
||
|
int id_irq_num = 0;
|
||
|
unsigned long irq_flags = 0;
|
||
|
int ret = 0, i;
|
||
|
|
||
|
if (!gpio_is_valid(gpio_power->axp_acin_det.gpio)) {
|
||
|
pr_warning("get pmu_acin_det_gpio is fail\n");
|
||
|
return -EPROBE_DEFER;
|
||
|
}
|
||
|
|
||
|
ret = gpio_request(
|
||
|
gpio_power->axp_acin_det.gpio,
|
||
|
"pmu_acin_det_gpio");
|
||
|
if (ret != 0) {
|
||
|
pr_warning("pmu_acin_det gpio_request failed\n");
|
||
|
return -EPROBE_DEFER;
|
||
|
}
|
||
|
/* set acin input */
|
||
|
gpio_direction_input(gpio_power->axp_acin_det.gpio);
|
||
|
|
||
|
/* irq config setting */
|
||
|
config_set = SUNXI_PINCFG_PACK(
|
||
|
PIN_CONFIG_BIAS_PULL_UP,
|
||
|
pull);
|
||
|
irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
|
||
|
IRQF_ONESHOT | IRQF_NO_SUSPEND;
|
||
|
/* set id gpio pull up */
|
||
|
sunxi_gpio_to_name(gpio_power->axp_acin_det.gpio, pin_name);
|
||
|
pin_config_set(SUNXI_PINCTRL, pin_name, config_set);
|
||
|
|
||
|
id_irq_num = gpio_to_irq(gpio_power->axp_acin_det.gpio);
|
||
|
axp_gpio_irq[0].irq = id_irq_num;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(axp_gpio_irq); i++) {
|
||
|
ret = devm_request_any_context_irq(gpio_power->dev, axp_gpio_irq[i].irq, axp_gpio_irq[i].isr, irq_flags,
|
||
|
axp_gpio_irq[i].name, gpio_power);
|
||
|
if (IS_ERR_VALUE((unsigned long)ret)) {
|
||
|
pr_warning("Requested %s IRQ %d failed, err %d\n",
|
||
|
axp_gpio_irq[i].name, axp_gpio_irq[i].irq, ret);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
dev_dbg(gpio_power->dev, "Requested %s IRQ %d: %d\n",
|
||
|
axp_gpio_irq[i].name, axp_gpio_irq[i].irq, ret);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
static void axp2202_gpio_power_set_current_fsm(struct work_struct *work)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power =
|
||
|
container_of(work, typeof(*gpio_power), gpio_chg_state.work);
|
||
|
int acin_det_gpio_value;
|
||
|
|
||
|
acin_det_gpio_value = __gpio_get_value(gpio_power->axp_acin_det.gpio);
|
||
|
|
||
|
pr_info("[ac_irq] ac_in_flag :%d\n", acin_det_gpio_value);
|
||
|
if (acin_det_gpio_value == 1) {
|
||
|
axp2202_irq_handler_gpio_in(gpio_power);
|
||
|
} else {
|
||
|
axp2202_irq_handler_gpio_out(gpio_power);
|
||
|
}
|
||
|
}
|
||
|
static void axp2202_gpio_power_monitor(struct work_struct *work)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power =
|
||
|
container_of(work, typeof(*gpio_power), gpio_supply_mon.work);
|
||
|
|
||
|
schedule_delayed_work(&gpio_power->gpio_supply_mon, msecs_to_jiffies(1000));
|
||
|
}
|
||
|
|
||
|
int axp2202_gpio_dt_parse(struct device_node *node,
|
||
|
struct axp_config_info *axp_config)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void axp2202_gpio_parse_device_tree(struct axp2202_gpio_power *gpio_power)
|
||
|
{
|
||
|
int ret;
|
||
|
struct axp_config_info *cfg;
|
||
|
|
||
|
if (!gpio_power->dev->of_node) {
|
||
|
pr_info("can not find device tree\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
cfg = &gpio_power->dts_info;
|
||
|
ret = axp2202_gpio_dt_parse(gpio_power->dev->of_node, cfg);
|
||
|
if (ret) {
|
||
|
pr_info("can not parse device tree err\n");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int axp2202_gpio_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
struct axp2202_gpio_power *gpio_power;
|
||
|
|
||
|
struct device_node *node = pdev->dev.of_node;
|
||
|
struct axp20x_dev *axp_dev = dev_get_drvdata(pdev->dev.parent);
|
||
|
struct power_supply_config psy_cfg = {};
|
||
|
|
||
|
if (!of_device_is_available(node)) {
|
||
|
pr_err("axp2202-acin device is not configed\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
if (!axp_dev->irq) {
|
||
|
pr_err("can not register axp2202-acin without irq\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
gpio_power = devm_kzalloc(&pdev->dev, sizeof(*gpio_power), GFP_KERNEL);
|
||
|
if (gpio_power == NULL) {
|
||
|
pr_err("axp2202_gpio_power alloc failed\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
gpio_power->name = "axp2202_gpio";
|
||
|
gpio_power->dev = &pdev->dev;
|
||
|
gpio_power->regmap = axp_dev->regmap;
|
||
|
|
||
|
/* parse device tree and set register */
|
||
|
axp2202_gpio_parse_device_tree(gpio_power);
|
||
|
|
||
|
psy_cfg.of_node = pdev->dev.of_node;
|
||
|
psy_cfg.drv_data = gpio_power;
|
||
|
|
||
|
gpio_power->gpio_supply = devm_power_supply_register(gpio_power->dev,
|
||
|
&axp2202_gpio_desc, &psy_cfg);
|
||
|
|
||
|
if (IS_ERR(gpio_power->gpio_supply)) {
|
||
|
pr_err("axp2202 failed to register acin power-sypply\n");
|
||
|
ret = PTR_ERR(gpio_power->gpio_supply);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (of_find_property(gpio_power->dev->of_node, "det_usb_supply", NULL)) {
|
||
|
gpio_power->usb_psy = devm_power_supply_get_by_phandle(gpio_power->dev, "det_usb_supply");
|
||
|
} else {
|
||
|
pr_err("axp2202 acin-sypply failed to find usb power\n");
|
||
|
gpio_power->usb_psy = NULL;
|
||
|
}
|
||
|
|
||
|
INIT_DELAYED_WORK(&gpio_power->gpio_supply_mon, axp2202_gpio_power_monitor);
|
||
|
INIT_DELAYED_WORK(&gpio_power->gpio_chg_state, axp2202_gpio_power_set_current_fsm);
|
||
|
|
||
|
gpio_power->axp_acin_det.gpio =
|
||
|
of_get_named_gpio(pdev->dev.of_node,
|
||
|
"pmu_acin_det_gpio", 0);
|
||
|
|
||
|
ret = axp_acin_gpio_init(gpio_power);
|
||
|
if (ret != 0) {
|
||
|
pr_err("axp acin init failed\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
schedule_delayed_work(&gpio_power->gpio_supply_mon, msecs_to_jiffies(1000));
|
||
|
platform_set_drvdata(pdev, gpio_power);
|
||
|
pr_info("axp2202_gpio_probe finish: %s , %d \n", __func__, __LINE__);
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
err:
|
||
|
pr_err("%s,probe fail, ret = %d\n", __func__, ret);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int axp2202_gpio_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power = platform_get_drvdata(pdev);
|
||
|
|
||
|
cancel_delayed_work_sync(&gpio_power->gpio_supply_mon);
|
||
|
cancel_delayed_work_sync(&gpio_power->gpio_chg_state);
|
||
|
|
||
|
dev_dbg(&pdev->dev, "==============AXP2202 gpio unegister==============\n");
|
||
|
if (gpio_power->gpio_supply)
|
||
|
power_supply_unregister(gpio_power->gpio_supply);
|
||
|
dev_dbg(&pdev->dev, "axp2202 teardown gpio dev\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void axp2202_gpio_shutdown(struct platform_device *pdev)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power = platform_get_drvdata(pdev);
|
||
|
|
||
|
cancel_delayed_work_sync(&gpio_power->gpio_supply_mon);
|
||
|
cancel_delayed_work_sync(&gpio_power->gpio_chg_state);
|
||
|
}
|
||
|
|
||
|
static int axp2202_gpio_suspend(struct platform_device *pdev, pm_message_t state)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power = platform_get_drvdata(pdev);
|
||
|
|
||
|
cancel_delayed_work_sync(&gpio_power->gpio_supply_mon);
|
||
|
cancel_delayed_work_sync(&gpio_power->gpio_chg_state);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int axp2202_gpio_resume(struct platform_device *pdev)
|
||
|
{
|
||
|
struct axp2202_gpio_power *gpio_power = platform_get_drvdata(pdev);
|
||
|
|
||
|
schedule_delayed_work(&gpio_power->gpio_chg_state, 0);
|
||
|
schedule_delayed_work(&gpio_power->gpio_supply_mon, 0);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id axp2202_gpio_power_match[] = {
|
||
|
{
|
||
|
.compatible = "x-powers,gpio-supply",
|
||
|
.data = (void *)AXP2202_ID,
|
||
|
}, {/* sentinel */}
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, axp2202_gpio_power_match);
|
||
|
|
||
|
static struct platform_driver axp2202_gpio_power_driver = {
|
||
|
.driver = {
|
||
|
.name = "gpio-supply",
|
||
|
.of_match_table = axp2202_gpio_power_match,
|
||
|
},
|
||
|
.probe = axp2202_gpio_probe,
|
||
|
.remove = axp2202_gpio_remove,
|
||
|
.shutdown = axp2202_gpio_shutdown,
|
||
|
.suspend = axp2202_gpio_suspend,
|
||
|
.resume = axp2202_gpio_resume,
|
||
|
};
|
||
|
|
||
|
module_platform_driver(axp2202_gpio_power_driver);
|
||
|
|
||
|
MODULE_AUTHOR("xinouyang <xinouyang@allwinnertech.com>");
|
||
|
MODULE_DESCRIPTION("axp2202 gpio driver");
|
||
|
MODULE_LICENSE("GPL");
|