// SPDX-License-Identifier: GPL-2.0-only
/*
* tusb1210 . c - TUSB1210 USB ULPI PHY driver
*
* Copyright ( C ) 2015 Intel Corporation
*
* Author : Heikki Krogerus < heikki . krogerus @ linux . intel . com >
*/
#include <linux/module.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/ulpi/driver.h>
#include <linux/ulpi/regs.h>
#include <linux/gpio/consumer.h>
#include <linux/phy/ulpi_phy.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/workqueue.h>
#define TI_VENDOR_ID 0 x0451
#define TI_DEVICE_TUSB1210 0 x1507
#define TI_DEVICE_TUSB1211 0 x1508
#define TUSB1211_POWER_CONTROL 0 x3d
#define TUSB1211_POWER_CONTROL_SET 0 x3e
#define TUSB1211_POWER_CONTROL_CLEAR 0 x3f
#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_VENDOR_SPECIFIC2 0 x80
#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 0 x85
#define TUSB1211_VENDOR_SPECIFIC3_SET 0 x86
#define TUSB1211_VENDOR_SPECIFIC3_CLEAR 0 x87
#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 device *dev;
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;
#endif
};
static int tusb1210_ulpi_write(struct tusb1210 *tusb, u8 reg, u8 val)
{
struct device *dev = tusb->dev;
int ret;
ret = ulpi_write(to_ulpi_dev(dev), reg, val);
if (ret)
dev_err(dev, "error %d writing val 0x%02x to reg 0x%02x\n" , ret, val, reg);
return ret;
}
static int tusb1210_ulpi_read(struct tusb1210 *tusb, u8 reg, u8 *val)
{
struct device *dev = tusb->dev;
int ret;
ret = ulpi_read(to_ulpi_dev(dev), reg);
if (ret >= 0 ) {
*val = ret;
ret = 0 ;
} else {
dev_err(dev, "error %d reading reg 0x%02x\n" , ret, reg);
}
return ret;
}
static int tusb1210_power_on(struct phy *phy)
{
struct tusb1210 *tusb = phy_get_drvdata(phy);
gpiod_set_value_cansleep(tusb->gpio_reset, 1 );
gpiod_set_value_cansleep(tusb->gpio_cs, 1 );
msleep(TUSB1210_RESET_TIME_MS);
/* Restore the optional eye diagram optimization value */
tusb1210_ulpi_write(tusb, TUSB1210_VENDOR_SPECIFIC2, tusb->vendor_specific2);
return 0 ;
}
static int tusb1210_power_off(struct phy *phy)
{
struct tusb1210 *tusb = phy_get_drvdata(phy);
gpiod_set_value_cansleep(tusb->gpio_reset, 0 );
gpiod_set_value_cansleep(tusb->gpio_cs, 0 );
return 0 ;
}
static int tusb1210_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
struct tusb1210 *tusb = phy_get_drvdata(phy);
int ret;
u8 reg;
ret = tusb1210_ulpi_read(tusb, ULPI_OTG_CTRL, ®);
if (ret < 0 )
return ret;
switch (mode) {
case PHY_MODE_USB_HOST:
reg |= (ULPI_OTG_CTRL_DRVVBUS_EXT
| ULPI_OTG_CTRL_ID_PULLUP
| ULPI_OTG_CTRL_DP_PULLDOWN
| ULPI_OTG_CTRL_DM_PULLDOWN);
tusb1210_ulpi_write(tusb, ULPI_OTG_CTRL, reg);
reg |= ULPI_OTG_CTRL_DRVVBUS;
break ;
case PHY_MODE_USB_DEVICE:
reg &= ~(ULPI_OTG_CTRL_DRVVBUS
| ULPI_OTG_CTRL_DP_PULLDOWN
| ULPI_OTG_CTRL_DM_PULLDOWN);
tusb1210_ulpi_write(tusb, ULPI_OTG_CTRL, reg);
reg &= ~ULPI_OTG_CTRL_DRVVBUS_EXT;
break ;
default :
/* nothing */
return 0 ;
}
tusb->otg_ctrl = reg;
return tusb1210_ulpi_write(tusb, ULPI_OTG_CTRL, reg);
}
#ifdef CONFIG_POWER_SUPPLY
static 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->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->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)
{
struct power_supply *charger = NULL;
union power_supply_propval val;
bool online = false ;
int i, ret;
for (i = 0 ; i < ARRAY_SIZE(tusb1210_chargers) && !charger; i++)
charger = power_supply_get_by_name(tusb1210_chargers[i]);
if (!charger)
return false ;
ret = power_supply_get_property(charger, POWER_SUPPLY_PROP_ONLINE, &val);
if (ret == 0 )
online = val.intval;
power_supply_put(charger);
return online;
}
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->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->dev->parent);
if (ret < 0 ) {
dev_err(tusb->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->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 . 600 ms longer then the charger
* chip ( which is used to get vbus_present ) to determine Vbus
* session end . Wait 800 ms 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_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 = BIT(POWER_SUPPLY_USB_TYPE_SDP) |
BIT(POWER_SUPPLY_USB_TYPE_DCP) |
BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN),
.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->dev;
struct ulpi *ulpi = to_ulpi_dev(dev);
int ret;
if (!device_property_read_bool(dev->parent, "linux,phy_charger_detect" ))
return ;
if (ulpi->id.product != TI_DEVICE_TUSB1211) {
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);
}
}
#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,
.set_mode = tusb1210_set_mode,
.owner = THIS_MODULE,
};
static int tusb1210_probe(struct ulpi *ulpi)
{
struct device *dev = &ulpi->dev;
struct tusb1210 *tusb;
u8 val, reg;
int ret;
tusb = devm_kzalloc(dev, sizeof (*tusb), GFP_KERNEL);
if (!tusb)
return -ENOMEM;
tusb->dev = dev;
tusb->gpio_reset = devm_gpiod_get_optional(dev, "reset" , GPIOD_OUT_LOW);
if (IS_ERR(tusb->gpio_reset))
return PTR_ERR(tusb->gpio_reset);
gpiod_set_value_cansleep(tusb->gpio_reset, 1 );
tusb->gpio_cs = devm_gpiod_get_optional(dev, "cs" , GPIOD_OUT_LOW);
if (IS_ERR(tusb->gpio_cs))
return PTR_ERR(tusb->gpio_cs);
gpiod_set_value_cansleep(tusb->gpio_cs, 1 );
/*
* VENDOR_SPECIFIC2 register in TUSB1210 can be used for configuring eye
* diagram optimization and DP / DM swap .
*/
ret = tusb1210_ulpi_read(tusb, TUSB1210_VENDOR_SPECIFIC2, ®);
if (ret)
return ret;
/* High speed output drive strength configuration */
if (!device_property_read_u8(dev, "ihstx" , &val))
u8p_replace_bits(®, val, (u8)TUSB1210_VENDOR_SPECIFIC2_IHSTX_MASK);
/* High speed output impedance configuration */
if (!device_property_read_u8(dev, "zhsdrv" , &val))
u8p_replace_bits(®, val, (u8)TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_MASK);
/* DP/DM swap control */
if (!device_property_read_u8(dev, "datapolarity" , &val))
u8p_replace_bits(®, val, (u8)TUSB1210_VENDOR_SPECIFIC2_DP_MASK);
ret = tusb1210_ulpi_write(tusb, TUSB1210_VENDOR_SPECIFIC2, reg);
if (ret)
return ret;
tusb->vendor_specific2 = reg;
tusb1210_probe_charger_detect(tusb);
tusb->phy = ulpi_phy_create(ulpi, &phy_ops);
if (IS_ERR(tusb->phy)) {
ret = PTR_ERR(tusb->phy);
goto err_remove_charger;
}
phy_set_drvdata(tusb->phy, tusb);
ulpi_set_drvdata(ulpi, tusb);
return 0 ;
err_remove_charger:
tusb1210_remove_charger_detect(tusb);
return ret;
}
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);
}
static const struct ulpi_device_id tusb1210_ulpi_id[] = {
{ TI_VENDOR_ID, TI_DEVICE_TUSB1210 },
{ TI_VENDOR_ID, TI_DEVICE_TUSB1211 },
{ },
};
MODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id);
static struct ulpi_driver tusb1210_driver = {
.id_table = tusb1210_ulpi_id,
.probe = tusb1210_probe,
.remove = tusb1210_remove,
.driver = {
.name = "tusb1210" ,
.owner = THIS_MODULE,
},
};
module_ulpi_driver(tusb1210_driver);
MODULE_AUTHOR("Intel Corporation" );
MODULE_LICENSE("GPL v2" );
MODULE_DESCRIPTION("TUSB1210 ULPI PHY driver" );
Messung V0.5 in Prozent C=97 H=99 G=97
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland