blob: d5989b649f22fe91dbf9509bab31a13ede79dd89 [file] [log] [blame]
/*
* Copyright (C) 2013-2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/power/imx6_usb_charger.h>
#include <linux/regmap.h>
#define HW_ANADIG_USB1_CHRG_DETECT_SET (0x000001b4)
#define HW_ANADIG_USB1_CHRG_DETECT_CLR (0x000001b8)
#define BM_ANADIG_USB1_CHRG_DETECT_EN_B 0x00100000
#define BM_ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B 0x00080000
#define BM_ANADIG_USB1_CHRG_DETECT_CHK_CONTACT 0x00040000
#define HW_ANADIG_USB1_VBUS_DET_STAT (0x000001c0)
#define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID 0x00000008
#define HW_ANADIG_USB1_CHRG_DET_STAT (0x000001d0)
#define BM_ANADIG_USB1_CHRG_DET_STAT_DM_STATE 0x00000004
#define BM_ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED 0x00000002
#define BM_ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT 0x00000001
static char *imx6_usb_charger_supplied_to[] = {
"imx6_usb_charger",
};
static enum power_supply_property imx6_usb_charger_power_props[] = {
POWER_SUPPLY_PROP_PRESENT, /* Charger detected */
POWER_SUPPLY_PROP_ONLINE, /* VBUS online */
POWER_SUPPLY_PROP_CURRENT_MAX, /* Maximum current in mA */
};
static int imx6_usb_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct usb_charger *charger =
container_of(psy, struct usb_charger, psy);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = charger->present;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = charger->online;
break;
case POWER_SUPPLY_PROP_CURRENT_MAX:
val->intval = charger->max_current;
break;
default:
return -EINVAL;
}
return 0;
}
static void disable_charger_detector(struct regmap *regmap)
{
regmap_write(regmap, HW_ANADIG_USB1_CHRG_DETECT_SET,
BM_ANADIG_USB1_CHRG_DETECT_EN_B |
BM_ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
}
/* Return value if the charger is present */
static int imx6_usb_charger_detect(struct usb_charger *charger)
{
struct regmap *regmap = charger->anatop;
u32 val;
int i, data_pin_contact_count = 0;
/* check if vbus is valid */
regmap_read(regmap, HW_ANADIG_USB1_VBUS_DET_STAT, &val);
if (!(val & BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)) {
dev_err(charger->dev, "vbus is error\n");
return -EINVAL;
}
/* Enable charger detector */
regmap_write(regmap, HW_ANADIG_USB1_CHRG_DETECT_CLR,
BM_ANADIG_USB1_CHRG_DETECT_EN_B);
/*
* - Do not check whether a charger is connected to the USB port
* - Check whether the USB plug has been in contact with each other
*/
regmap_write(regmap, HW_ANADIG_USB1_CHRG_DETECT_SET,
BM_ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
BM_ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
/* Check if plug is connected */
for (i = 0; i < 100; i = i + 1) {
regmap_read(regmap, HW_ANADIG_USB1_CHRG_DET_STAT, &val);
if (val & BM_ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) {
if (data_pin_contact_count++ > 5)
/* Data pin makes contact */
break;
else
usleep_range(5000, 10000);
} else {
data_pin_contact_count = 0;
usleep_range(5000, 6000);
}
}
if (i == 100) {
dev_err(charger->dev,
"VBUS is coming from a dedicated power supply.\n");
disable_charger_detector(regmap);
return -ENXIO;
}
/*
* - Do check whether a charger is connected to the USB port
* - Do not Check whether the USB plug has been in contact with
* each other
*/
regmap_write(regmap, HW_ANADIG_USB1_CHRG_DETECT_CLR,
BM_ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
BM_ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
msleep(100);
/* Check if it is a charger */
regmap_read(regmap, HW_ANADIG_USB1_CHRG_DET_STAT, &val);
if (!(val & BM_ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) {
dev_dbg(charger->dev, "It is a stardard downstream port\n");
charger->psy.type = POWER_SUPPLY_TYPE_USB;
charger->max_current = 500;
disable_charger_detector(regmap);
} else {
/* It is a charger */
disable_charger_detector(regmap);
msleep(45);
}
return 0;
}
static void usb_charger_is_present(struct usb_charger *charger, bool present)
{
if (present)
charger->present = 1;
else
charger->present = 0;
power_supply_changed(&charger->psy);
sysfs_notify(&charger->psy.dev->kobj, NULL, "present");
}
/*
* imx6_usb_vbus_connect - inform about VBUS connection
* @charger: the usb charger
*
* Inform the charger VBUS is connected, vbus detect supplier should call it.
* Besides, the USB device controller is expected to keep the dataline
* pullups disabled.
*/
int imx6_usb_vbus_connect(struct usb_charger *charger)
{
int ret;
charger->online = 1;
mutex_lock(&charger->lock);
/* Start the 1st period charger detection. */
ret = imx6_usb_charger_detect(charger);
if (ret) {
dev_err(charger->dev,
"Error occurs during detection: %d\n",
ret);
} else {
if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
usb_charger_is_present(charger, true);
}
mutex_unlock(&charger->lock);
return ret;
}
EXPORT_SYMBOL(imx6_usb_vbus_connect);
/*
* It must be called after dp is pulled up (from USB controller driver),
* That is used to differentiate DCP and CDP
*/
int imx6_usb_charger_detect_post(struct usb_charger *charger)
{
struct regmap *regmap = charger->anatop;
int val;
int retval = 0;
mutex_lock(&charger->lock);
msleep(40);
regmap_read(regmap, HW_ANADIG_USB1_CHRG_DET_STAT, &val);
if (val & BM_ANADIG_USB1_CHRG_DET_STAT_DM_STATE) {
dev_dbg(charger->dev, "It is a dedicate charging port\n");
charger->psy.type = POWER_SUPPLY_TYPE_USB_DCP;
charger->max_current = 1500;
retval = POWER_SUPPLY_TYPE_USB_DCP;
} else {
dev_dbg(charger->dev, "It is a charging downstream port\n");
charger->psy.type = POWER_SUPPLY_TYPE_USB_CDP;
charger->max_current = 900;
retval = POWER_SUPPLY_TYPE_USB_CDP;
}
usb_charger_is_present(charger, true);
mutex_unlock(&charger->lock);
return retval;
}
EXPORT_SYMBOL(imx6_usb_charger_detect_post);
/*
* imx6_usb_vbus_disconnect - inform about VBUS disconnection
* @charger: the usb charger
*
* Inform the charger that VBUS is disconnected. The charging will be
* stopped and the charger properties cleared.
*/
int imx6_usb_vbus_disconnect(struct usb_charger *charger)
{
charger->online = 0;
charger->max_current = 0;
charger->psy.type = POWER_SUPPLY_TYPE_MAINS;
usb_charger_is_present(charger, false);
return 0;
}
EXPORT_SYMBOL(imx6_usb_vbus_disconnect);
/*
* imx6_usb_create_charger - create a USB charger
* @charger: the charger to be initialized
* @name: name for the power supply
* Registers a power supply for the charger. The USB Controller
* driver will call this after filling struct usb_charger.
*/
int imx6_usb_create_charger(struct usb_charger *charger,
const char *name)
{
struct power_supply *psy = &charger->psy;
if (!charger->dev)
return -EINVAL;
if (name)
psy->name = name;
else
psy->name = "imx6_usb_charger";
charger->bc = BATTERY_CHARGING_SPEC_1_2;
mutex_init(&charger->lock);
psy->type = POWER_SUPPLY_TYPE_MAINS;
psy->properties = imx6_usb_charger_power_props;
psy->num_properties = ARRAY_SIZE(imx6_usb_charger_power_props);
psy->get_property = imx6_usb_charger_get_property;
psy->supplied_to = imx6_usb_charger_supplied_to;
psy->num_supplicants = sizeof(imx6_usb_charger_supplied_to)
/ sizeof(char *);
return power_supply_register(charger->dev, psy);
}
EXPORT_SYMBOL(imx6_usb_create_charger);
/*
* imx6_usb_remove_charger - remove a USB charger
* @charger: the charger to be removed
*
* Unregister the chargers power supply.
*/
void imx6_usb_remove_charger(struct usb_charger *charger)
{
power_supply_unregister(&charger->psy);
}
EXPORT_SYMBOL(imx6_usb_remove_charger);