phy: ti: tusb1210: Add charger detection

Some Android x86 tablets with a Bay Trail (BYT) SoC and a Crystal Cove
PMIC, which does not support charger-detection, rely on a TUSB1211
phy for charger-detection.

Add support for charger detection on TUSB1211 phy-s and export
the information about the detected charger through the standard
power_supply class interface. power_supply class charger IC drivers
like the bq24190_charger.c driver will then pick this up and set
their input_current_limit based on this.

Note the "linux,phy_charger_detect" property used to enable this is
a special kernel-internal (so not part of the dt-bindings) property
used by dwc3 platform code to indicate that the phy needs to do
charger-detection.

Changes by Hans de Goede:
- Use "linux,phy_charger_detect" property to enable charger-detect
- Switch from a linear flow to a state-machine, with retries on
  ulpi communication errors
- Use SW_CONTROL bit to disable the FSM when detection is finished
- Do a phy-reset on disconnect to work around the phy often refusing
  ulpi_read()/_write() commands after a disconnect
- Use power_supply_reg_notifier() for Vbus monitoring
- Export the detection result through a power_supply class device

Signed-off-by: Stephan Gerhold <stephan@gerhold.net>
Co-developed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20220213130524.18748-10-hdegoede@redhat.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
Stephan Gerhold 2022-02-13 14:05:24 +01:00 committed by Vinod Koul
parent df37c99815
commit 48969a5623
1 changed files with 368 additions and 5 deletions

View File

@ -13,20 +13,59 @@
#include <linux/ulpi/regs.h>
#include <linux/gpio/consumer.h>
#include <linux/phy/ulpi_phy.h>
#include <linux/power_supply.h>
#include <linux/workqueue.h>
#define TUSB1210_VENDOR_SPECIFIC2 0x80
#define TUSB1210_VENDOR_SPECIFIC2_IHSTX_MASK GENMASK(3, 0)
#define TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_MASK GENMASK(5, 4)
#define TUSB1210_VENDOR_SPECIFIC2_DP_MASK BIT(6)
#define TUSB1211_POWER_CONTROL 0x3d
#define TUSB1211_POWER_CONTROL_SET 0x3e
#define TUSB1211_POWER_CONTROL_CLEAR 0x3f
#define TUSB1211_POWER_CONTROL_SW_CONTROL BIT(0)
#define TUSB1211_POWER_CONTROL_DET_COMP BIT(1)
#define TUSB1211_POWER_CONTROL_DP_VSRC_EN BIT(6)
#define TUSB1210_RESET_TIME_MS 30
#define TUSB1210_VENDOR_SPECIFIC2 0x80
#define TUSB1210_VENDOR_SPECIFIC2_IHSTX_MASK GENMASK(3, 0)
#define TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_MASK GENMASK(5, 4)
#define TUSB1210_VENDOR_SPECIFIC2_DP_MASK BIT(6)
#define TUSB1211_VENDOR_SPECIFIC3 0x85
#define TUSB1211_VENDOR_SPECIFIC3_SET 0x86
#define TUSB1211_VENDOR_SPECIFIC3_CLEAR 0x87
#define TUSB1211_VENDOR_SPECIFIC3_SW_USB_DET BIT(4)
#define TUSB1211_VENDOR_SPECIFIC3_CHGD_IDP_SRC_EN BIT(6)
#define TUSB1210_RESET_TIME_MS 50
#define TUSB1210_CHG_DET_MAX_RETRIES 5
/* TUSB1210 charger detection work states */
enum tusb1210_chg_det_state {
TUSB1210_CHG_DET_CONNECTING,
TUSB1210_CHG_DET_START_DET,
TUSB1210_CHG_DET_READ_DET,
TUSB1210_CHG_DET_FINISH_DET,
TUSB1210_CHG_DET_CONNECTED,
TUSB1210_CHG_DET_DISCONNECTING,
TUSB1210_CHG_DET_DISCONNECTING_DONE,
TUSB1210_CHG_DET_DISCONNECTED,
};
struct tusb1210 {
struct ulpi *ulpi;
struct phy *phy;
struct gpio_desc *gpio_reset;
struct gpio_desc *gpio_cs;
u8 otg_ctrl;
u8 vendor_specific2;
#ifdef CONFIG_POWER_SUPPLY
enum power_supply_usb_type chg_type;
enum tusb1210_chg_det_state chg_det_state;
int chg_det_retries;
struct delayed_work chg_det_work;
struct notifier_block psy_nb;
struct power_supply *psy;
struct power_supply *charger;
#endif
};
static int tusb1210_ulpi_write(struct tusb1210 *tusb, u8 reg, u8 val)
@ -111,9 +150,330 @@ static int tusb1210_set_mode(struct phy *phy, enum phy_mode mode, int submode)
return 0;
}
tusb->otg_ctrl = reg;
return tusb1210_ulpi_write(tusb, ULPI_OTG_CTRL, reg);
}
#ifdef CONFIG_POWER_SUPPLY
const char * const tusb1210_chg_det_states[] = {
"CHG_DET_CONNECTING",
"CHG_DET_START_DET",
"CHG_DET_READ_DET",
"CHG_DET_FINISH_DET",
"CHG_DET_CONNECTED",
"CHG_DET_DISCONNECTING",
"CHG_DET_DISCONNECTING_DONE",
"CHG_DET_DISCONNECTED",
};
static void tusb1210_reset(struct tusb1210 *tusb)
{
gpiod_set_value_cansleep(tusb->gpio_reset, 0);
usleep_range(200, 500);
gpiod_set_value_cansleep(tusb->gpio_reset, 1);
}
static void tusb1210_chg_det_set_type(struct tusb1210 *tusb,
enum power_supply_usb_type type)
{
dev_dbg(&tusb->ulpi->dev, "charger type: %d\n", type);
tusb->chg_type = type;
tusb->chg_det_retries = 0;
power_supply_changed(tusb->psy);
}
static void tusb1210_chg_det_set_state(struct tusb1210 *tusb,
enum tusb1210_chg_det_state new_state,
int delay_ms)
{
if (delay_ms)
dev_dbg(&tusb->ulpi->dev, "chg_det new state %s in %d ms\n",
tusb1210_chg_det_states[new_state], delay_ms);
tusb->chg_det_state = new_state;
mod_delayed_work(system_long_wq, &tusb->chg_det_work,
msecs_to_jiffies(delay_ms));
}
static void tusb1210_chg_det_handle_ulpi_error(struct tusb1210 *tusb)
{
tusb1210_reset(tusb);
if (tusb->chg_det_retries < TUSB1210_CHG_DET_MAX_RETRIES) {
tusb->chg_det_retries++;
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_START_DET,
TUSB1210_RESET_TIME_MS);
} else {
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_FINISH_DET,
TUSB1210_RESET_TIME_MS);
}
}
/*
* Boards using a TUSB121x for charger-detection have 3 power_supply class devs:
*
* tusb1211-charger-detect(1) -> charger -> fuel-gauge
*
* To determine if an USB charger is connected to the board, the online prop of
* the charger psy needs to be read. Since the tusb1211-charger-detect psy is
* the start of the supplier -> supplied-to chain, power_supply_am_i_supplied()
* cannot be used here.
*
* Instead, below is a list of the power_supply names of known chargers for
* these boards and the charger psy is looked up by name from this list.
*
* (1) modelling the external USB charger
*/
static const char * const tusb1210_chargers[] = {
"bq24190-charger",
};
static bool tusb1210_get_online(struct tusb1210 *tusb)
{
union power_supply_propval val;
int i;
for (i = 0; i < ARRAY_SIZE(tusb1210_chargers) && !tusb->charger; i++)
tusb->charger = power_supply_get_by_name(tusb1210_chargers[i]);
if (!tusb->charger)
return false;
if (power_supply_get_property(tusb->charger, POWER_SUPPLY_PROP_ONLINE, &val))
return false;
return val.intval;
}
static void tusb1210_chg_det_work(struct work_struct *work)
{
struct tusb1210 *tusb = container_of(work, struct tusb1210, chg_det_work.work);
bool vbus_present = tusb1210_get_online(tusb);
int ret;
u8 val;
dev_dbg(&tusb->ulpi->dev, "chg_det state %s vbus_present %d\n",
tusb1210_chg_det_states[tusb->chg_det_state], vbus_present);
switch (tusb->chg_det_state) {
case TUSB1210_CHG_DET_CONNECTING:
tusb->chg_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
tusb->chg_det_retries = 0;
/* Power on USB controller for ulpi_read()/_write() */
ret = pm_runtime_resume_and_get(tusb->ulpi->dev.parent);
if (ret < 0) {
dev_err(&tusb->ulpi->dev, "error %d runtime-resuming\n", ret);
/* Should never happen, skip charger detection */
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_CONNECTED, 0);
return;
}
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_START_DET, 0);
break;
case TUSB1210_CHG_DET_START_DET:
/*
* Use the builtin charger detection FSM to keep things simple.
* This only detects DCP / SDP. This is good enough for the few
* boards which actually rely on the phy for charger detection.
*/
mutex_lock(&tusb->phy->mutex);
ret = tusb1210_ulpi_write(tusb, TUSB1211_VENDOR_SPECIFIC3_SET,
TUSB1211_VENDOR_SPECIFIC3_SW_USB_DET);
mutex_unlock(&tusb->phy->mutex);
if (ret) {
tusb1210_chg_det_handle_ulpi_error(tusb);
break;
}
/* Wait 400 ms for the charger detection FSM to finish */
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_READ_DET, 400);
break;
case TUSB1210_CHG_DET_READ_DET:
mutex_lock(&tusb->phy->mutex);
ret = tusb1210_ulpi_read(tusb, TUSB1211_POWER_CONTROL, &val);
mutex_unlock(&tusb->phy->mutex);
if (ret) {
tusb1210_chg_det_handle_ulpi_error(tusb);
break;
}
if (val & TUSB1211_POWER_CONTROL_DET_COMP)
tusb1210_chg_det_set_type(tusb, POWER_SUPPLY_USB_TYPE_DCP);
else
tusb1210_chg_det_set_type(tusb, POWER_SUPPLY_USB_TYPE_SDP);
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_FINISH_DET, 0);
break;
case TUSB1210_CHG_DET_FINISH_DET:
mutex_lock(&tusb->phy->mutex);
/* Set SW_CONTROL to stop the charger-det FSM */
ret = tusb1210_ulpi_write(tusb, TUSB1211_POWER_CONTROL_SET,
TUSB1211_POWER_CONTROL_SW_CONTROL);
/* Clear DP_VSRC_EN which may have been enabled by the charger-det FSM */
ret |= tusb1210_ulpi_write(tusb, TUSB1211_POWER_CONTROL_CLEAR,
TUSB1211_POWER_CONTROL_DP_VSRC_EN);
/* Clear CHGD_IDP_SRC_EN (may have been enabled by the charger-det FSM) */
ret |= tusb1210_ulpi_write(tusb, TUSB1211_VENDOR_SPECIFIC3_CLEAR,
TUSB1211_VENDOR_SPECIFIC3_CHGD_IDP_SRC_EN);
/* If any of the above fails reset the phy */
if (ret) {
tusb1210_reset(tusb);
msleep(TUSB1210_RESET_TIME_MS);
}
/* Restore phy-parameters and OTG_CTRL register */
tusb1210_ulpi_write(tusb, ULPI_OTG_CTRL, tusb->otg_ctrl);
tusb1210_ulpi_write(tusb, TUSB1210_VENDOR_SPECIFIC2,
tusb->vendor_specific2);
mutex_unlock(&tusb->phy->mutex);
pm_runtime_put(tusb->ulpi->dev.parent);
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_CONNECTED, 0);
break;
case TUSB1210_CHG_DET_CONNECTED:
if (!vbus_present)
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_DISCONNECTING, 0);
break;
case TUSB1210_CHG_DET_DISCONNECTING:
/*
* The phy seems to take approx. 600ms longer then the charger
* chip (which is used to get vbus_present) to determine Vbus
* session end. Wait 800ms to ensure the phy has detected and
* signalled Vbus session end.
*/
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_DISCONNECTING_DONE, 800);
break;
case TUSB1210_CHG_DET_DISCONNECTING_DONE:
/*
* The phy often stops reacting to ulpi_read()/_write requests
* after a Vbus-session end. Reset it to work around this.
*/
tusb1210_reset(tusb);
tusb1210_chg_det_set_type(tusb, POWER_SUPPLY_USB_TYPE_UNKNOWN);
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_DISCONNECTED, 0);
break;
case TUSB1210_CHG_DET_DISCONNECTED:
if (vbus_present)
tusb1210_chg_det_set_state(tusb, TUSB1210_CHG_DET_CONNECTING, 0);
break;
}
}
static int tusb1210_psy_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct tusb1210 *tusb = container_of(nb, struct tusb1210, psy_nb);
struct power_supply *psy = ptr;
if (psy != tusb->psy && psy->desc->type == POWER_SUPPLY_TYPE_USB)
queue_delayed_work(system_long_wq, &tusb->chg_det_work, 0);
return NOTIFY_OK;
}
static int tusb1210_psy_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct tusb1210 *tusb = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = tusb1210_get_online(tusb);
break;
case POWER_SUPPLY_PROP_USB_TYPE:
val->intval = tusb->chg_type;
break;
case POWER_SUPPLY_PROP_CURRENT_MAX:
if (tusb->chg_type == POWER_SUPPLY_USB_TYPE_DCP)
val->intval = 2000000;
else
val->intval = 500000;
break;
default:
return -EINVAL;
}
return 0;
}
static const enum power_supply_usb_type tusb1210_psy_usb_types[] = {
POWER_SUPPLY_USB_TYPE_SDP,
POWER_SUPPLY_USB_TYPE_DCP,
POWER_SUPPLY_USB_TYPE_UNKNOWN,
};
static const enum power_supply_property tusb1210_psy_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_CURRENT_MAX,
};
static const struct power_supply_desc tusb1210_psy_desc = {
.name = "tusb1211-charger-detect",
.type = POWER_SUPPLY_TYPE_USB,
.usb_types = tusb1210_psy_usb_types,
.num_usb_types = ARRAY_SIZE(tusb1210_psy_usb_types),
.properties = tusb1210_psy_props,
.num_properties = ARRAY_SIZE(tusb1210_psy_props),
.get_property = tusb1210_psy_get_prop,
};
/* Setup charger detection if requested, on errors continue without chg-det */
static void tusb1210_probe_charger_detect(struct tusb1210 *tusb)
{
struct power_supply_config psy_cfg = { .drv_data = tusb };
struct device *dev = &tusb->ulpi->dev;
int ret;
if (!device_property_read_bool(dev->parent, "linux,phy_charger_detect"))
return;
if (tusb->ulpi->id.product != 0x1508) {
dev_err(dev, "error charger detection is only supported on the TUSB1211\n");
return;
}
ret = tusb1210_ulpi_read(tusb, ULPI_OTG_CTRL, &tusb->otg_ctrl);
if (ret)
return;
tusb->psy = power_supply_register(dev, &tusb1210_psy_desc, &psy_cfg);
if (IS_ERR(tusb->psy))
return;
/*
* Delay initial run by 2 seconds to allow the charger driver,
* which is used to determine vbus_present, to load.
*/
tusb->chg_det_state = TUSB1210_CHG_DET_DISCONNECTED;
INIT_DELAYED_WORK(&tusb->chg_det_work, tusb1210_chg_det_work);
queue_delayed_work(system_long_wq, &tusb->chg_det_work, 2 * HZ);
tusb->psy_nb.notifier_call = tusb1210_psy_notifier;
power_supply_reg_notifier(&tusb->psy_nb);
}
static void tusb1210_remove_charger_detect(struct tusb1210 *tusb)
{
if (!IS_ERR_OR_NULL(tusb->psy)) {
power_supply_unreg_notifier(&tusb->psy_nb);
cancel_delayed_work_sync(&tusb->chg_det_work);
power_supply_unregister(tusb->psy);
}
if (tusb->charger)
power_supply_put(tusb->charger);
}
#else
static void tusb1210_probe_charger_detect(struct tusb1210 *tusb) { }
static void tusb1210_remove_charger_detect(struct tusb1210 *tusb) { }
#endif
static const struct phy_ops phy_ops = {
.power_on = tusb1210_power_on,
.power_off = tusb1210_power_off,
@ -174,6 +534,8 @@ static int tusb1210_probe(struct ulpi *ulpi)
tusb->vendor_specific2 = reg;
tusb1210_probe_charger_detect(tusb);
tusb->phy = ulpi_phy_create(ulpi, &phy_ops);
if (IS_ERR(tusb->phy))
return PTR_ERR(tusb->phy);
@ -188,6 +550,7 @@ static void tusb1210_remove(struct ulpi *ulpi)
struct tusb1210 *tusb = ulpi_get_drvdata(ulpi);
ulpi_phy_destroy(ulpi, tusb->phy);
tusb1210_remove_charger_detect(tusb);
}
#define TI_VENDOR_ID 0x0451