| // 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, ®); | |
| 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_dfl_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_dfl_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_dfl_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"); |