root/drivers/phy/ti/phy-tusb1210.c
// 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            0x0451
#define TI_DEVICE_TUSB1210      0x1507
#define TI_DEVICE_TUSB1211      0x1508

#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_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 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, &reg);
        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. 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_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, &reg);
        if (ret)
                return ret;

        /* High speed output drive strength configuration */
        if (!device_property_read_u8(dev, "ihstx", &val))
                u8p_replace_bits(&reg, val, (u8)TUSB1210_VENDOR_SPECIFIC2_IHSTX_MASK);

        /* High speed output impedance configuration */
        if (!device_property_read_u8(dev, "zhsdrv", &val))
                u8p_replace_bits(&reg, val, (u8)TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_MASK);

        /* DP/DM swap control */
        if (!device_property_read_u8(dev, "datapolarity", &val))
                u8p_replace_bits(&reg, 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");