901 lines
23 KiB
C
901 lines
23 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/mfd/axp2101.h"
|
|
#include <linux/sunxi-gpio.h>
|
|
#include <linux/pinctrl/pinctrl.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <linux/err.h>
|
|
//#include "../drivers/gpio/gpiolib.h"
|
|
#include "axp2202_charger.h"
|
|
|
|
static int axp2202_close_vbus_out(void *data, bool enable);
|
|
|
|
struct axp2202_usb_power {
|
|
char *name;
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct power_supply *usb_supply;
|
|
struct axp_config_info dts_info;
|
|
struct delayed_work usb_supply_mon;
|
|
struct delayed_work usb_chg_state;
|
|
struct delayed_work usb_det_mon;
|
|
struct gpio_config axp_vbus_det;
|
|
struct gpio_config usbid_drv;
|
|
int vbus_det_used;
|
|
|
|
atomic_t set_current_limit;
|
|
};
|
|
|
|
static enum power_supply_property axp2202_usb_props[] = {
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_MANUFACTURER,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
|
|
|
};
|
|
|
|
static int axp2202_get_vbus_vol(struct power_supply *ps,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct axp2202_usb_power *usb_power = power_supply_get_drvdata(ps);
|
|
struct regmap *regmap = usb_power->regmap;
|
|
|
|
uint8_t data[2];
|
|
uint16_t vol;
|
|
int ret = 0;
|
|
|
|
ret = regmap_bulk_read(regmap, AXP2202_VBUS_H, data, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
vol = (((data[0] & GENMASK(5, 0)) << 8) | (data[1])); /* mA */
|
|
val->intval = vol;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp2202_get_vbus_online(struct power_supply *ps,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct axp2202_usb_power *usb_power = power_supply_get_drvdata(ps);
|
|
struct regmap *regmap = usb_power->regmap;
|
|
|
|
unsigned int data;
|
|
int ret = 0;
|
|
|
|
if (usb_power->vbus_det_used) {
|
|
val->intval = __gpio_get_value(usb_power->axp_vbus_det.gpio);
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_read(regmap, AXP2202_COMM_STAT0, &data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (data & AXP2202_MASK_VBUS_STAT)
|
|
val->intval = 1;
|
|
else
|
|
val->intval = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_get_vbus_state(struct power_supply *ps,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct axp2202_usb_power *usb_power = power_supply_get_drvdata(ps);
|
|
struct regmap *regmap = usb_power->regmap;
|
|
unsigned int data;
|
|
int ret = 0;
|
|
|
|
if (usb_power->vbus_det_used) {
|
|
val->intval = __gpio_get_value(usb_power->axp_vbus_det.gpio);
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_read(regmap, AXP2202_COMM_STAT0, &data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* vbus is good when vbus state set */
|
|
val->intval = !!(data & AXP2202_MASK_VBUS_STAT);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_get_iin_limit(struct power_supply *ps,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct axp2202_usb_power *usb_power = power_supply_get_drvdata(ps);
|
|
struct regmap *regmap = usb_power->regmap;
|
|
unsigned int data;
|
|
int ret = 0;
|
|
|
|
ret = regmap_read(regmap, AXP2202_IIN_LIM, &data);
|
|
if (ret < 0)
|
|
return ret;
|
|
data &= 0x3F;
|
|
data = (data * 50) + 100;
|
|
val->intval = data;
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_get_vindpm(struct power_supply *ps,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct axp2202_usb_power *usb_power = power_supply_get_drvdata(ps);
|
|
struct regmap *regmap = usb_power->regmap;
|
|
unsigned int data;
|
|
int ret = 0;
|
|
|
|
ret = regmap_read(regmap, AXP2202_VINDPM_CFG, &data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
data = (data * 80) + 3880;
|
|
val->intval = data;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_get_usb_type(struct power_supply *ps,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct axp2202_usb_power *usb_power = power_supply_get_drvdata(ps);
|
|
int ret = 0;
|
|
|
|
if (atomic_read(&usb_power->set_current_limit)) {
|
|
val->intval = POWER_SUPPLY_USB_TYPE_SDP;
|
|
} else {
|
|
val->intval = POWER_SUPPLY_USB_TYPE_DCP;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_get_cc_status(struct power_supply *ps,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct axp2202_usb_power *usb_power = power_supply_get_drvdata(ps);
|
|
struct axp_config_info *dinfo = &usb_power->dts_info;
|
|
struct regmap *regmap = usb_power->regmap;
|
|
unsigned int data;
|
|
int ret = 0;
|
|
|
|
if (!dinfo->pmu_usb_typec_used) {
|
|
val->intval = POWER_SUPPLY_SCOPE_UNKNOWN;
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_read(regmap, AXP2202_CC_STAT0, &data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
data &= 0x0f;
|
|
|
|
if (data == 5 || data == 6 || data == 9 || data == 12) {
|
|
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;//source/host
|
|
} else if (data == 2 || data == 3 || data == 10 || data == 11) {
|
|
val->intval = POWER_SUPPLY_SCOPE_DEVICE;//sink/
|
|
} else {
|
|
val->intval = POWER_SUPPLY_SCOPE_UNKNOWN;//disable
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_set_iin_limit(struct regmap *regmap, int mA)
|
|
{
|
|
unsigned int data;
|
|
int ret = 0;
|
|
|
|
data = mA;
|
|
if (data > 3250)
|
|
data = 3250;
|
|
if (data < 100)
|
|
data = 100;
|
|
data = ((data - 100) / 50);
|
|
ret = regmap_update_bits(regmap, AXP2202_IIN_LIM, GENMASK(5, 0),
|
|
data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp2202_set_vindpm(struct regmap *regmap, int mV)
|
|
{
|
|
unsigned int data;
|
|
int ret = 0;
|
|
|
|
data = mV;
|
|
|
|
if (data > 5080)
|
|
data = 5080;
|
|
if (data < 3880)
|
|
data = 3880;
|
|
data = ((data - 3880) / 80);
|
|
ret = regmap_update_bits(regmap, AXP2202_VINDPM_CFG, GENMASK(3, 0),
|
|
data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp2202_usb_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret = 0;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
ret = axp2202_get_vbus_online(psy, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
ret = axp2202_get_vbus_state(psy, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
ret = axp2202_get_vbus_vol(psy, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_MANUFACTURER:
|
|
val->strval = AXP2202_MANUFACTURER;
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
ret = axp2202_get_iin_limit(psy, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
ret = axp2202_get_vindpm(psy, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_USB_TYPE:
|
|
ret = axp2202_get_usb_type(psy, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_SCOPE:
|
|
ret = axp2202_get_cc_status(psy, val);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_usb_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct axp2202_usb_power *usb_power = power_supply_get_drvdata(psy);
|
|
|
|
struct regmap *regmap = usb_power->regmap;
|
|
struct power_supply *ps = NULL;
|
|
union power_supply_propval temp;
|
|
int ret = 0, usb_cur;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
usb_cur = val->intval;
|
|
|
|
if (usb_cur < usb_power->dts_info.pmu_usbad_cur) {
|
|
atomic_set(&usb_power->set_current_limit, 1);
|
|
}
|
|
|
|
if (of_find_property(usb_power->dev->of_node, "det_acin_supply", NULL))
|
|
ps = devm_power_supply_get_by_phandle(usb_power->dev,
|
|
"det_acin_supply");
|
|
if (ps && (!IS_ERR(ps))) {
|
|
if (of_device_is_available(ps->of_node)) {
|
|
power_supply_get_property(ps, POWER_SUPPLY_PROP_ONLINE, &temp);
|
|
if (temp.intval) {
|
|
usb_cur = usb_power->dts_info.pmu_usbad_cur;
|
|
}
|
|
}
|
|
}
|
|
ret = axp2202_set_iin_limit(regmap, usb_cur);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
ret = axp2202_set_vindpm(regmap, val->intval);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_usb_power_property_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property psp)
|
|
{
|
|
int ret = 0;
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
ret = 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
|
|
}
|
|
|
|
static const struct power_supply_desc axp2202_usb_desc = {
|
|
.name = "axp2202-usb",
|
|
.type = POWER_SUPPLY_TYPE_USB,
|
|
.get_property = axp2202_usb_get_property,
|
|
.properties = axp2202_usb_props,
|
|
.set_property = axp2202_usb_set_property,
|
|
.num_properties = ARRAY_SIZE(axp2202_usb_props),
|
|
.property_is_writeable = axp2202_usb_power_property_is_writeable,
|
|
};
|
|
|
|
static irqreturn_t axp2202_irq_handler_usb_in(int irq, void *data)
|
|
{
|
|
struct axp2202_usb_power *usb_power = data;
|
|
struct axp_config_info *axp_config = &usb_power->dts_info;
|
|
int ret;
|
|
|
|
regmap_read(usb_power->regmap, AXP2202_IRQ_EN1, &ret);
|
|
if (!usb_power->vbus_det_used) {
|
|
power_supply_changed(usb_power->usb_supply);
|
|
if (!axp_config->pmu_bc12_en) {
|
|
axp2202_set_iin_limit(usb_power->regmap, axp_config->pmu_usbpc_cur);
|
|
atomic_set(&usb_power->set_current_limit, 0);
|
|
cancel_delayed_work_sync(&usb_power->usb_chg_state);
|
|
schedule_delayed_work(&usb_power->usb_chg_state, msecs_to_jiffies(5 * 1000));
|
|
}
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t axp2202_irq_handler_usb_out(int irq, void *data)
|
|
{
|
|
struct axp2202_usb_power *usb_power = data;
|
|
|
|
if (!usb_power->vbus_det_used) {
|
|
power_supply_changed(usb_power->usb_supply);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t axp2202_irq_handler_typec_in(int irq, void *data)
|
|
{
|
|
struct axp2202_usb_power *usb_power = data;
|
|
unsigned int reg_val;
|
|
|
|
regmap_read(usb_power->regmap, AXP2202_CC_STAT0, ®_val);
|
|
|
|
switch (reg_val & 0xf) {
|
|
case 0x07:
|
|
break;
|
|
case 0x5:
|
|
case 0x6:
|
|
case 0x9:
|
|
case 0xc:
|
|
if (usb_power->vbus_det_used)
|
|
gpio_direction_output(usb_power->usbid_drv.gpio, 0);
|
|
break;
|
|
default:
|
|
pr_info("No operation cc_status\n");
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t axp2202_irq_handler_typec_out(int irq, void *data)
|
|
{
|
|
struct axp2202_usb_power *usb_power = data;
|
|
|
|
if (usb_power->vbus_det_used)
|
|
gpio_direction_output(usb_power->usbid_drv.gpio, 1);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t axp2202_acin_vbus_det_isr(int irq, void *data)
|
|
{
|
|
struct axp2202_usb_power *usb_power = data;
|
|
|
|
cancel_delayed_work_sync(&usb_power->usb_det_mon);
|
|
schedule_delayed_work(&usb_power->usb_det_mon, 0);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
enum axp2202_usb_virq_index {
|
|
AXP2202_VIRQ_USB_IN,
|
|
AXP2202_VIRQ_USB_OUT,
|
|
AXP2202_VIRQ_TYPEC_IN,
|
|
AXP2202_VIRQ_TYPEC_OUT,
|
|
|
|
AXP2202_USB_VIRQ_MAX_VIRQ,
|
|
};
|
|
|
|
static struct axp_interrupts axp_usb_irq[] = {
|
|
[AXP2202_VIRQ_USB_IN] = { "vbus_insert", axp2202_irq_handler_usb_in },
|
|
[AXP2202_VIRQ_USB_OUT] = { "vbus_remove", axp2202_irq_handler_usb_out },
|
|
[AXP2202_VIRQ_TYPEC_IN] = { "type-c_insert", axp2202_irq_handler_typec_in },
|
|
[AXP2202_VIRQ_TYPEC_OUT] = { "type-c_remove", axp2202_irq_handler_typec_out },
|
|
|
|
};
|
|
|
|
static void axp2202_usb_power_monitor(struct work_struct *work)
|
|
{
|
|
struct axp2202_usb_power *usb_power =
|
|
container_of(work, typeof(*usb_power), usb_supply_mon.work);
|
|
|
|
schedule_delayed_work(&usb_power->usb_supply_mon, msecs_to_jiffies(500));
|
|
}
|
|
|
|
static void axp2202_usb_set_current_fsm(struct work_struct *work)
|
|
{
|
|
struct axp2202_usb_power *usb_power =
|
|
container_of(work, typeof(*usb_power), usb_chg_state.work);
|
|
struct axp_config_info *axp_config = &usb_power->dts_info;
|
|
|
|
if (atomic_read(&usb_power->set_current_limit)) {
|
|
pr_info("current limit setted: usb pc type\n");
|
|
} else {
|
|
axp2202_set_iin_limit(usb_power->regmap, axp_config->pmu_usbad_cur);
|
|
pr_info("current limit not set: usb adapter type\n");
|
|
}
|
|
}
|
|
|
|
static void axp2202_usb_det_monitor(struct work_struct *work)
|
|
{
|
|
struct axp2202_usb_power *usb_power =
|
|
container_of(work, typeof(*usb_power), usb_det_mon.work);
|
|
struct axp_config_info *axp_config = &usb_power->dts_info;
|
|
int vbus_det_gpio_value;
|
|
|
|
if (!usb_power->vbus_det_used) {
|
|
pr_info("[usb_det] acin_usb_det not used\n");
|
|
return;
|
|
}
|
|
|
|
vbus_det_gpio_value = __gpio_get_value(usb_power->axp_vbus_det.gpio);
|
|
|
|
pr_info("[usb_det] vbus_dev_flag = %d\n", vbus_det_gpio_value);
|
|
power_supply_changed(usb_power->usb_supply);
|
|
if (vbus_det_gpio_value) {
|
|
if (!axp_config->pmu_bc12_en) {
|
|
axp2202_set_iin_limit(usb_power->regmap, axp_config->pmu_usbpc_cur);
|
|
atomic_set(&usb_power->set_current_limit, 0);
|
|
cancel_delayed_work_sync(&usb_power->usb_chg_state);
|
|
schedule_delayed_work(&usb_power->usb_chg_state, msecs_to_jiffies(5 * 1000));
|
|
}
|
|
}
|
|
}
|
|
|
|
static int axp2202_acin_vbus_det_init(struct axp2202_usb_power *usb_power)
|
|
{
|
|
unsigned long int config_set;
|
|
int pull = 0, ret = 0, vbus_det_irq_num = 0;
|
|
unsigned long irq_flags = 0;
|
|
char pin_name[SUNXI_PIN_NAME_MAX_LEN];
|
|
|
|
usb_power->vbus_det_used = 0;
|
|
|
|
usb_power->axp_vbus_det.gpio =
|
|
of_get_named_gpio(usb_power->dev->of_node,
|
|
"pmu_vbus_det_gpio", 0);
|
|
if (!gpio_is_valid(usb_power->axp_vbus_det.gpio)) {
|
|
pr_warning("get axp_vbus_det_gpio is fail\n");
|
|
usb_power->axp_vbus_det.gpio = 0;
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
usb_power->usbid_drv.gpio =
|
|
of_get_named_gpio(usb_power->dev->of_node,
|
|
"pmu_acin_usbid_drv", 0);
|
|
if (!gpio_is_valid(usb_power->usbid_drv.gpio)) {
|
|
pr_warning("get pmu_usbid_drv_gpio is fail\n");
|
|
usb_power->usbid_drv.gpio = 0;
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
ret = gpio_request(
|
|
usb_power->axp_vbus_det.gpio,
|
|
"pmu_vbus_det_gpio");
|
|
if (ret != 0) {
|
|
pr_warning("pmu_vbus_det_gpio gpio_request failed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = gpio_request(
|
|
usb_power->usbid_drv.gpio,
|
|
"pmu_acin_usbid_drv");
|
|
if (ret != 0) {
|
|
pr_warning("pmu_usbid_drv gpio_request failed\n");
|
|
return -EINVAL;
|
|
}
|
|
/* set vbus_det input usbid output */
|
|
gpio_direction_input(usb_power->axp_vbus_det.gpio);
|
|
gpio_direction_output(usb_power->usbid_drv.gpio, 1);
|
|
|
|
/* init delay work */
|
|
INIT_DELAYED_WORK(&usb_power->usb_det_mon, axp2202_usb_det_monitor);
|
|
|
|
/* 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(usb_power->axp_vbus_det.gpio, pin_name);
|
|
pin_config_set(SUNXI_PINCTRL, pin_name, config_set);
|
|
|
|
vbus_det_irq_num = gpio_to_irq(usb_power->axp_vbus_det.gpio);
|
|
|
|
ret = devm_request_any_context_irq(usb_power->dev, vbus_det_irq_num, axp2202_acin_vbus_det_isr, irq_flags,
|
|
"pmu_vbus_det_gpio", usb_power);
|
|
if (IS_ERR_VALUE((unsigned long)ret)) {
|
|
cancel_delayed_work_sync(&usb_power->usb_det_mon);
|
|
pr_warning("Requested pmu_vbus_det_gpio IRQ failed, err %d\n", ret);
|
|
return -EINVAL;
|
|
}
|
|
dev_dbg(usb_power->dev, "Requested pmu_vbus_det_gpio IRQ successed: %d\n", ret);
|
|
|
|
usb_power->vbus_det_used = 1;
|
|
return 0;
|
|
}
|
|
|
|
static void axp2202_usb_power_init(struct axp2202_usb_power *usb_power)
|
|
{
|
|
struct regmap *regmap = usb_power->regmap;
|
|
struct axp_config_info *dinfo = &usb_power->dts_info;
|
|
|
|
unsigned int data = 0;
|
|
|
|
/* set vindpm value */
|
|
axp2202_set_vindpm(regmap, dinfo->pmu_usbad_vol);
|
|
|
|
/* set bc12 en/disable */
|
|
if (dinfo->pmu_bc12_en) {
|
|
regmap_update_bits(regmap, AXP2202_CLK_EN, BIT(4), BIT(4));
|
|
regmap_update_bits(regmap, AXP2202_IIN_LIM, BIT(7), 0);
|
|
} else {
|
|
regmap_update_bits(regmap, AXP2202_CLK_EN, BIT(4), 0);
|
|
regmap_update_bits(regmap, AXP2202_IIN_LIM, BIT(7), BIT(7));
|
|
}
|
|
|
|
/* set boost vol */
|
|
data = (dinfo->pmu_boost_vol - 4550) / 64;
|
|
regmap_update_bits(regmap, AXP2202_BST_CFG0, GENMASK(7, 4), data << 4);
|
|
regmap_write(regmap, AXP2202_BST_CFG1, 0x03);
|
|
|
|
/* set type-c en/disable & mode */
|
|
if (dinfo->pmu_usb_typec_used) {
|
|
regmap_update_bits(regmap, AXP2202_CLK_EN, BIT(3), BIT(3));
|
|
regmap_update_bits(regmap, AXP2202_CC_GLB_CTRL, BIT(2), 0);
|
|
regmap_update_bits(regmap, AXP2202_CC_GLB_CTRL, BIT(5), BIT(5));
|
|
regmap_update_bits(regmap, AXP2202_CC_MODE_CTRL, BIT(1), BIT(1));
|
|
regmap_update_bits(regmap, AXP2202_CC_MODE_CTRL, BIT(0), BIT(0));
|
|
} else {
|
|
regmap_update_bits(regmap, AXP2202_CLK_EN, BIT(3), 0);
|
|
regmap_update_bits(regmap, AXP2202_CC_MODE_CTRL, BIT(1), 0);
|
|
regmap_update_bits(regmap, AXP2202_CC_MODE_CTRL, BIT(0), 0);
|
|
}
|
|
}
|
|
|
|
int axp2202_usb_dt_parse(struct device_node *node,
|
|
struct axp_config_info *axp_config)
|
|
{
|
|
if (!of_device_is_available(node)) {
|
|
pr_err("%s: failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
|
|
AXP_OF_PROP_READ(pmu_usbpc_vol, 4600);
|
|
AXP_OF_PROP_READ(pmu_usbpc_cur, 500);
|
|
AXP_OF_PROP_READ(pmu_usbad_vol, 4600);
|
|
AXP_OF_PROP_READ(pmu_usbad_cur, 1500);
|
|
AXP_OF_PROP_READ(pmu_bc12_en, 0);
|
|
AXP_OF_PROP_READ(pmu_cc_logic_en, 1);
|
|
AXP_OF_PROP_READ(pmu_boost_en, 0);
|
|
AXP_OF_PROP_READ(pmu_boost_vol, 5126);
|
|
AXP_OF_PROP_READ(pmu_usb_typec_used, 0);
|
|
|
|
axp_config->wakeup_usb_in =
|
|
of_property_read_bool(node, "wakeup_usb_in");
|
|
axp_config->wakeup_usb_out =
|
|
of_property_read_bool(node, "wakeup_usb_out");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void axp2202_usb_parse_device_tree(struct axp2202_usb_power *usb_power)
|
|
{
|
|
int ret;
|
|
struct axp_config_info *cfg;
|
|
|
|
/* set input current limit */
|
|
if (!usb_power->dev->of_node) {
|
|
pr_info("can not find device tree\n");
|
|
return;
|
|
}
|
|
|
|
cfg = &usb_power->dts_info;
|
|
ret = axp2202_usb_dt_parse(usb_power->dev->of_node, cfg);
|
|
if (ret) {
|
|
pr_info("can not parse device tree err\n");
|
|
return;
|
|
}
|
|
|
|
/*init axp2202 usb by device tree*/
|
|
axp2202_usb_power_init(usb_power);
|
|
}
|
|
|
|
static int axp2202_usb_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
int i = 0, irq;
|
|
|
|
struct axp2202_usb_power *usb_power;
|
|
|
|
struct axp20x_dev *axp_dev = dev_get_drvdata(pdev->dev.parent);
|
|
struct power_supply_config psy_cfg = {};
|
|
struct device_node *np = NULL;
|
|
|
|
if (!axp_dev->irq) {
|
|
pr_err("can not register axp2202-usb without irq\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
usb_power = devm_kzalloc(&pdev->dev, sizeof(*usb_power), GFP_KERNEL);
|
|
if (usb_power == NULL) {
|
|
pr_err("axp2202_usb_power alloc failed\n");
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
usb_power->name = "axp2202_usb";
|
|
usb_power->dev = &pdev->dev;
|
|
usb_power->regmap = axp_dev->regmap;
|
|
|
|
/* parse device tree and set register */
|
|
axp2202_usb_parse_device_tree(usb_power);
|
|
|
|
psy_cfg.of_node = pdev->dev.of_node;
|
|
psy_cfg.drv_data = usb_power;
|
|
|
|
usb_power->usb_supply = devm_power_supply_register(usb_power->dev,
|
|
&axp2202_usb_desc, &psy_cfg);
|
|
|
|
if (IS_ERR(usb_power->usb_supply)) {
|
|
pr_err("axp2202 failed to register usb power\n");
|
|
ret = PTR_ERR(usb_power->usb_supply);
|
|
return ret;
|
|
}
|
|
|
|
if (!usb_power->dts_info.pmu_bc12_en) {
|
|
INIT_DELAYED_WORK(&usb_power->usb_supply_mon, axp2202_usb_power_monitor);
|
|
INIT_DELAYED_WORK(&usb_power->usb_chg_state, axp2202_usb_set_current_fsm);
|
|
}
|
|
|
|
np = of_parse_phandle(usb_power->dev->of_node, "det_acin_supply", 0);
|
|
if (!of_device_is_available(np)) {
|
|
pr_warning("axp2202-acin device is not configed, not use vbus-det\n");
|
|
} else {
|
|
ret = axp2202_acin_vbus_det_init(usb_power);
|
|
if (ret < 0) {
|
|
pr_warning("failed to register axp2202-acin function\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(axp_usb_irq); i++) {
|
|
irq = platform_get_irq_byname(pdev, axp_usb_irq[i].name);
|
|
if (irq < 0)
|
|
continue;
|
|
|
|
irq = regmap_irq_get_virq(axp_dev->regmap_irqc, irq);
|
|
if (irq < 0) {
|
|
dev_err(&pdev->dev, "can not get irq\n");
|
|
ret = irq;
|
|
goto cancel_work;
|
|
}
|
|
/* we use this variable to suspend irq */
|
|
axp_usb_irq[i].irq = irq;
|
|
ret = devm_request_any_context_irq(&pdev->dev, irq,
|
|
axp_usb_irq[i].isr, 0,
|
|
axp_usb_irq[i].name, usb_power);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "failed to request %s IRQ %d: %d\n",
|
|
axp_usb_irq[i].name, irq, ret);
|
|
goto cancel_work;
|
|
} else {
|
|
ret = 0;
|
|
}
|
|
|
|
dev_dbg(&pdev->dev, "Requested %s IRQ %d: %d\n",
|
|
axp_usb_irq[i].name, irq, ret);
|
|
}
|
|
|
|
if (!usb_power->dts_info.pmu_bc12_en) {
|
|
schedule_delayed_work(&usb_power->usb_supply_mon, msecs_to_jiffies(500));
|
|
schedule_delayed_work(&usb_power->usb_chg_state, msecs_to_jiffies(20 * 1000));
|
|
}
|
|
|
|
platform_set_drvdata(pdev, usb_power);
|
|
|
|
return ret;
|
|
|
|
cancel_work:
|
|
if (!usb_power->dts_info.pmu_bc12_en) {
|
|
cancel_delayed_work_sync(&usb_power->usb_supply_mon);
|
|
cancel_delayed_work_sync(&usb_power->usb_chg_state);
|
|
}
|
|
|
|
|
|
err:
|
|
pr_err("%s,probe fail, ret = %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp2202_usb_remove(struct platform_device *pdev)
|
|
{
|
|
struct axp2202_usb_power *usb_power = platform_get_drvdata(pdev);
|
|
|
|
if (!usb_power->dts_info.pmu_bc12_en) {
|
|
cancel_delayed_work_sync(&usb_power->usb_supply_mon);
|
|
cancel_delayed_work_sync(&usb_power->usb_chg_state);
|
|
}
|
|
if (usb_power->vbus_det_used)
|
|
cancel_delayed_work_sync(&usb_power->usb_det_mon);
|
|
|
|
dev_dbg(&pdev->dev, "==============AXP2202 usb unegister==============\n");
|
|
if (usb_power->usb_supply)
|
|
power_supply_unregister(usb_power->usb_supply);
|
|
dev_dbg(&pdev->dev, "axp2202 teardown usb dev\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void axp2202_usb_irq_set(unsigned int irq, bool enable)
|
|
{
|
|
if (enable)
|
|
enable_irq(irq);
|
|
else
|
|
disable_irq(irq);
|
|
}
|
|
|
|
static int axp2202_close_vbus_out(void *data, bool enable)
|
|
{
|
|
struct axp2202_usb_power *usb_power = data;
|
|
static bool _enable = true;
|
|
|
|
if (_enable == enable)
|
|
return 0;
|
|
|
|
if (enable)
|
|
regmap_update_bits(usb_power->regmap, AXP2202_IRQ_EN1, BIT(6), BIT(6));
|
|
else
|
|
regmap_update_bits(usb_power->regmap, AXP2202_IRQ_EN1, BIT(6), 0);
|
|
|
|
axp2202_usb_irq_set(axp_usb_irq[AXP2202_VIRQ_USB_OUT].irq, enable);
|
|
|
|
_enable = enable;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void axp2202_usb_virq_dts_set(struct axp2202_usb_power *usb_power, bool enable)
|
|
{
|
|
struct axp_config_info *dts_info = &usb_power->dts_info;
|
|
int data;
|
|
|
|
if (!dts_info->wakeup_usb_in)
|
|
axp2202_usb_irq_set(axp_usb_irq[AXP2202_VIRQ_USB_IN].irq,
|
|
enable);
|
|
|
|
regmap_read(usb_power->regmap, AXP2202_COMM_STAT0, &data);
|
|
if ((!enable) && (data & AXP2202_MASK_VBUS_STAT)) {
|
|
if (!dts_info->wakeup_usb_out)
|
|
axp2202_close_vbus_out(usb_power, enable);
|
|
} else {
|
|
axp2202_close_vbus_out(usb_power, enable);
|
|
}
|
|
|
|
}
|
|
|
|
static void axp2202_usb_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct axp2202_usb_power *usb_power = platform_get_drvdata(pdev);
|
|
|
|
cancel_delayed_work_sync(&usb_power->usb_supply_mon);
|
|
cancel_delayed_work_sync(&usb_power->usb_chg_state);
|
|
|
|
}
|
|
|
|
static int axp2202_usb_suspend(struct platform_device *p, pm_message_t state)
|
|
{
|
|
struct axp2202_usb_power *usb_power = platform_get_drvdata(p);
|
|
|
|
if (!usb_power->dts_info.pmu_bc12_en) {
|
|
cancel_delayed_work_sync(&usb_power->usb_supply_mon);
|
|
cancel_delayed_work_sync(&usb_power->usb_chg_state);
|
|
}
|
|
if (usb_power->vbus_det_used)
|
|
cancel_delayed_work_sync(&usb_power->usb_det_mon);
|
|
|
|
axp2202_usb_virq_dts_set(usb_power, false);
|
|
return 0;
|
|
}
|
|
|
|
static int axp2202_usb_resume(struct platform_device *p)
|
|
{
|
|
struct axp2202_usb_power *usb_power = platform_get_drvdata(p);
|
|
|
|
if (!usb_power->dts_info.pmu_bc12_en) {
|
|
schedule_delayed_work(&usb_power->usb_supply_mon, 0);
|
|
schedule_delayed_work(&usb_power->usb_chg_state, 0);
|
|
}
|
|
if (usb_power->vbus_det_used)
|
|
schedule_delayed_work(&usb_power->usb_det_mon, 0);
|
|
axp2202_usb_virq_dts_set(usb_power, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id axp2202_usb_power_match[] = {
|
|
{
|
|
.compatible = "x-powers,axp2202-usb-power-supply",
|
|
.data = (void *)AXP2202_ID,
|
|
}, {/* sentinel */}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, axp2202_usb_power_match);
|
|
|
|
static struct platform_driver axp2202_usb_power_driver = {
|
|
.driver = {
|
|
.name = "axp2202-usb-power-supply",
|
|
.of_match_table = axp2202_usb_power_match,
|
|
},
|
|
.probe = axp2202_usb_probe,
|
|
.remove = axp2202_usb_remove,
|
|
.shutdown = axp2202_usb_shutdown,
|
|
.suspend = axp2202_usb_suspend,
|
|
.resume = axp2202_usb_resume,
|
|
};
|
|
|
|
module_platform_driver(axp2202_usb_power_driver);
|
|
|
|
MODULE_AUTHOR("wangxiaoliang <wangxiaoliang@x-powers.com>");
|
|
MODULE_DESCRIPTION("axp2202 usb driver");
|
|
MODULE_LICENSE("GPL");
|
|
|