diff options
Diffstat (limited to 'drivers/extcon')
| -rw-r--r-- | drivers/extcon/Kconfig | 39 | ||||
| -rw-r--r-- | drivers/extcon/Makefile | 3 | ||||
| -rw-r--r-- | drivers/extcon/extcon-adc-jack.c | 8 | ||||
| -rw-r--r-- | drivers/extcon/extcon-axp288.c | 4 | ||||
| -rw-r--r-- | drivers/extcon/extcon-fsa9480.c | 4 | ||||
| -rw-r--r-- | drivers/extcon/extcon-intel-cht-wc.c | 19 | ||||
| -rw-r--r-- | drivers/extcon/extcon-intel-mrfld.c | 24 | ||||
| -rw-r--r-- | drivers/extcon/extcon-lc824206xa.c | 495 | ||||
| -rw-r--r-- | drivers/extcon/extcon-max14526.c | 302 | ||||
| -rw-r--r-- | drivers/extcon/extcon-max3355.c | 4 | ||||
| -rw-r--r-- | drivers/extcon/extcon-max77693.c | 7 | ||||
| -rw-r--r-- | drivers/extcon/extcon-max77843.c | 11 | ||||
| -rw-r--r-- | drivers/extcon/extcon-ptn5150.c | 2 | ||||
| -rw-r--r-- | drivers/extcon/extcon-qcom-spmi-misc.c | 4 | ||||
| -rw-r--r-- | drivers/extcon/extcon-rtk-type-c.c | 1795 | ||||
| -rw-r--r-- | drivers/extcon/extcon-usb-gpio.c | 4 | ||||
| -rw-r--r-- | drivers/extcon/extcon-usbc-cros-ec.c | 4 | ||||
| -rw-r--r-- | drivers/extcon/extcon-usbc-tusb320.c | 9 | ||||
| -rw-r--r-- | drivers/extcon/extcon.c | 3 |
19 files changed, 2686 insertions, 55 deletions
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 0ef1971d22bb..aec46bf03302 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -62,6 +62,7 @@ config EXTCON_INTEL_CHT_WC tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver" depends on INTEL_SOC_PMIC_CHTWC depends on USB_SUPPORT + depends on POWER_SUPPLY select USB_ROLE_SWITCH help Say Y here to enable extcon support for charger detection / control @@ -74,6 +75,17 @@ config EXTCON_INTEL_MRFLD Say Y here to enable extcon support for charger detection / control on the Intel Merrifield Basin Cove PMIC. +config EXTCON_LC824206XA + tristate "LC824206XA extcon Support" + depends on I2C + depends on POWER_SUPPLY + help + Say Y here to enable support for the ON Semiconductor LC824206XA + microUSB switch and accessory detector chip. The LC824206XA is a USB + port accessory detector and switch. The LC824206XA is fully controlled + using I2C and enables USB data, stereo and mono audio, video, + microphone and UART data to use a common connector port. + config EXTCON_MAX14577 tristate "Maxim MAX14577/77836 EXTCON Support" depends on MFD_MAX14577 @@ -115,12 +127,26 @@ config EXTCON_MAX77843 config EXTCON_MAX8997 tristate "Maxim MAX8997 EXTCON Support" - depends on MFD_MAX8997 && IRQ_DOMAIN + depends on MFD_MAX8997 + select IRQ_DOMAIN help If you say yes here you get support for the MUIC device of Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory detector and switch. +config EXTCON_MAX14526 + tristate "Maxim MAX14526 EXTCON Support" + depends on I2C + select IRQ_DOMAIN + select REGMAP_I2C + help + If you say yes here you get support for the Maxim MAX14526 + MUIC device. The MAX14526 MUIC is a USB port accessory + detector and switch. The MAX14526 is designed to simplify + interface requirements on portable devices by multiplexing + common inputs (USB, UART, Microphone, Stereo Audio and + Composite Video) on a single micro/mini USB connector. + config EXTCON_PALMAS tristate "Palmas USB EXTCON support" depends on MFD_PALMAS @@ -190,4 +216,15 @@ config EXTCON_USBC_TUSB320 Say Y here to enable support for USB Type C cable detection extcon support using a TUSB320. +config EXTCON_RTK_TYPE_C + tristate "Realtek RTD SoC extcon Type-C Driver" + depends on ARCH_REALTEK || COMPILE_TEST + depends on TYPEC + select USB_COMMON + help + Say Y here to enable extcon support for USB Type C cable detection + when using the Realtek RTD SoC USB Type-C port. + The DHC (Digital Home Hub) RTD series SoC contains a type c module. + This driver will detect the status of the type-c port. + endif diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 1b390d934ca9..6482f2bfd661 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -12,11 +12,13 @@ obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o obj-$(CONFIG_EXTCON_INTEL_MRFLD) += extcon-intel-mrfld.o +obj-$(CONFIG_EXTCON_LC824206XA) += extcon-lc824206xa.o obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX77843) += extcon-max77843.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o +obj-$(CONFIG_EXTCON_MAX14526) += extcon-max14526.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o obj-$(CONFIG_EXTCON_PTN5150) += extcon-ptn5150.o obj-$(CONFIG_EXTCON_QCOM_SPMI_MISC) += extcon-qcom-spmi-misc.o @@ -25,3 +27,4 @@ obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o +obj-$(CONFIG_EXTCON_RTK_TYPE_C) += extcon-rtk-type-c.o diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index 0317b614b680..7e3c9f38297b 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -26,6 +26,7 @@ /** * struct adc_jack_data - internal data for adc_jack device driver + * @dev: The device structure associated with the adc_jack. * @edev: extcon device. * @cable_names: list of supported cables. * @adc_conditions: list of adc value conditions. @@ -35,6 +36,7 @@ * handling at handling_delay jiffies. * @handler: extcon event handler called by interrupt handler. * @chan: iio channel being queried. + * @wakeup_source: Indicates if the device can wake up the system. */ struct adc_jack_data { struct device *dev; @@ -158,14 +160,14 @@ static int adc_jack_probe(struct platform_device *pdev) return 0; } -static int adc_jack_remove(struct platform_device *pdev) +static void adc_jack_remove(struct platform_device *pdev) { struct adc_jack_data *data = platform_get_drvdata(pdev); + if (data->wakeup_source) + device_init_wakeup(&pdev->dev, false); free_irq(data->irq, data); cancel_work_sync(&data->handler.work); - - return 0; } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c index a703a8315634..19856dddade6 100644 --- a/drivers/extcon/extcon-axp288.c +++ b/drivers/extcon/extcon-axp288.c @@ -108,7 +108,7 @@ struct axp288_extcon_info { }; static const struct x86_cpu_id cherry_trail_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT, NULL), + X86_MATCH_VFM(INTEL_ATOM_AIRMONT, NULL), {} }; @@ -470,7 +470,7 @@ static int axp288_extcon_probe(struct platform_device *pdev) if (ret < 0) return ret; - device_init_wakeup(dev, true); + devm_device_init_wakeup(dev); platform_set_drvdata(pdev, info); return 0; diff --git a/drivers/extcon/extcon-fsa9480.c b/drivers/extcon/extcon-fsa9480.c index e458ce0c45ab..a031eb0914a0 100644 --- a/drivers/extcon/extcon-fsa9480.c +++ b/drivers/extcon/extcon-fsa9480.c @@ -317,7 +317,7 @@ static int fsa9480_probe(struct i2c_client *client) return ret; } - device_init_wakeup(info->dev, true); + devm_device_init_wakeup(info->dev); fsa9480_detect_dev(info); return 0; @@ -350,7 +350,7 @@ static const struct dev_pm_ops fsa9480_pm_ops = { }; static const struct i2c_device_id fsa9480_id[] = { - { "fsa9480", 0 }, + { "fsa9480" }, {} }; MODULE_DEVICE_TABLE(i2c, fsa9480_id); diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index 2c55f06ba699..8131a3d7d562 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -461,14 +461,6 @@ static int cht_wc_extcon_psy_get_prop(struct power_supply *psy, return 0; } -static const enum power_supply_usb_type cht_wc_extcon_psy_usb_types[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_ACA, - POWER_SUPPLY_USB_TYPE_UNKNOWN, -}; - static const enum power_supply_property cht_wc_extcon_psy_props[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_ONLINE, @@ -477,8 +469,11 @@ static const enum power_supply_property cht_wc_extcon_psy_props[] = { static const struct power_supply_desc cht_wc_extcon_psy_desc = { .name = "cht_wcove_pwrsrc", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = cht_wc_extcon_psy_usb_types, - .num_usb_types = ARRAY_SIZE(cht_wc_extcon_psy_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_ACA) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = cht_wc_extcon_psy_props, .num_properties = ARRAY_SIZE(cht_wc_extcon_psy_props), .get_property = cht_wc_extcon_psy_get_prop, @@ -617,13 +612,11 @@ disable_sw_control: return ret; } -static int cht_wc_extcon_remove(struct platform_device *pdev) +static void cht_wc_extcon_remove(struct platform_device *pdev) { struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev); cht_wc_extcon_sw_control(ext, false); - - return 0; } static const struct platform_device_id cht_wc_extcon_table[] = { diff --git a/drivers/extcon/extcon-intel-mrfld.c b/drivers/extcon/extcon-intel-mrfld.c index cd1a5f230077..9219f4328d70 100644 --- a/drivers/extcon/extcon-intel-mrfld.c +++ b/drivers/extcon/extcon-intel-mrfld.c @@ -214,27 +214,21 @@ static int mrfld_extcon_probe(struct platform_device *pdev) data->edev = devm_extcon_dev_allocate(dev, mrfld_extcon_cable); if (IS_ERR(data->edev)) - return -ENOMEM; + return PTR_ERR(data->edev); ret = devm_extcon_dev_register(dev, data->edev); - if (ret < 0) { - dev_err(dev, "can't register extcon device: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "can't register extcon device\n"); ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_extcon_interrupt, IRQF_ONESHOT | IRQF_SHARED, pdev->name, data); - if (ret) { - dev_err(dev, "can't register IRQ handler: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "can't register IRQ handler\n"); ret = regmap_read(regmap, BCOVE_ID, &id); - if (ret) { - dev_err(dev, "can't read PMIC ID: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "can't read PMIC ID\n"); data->id = id; @@ -263,13 +257,11 @@ static int mrfld_extcon_probe(struct platform_device *pdev) return 0; } -static int mrfld_extcon_remove(struct platform_device *pdev) +static void mrfld_extcon_remove(struct platform_device *pdev) { struct mrfld_extcon_data *data = platform_get_drvdata(pdev); mrfld_extcon_sw_control(data, false); - - return 0; } static const struct platform_device_id mrfld_extcon_id_table[] = { diff --git a/drivers/extcon/extcon-lc824206xa.c b/drivers/extcon/extcon-lc824206xa.c new file mode 100644 index 000000000000..56938748aea8 --- /dev/null +++ b/drivers/extcon/extcon-lc824206xa.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ON Semiconductor LC824206XA Micro USB Switch driver + * + * Copyright (c) 2024 Hans de Goede <hansg@kernel.org> + * + * ON Semiconductor has an "Advance Information" datasheet available + * (ENA2222-D.PDF), but no full datasheet. So there is no documentation + * available for the registers. + * + * This driver is based on the register info from the extcon-fsa9285.c driver, + * from the Lollipop Android sources for the Lenovo Yoga Tablet 2 (Pro) + * 830 / 1050 / 1380 models. Note despite the name this is actually a driver + * for the LC824206XA not the FSA9285. The Android sources can be downloaded + * from Lenovo's support page for these tablets, filename: + * yoga_tab_2_osc_android_to_lollipop_201505.rar. + */ + +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/extcon-provider.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/workqueue.h> + +/* + * Register defines as mentioned above there is no datasheet with register + * info, so this may not be 100% accurate. + */ +#define REG00 0x00 +#define REG00_INIT_VALUE 0x01 + +#define REG_STATUS 0x01 +#define STATUS_OVP BIT(0) +#define STATUS_DATA_SHORT BIT(1) +#define STATUS_VBUS_PRESENT BIT(2) +#define STATUS_USB_ID GENMASK(7, 3) +#define STATUS_USB_ID_GND 0x80 +#define STATUS_USB_ID_ACA 0xf0 +#define STATUS_USB_ID_FLOAT 0xf8 + +/* + * This controls the DP/DM muxes + other switches, + * meaning of individual bits is unknown. + */ +#define REG_SWITCH_CONTROL 0x02 +#define SWITCH_STEREO_MIC 0xc8 +#define SWITCH_USB_HOST 0xec +#define SWITCH_DISCONNECTED 0xf8 +#define SWITCH_USB_DEVICE 0xfc + +/* 5 bits? ADC 0x10 GND, 0x1a-0x1f ACA, 0x1f float */ +#define REG_ID_PIN_ADC_VALUE 0x03 + +/* Masks for all 3 interrupt registers */ +#define INTR_ID_PIN_CHANGE BIT(0) +#define INTR_VBUS_CHANGE BIT(1) +/* Both of these get set after a continuous mode ADC conversion */ +#define INTR_ID_PIN_ADC_INT1 BIT(2) +#define INTR_ID_PIN_ADC_INT2 BIT(3) +/* Charger type available in reg 0x09 */ +#define INTR_CHARGER_DET_DONE BIT(4) +#define INTR_OVP BIT(5) + +/* There are 7 interrupt sources, bit 6 use is unknown (OCP?) */ +#define INTR_ALL GENMASK(6, 0) + +/* Unmask interrupts this driver cares about */ +#define INTR_MASK \ + (INTR_ALL & ~(INTR_ID_PIN_CHANGE | INTR_VBUS_CHANGE | INTR_CHARGER_DET_DONE)) + +/* Active (event happened and not cleared yet) interrupts */ +#define REG_INTR_STATUS 0x04 + +/* + * Writing a 1 to a bit here clears it in INTR_STATUS. These bits do NOT + * auto-reset to 0, so these must be set to 0 manually after clearing. + */ +#define REG_INTR_CLEAR 0x05 + +/* Interrupts which bit is set to 1 here will not raise the HW IRQ */ +#define REG_INTR_MASK 0x06 + +/* ID pin ADC control, meaning of individual bits is unknown */ +#define REG_ID_PIN_ADC_CTRL 0x07 +#define ID_PIN_ADC_AUTO 0x40 +#define ID_PIN_ADC_CONTINUOUS 0x44 + +#define REG_CHARGER_DET 0x08 +#define CHARGER_DET_ON BIT(0) +#define CHARGER_DET_CDP_ON BIT(1) +#define CHARGER_DET_CDP_VAL BIT(2) + +#define REG_CHARGER_TYPE 0x09 +#define CHARGER_TYPE_UNKNOWN 0x00 +#define CHARGER_TYPE_DCP 0x01 +#define CHARGER_TYPE_SDP_OR_CDP 0x04 +#define CHARGER_TYPE_QC 0x06 + +#define REG10 0x10 +#define REG10_INIT_VALUE 0x00 + +struct lc824206xa_data { + struct work_struct work; + struct i2c_client *client; + struct extcon_dev *edev; + struct power_supply *psy; + struct regulator *vbus_boost; + unsigned int usb_type; + unsigned int cable; + unsigned int previous_cable; + u8 switch_control; + u8 previous_switch_control; + bool vbus_ok; + bool vbus_boost_enabled; + bool fastcharge_over_miclr; +}; + +static const unsigned int lc824206xa_cables[] = { + EXTCON_USB_HOST, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, + EXTCON_CHG_USB_ACA, + EXTCON_CHG_USB_FAST, + EXTCON_NONE, +}; + +/* read/write reg helpers to add error logging to smbus byte functions */ +static int lc824206xa_read_reg(struct lc824206xa_data *data, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, reg); + if (ret < 0) + dev_err(&data->client->dev, "Error %d reading reg 0x%02x\n", ret, reg); + + return ret; +} + +static int lc824206xa_write_reg(struct lc824206xa_data *data, u8 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, reg, val); + if (ret < 0) + dev_err(&data->client->dev, "Error %d writing reg 0x%02x\n", ret, reg); + + return ret; +} + +static int lc824206xa_get_id(struct lc824206xa_data *data) +{ + int ret; + + ret = lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_CONTINUOUS); + if (ret) + return ret; + + ret = lc824206xa_read_reg(data, REG_ID_PIN_ADC_VALUE); + + lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_AUTO); + + return ret; +} + +static void lc824206xa_set_vbus_boost(struct lc824206xa_data *data, bool enable) +{ + int ret; + + if (data->vbus_boost_enabled == enable) + return; + + if (enable) + ret = regulator_enable(data->vbus_boost); + else + ret = regulator_disable(data->vbus_boost); + + if (ret == 0) + data->vbus_boost_enabled = enable; + else + dev_err(&data->client->dev, "Error updating Vbus boost regulator: %d\n", ret); +} + +static void lc824206xa_charger_detect(struct lc824206xa_data *data) +{ + int charger_type, ret; + + charger_type = lc824206xa_read_reg(data, REG_CHARGER_TYPE); + if (charger_type < 0) + return; + + dev_dbg(&data->client->dev, "charger type 0x%02x\n", charger_type); + + switch (charger_type) { + case CHARGER_TYPE_UNKNOWN: + data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + /* Treat as SDP */ + data->cable = EXTCON_CHG_USB_SDP; + data->switch_control = SWITCH_USB_DEVICE; + break; + case CHARGER_TYPE_SDP_OR_CDP: + data->usb_type = POWER_SUPPLY_USB_TYPE_SDP; + data->cable = EXTCON_CHG_USB_SDP; + data->switch_control = SWITCH_USB_DEVICE; + + ret = lc824206xa_write_reg(data, REG_CHARGER_DET, + CHARGER_DET_CDP_ON | CHARGER_DET_ON); + if (ret < 0) + break; + + msleep(100); + ret = lc824206xa_read_reg(data, REG_CHARGER_DET); + if (ret >= 0 && (ret & CHARGER_DET_CDP_VAL)) { + data->usb_type = POWER_SUPPLY_USB_TYPE_CDP; + data->cable = EXTCON_CHG_USB_CDP; + } + + lc824206xa_write_reg(data, REG_CHARGER_DET, CHARGER_DET_ON); + break; + case CHARGER_TYPE_DCP: + data->usb_type = POWER_SUPPLY_USB_TYPE_DCP; + data->cable = EXTCON_CHG_USB_DCP; + if (data->fastcharge_over_miclr) + data->switch_control = SWITCH_STEREO_MIC; + else + data->switch_control = SWITCH_DISCONNECTED; + break; + case CHARGER_TYPE_QC: + data->usb_type = POWER_SUPPLY_USB_TYPE_DCP; + data->cable = EXTCON_CHG_USB_DCP; + data->switch_control = SWITCH_DISCONNECTED; + break; + default: + dev_warn(&data->client->dev, "Unknown charger type: 0x%02x\n", charger_type); + break; + } +} + +static void lc824206xa_work(struct work_struct *work) +{ + struct lc824206xa_data *data = container_of(work, struct lc824206xa_data, work); + bool vbus_boost_enable = false; + int status, id; + + status = lc824206xa_read_reg(data, REG_STATUS); + if (status < 0) + return; + + dev_dbg(&data->client->dev, "status 0x%02x\n", status); + + data->vbus_ok = (status & (STATUS_VBUS_PRESENT | STATUS_OVP)) == STATUS_VBUS_PRESENT; + + /* Read id pin ADC if necessary */ + switch (status & STATUS_USB_ID) { + case STATUS_USB_ID_GND: + case STATUS_USB_ID_FLOAT: + break; + default: + /* Happens when the connector is inserted slowly, log at dbg level */ + dev_dbg(&data->client->dev, "Unknown status 0x%02x\n", status); + fallthrough; + case STATUS_USB_ID_ACA: + id = lc824206xa_get_id(data); + dev_dbg(&data->client->dev, "RID 0x%02x\n", id); + switch (id) { + case 0x10: + status = STATUS_USB_ID_GND; + break; + case 0x18 ... 0x1e: + status = STATUS_USB_ID_ACA; + break; + case 0x1f: + status = STATUS_USB_ID_FLOAT; + break; + default: + dev_warn(&data->client->dev, "Unknown RID 0x%02x\n", id); + return; + } + } + + /* Check for out of spec OTG charging hubs, treat as ACA */ + if ((status & STATUS_USB_ID) == STATUS_USB_ID_GND && + data->vbus_ok && !data->vbus_boost_enabled) { + dev_info(&data->client->dev, "Out of spec USB host adapter with Vbus present, not enabling 5V output\n"); + status = STATUS_USB_ID_ACA; + } + + switch (status & STATUS_USB_ID) { + case STATUS_USB_ID_ACA: + data->usb_type = POWER_SUPPLY_USB_TYPE_ACA; + data->cable = EXTCON_CHG_USB_ACA; + data->switch_control = SWITCH_USB_HOST; + break; + case STATUS_USB_ID_GND: + data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + data->cable = EXTCON_USB_HOST; + data->switch_control = SWITCH_USB_HOST; + vbus_boost_enable = true; + break; + case STATUS_USB_ID_FLOAT: + /* When fast charging with Vbus > 5V, OVP will be set */ + if (data->fastcharge_over_miclr && + data->switch_control == SWITCH_STEREO_MIC && + (status & STATUS_OVP)) { + data->cable = EXTCON_CHG_USB_FAST; + break; + } + + if (data->vbus_ok) { + lc824206xa_charger_detect(data); + } else { + data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + data->cable = EXTCON_NONE; + data->switch_control = SWITCH_DISCONNECTED; + } + break; + } + + lc824206xa_set_vbus_boost(data, vbus_boost_enable); + + if (data->switch_control != data->previous_switch_control) { + lc824206xa_write_reg(data, REG_SWITCH_CONTROL, data->switch_control); + data->previous_switch_control = data->switch_control; + } + + if (data->cable != data->previous_cable) { + extcon_set_state_sync(data->edev, data->previous_cable, false); + extcon_set_state_sync(data->edev, data->cable, true); + data->previous_cable = data->cable; + } + + power_supply_changed(data->psy); +} + +static irqreturn_t lc824206xa_irq(int irq, void *_data) +{ + struct lc824206xa_data *data = _data; + int intr_status; + + intr_status = lc824206xa_read_reg(data, REG_INTR_STATUS); + if (intr_status < 0) + intr_status = INTR_ALL; /* Should never happen, clear all */ + + dev_dbg(&data->client->dev, "interrupt 0x%02x\n", intr_status); + + lc824206xa_write_reg(data, REG_INTR_CLEAR, intr_status); + lc824206xa_write_reg(data, REG_INTR_CLEAR, 0); + + schedule_work(&data->work); + return IRQ_HANDLED; +} + +/* + * Newer charger (power_supply) drivers expect the max input current to be + * provided by a parent power_supply device for the charger chip. + */ +static int lc824206xa_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lc824206xa_data *data = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = data->vbus_ok && !data->vbus_boost_enabled; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = data->usb_type; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + switch (data->usb_type) { + case POWER_SUPPLY_USB_TYPE_DCP: + case POWER_SUPPLY_USB_TYPE_ACA: + val->intval = 2000000; + break; + case POWER_SUPPLY_USB_TYPE_CDP: + val->intval = 1500000; + break; + default: + val->intval = 500000; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_property lc824206xa_psy_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static const struct power_supply_desc lc824206xa_psy_desc = { + .name = "lc824206xa-charger-detect", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_ACA) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), + .properties = lc824206xa_psy_props, + .num_properties = ARRAY_SIZE(lc824206xa_psy_props), + .get_property = lc824206xa_psy_get_prop, +}; + +static int lc824206xa_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = { }; + struct device *dev = &client->dev; + struct lc824206xa_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + INIT_WORK(&data->work, lc824206xa_work); + data->cable = EXTCON_NONE; + data->previous_cable = EXTCON_NONE; + data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + /* Some designs use a custom fast-charge protocol over the mic L/R inputs */ + data->fastcharge_over_miclr = + device_property_read_bool(dev, "onnn,enable-miclr-for-dcp"); + + data->vbus_boost = devm_regulator_get(dev, "vbus"); + if (IS_ERR(data->vbus_boost)) + return dev_err_probe(dev, PTR_ERR(data->vbus_boost), + "getting regulator\n"); + + /* Init */ + ret = lc824206xa_write_reg(data, REG00, REG00_INIT_VALUE); + ret |= lc824206xa_write_reg(data, REG10, REG10_INIT_VALUE); + msleep(100); + ret |= lc824206xa_write_reg(data, REG_INTR_CLEAR, INTR_ALL); + ret |= lc824206xa_write_reg(data, REG_INTR_CLEAR, 0); + ret |= lc824206xa_write_reg(data, REG_INTR_MASK, INTR_MASK); + ret |= lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_AUTO); + ret |= lc824206xa_write_reg(data, REG_CHARGER_DET, CHARGER_DET_ON); + if (ret) + return -EIO; + + /* Initialize extcon device */ + data->edev = devm_extcon_dev_allocate(dev, lc824206xa_cables); + if (IS_ERR(data->edev)) + return PTR_ERR(data->edev); + + ret = devm_extcon_dev_register(dev, data->edev); + if (ret) + return dev_err_probe(dev, ret, "registering extcon device\n"); + + psy_cfg.drv_data = data; + data->psy = devm_power_supply_register(dev, &lc824206xa_psy_desc, &psy_cfg); + if (IS_ERR(data->psy)) + return dev_err_probe(dev, PTR_ERR(data->psy), "registering power supply\n"); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, lc824206xa_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + KBUILD_MODNAME, data); + if (ret) + return dev_err_probe(dev, ret, "requesting IRQ\n"); + + /* Sync initial state */ + schedule_work(&data->work); + return 0; +} + +static const struct i2c_device_id lc824206xa_i2c_ids[] = { + { "lc824206xa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lc824206xa_i2c_ids); + +static struct i2c_driver lc824206xa_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = lc824206xa_probe, + .id_table = lc824206xa_i2c_ids, +}; + +module_i2c_driver(lc824206xa_driver); + +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_DESCRIPTION("LC824206XA Micro USB Switch driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/extcon/extcon-max14526.c b/drivers/extcon/extcon-max14526.c new file mode 100644 index 000000000000..3750a5c20612 --- /dev/null +++ b/drivers/extcon/extcon-max14526.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/extcon-provider.h> +#include <linux/i2c.h> +#include <linux/mod_devicetable.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/regmap.h> + +/* I2C addresses of MUIC internal registers */ +#define MAX14526_DEVICE_ID 0x00 +#define MAX14526_ID 0x02 + +/* CONTROL_1 register masks */ +#define MAX14526_CONTROL_1 0x01 +#define ID_2P2 BIT(6) +#define ID_620 BIT(5) +#define ID_200 BIT(4) +#define VLDO BIT(3) +#define SEMREN BIT(2) +#define ADC_EN BIT(1) +#define CP_EN BIT(0) + +/* CONTROL_2 register masks */ +#define MAX14526_CONTROL_2 0x02 +#define INTPOL BIT(7) +#define INT_EN BIT(6) +#define MIC_LP BIT(5) +#define CP_AUD BIT(4) +#define CHG_TYPE BIT(1) +#define USB_DET_DIS BIT(0) + +/* SW_CONTROL register masks */ +#define MAX14526_SW_CONTROL 0x03 +#define SW_DATA 0x00 +#define SW_UART 0x01 +#define SW_AUDIO 0x02 +#define SW_OPEN 0x07 + +/* INT_STATUS register masks */ +#define MAX14526_INT_STAT 0x04 +#define CHGDET BIT(7) +#define MR_COMP BIT(6) +#define SENDEND BIT(5) +#define V_VBUS BIT(4) + +/* STATUS register masks */ +#define MAX14526_STATUS 0x05 +#define CPORT BIT(7) +#define CHPORT BIT(6) +#define C1COMP BIT(0) + +enum max14526_idno_resistance { + MAX14526_GND, + MAX14526_24KOHM, + MAX14526_56KOHM, + MAX14526_100KOHM, + MAX14526_130KOHM, + MAX14526_180KOHM, + MAX14526_240KOHM, + MAX14526_330KOHM, + MAX14526_430KOHM, + MAX14526_620KOHM, + MAX14526_910KOHM, + MAX14526_OPEN +}; + +enum max14526_field_idx { + VENDOR_ID, CHIP_REV, /* DEVID */ + DM, DP, /* SW_CONTROL */ + MAX14526_N_REGMAP_FIELDS +}; + +static const struct reg_field max14526_reg_field[MAX14526_N_REGMAP_FIELDS] = { + [VENDOR_ID] = REG_FIELD(MAX14526_DEVICE_ID, 4, 7), + [CHIP_REV] = REG_FIELD(MAX14526_DEVICE_ID, 0, 3), + [DM] = REG_FIELD(MAX14526_SW_CONTROL, 0, 2), + [DP] = REG_FIELD(MAX14526_SW_CONTROL, 3, 5), +}; + +struct max14526_data { + struct i2c_client *client; + struct extcon_dev *edev; + + struct regmap *regmap; + struct regmap_field *rfield[MAX14526_N_REGMAP_FIELDS]; + + int last_state; + int cable; +}; + +enum max14526_muic_modes { + MAX14526_OTG = MAX14526_GND, /* no power */ + MAX14526_MHL = MAX14526_56KOHM, /* no power */ + MAX14526_OTG_Y = MAX14526_GND | V_VBUS, + MAX14526_MHL_CHG = MAX14526_GND | V_VBUS | CHGDET, + MAX14526_NONE = MAX14526_OPEN, + MAX14526_USB = MAX14526_OPEN | V_VBUS, + MAX14526_CHG = MAX14526_OPEN | V_VBUS | CHGDET, +}; + +static const unsigned int max14526_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_FAST, + EXTCON_DISP_MHL, + EXTCON_NONE, +}; + +static int max14526_ap_usb_mode(struct max14526_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + /* Enable USB Path */ + ret = regmap_field_write(priv->rfield[DM], SW_DATA); + if (ret) + return ret; + + ret = regmap_field_write(priv->rfield[DP], SW_DATA); + if (ret) + return ret; + + /* Enable 200K, Charger Pump and ADC */ + ret = regmap_write(priv->regmap, MAX14526_CONTROL_1, + ID_200 | ADC_EN | CP_EN); + if (ret) + return ret; + + dev_dbg(dev, "AP USB mode set\n"); + + return 0; +} + +static irqreturn_t max14526_interrupt(int irq, void *dev_id) +{ + struct max14526_data *priv = dev_id; + struct device *dev = &priv->client->dev; + int state, ret; + + /* + * Upon an MUIC IRQ (MUIC_INT_N falls), wait at least 70ms + * before reading INT_STAT and STATUS. After the reads, + * MUIC_INT_N returns to high (but the INT_STAT and STATUS + * contents will be held). + */ + msleep(100); + + ret = regmap_read(priv->regmap, MAX14526_INT_STAT, &state); + if (ret) + dev_err(dev, "failed to read MUIC state %d\n", ret); + + if (state == priv->last_state) + return IRQ_HANDLED; + + /* Detach previous device */ + extcon_set_state_sync(priv->edev, priv->cable, false); + + switch (state) { + case MAX14526_USB: + priv->cable = EXTCON_USB; + break; + + case MAX14526_CHG: + priv->cable = EXTCON_CHG_USB_FAST; + break; + + case MAX14526_OTG: + case MAX14526_OTG_Y: + priv->cable = EXTCON_USB_HOST; + break; + + case MAX14526_MHL: + case MAX14526_MHL_CHG: + priv->cable = EXTCON_DISP_MHL; + break; + + case MAX14526_NONE: + default: + priv->cable = EXTCON_NONE; + break; + } + + extcon_set_state_sync(priv->edev, priv->cable, true); + + priv->last_state = state; + + return IRQ_HANDLED; +} + +static const struct regmap_config max14526_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX14526_STATUS, +}; + +static int max14526_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max14526_data *priv; + int ret, dev_id, rev, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &max14526_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n"); + + for (i = 0; i < MAX14526_N_REGMAP_FIELDS; i++) { + priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, + max14526_reg_field[i]); + if (IS_ERR(priv->rfield[i])) + return dev_err_probe(dev, PTR_ERR(priv->rfield[i]), + "cannot allocate regmap field\n"); + } + + /* Detect if MUIC version is supported */ + ret = regmap_field_read(priv->rfield[VENDOR_ID], &dev_id); + if (ret) + return dev_err_probe(dev, ret, "failed to read MUIC ID\n"); + + regmap_field_read(priv->rfield[CHIP_REV], &rev); + + if (dev_id == MAX14526_ID) + dev_info(dev, "detected MAX14526 MUIC with id 0x%x, rev 0x%x\n", dev_id, rev); + else + dev_err_probe(dev, -EINVAL, "MUIC vendor id 0x%X is not recognized\n", dev_id); + + priv->edev = devm_extcon_dev_allocate(dev, max14526_extcon_cable); + if (IS_ERR(priv->edev)) + return dev_err_probe(dev, (IS_ERR(priv->edev)), + "failed to allocate extcon device\n"); + + ret = devm_extcon_dev_register(dev, priv->edev); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to register extcon device\n"); + + ret = max14526_ap_usb_mode(priv); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set AP USB mode\n"); + + regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, INT_EN, INT_EN); + regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, USB_DET_DIS, (u32)~USB_DET_DIS); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, &max14526_interrupt, + IRQF_ONESHOT | IRQF_SHARED, client->name, priv); + if (ret) + return dev_err_probe(dev, ret, "failed to register IRQ\n"); + + irq_wake_thread(client->irq, priv); + + return 0; +} + +static int max14526_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max14526_data *priv = i2c_get_clientdata(client); + + irq_wake_thread(client->irq, priv); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(max14526_pm_ops, NULL, max14526_resume); + +static const struct of_device_id max14526_match[] = { + { .compatible = "maxim,max14526" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, max14526_match); + +static const struct i2c_device_id max14526_id[] = { + { "max14526" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max14526_id); + +static struct i2c_driver max14526_driver = { + .driver = { + .name = "max14526", + .of_match_table = max14526_match, + .pm = &max14526_pm_ops, + }, + .probe = max14526_probe, + .id_table = max14526_id, +}; +module_i2c_driver(max14526_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("MAX14526 extcon driver to support MUIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/extcon/extcon-max3355.c b/drivers/extcon/extcon-max3355.c index d7795607f693..b2ee4ff8b04d 100644 --- a/drivers/extcon/extcon-max3355.c +++ b/drivers/extcon/extcon-max3355.c @@ -112,13 +112,11 @@ static int max3355_probe(struct platform_device *pdev) return 0; } -static int max3355_remove(struct platform_device *pdev) +static void max3355_remove(struct platform_device *pdev) { struct max3355_data *data = platform_get_drvdata(pdev); gpiod_set_value_cansleep(data->shdn_gpiod, 0); - - return 0; } static const struct of_device_id max3355_match_table[] = { diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 1f1d9ab0c5c7..2c567e0b7b7f 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1258,9 +1258,16 @@ static int max77693_muic_probe(struct platform_device *pdev) return ret; } +static const struct of_device_id of_max77693_muic_dt_match[] = { + { .compatible = "maxim,max77693-muic", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, of_max77693_muic_dt_match); + static struct platform_driver max77693_muic_driver = { .driver = { .name = DEV_NAME, + .of_match_table = of_max77693_muic_dt_match, }, .probe = max77693_muic_probe, }; diff --git a/drivers/extcon/extcon-max77843.c b/drivers/extcon/extcon-max77843.c index 1bc0426ce3f1..2ae9f7f1a67f 100644 --- a/drivers/extcon/extcon-max77843.c +++ b/drivers/extcon/extcon-max77843.c @@ -928,7 +928,7 @@ err_muic_irq: return ret; } -static int max77843_muic_remove(struct platform_device *pdev) +static void max77843_muic_remove(struct platform_device *pdev) { struct max77843_muic_info *info = platform_get_drvdata(pdev); struct max77693_dev *max77843 = info->max77843; @@ -936,8 +936,6 @@ static int max77843_muic_remove(struct platform_device *pdev) cancel_work_sync(&info->irq_work); regmap_del_irq_chip(max77843->irq, max77843->irq_data_muic); i2c_unregister_device(max77843->i2c_muic); - - return 0; } static const struct platform_device_id max77843_muic_id[] = { @@ -946,9 +944,16 @@ static const struct platform_device_id max77843_muic_id[] = { }; MODULE_DEVICE_TABLE(platform, max77843_muic_id); +static const struct of_device_id of_max77843_muic_dt_match[] = { + { .compatible = "maxim,max77843-muic", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, of_max77843_muic_dt_match); + static struct platform_driver max77843_muic_driver = { .driver = { .name = "max77843-muic", + .of_match_table = of_max77843_muic_dt_match, }, .probe = max77843_muic_probe, .remove = max77843_muic_remove, diff --git a/drivers/extcon/extcon-ptn5150.c b/drivers/extcon/extcon-ptn5150.c index 4616da7e5430..78ad86c4a3be 100644 --- a/drivers/extcon/extcon-ptn5150.c +++ b/drivers/extcon/extcon-ptn5150.c @@ -338,7 +338,7 @@ static const struct of_device_id ptn5150_dt_match[] = { MODULE_DEVICE_TABLE(of, ptn5150_dt_match); static const struct i2c_device_id ptn5150_i2c_id[] = { - { "ptn5150", 0 }, + { "ptn5150" }, { } }; MODULE_DEVICE_TABLE(i2c, ptn5150_i2c_id); diff --git a/drivers/extcon/extcon-qcom-spmi-misc.c b/drivers/extcon/extcon-qcom-spmi-misc.c index f72e90ceca53..afaba5685c3d 100644 --- a/drivers/extcon/extcon-qcom-spmi-misc.c +++ b/drivers/extcon/extcon-qcom-spmi-misc.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/** +/* * extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID * and VBUS detection based on extcon-usb-gpio.c. * @@ -155,7 +155,7 @@ static int qcom_usb_extcon_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); - device_init_wakeup(dev, 1); + devm_device_init_wakeup(dev); /* Perform initial detection */ qcom_usb_extcon_detect_cable(&info->wq_detcable.work); diff --git a/drivers/extcon/extcon-rtk-type-c.c b/drivers/extcon/extcon-rtk-type-c.c new file mode 100644 index 000000000000..82b60b927e41 --- /dev/null +++ b/drivers/extcon/extcon-rtk-type-c.c @@ -0,0 +1,1795 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * * extcon-rtk-type-c.c - Realtek Extcon Type C driver + * + * Copyright (C) 2023 Realtek Semiconductor Corporation + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/syscalls.h> +#include <linux/suspend.h> +#include <linux/debugfs.h> +#include <linux/extcon.h> +#include <linux/extcon-provider.h> +#include <linux/sys_soc.h> +#include <linux/nvmem-consumer.h> +#include <linux/gpio/consumer.h> +#include <linux/usb/otg.h> +#include <linux/usb/typec.h> + +struct cc_param { + u32 rp_4p7k_code; + u32 rp_36k_code; + u32 rp_12k_code; + u32 rd_code; + u32 ra_code; + u32 vref_2p6v; + u32 vref_1p23v; + u32 vref_0p8v; + u32 vref_0p66v; + u32 vref_0p4v; + u32 vref_0p2v; + u32 vref_1_1p6v; + u32 vref_0_1p6v; +}; + +struct type_c_cfg { + int parameter_ver; /* Parameter version */ + int cc_dfp_mode; + struct cc_param cc1_param; + struct cc_param cc2_param; + + u32 debounce_val; + bool use_defalut_parameter; +}; + +struct type_c_data { + void __iomem *reg_base; + struct device *dev; + struct extcon_dev *edev; + + u32 irq; + + /* rd control GPIO only for rtd1295 */ + struct gpio_desc *rd_ctrl_gpio_desc; + + /* Parameters */ + struct type_c_cfg *type_c_cfg; + u32 dfp_mode_rp_en; + u32 ufp_mode_rd_en; + u32 cc1_code; + u32 cc2_code; + u32 cc1_vref; + u32 cc2_vref; + u32 debounce; /* 1b,1us 7f,4.7us */ + + /* type_c state */ + int connect_change; +#define CONNECT_CHANGE 1 +#define CONNECT_NO_CHANGE 0 + int cc_mode; /* cc is host or device */ +#define IN_HOST_MODE 0x10 +#define IN_DEVICE_MODE 0x20 + int is_attach; +#define IN_ATTACH 1 +#define TO_ATTACH 1 +#define IN_DETACH 0 +#define TO_DETACH 0 + int at_cc1; +#define AT_CC1 1 +#define AT_CC2 0 + + u32 int_status; + u32 cc_status; + /* protect the data member */ + spinlock_t lock; + struct delayed_work delayed_work; + + bool rd_en_at_first; + + struct dentry *debug_dir; + + struct typec_port *port; +}; + +/* Type C register offset */ +#define USB_TYPEC_CTRL_CC1_0 0x0 +#define USB_TYPEC_CTRL_CC1_1 0x4 +#define USB_TYPEC_CTRL_CC2_0 0x8 +#define USB_TYPEC_CTRL_CC2_1 0xC +#define USB_TYPEC_STS 0x10 +#define USB_TYPEC_CTRL 0x14 +#define USB_DBUS_PWR_CTRL 0x18 + +#define ENABLE_CC1 0x1 +#define ENABLE_CC2 0x2 +#define DISABLE_CC 0x0 + +/* Bit mapping USB_TYPEC_CTRL_CC1_0 and USB_TYPEC_CTRL_CC2_0 */ +#define PLR_EN BIT(29) +#define CC_SWITCH_MASK (BIT(29) | BIT(28) | BIT(27)) +#define CC_CODE_MASK (0xfffff << 7) +#define rp4pk_code(val) ((0x1f & (val)) << 22) +#define code_rp4pk(val) (((val) >> 22) & 0x1f) +#define rp36k_code(val) ((0x1f & (val)) << 17) +#define code_rp36k(val) (((val) >> 17) & 0x1f) +#define rp12k_code(val) ((0x1f & (val)) << 12) +#define code_rp12k(val) (((val) >> 12) & 0x1f) +#define rd_code(val) ((0x1f & (val)) << 7) +#define code_rd(val) (((val) >> 7) & 0x1f) +#define dfp_mode(val) ((0x3 & (val)) << 5) +#define EN_RP4P7K BIT(4) +#define EN_RP36K BIT(3) +#define EN_RP12K BIT(2) +#define EN_RD BIT(1) +#define EN_CC_DET BIT(0) + +#define CC_MODE_UFP 0x0 +#define CC_MODE_DFP_USB 0x1 +#define CC_MODE_DFP_1_5 0x2 +#define CC_MODE_DFP_3_0 0x3 + +/* + * PARAMETER_V0: + * Realtek Kylin rtd1295 + * Realtek Hercules rtd1395 + * Realtek Thor rtd1619 + * Realtek Hank rtd1319 + * Realtek Groot rtd1312c + * PARAMETER_V1: + * Realtek Stark rtd1619b + * Realtek Parker rtd1319d + * Realtek Danvers rtd1315e + */ +enum parameter_version { + PARAMETER_V0 = 0, + PARAMETER_V1 = 1, +}; + +/* Bit mapping USB_TYPEC_CTRL_CC1_1 and USB_TYPEC_CTRL_CC2_1 */ +#define V0_vref_2p6v(val) ((0xf & (val)) << 26) /* Bit 29 for groot */ +#define V0_vref_1p23v(val) ((0xf & (val)) << 22) +#define V0_vref_0p8v(val) ((0xf & (val)) << 18) +#define V0_vref_0p66v(val) ((0xf & (val)) << 14) +#define V0_vref_0p4v(val) ((0x7 & (val)) << 11) +#define V0_vref_0p2v(val) ((0x7 & (val)) << 8) +#define V0_vref_1_1p6v(val) ((0xf & (val)) << 4) +#define V0_vref_0_1p6v(val) ((0xf & (val)) << 0) + +#define V0_decode_2p6v(val) (((val) >> 26) & 0xf) /* Bit 29 for groot */ +#define V0_decode_1p23v(val) (((val) >> 22) & 0xf) +#define V0_decode_0p8v(val) (((val) >> 18) & 0xf) +#define V0_decode_0p66v(val) (((val) >> 14) & 0xf) +#define V0_decode_0p4v(val) (((val) >> 11) & 0x7) +#define V0_decode_0p2v(val) (((val) >> 8) & 0x7) +#define V0_decode_1_1p6v(val) (((val) >> 4) & 0xf) +#define V0_decode_0_1p6v(val) (((val) >> 0) & 0xf) + +/* new Bit mapping USB_TYPEC_CTRL_CC1_1 and USB_TYPEC_CTRL_CC2_1 */ +#define V1_vref_2p6v(val) ((0xf & (val)) << 28) +#define V1_vref_1p23v(val) ((0xf & (val)) << 24) +#define V1_vref_0p8v(val) ((0xf & (val)) << 20) +#define V1_vref_0p66v(val) ((0xf & (val)) << 16) +#define V1_vref_0p4v(val) ((0xf & (val)) << 12) +#define V1_vref_0p2v(val) ((0xf & (val)) << 8) +#define V1_vref_1_1p6v(val) ((0xf & (val)) << 4) +#define V1_vref_0_1p6v(val) ((0xf & (val)) << 0) + +#define V1_decode_2p6v(val) (((val) >> 28) & 0xf) +#define V1_decode_1p23v(val) (((val) >> 24) & 0xf) +#define V1_decode_0p8v(val) (((val) >> 20) & 0xf) +#define V1_decode_0p66v(val) (((val) >> 16) & 0xf) +#define V1_decode_0p4v(val) (((val) >> 12) & 0xf) +#define V1_decode_0p2v(val) (((val) >> 8) & 0xf) +#define V1_decode_1_1p6v(val) (((val) >> 4) & 0xf) +#define V1_decode_0_1p6v(val) (((val) >> 0) & 0xf) + +/* Bit mapping USB_TYPEC_STS */ +#define DET_STS 0x7 +#define CC1_DET_STS (DET_STS) +#define CC2_DET_STS (DET_STS << 3) +#define DET_STS_RA 0x1 +#define DET_STS_RD 0x3 +#define DET_STS_RP 0x1 +#define CC1_DET_STS_RA (DET_STS_RA) +#define CC1_DET_STS_RD (DET_STS_RD) +#define CC1_DET_STS_RP (DET_STS_RP) +#define CC2_DET_STS_RA (DET_STS_RA << 3) +#define CC2_DET_STS_RD (DET_STS_RD << 3) +#define CC2_DET_STS_RP (DET_STS_RP << 3) + +/* Bit mapping USB_TYPEC_CTRL */ +#define CC2_INT_EN BIT(11) +#define CC1_INT_EN BIT(10) +#define CC2_INT_STS BIT(9) +#define CC1_INT_STS BIT(8) +#define DEBOUNCE_TIME_MASK 0xff +#define DEBOUNCE_EN BIT(0) +#define ENABLE_TYPE_C_DETECT (CC1_INT_EN | CC2_INT_EN) +#define ALL_CC_INT_STS (CC1_INT_STS | CC2_INT_STS) + +/* Parameter */ +#define DETECT_TIME 50 /* ms */ + +static const unsigned int usb_type_c_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +enum usb_data_roles { + DR_NONE, + DR_HOST, + DR_DEVICE, +}; + +static const struct soc_device_attribute rtk_soc_kylin[] = { + { .family = "Realtek Kylin", }, + { /* empty */ } +}; + +static int rtd129x_switch_type_c_plug_config(struct type_c_data *type_c, + int dr_mode, int cc) +{ + void __iomem *reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_0; + int val_cc; + +#define TYPE_C_EN_SWITCH BIT(29) +#define TYPE_C_TXRX_SEL (BIT(28) | BIT(27)) +#define TYPE_C_SWITCH_MASK (TYPE_C_EN_SWITCH | TYPE_C_TXRX_SEL) +#define TYPE_C_ENABLE_CC1 TYPE_C_EN_SWITCH +#define TYPE_C_ENABLE_CC2 (TYPE_C_EN_SWITCH | TYPE_C_TXRX_SEL) +#define TYPE_C_DISABLE_CC ~TYPE_C_SWITCH_MASK + + val_cc = readl(reg); + val_cc &= ~TYPE_C_SWITCH_MASK; + + if (cc == DISABLE_CC) { + val_cc &= TYPE_C_DISABLE_CC; + } else if (cc == ENABLE_CC1) { + val_cc |= TYPE_C_ENABLE_CC1; + } else if (cc == ENABLE_CC2) { + val_cc |= TYPE_C_ENABLE_CC2; + } else { + dev_err(type_c->dev, "%s: Error cc setting cc=0x%x\n", __func__, cc); + return -EINVAL; + } + writel(val_cc, reg); + + /* waiting cc stable for enable/disable */ + mdelay(1); + + dev_dbg(type_c->dev, "%s: cc=0x%x val_cc=0x%x usb_typec_ctrl_cc1_0=0x%x\n", + __func__, cc, val_cc, readl(reg)); + + return 0; +} + +static inline void switch_type_c_plug_config(struct type_c_data *type_c, + int dr_mode, int cc) +{ + int ret = 0; + + if (soc_device_match(rtk_soc_kylin)) + ret = rtd129x_switch_type_c_plug_config(type_c, dr_mode, cc); + + if (ret < 0) + dev_err(type_c->dev, "%s: Error set type c plug config\n", + __func__); +} + +static void switch_type_c_dr_mode(struct type_c_data *type_c, int dr_mode, int cc) +{ + bool is_host = false; + bool is_device = false; + bool polarity = false; + bool vbus = false; + bool ss = true; + + switch_type_c_plug_config(type_c, dr_mode, cc); + if (cc == ENABLE_CC2) + polarity = true; + + switch (dr_mode) { + case USB_DR_MODE_HOST: + is_host = true; + break; + case USB_DR_MODE_PERIPHERAL: + is_device = true; + vbus = true; + break; + default: + dev_dbg(type_c->dev, "%s dr_mode=%d ==> no host or device\n", + __func__, dr_mode); + break; + } + + dev_dbg(type_c->dev, "%s is_host=%d is_device=%d vbus=%d polarity=%d\n", + __func__, is_host, is_device, vbus, polarity); + + /* for EXTCON_USB device mode */ + extcon_set_state(type_c->edev, EXTCON_USB, is_device); + extcon_set_property(type_c->edev, EXTCON_USB, + EXTCON_PROP_USB_VBUS, + (union extcon_property_value)(int)vbus); + extcon_set_property(type_c->edev, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY, + (union extcon_property_value)(int)polarity); + extcon_set_property(type_c->edev, EXTCON_USB, + EXTCON_PROP_USB_SS, + (union extcon_property_value)(int)ss); + + /* for EXTCON_USB_HOST host mode */ + extcon_set_state(type_c->edev, EXTCON_USB_HOST, is_host); + extcon_set_property(type_c->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_VBUS, + (union extcon_property_value)(int)vbus); + extcon_set_property(type_c->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY, + (union extcon_property_value)(int)polarity); + extcon_set_property(type_c->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS, + (union extcon_property_value)(int)ss); + + /* sync EXTCON_USB and EXTCON_USB_HOST */ + extcon_sync(type_c->edev, EXTCON_USB); + extcon_sync(type_c->edev, EXTCON_USB_HOST); + + if (type_c->port) { + switch (dr_mode) { + case USB_DR_MODE_HOST: + typec_set_data_role(type_c->port, TYPEC_HOST); + typec_set_pwr_role(type_c->port, TYPEC_SOURCE); + break; + case USB_DR_MODE_PERIPHERAL: + typec_set_data_role(type_c->port, TYPEC_DEVICE); + typec_set_pwr_role(type_c->port, TYPEC_SINK); + break; + default: + dev_dbg(type_c->dev, "%s unknown dr_mode=%d\n", + __func__, dr_mode); + break; + } + } +} + +/* connector attached/detached */ +static int connector_attached(struct type_c_data *type_c, u32 cc, int dr_mode) +{ + void __iomem *reg = type_c->reg_base + USB_TYPEC_CTRL; + + cancel_delayed_work(&type_c->delayed_work); + + switch_type_c_dr_mode(type_c, dr_mode, cc); + + writel(ENABLE_TYPE_C_DETECT | readl(reg), reg); + + return 0; +} + +static int connector_detached(struct type_c_data *type_c, u32 cc, int dr_mode) +{ + void __iomem *reg = type_c->reg_base + USB_TYPEC_CTRL; + + writel(~ENABLE_TYPE_C_DETECT & readl(reg), reg); + + switch_type_c_dr_mode(type_c, 0, cc); + + schedule_delayed_work(&type_c->delayed_work, msecs_to_jiffies(DETECT_TIME)); + + return 0; +} + +/* detect host device switch */ +static int __detect_host_device(struct type_c_data *type_c, u32 rp_or_rd_en) +{ + struct device *dev = type_c->dev; + void __iomem *reg_base = type_c->reg_base; + u32 cc1_config, cc2_config, default_ctrl; + u32 cc1_switch = 0; + + default_ctrl = readl(reg_base + USB_TYPEC_CTRL) & DEBOUNCE_TIME_MASK; + writel(default_ctrl, reg_base + USB_TYPEC_CTRL); + + cc1_config = readl(reg_base + USB_TYPEC_CTRL_CC1_0); + cc2_config = readl(reg_base + USB_TYPEC_CTRL_CC2_0); + + cc1_config &= ~EN_CC_DET; + cc2_config &= ~EN_CC_DET; + writel(cc1_config, reg_base + USB_TYPEC_CTRL_CC1_0); + writel(cc2_config, reg_base + USB_TYPEC_CTRL_CC2_0); + + if (soc_device_match(rtk_soc_kylin)) + cc1_switch = cc1_config & CC_SWITCH_MASK; + + cc1_config &= CC_CODE_MASK; + cc1_config |= rp_or_rd_en | cc1_switch; + cc2_config &= CC_CODE_MASK; + cc2_config |= rp_or_rd_en; + writel(cc2_config, reg_base + USB_TYPEC_CTRL_CC2_0); + writel(cc1_config, reg_base + USB_TYPEC_CTRL_CC1_0); + + /* For kylin to disable external rd control gpio */ + if (soc_device_match(rtk_soc_kylin)) { + struct gpio_desc *gpio = type_c->rd_ctrl_gpio_desc; + + if (gpio && gpiod_direction_output(gpio, 1)) + dev_err(dev, "%s ERROR set rd_ctrl_gpio_desc fail\n", __func__); + } + + cc1_config |= EN_CC_DET; + cc2_config |= EN_CC_DET; + writel(cc1_config, reg_base + USB_TYPEC_CTRL_CC1_0); + writel(cc2_config, reg_base + USB_TYPEC_CTRL_CC2_0); + + return 0; +} + +static int detect_device(struct type_c_data *type_c) +{ + return __detect_host_device(type_c, type_c->dfp_mode_rp_en); +} + +static int detect_host(struct type_c_data *type_c) +{ + return __detect_host_device(type_c, type_c->ufp_mode_rd_en); +} + +static int host_device_switch_detection(struct type_c_data *type_c) +{ + if (type_c->cc_mode == IN_HOST_MODE) { + type_c->cc_mode = IN_DEVICE_MODE; + detect_host(type_c); + } else { + type_c->cc_mode = IN_HOST_MODE; + detect_device(type_c); + } + + return 0; +} + +static int detect_type_c_state(struct type_c_data *type_c) +{ + struct device *dev = type_c->dev; + void __iomem *reg_base = type_c->reg_base; + u32 int_status, cc_status, cc_status_check; + unsigned long flags; + + spin_lock_irqsave(&type_c->lock, flags); + + int_status = readl(reg_base + USB_TYPEC_CTRL); + cc_status = readl(reg_base + USB_TYPEC_STS); + + type_c->connect_change = CONNECT_NO_CHANGE; + + switch (type_c->cc_mode | type_c->is_attach) { + case IN_HOST_MODE | IN_ATTACH: + if (((cc_status & CC1_DET_STS) == CC1_DET_STS) && type_c->at_cc1 == AT_CC1) { + dev_dbg(dev, "IN host mode and cc1 device detach (cc_status=0x%x)", + cc_status); + type_c->is_attach = TO_DETACH; + type_c->connect_change = CONNECT_CHANGE; + } else if (((cc_status & CC2_DET_STS) == CC2_DET_STS) && + type_c->at_cc1 == AT_CC2) { + dev_dbg(dev, "IN host mode and cc2 device detach (cc_status=0x%x)", + cc_status); + type_c->is_attach = TO_DETACH; + type_c->connect_change = CONNECT_CHANGE; + } + break; + case IN_HOST_MODE | IN_DETACH: + cc_status_check = readl(reg_base + USB_TYPEC_STS); + if (cc_status_check != (CC1_DET_STS | CC2_DET_STS)) { + if (in_interrupt()) { + /* Add delay time to avoid capacitive effect of cable. */ + mdelay(300); + } else { + spin_unlock_irqrestore(&type_c->lock, flags); + /* Add delay time to avoid capacitive effect of cable. */ + msleep(300); + spin_lock_irqsave(&type_c->lock, flags); + } + cc_status_check = readl(reg_base + USB_TYPEC_STS); + } + if (cc_status != cc_status_check) { + dev_warn(dev, "IN_HOST_MODE: cc_status (0x%x) != cc_status_check (0x%x)\n", + cc_status, cc_status_check); + cc_status = readl(reg_base + USB_TYPEC_STS); + } + + if ((cc_status & CC1_DET_STS) == CC1_DET_STS_RD) { + dev_dbg(dev, "IN host mode and cc1 device attach (cc_status=0x%x)", + cc_status); + type_c->is_attach = TO_ATTACH; + type_c->at_cc1 = AT_CC1; + type_c->connect_change = CONNECT_CHANGE; + } else if ((cc_status & CC2_DET_STS) == CC2_DET_STS_RD) { + dev_dbg(dev, "In host mode and cc2 device attach (cc_status=0x%x)", + cc_status); + type_c->is_attach = TO_ATTACH; + type_c->at_cc1 = AT_CC2; + type_c->connect_change = CONNECT_CHANGE; + } + break; + case IN_DEVICE_MODE | IN_ATTACH: + if ((cc_status & CC1_DET_STS) < CC1_DET_STS_RP || + (cc_status & CC2_DET_STS) < CC2_DET_STS_RP) { + /* Add a sw debounce to filter cc signal sent from apple pd adapter */ + mdelay(5); + cc_status_check = readl(reg_base + USB_TYPEC_STS); + + if (cc_status != cc_status_check) { + dev_dbg(dev, "IN_DEVICE_MODE: cc_status (0x%x) != cc_status_check (0x%x) maybe use a pd adapter\n", + cc_status, cc_status_check); + cc_status = cc_status_check; + } + } + + if ((cc_status & CC1_DET_STS) < CC1_DET_STS_RP && type_c->at_cc1 == AT_CC1) { + dev_dbg(dev, "IN device mode and cc1 host disconnect (cc_status=0x%x)", + cc_status); + type_c->is_attach = TO_DETACH; + type_c->connect_change = CONNECT_CHANGE; + } else if ((cc_status & CC2_DET_STS) < CC2_DET_STS_RP && + type_c->at_cc1 == AT_CC2) { + dev_dbg(dev, "IN device mode and cc2 host disconnect (cc_status=0x%x)", + cc_status); + type_c->is_attach = TO_DETACH; + type_c->connect_change = CONNECT_CHANGE; + } + break; + case IN_DEVICE_MODE | IN_DETACH: + cc_status_check = readl(reg_base + USB_TYPEC_STS); + if (cc_status_check != 0x0) { + if (in_interrupt()) { + /* Add delay time to avoid capacitive effect of cable. */ + mdelay(300); + } else { + spin_unlock_irqrestore(&type_c->lock, flags); + /* Add delay time to avoid capacitive effect of cable. */ + msleep(300); + spin_lock_irqsave(&type_c->lock, flags); + } + cc_status_check = readl(reg_base + USB_TYPEC_STS); + } + + if (cc_status != cc_status_check) { + dev_warn(dev, "IN_DEVICE_MODE: cc_status (0x%x) != cc_status_check (0x%x)\n", + cc_status, cc_status_check); + cc_status = readl(reg_base + USB_TYPEC_STS); + } + + if ((cc_status & CC1_DET_STS) >= CC1_DET_STS_RP) { + dev_dbg(dev, "IN device mode and cc1 host connect (cc_status=0x%x)", + cc_status); + type_c->at_cc1 = AT_CC1; + type_c->is_attach = TO_ATTACH; + type_c->connect_change = CONNECT_CHANGE; + } else if ((cc_status & CC2_DET_STS) >= CC2_DET_STS_RP) { + dev_dbg(dev, "IN device mode and cc2 host connect (cc_status=0x%x)", + cc_status); + type_c->at_cc1 = AT_CC2; + type_c->is_attach = TO_ATTACH; + type_c->connect_change = CONNECT_CHANGE; + } + break; + default: + dev_err(dev, "error host or device mode (cc_mode=%d, is_attach=%d) ", + type_c->cc_mode, type_c->is_attach); + } + + type_c->int_status = int_status; + type_c->cc_status = cc_status; + + spin_unlock_irqrestore(&type_c->lock, flags); + return 0; +} + +static void host_device_switch(struct work_struct *work) +{ + struct type_c_data *type_c = container_of(work, struct type_c_data, + delayed_work.work); + struct device *dev = type_c->dev; + unsigned long flags; + int connect_change = 0; + int cc_mode = 0; + int is_attach = 0; + int at_cc1 = 0; + + spin_lock_irqsave(&type_c->lock, flags); + if (type_c->connect_change) + connect_change = type_c->connect_change; + spin_unlock_irqrestore(&type_c->lock, flags); + + if (!connect_change) + detect_type_c_state(type_c); + + spin_lock_irqsave(&type_c->lock, flags); + if (type_c->connect_change) { + connect_change = type_c->connect_change; + cc_mode = type_c->cc_mode; + is_attach = type_c->is_attach; + at_cc1 = type_c->at_cc1; + type_c->connect_change = CONNECT_NO_CHANGE; + } else { + host_device_switch_detection(type_c); + + schedule_delayed_work(&type_c->delayed_work, msecs_to_jiffies(DETECT_TIME)); + } + spin_unlock_irqrestore(&type_c->lock, flags); + + if (!connect_change) + return; + + dev_dbg(dev, "%s: usb cable connection change\n", __func__); + if (cc_mode == IN_HOST_MODE) { + if (is_attach && at_cc1) + connector_attached(type_c, ENABLE_CC1, USB_DR_MODE_HOST); + else if (is_attach && !at_cc1) + connector_attached(type_c, ENABLE_CC2, USB_DR_MODE_HOST); + else + connector_detached(type_c, DISABLE_CC, USB_DR_MODE_HOST); + } else if (cc_mode == IN_DEVICE_MODE) { + if (is_attach && at_cc1) + connector_attached(type_c, ENABLE_CC1, USB_DR_MODE_PERIPHERAL); + else if (is_attach && !at_cc1) + connector_attached(type_c, ENABLE_CC2, USB_DR_MODE_PERIPHERAL); + else + connector_detached(type_c, DISABLE_CC, USB_DR_MODE_PERIPHERAL); + } else { + dev_err(dev, "Error: IN unknown mode %d to %s at %s (cc_status=0x%x)\n", + cc_mode, is_attach ? "attach" : "detach", + at_cc1 ? "cc1" : "cc2", type_c->cc_status); + } + dev_info(dev, "Connection change OK: IN %s mode to %s at %s (cc_status=0x%x)\n", + cc_mode == IN_HOST_MODE ? "host" : "device", + is_attach ? "attach" : "detach", + at_cc1 ? "cc1" : "cc2", type_c->cc_status); +} + +static irqreturn_t type_c_detect_irq(int irq, void *__data) +{ + struct type_c_data *type_c = (struct type_c_data *)__data; + struct device *dev = type_c->dev; + void __iomem *reg = type_c->reg_base + USB_TYPEC_CTRL; + unsigned long flags; + + detect_type_c_state(type_c); + + spin_lock_irqsave(&type_c->lock, flags); + + if (type_c->connect_change) { + dev_dbg(dev, "%s: IN %s mode to %s (at %s interrupt) int_status=0x%x, cc_status=0x%x", + __func__, + type_c->cc_mode == IN_HOST_MODE ? "host" : "device", + type_c->is_attach ? "attach" : "detach", + type_c->at_cc1 ? "cc1" : "cc2", + type_c->int_status, type_c->cc_status); + + /* clear interrupt status */ + writel(~ALL_CC_INT_STS & readl(reg), reg); + + cancel_delayed_work(&type_c->delayed_work); + schedule_delayed_work(&type_c->delayed_work, msecs_to_jiffies(0)); + } else { + static int local_count; + + /* if no connect_change, we keep the status to avoid status lose */ + if (local_count++ > 10) { + /* clear interrupt status */ + writel(~ALL_CC_INT_STS & readl(reg), reg); + local_count = 0; + } + } + + spin_unlock_irqrestore(&type_c->lock, flags); + + return IRQ_HANDLED; +} + +static int type_c_port_dr_set(struct typec_port *port, + enum typec_data_role role) +{ + struct type_c_data *type_c = typec_get_drvdata(port); + u32 enable_cc; + unsigned long flags; + + spin_lock_irqsave(&type_c->lock, flags); + enable_cc = type_c->at_cc1 ? ENABLE_CC1 : ENABLE_CC2; + spin_unlock_irqrestore(&type_c->lock, flags); + + if (role == TYPEC_HOST) + switch_type_c_dr_mode(type_c, USB_DR_MODE_HOST, enable_cc); + else if (role == TYPEC_DEVICE) + switch_type_c_dr_mode(type_c, USB_DR_MODE_PERIPHERAL, enable_cc); + else + switch_type_c_dr_mode(type_c, 0, DISABLE_CC); + + return 0; +} + +static const struct typec_operations type_c_port_ops = { + .dr_set = type_c_port_dr_set, +}; + +#ifdef CONFIG_DEBUG_FS +static int type_c_parameter_show(struct seq_file *s, void *unused) +{ + struct type_c_data *type_c = s->private; + struct type_c_cfg *type_c_cfg = type_c->type_c_cfg; + struct cc_param *cc_param; + unsigned long flags; + + spin_lock_irqsave(&type_c->lock, flags); + + seq_printf(s, "cc_dfp_mode %s\n", + ({ char *tmp; + switch (type_c_cfg->cc_dfp_mode) { + case CC_MODE_DFP_USB: + tmp = "CC_MODE_DFP_USB"; break; + case CC_MODE_DFP_1_5: + tmp = "CC_MODE_DFP_1_5"; break; + case CC_MODE_DFP_3_0: + tmp = "CC_MODE_DFP_3_0"; break; + default: + tmp = "?"; break; + } tmp; })); + + seq_printf(s, "dfp_mode_rp_en 0x%x\n", type_c->dfp_mode_rp_en); + seq_printf(s, "ufp_mode_rd_en 0x%x\n", type_c->ufp_mode_rd_en); + seq_printf(s, "cc1_code 0x%x\n", type_c->cc1_code); + seq_printf(s, "cc2_code 0x%x\n", type_c->cc2_code); + seq_printf(s, "cc1_vref 0x%x\n", type_c->cc1_vref); + seq_printf(s, "cc2_vref 0x%x\n", type_c->cc2_vref); + seq_printf(s, "debounce 0x%x\n", type_c->debounce); + seq_puts(s, "\n"); + + cc_param = &type_c_cfg->cc1_param; + seq_puts(s, "cc1_param:\n"); + seq_printf(s, " rp_4p7k_code 0x%x\n", cc_param->rp_4p7k_code); + seq_printf(s, " rp_36k_code 0x%x\n", cc_param->rp_36k_code); + seq_printf(s, " rp_12k_code 0x%x\n", cc_param->rp_12k_code); + seq_printf(s, " rd_code 0x%x\n", cc_param->rd_code); + seq_printf(s, " vref_2p6v 0x%x\n", cc_param->vref_2p6v); + seq_printf(s, " vref_1p23v 0x%x\n", cc_param->vref_1p23v); + seq_printf(s, " vref_0p8v 0x%x\n", cc_param->vref_0p8v); + seq_printf(s, " vref_0p66v 0x%x\n", cc_param->vref_0p66v); + seq_printf(s, " vref_0p4v 0x%x\n", cc_param->vref_0p4v); + seq_printf(s, " vref_0p2v 0x%x\n", cc_param->vref_0p2v); + seq_printf(s, " vref_1_1p6v 0x%x\n", cc_param->vref_1_1p6v); + seq_printf(s, " vref_0_1p6v 0x%x\n", cc_param->vref_0_1p6v); + + cc_param = &type_c_cfg->cc2_param; + seq_puts(s, "cc2_param:\n"); + seq_printf(s, " rp_4p7k_code 0x%x\n", cc_param->rp_4p7k_code); + seq_printf(s, " rp_36k_code 0x%x\n", cc_param->rp_36k_code); + seq_printf(s, " rp_12k_code 0x%x\n", cc_param->rp_12k_code); + seq_printf(s, " rd_code 0x%x\n", cc_param->rd_code); + seq_printf(s, " vref_2p6v 0x%x\n", cc_param->vref_2p6v); + seq_printf(s, " vref_1p23v 0x%x\n", cc_param->vref_1p23v); + seq_printf(s, " vref_0p8v 0x%x\n", cc_param->vref_0p8v); + seq_printf(s, " vref_0p66v 0x%x\n", cc_param->vref_0p66v); + seq_printf(s, " vref_0p4v 0x%x\n", cc_param->vref_0p4v); + seq_printf(s, " vref_0p2v 0x%x\n", cc_param->vref_0p2v); + seq_printf(s, " vref_1_1p6v 0x%x\n", cc_param->vref_1_1p6v); + seq_printf(s, " vref_0_1p6v 0x%x\n", cc_param->vref_0_1p6v); + + spin_unlock_irqrestore(&type_c->lock, flags); + + return 0; +} + +static int type_c_parameter_open(struct inode *inode, struct file *file) +{ + return single_open(file, type_c_parameter_show, inode->i_private); +} + +static const struct file_operations type_c_parameter_fops = { + .open = type_c_parameter_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int type_c_status_show(struct seq_file *s, void *unused) +{ + struct type_c_data *type_c = s->private; + unsigned long flags; + + spin_lock_irqsave(&type_c->lock, flags); + + seq_printf(s, "In %s mode %s at %s (cc_status=0x%x)\n", + type_c->cc_mode == IN_HOST_MODE ? "host" : "device", + type_c->is_attach ? "attach" : "detach", + type_c->at_cc1 ? "cc1" : "cc2", type_c->cc_status); + + seq_printf(s, "Read Register (type_c_ctrl_cc1_0=0x%x)\n", + readl(type_c->reg_base + 0x0)); + seq_printf(s, "Read Register (type_c_ctrl_cc1_1=0x%x)\n", + readl(type_c->reg_base + 0x4)); + seq_printf(s, "Read Register (type_c_ctrl_cc2_0=0x%x)\n", + readl(type_c->reg_base + 0x8)); + seq_printf(s, "Read Register (type_c_ctrl_cc2_1=0x%x)\n", + readl(type_c->reg_base + 0xc)); + seq_printf(s, "Read Register (type_c_status=0x%x)\n", + readl(type_c->reg_base + 0x10)); + seq_printf(s, "Read Register (type_c_ctrl=0x%x)\n", + readl(type_c->reg_base + 0x14)); + + spin_unlock_irqrestore(&type_c->lock, flags); + + return 0; +} + +static int type_c_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, type_c_status_show, inode->i_private); +} + +static const struct file_operations type_c_status_fops = { + .open = type_c_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static inline void create_debug_files(struct type_c_data *type_c) +{ + type_c->debug_dir = debugfs_create_dir("type_c", usb_debug_root); + + debugfs_create_file("parameter", 0444, type_c->debug_dir, type_c, + &type_c_parameter_fops); + + debugfs_create_file("status", 0444, type_c->debug_dir, type_c, + &type_c_status_fops); +} + +static inline void remove_debug_files(struct type_c_data *type_c) +{ + debugfs_remove_recursive(type_c->debug_dir); +} +#else +static inline void create_debug_files(struct type_c_data *type_c) { } +static inline void remove_debug_files(struct type_c_data *type_c) { } +#endif /* CONFIG_DEBUG_FS */ + +/* Init and probe */ + +static inline s8 get_value(s8 value) +{ + return (((s8)value & 0x8) ? (-(s8)(0x7 & value)) : ((s8)(value))); +} + +static int __updated_type_c_parameter_by_efuse(struct type_c_data *type_c) +{ + struct type_c_cfg *type_c_cfg = type_c->type_c_cfg; + struct cc_param *cc_param; + struct nvmem_cell *cell; + s8 cc1_4p7k = 0; + s8 cc1_12k = 0; + s8 cc1_0p2v = 0; + s8 cc1_0p8v = 0; + s8 cc1_2p6v = 0; + s8 cc1_0p66v = 0; + s8 cc1_1p23v = 0; + s8 cc2_4p7k = 0; + s8 cc2_12k = 0; + s8 cc2_0p2v = 0; + s8 cc2_0p8v = 0; + s8 cc2_2p6v = 0; + s8 cc2_0p66v = 0; + s8 cc2_1p23v = 0; + + cell = nvmem_cell_get(type_c->dev, "usb-cal"); + if (IS_ERR(cell)) { + dev_warn(type_c->dev, "%s failed to get usb-cal: %ld\n", + __func__, PTR_ERR(cell)); + } else { + unsigned char *buf; + size_t buf_size; + int value_size = 4; + int value_mask = (BIT(value_size) - 1); + + buf = nvmem_cell_read(cell, &buf_size); + if (!IS_ERR(buf)) { + cc1_0p2v = get_value((buf[0] >> value_size * 0) & value_mask); + cc1_0p8v = get_value((buf[0] >> value_size * 1) & value_mask); + cc1_2p6v = get_value((buf[1] >> value_size * 0) & value_mask); + cc1_0p66v = get_value((buf[1] >> value_size * 1) & value_mask); + cc1_1p23v = get_value((buf[2] >> value_size * 0) & value_mask); + + cc2_0p2v = get_value((buf[3] >> value_size * 0) & value_mask); + cc2_0p8v = get_value((buf[3] >> value_size * 1) & value_mask); + cc2_2p6v = get_value((buf[4] >> value_size * 0) & value_mask); + cc2_0p66v = get_value((buf[4] >> value_size * 1) & value_mask); + cc2_1p23v = get_value((buf[5] >> value_size * 0) & value_mask); + + cc1_4p7k = get_value((buf[6] >> value_size * 0) & value_mask); + cc1_12k = get_value((buf[6] >> value_size * 1) & value_mask); + cc2_4p7k = get_value((buf[7] >> value_size * 0) & value_mask); + cc2_12k = get_value((buf[7] >> value_size * 1) & value_mask); + + kfree(buf); + } + nvmem_cell_put(cell); + } + + dev_dbg(type_c->dev, "check efuse cc1_4p7k=%d cc1_12k=%d cc2_4p7k=%d cc2_12k=%d\n", + cc1_4p7k, cc1_12k, cc2_4p7k, cc2_12k); + dev_dbg(type_c->dev, "check efuse cc1_0p2v=%d cc1_0p8v=%d cc1_2p6v=%d cc1_0p66v=%d cc1_1p23v=%d\n", + cc1_0p2v, cc1_0p8v, cc1_2p6v, cc1_0p66v, cc1_1p23v); + dev_dbg(type_c->dev, "check efuse cc2_0p2v=%d cc2_0p8v=%d cc2_2p6v=%d cc2_0p66v=%d cc2_1p23v=%d\n", + cc2_0p2v, cc2_0p8v, cc2_2p6v, cc2_0p66v, cc2_1p23v); + + cc_param = &type_c_cfg->cc1_param; + cc_param->rp_4p7k_code = cc_param->rp_4p7k_code + cc1_4p7k; + cc_param->rp_12k_code = cc_param->rp_12k_code + cc1_12k; + + cc_param->vref_1p23v = cc_param->vref_1p23v + cc1_1p23v; + cc_param->vref_0p66v = cc_param->vref_0p66v + cc1_0p66v; + cc_param->vref_2p6v = cc_param->vref_2p6v + cc1_2p6v; + cc_param->vref_0p8v = cc_param->vref_0p8v + cc1_0p8v; + cc_param->vref_0p2v = cc_param->vref_0p2v + cc1_0p2v; + + cc_param = &type_c_cfg->cc2_param; + cc_param->rp_4p7k_code = cc_param->rp_4p7k_code + cc2_4p7k; + cc_param->rp_12k_code = cc_param->rp_12k_code + cc2_12k; + + cc_param->vref_1p23v = cc_param->vref_1p23v + cc2_1p23v; + cc_param->vref_0p66v = cc_param->vref_0p66v + cc2_0p66v; + cc_param->vref_2p6v = cc_param->vref_2p6v + cc2_2p6v; + cc_param->vref_0p8v = cc_param->vref_0p8v + cc2_0p8v; + cc_param->vref_0p2v = cc_param->vref_0p2v + cc2_0p2v; + + return 0; +} + +static int __updated_type_c_parameter_by_efuse_v2(struct type_c_data *type_c) +{ + struct type_c_cfg *type_c_cfg = type_c->type_c_cfg; + struct cc_param *cc_param; + struct nvmem_cell *cell; + s8 cc1_4p7k = 0; + s8 cc1_12k = 0; + s8 cc1_0p2v = 0; + s8 cc1_0p8v = 0; + s8 cc1_2p6v = 0; + s8 cc1_0p66v = 0; + s8 cc1_1p23v = 0; + s8 cc2_4p7k = 0; + s8 cc2_12k = 0; + s8 cc2_0p2v = 0; + s8 cc2_0p8v = 0; + s8 cc2_2p6v = 0; + s8 cc2_0p66v = 0; + s8 cc2_1p23v = 0; + + cell = nvmem_cell_get(type_c->dev, "usb-type-c-cal"); + if (IS_ERR(cell)) { + dev_warn(type_c->dev, "%s failed to get usb-type-c-cal: %ld\n", + __func__, PTR_ERR(cell)); + } else { + unsigned char *buf; + size_t buf_size; + int value_size = 0; + int value_mask = (BIT(value_size) - 1); + + buf = nvmem_cell_read(cell, &buf_size); + if (!IS_ERR(buf)) { + value_size = 5; + value_mask = (BIT(value_size) - 1); + cc1_4p7k = buf[0] & value_mask; + cc1_12k = buf[1] & value_mask; + cc2_4p7k = buf[2] & value_mask; + cc2_12k = buf[3] & value_mask; + + value_size = 4; + value_mask = (BIT(value_size) - 1); + cc1_0p2v = (buf[4] >> value_size * 0) & value_mask; + cc1_0p66v = (buf[4] >> value_size * 1) & value_mask; + cc1_0p8v = (buf[5] >> value_size * 0) & value_mask; + cc1_1p23v = (buf[5] >> value_size * 1) & value_mask; + cc1_2p6v = (buf[6] >> value_size * 0) & value_mask; + + cc2_0p2v = (buf[6] >> value_size * 1) & value_mask; + cc2_0p66v = (buf[7] >> value_size * 0) & value_mask; + cc2_0p8v = (buf[7] >> value_size * 1) & value_mask; + cc2_1p23v = (buf[8] >> value_size * 0) & value_mask; + cc2_2p6v = (buf[8] >> value_size * 1) & value_mask; + + kfree(buf); + } + nvmem_cell_put(cell); + } + + dev_dbg(type_c->dev, "check efuse v2 cc1_4p7k=%d cc1_12k=%d cc2_4p7k=%d cc2_12k=%d\n", + cc1_4p7k, cc1_12k, cc2_4p7k, cc2_12k); + dev_dbg(type_c->dev, "check efuse v2 cc1_0p2v=%d cc1_0p8v=%d cc1_2p6v=%d cc1_0p66v=%d cc1_1p23v=%d\n", + cc1_0p2v, cc1_0p8v, cc1_2p6v, cc1_0p66v, cc1_1p23v); + dev_dbg(type_c->dev, "check efuse v2 cc2_0p2v=%d cc2_0p8v=%d cc2_2p6v=%d cc2_0p66v=%d cc2_1p23v=%d\n", + cc2_0p2v, cc2_0p8v, cc2_2p6v, cc2_0p66v, cc2_1p23v); + + cc_param = &type_c_cfg->cc1_param; + if (cc1_4p7k) + cc_param->rp_4p7k_code = cc1_4p7k; + if (cc1_12k) + cc_param->rp_12k_code = cc1_12k; + + if (cc1_1p23v) + cc_param->vref_1p23v = cc1_1p23v; + if (cc1_0p66v) + cc_param->vref_0p66v = cc1_0p66v; + if (cc1_2p6v) + cc_param->vref_2p6v = cc1_2p6v; + if (cc1_0p8v) + cc_param->vref_0p8v = cc1_0p8v; + if (cc1_0p2v) + cc_param->vref_0p2v = cc1_0p2v; + + cc_param = &type_c_cfg->cc2_param; + if (cc2_4p7k) + cc_param->rp_4p7k_code = cc2_4p7k; + if (cc2_12k) + cc_param->rp_12k_code = cc2_12k; + + if (cc2_1p23v) + cc_param->vref_1p23v = cc2_1p23v; + if (cc2_0p66v) + cc_param->vref_0p66v = cc2_0p66v; + if (cc2_2p6v) + cc_param->vref_2p6v = cc2_2p6v; + if (cc2_0p8v) + cc_param->vref_0p8v = cc2_0p8v; + if (cc2_0p2v) + cc_param->vref_0p2v = cc2_0p2v; + + return 0; +} + +static void get_default_type_c_parameter(struct type_c_data *type_c) +{ + void __iomem *reg; + int val; + + type_c->dfp_mode_rp_en = dfp_mode(CC_MODE_DFP_3_0) | EN_RP4P7K; + type_c->ufp_mode_rd_en = EN_RD; + + reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_0; + val = readl(reg); + type_c->cc1_code = CC_CODE_MASK & val; + + reg = type_c->reg_base + USB_TYPEC_CTRL_CC2_0; + val = readl(reg); + type_c->cc2_code = CC_CODE_MASK & val; + + reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_1; + val = readl(reg); + type_c->cc1_vref = val; + + reg = type_c->reg_base + USB_TYPEC_CTRL_CC2_1; + val = readl(reg); + type_c->cc2_vref = val; + + reg = type_c->reg_base + USB_TYPEC_CTRL; + val = readl(reg); + type_c->debounce = DEBOUNCE_TIME_MASK & val; +} + +static int setup_type_c_parameter(struct type_c_data *type_c) +{ + struct type_c_cfg *type_c_cfg = type_c->type_c_cfg; + struct cc_param *cc_param; + struct soc_device_attribute rtk_soc_efuse_v1[] = { + { .family = "Realtek Phoenix",}, + { .family = "Realtek Kylin",}, + { .family = "Realtek Hercules",}, + { .family = "Realtek Thor",}, + { .family = "Realtek Hank",}, + { .family = "Realtek Groot",}, + { .family = "Realtek Stark",}, + { .family = "Realtek Parker",}, + { /* empty */ } + }; + + if (type_c_cfg->use_defalut_parameter) { + get_default_type_c_parameter(type_c); + return 0; + } + + if (soc_device_match(rtk_soc_efuse_v1)) + __updated_type_c_parameter_by_efuse(type_c); + else + __updated_type_c_parameter_by_efuse_v2(type_c); + + /* + * UFP rd vref_ufp : 1p23v, 0p66v, 0p2v + * DFP_USB rp36k vref_dfp_usb: 0_1p6v, 0p2v, unused + * DFP_1.5 rp12k vref_dfp_1_5: 1_1p6v, 0p4v, 0p2v + * DFP_3.0 rp4p7k vref_dfp_3_0: 2p6v, 0p8v, 0p2v + */ + + switch (type_c_cfg->cc_dfp_mode) { + case CC_MODE_DFP_USB: + type_c->dfp_mode_rp_en = dfp_mode(CC_MODE_DFP_USB) | EN_RP36K; + break; + case CC_MODE_DFP_1_5: + type_c->dfp_mode_rp_en = dfp_mode(CC_MODE_DFP_1_5) | EN_RP12K; + break; + case CC_MODE_DFP_3_0: + type_c->dfp_mode_rp_en = dfp_mode(CC_MODE_DFP_3_0) | EN_RP4P7K; + break; + default: + dev_err(type_c->dev, "%s: unknown cc_dfp_mode %d\n", + __func__, type_c_cfg->cc_dfp_mode); + } + + type_c->ufp_mode_rd_en = EN_RD; + + cc_param = &type_c_cfg->cc1_param; + type_c->cc1_code = rp4pk_code(cc_param->rp_4p7k_code) | + rp36k_code(cc_param->rp_36k_code) | + rp12k_code(cc_param->rp_12k_code) | + rd_code(cc_param->rd_code); + + if (type_c_cfg->parameter_ver == PARAMETER_V0) + type_c->cc1_vref = V0_vref_2p6v(cc_param->vref_2p6v) | + V0_vref_1p23v(cc_param->vref_1p23v) | + V0_vref_0p8v(cc_param->vref_0p8v) | + V0_vref_0p66v(cc_param->vref_0p66v) | + V0_vref_0p4v(cc_param->vref_0p4v) | + V0_vref_0p2v(cc_param->vref_0p2v) | + V0_vref_1_1p6v(cc_param->vref_1_1p6v) | + V0_vref_0_1p6v(cc_param->vref_0_1p6v); + else if (type_c_cfg->parameter_ver == PARAMETER_V1) + type_c->cc1_vref = V1_vref_2p6v(cc_param->vref_2p6v) | + V1_vref_1p23v(cc_param->vref_1p23v) | + V1_vref_0p8v(cc_param->vref_0p8v) | + V1_vref_0p66v(cc_param->vref_0p66v) | + V1_vref_0p4v(cc_param->vref_0p4v) | + V1_vref_0p2v(cc_param->vref_0p2v) | + V1_vref_1_1p6v(cc_param->vref_1_1p6v) | + V1_vref_0_1p6v(cc_param->vref_0_1p6v); + else + dev_err(type_c->dev, "%s: unknown parameter_ver %d\n", + __func__, type_c_cfg->parameter_ver); + + cc_param = &type_c_cfg->cc2_param; + type_c->cc2_code = rp4pk_code(cc_param->rp_4p7k_code) + | rp36k_code(cc_param->rp_36k_code) + | rp12k_code(cc_param->rp_12k_code) + | rd_code(cc_param->rd_code); + + if (type_c_cfg->parameter_ver == PARAMETER_V0) + type_c->cc2_vref = V0_vref_2p6v(cc_param->vref_2p6v) | + V0_vref_1p23v(cc_param->vref_1p23v) | + V0_vref_0p8v(cc_param->vref_0p8v) | + V0_vref_0p66v(cc_param->vref_0p66v) | + V0_vref_0p4v(cc_param->vref_0p4v) | + V0_vref_0p2v(cc_param->vref_0p2v) | + V0_vref_1_1p6v(cc_param->vref_1_1p6v) | + V0_vref_0_1p6v(cc_param->vref_0_1p6v); + else if (type_c_cfg->parameter_ver == PARAMETER_V1) + type_c->cc2_vref = V1_vref_2p6v(cc_param->vref_2p6v) | + V1_vref_1p23v(cc_param->vref_1p23v) | + V1_vref_0p8v(cc_param->vref_0p8v) | + V1_vref_0p66v(cc_param->vref_0p66v) | + V1_vref_0p4v(cc_param->vref_0p4v) | + V1_vref_0p2v(cc_param->vref_0p2v) | + V1_vref_1_1p6v(cc_param->vref_1_1p6v) | + V1_vref_0_1p6v(cc_param->vref_0_1p6v); + else + dev_err(type_c->dev, "%s: unknown parameter_ver %d\n", + __func__, type_c_cfg->parameter_ver); + + type_c->debounce = (type_c_cfg->debounce_val << 1) | DEBOUNCE_EN; + + return 0; +} + +static int extcon_rtk_type_c_init(struct type_c_data *type_c) +{ + struct device *dev = type_c->dev; + unsigned long flags; + void __iomem *reg; + int val; + + spin_lock_irqsave(&type_c->lock, flags); + + /* set parameter */ + reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_0; + val = readl(reg); + val = (~CC_CODE_MASK & val) | (type_c->cc1_code & CC_CODE_MASK); + writel(val, reg); + + reg = type_c->reg_base + USB_TYPEC_CTRL_CC2_0; + val = readl(reg); + val = (~CC_CODE_MASK & val) | (type_c->cc2_code & CC_CODE_MASK); + + reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_1; + writel(type_c->cc1_vref, reg); + + reg = type_c->reg_base + USB_TYPEC_CTRL_CC2_1; + writel(type_c->cc2_vref, reg); + + reg = type_c->reg_base + USB_TYPEC_CTRL; + val = readl(reg); + val = (~DEBOUNCE_TIME_MASK & val) | (type_c->debounce & DEBOUNCE_TIME_MASK); + + dev_info(dev, "First check USB_DR_MODE_PERIPHERAL"); + type_c->cc_mode = IN_DEVICE_MODE; + type_c->is_attach = IN_DETACH; + type_c->connect_change = CONNECT_NO_CHANGE; + + detect_host(type_c); + + spin_unlock_irqrestore(&type_c->lock, flags); + + schedule_delayed_work(&type_c->delayed_work, msecs_to_jiffies(0)); + + if (!type_c->port) { + struct typec_capability typec_cap = { }; + struct fwnode_handle *fwnode; + const char *buf; + int ret; + + typec_cap.revision = USB_TYPEC_REV_1_0; + typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + typec_cap.driver_data = type_c; + typec_cap.ops = &type_c_port_ops; + + fwnode = device_get_named_child_node(dev, "connector"); + if (!fwnode) + return -EINVAL; + + ret = fwnode_property_read_string(fwnode, "power-role", &buf); + if (ret) { + dev_err(dev, "power-role not found: %d\n", ret); + return ret; + } + + ret = typec_find_port_power_role(buf); + if (ret < 0) + return ret; + typec_cap.type = ret; + + ret = fwnode_property_read_string(fwnode, "data-role", &buf); + if (ret) { + dev_err(dev, "data-role not found: %d\n", ret); + return ret; + } + + ret = typec_find_port_data_role(buf); + if (ret < 0) + return ret; + typec_cap.data = ret; + + type_c->port = typec_register_port(type_c->dev, &typec_cap); + if (IS_ERR(type_c->port)) + return PTR_ERR(type_c->port); + } + + return 0; +} + +static int extcon_rtk_type_c_edev_register(struct type_c_data *type_c) +{ + struct device *dev = type_c->dev; + int ret = 0; + + type_c->edev = devm_extcon_dev_allocate(dev, usb_type_c_cable); + if (IS_ERR(type_c->edev)) { + dev_err(dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + + ret = devm_extcon_dev_register(dev, type_c->edev); + if (ret < 0) { + dev_err(dev, "failed to register extcon device\n"); + return ret; + } + + extcon_set_property_capability(type_c->edev, EXTCON_USB, + EXTCON_PROP_USB_VBUS); + extcon_set_property_capability(type_c->edev, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY); + extcon_set_property_capability(type_c->edev, EXTCON_USB, + EXTCON_PROP_USB_SS); + + extcon_set_property_capability(type_c->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_VBUS); + extcon_set_property_capability(type_c->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY); + extcon_set_property_capability(type_c->edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS); + + return ret; +} + +static int extcon_rtk_type_c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct type_c_data *type_c; + const struct type_c_cfg *type_c_cfg; + int ret = 0; + + type_c = devm_kzalloc(dev, sizeof(*type_c), GFP_KERNEL); + if (!type_c) + return -ENOMEM; + + type_c->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(type_c->reg_base)) + return PTR_ERR(type_c->reg_base); + + type_c->dev = dev; + + type_c->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + if (type_c->irq <= 0) { + dev_err(&pdev->dev, "Type C driver with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + ret = -ENODEV; + goto err; + } + + ret = devm_request_irq(dev, type_c->irq, type_c_detect_irq, + IRQF_SHARED, "type_c_detect", type_c); + + spin_lock_init(&type_c->lock); + + type_c->rd_ctrl_gpio_desc = NULL; + if (soc_device_match(rtk_soc_kylin)) { + struct gpio_desc *gpio; + + gpio = fwnode_gpiod_get_index(of_fwnode_handle(dev->of_node), + "realtek,rd-ctrl-gpios", + 0, GPIOD_OUT_HIGH, "rd-ctrl-gpio"); + if (IS_ERR(gpio)) { + dev_err(dev, "Error rd_ctrl-gpios no found (err=%d)\n", + (int)PTR_ERR(gpio)); + } else { + type_c->rd_ctrl_gpio_desc = gpio; + dev_dbg(dev, "%s get rd-ctrl-gpios (id=%d) OK\n", + __func__, desc_to_gpio(gpio)); + } + } + + type_c_cfg = of_device_get_match_data(dev); + if (!type_c_cfg) { + dev_err(dev, "type_c config are not assigned!\n"); + ret = -EINVAL; + goto err; + } + + type_c->type_c_cfg = devm_kzalloc(dev, sizeof(*type_c_cfg), GFP_KERNEL); + if (!type_c->type_c_cfg) + return -ENOMEM; + + memcpy(type_c->type_c_cfg, type_c_cfg, sizeof(*type_c_cfg)); + + if (setup_type_c_parameter(type_c)) { + dev_err(dev, "ERROR: %s to setup type c parameter!!", __func__); + ret = -EINVAL; + goto err; + } + + INIT_DELAYED_WORK(&type_c->delayed_work, host_device_switch); + + ret = extcon_rtk_type_c_init(type_c); + if (ret) { + dev_err(dev, "%s failed to init type_c\n", __func__); + goto err; + } + + platform_set_drvdata(pdev, type_c); + + ret = extcon_rtk_type_c_edev_register(type_c); + + create_debug_files(type_c); + + return 0; + +err: + dev_err(&pdev->dev, "%s: Probe fail, %d\n", __func__, ret); + + return ret; +} + +static void extcon_rtk_type_c_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct type_c_data *type_c = dev_get_drvdata(dev); + u32 default_ctrl; + unsigned long flags; + + remove_debug_files(type_c); + + if (type_c->port) { + typec_unregister_port(type_c->port); + type_c->port = NULL; + } + + cancel_delayed_work_sync(&type_c->delayed_work); + flush_delayed_work(&type_c->delayed_work); + WARN_ON_ONCE(delayed_work_pending(&type_c->delayed_work)); + + spin_lock_irqsave(&type_c->lock, flags); + /* disable interrupt */ + default_ctrl = readl(type_c->reg_base + USB_TYPEC_CTRL) & + DEBOUNCE_TIME_MASK; + writel(default_ctrl, type_c->reg_base + USB_TYPEC_CTRL); + + /* disable cc detect, rp, rd */ + writel(PLR_EN, type_c->reg_base + USB_TYPEC_CTRL_CC1_0); + writel(0, type_c->reg_base + USB_TYPEC_CTRL_CC2_0); + + spin_unlock_irqrestore(&type_c->lock, flags); + + if (type_c->rd_ctrl_gpio_desc) + gpiod_put(type_c->rd_ctrl_gpio_desc); + type_c->rd_ctrl_gpio_desc = NULL; + + free_irq(type_c->irq, type_c); +} + +static const struct type_c_cfg rtd1295_type_c_cfg = { + .parameter_ver = PARAMETER_V0, + .cc_dfp_mode = CC_MODE_DFP_3_0, + .cc1_param = { .rp_4p7k_code = 0xb, + .rp_36k_code = 0x17, + .rp_12k_code = 0x10, + .rd_code = 0, + .ra_code = 0, + .vref_2p6v = 0x0, + .vref_1p23v = 0x0, + .vref_0p8v = 0x3, + .vref_0p66v = 0x0, + .vref_0p4v = 0x0, + .vref_0p2v = 0x4, + .vref_1_1p6v = 0, + .vref_0_1p6v = 0 }, + .cc2_param = { .rp_4p7k_code = 0xc, + .rp_36k_code = 0x17, + .rp_12k_code = 0x12, + .rd_code = 0, + .ra_code = 0, + .vref_2p6v = 0x2, + .vref_1p23v = 0x0, + .vref_0p8v = 0x3, + .vref_0p66v = 0x0, + .vref_0p4v = 0x0, + .vref_0p2v = 0x5, + .vref_1_1p6v = 0, + .vref_0_1p6v = 0 }, + .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ + .use_defalut_parameter = false, +}; + +static const struct type_c_cfg rtd1395_type_c_cfg = { + .parameter_ver = PARAMETER_V0, + .cc_dfp_mode = CC_MODE_DFP_3_0, + .cc1_param = { .rp_4p7k_code = 0xc, + .rp_36k_code = 0xb, + .rp_12k_code = 0xe, + .rd_code = 0x10, + .ra_code = 0x0, + .vref_2p6v = 0x0, + .vref_1p23v = 0x1, + .vref_0p8v = 0x0, + .vref_0p66v = 0x0, + .vref_0p4v = 0x3, + .vref_0p2v = 0x0, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .cc2_param = { .rp_4p7k_code = 0xb, + .rp_36k_code = 0x9, + .rp_12k_code = 0xe, + .rd_code = 0xf, + .ra_code = 0x0, + .vref_2p6v = 0x1, + .vref_1p23v = 0x3, + .vref_0p8v = 0x3, + .vref_0p66v = 0x2, + .vref_0p4v = 0x3, + .vref_0p2v = 0x2, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ + .use_defalut_parameter = false, +}; + +static const struct type_c_cfg rtd1619_type_c_cfg = { + .parameter_ver = PARAMETER_V0, + .cc_dfp_mode = CC_MODE_DFP_3_0, + .cc1_param = { .rp_4p7k_code = 0xc, + .rp_36k_code = 0xf, + .rp_12k_code = 0xe, + .rd_code = 0x11, + .ra_code = 0x0, + .vref_2p6v = 0x5, + .vref_1p23v = 0x7, + .vref_0p8v = 0xa, + .vref_0p66v = 0xa, + .vref_0p4v = 0x3, + .vref_0p2v = 0x2, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .cc2_param = { .rp_4p7k_code = 0xc, + .rp_36k_code = 0xf, + .rp_12k_code = 0xe, + .rd_code = 0xf, + .ra_code = 0x0, + .vref_2p6v = 0x5, + .vref_1p23v = 0x8, + .vref_0p8v = 0xa, + .vref_0p66v = 0xa, + .vref_0p4v = 0x3, + .vref_0p2v = 0x2, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ + .use_defalut_parameter = false, +}; + +static const struct type_c_cfg rtd1319_type_c_cfg = { + .parameter_ver = PARAMETER_V0, + .cc_dfp_mode = CC_MODE_DFP_1_5, + .cc1_param = { .rp_4p7k_code = 0x9, + .rp_36k_code = 0xe, + .rp_12k_code = 0x9, + .rd_code = 0x9, + .ra_code = 0x7, + .vref_2p6v = 0x3, + .vref_1p23v = 0x7, + .vref_0p8v = 0x7, + .vref_0p66v = 0x6, + .vref_0p4v = 0x2, + .vref_0p2v = 0x3, + .vref_1_1p6v = 0x4, + .vref_0_1p6v = 0x7 }, + .cc2_param = { .rp_4p7k_code = 0x8, + .rp_36k_code = 0xe, + .rp_12k_code = 0x9, + .rd_code = 0x9, + .ra_code = 0x7, + .vref_2p6v = 0x3, + .vref_1p23v = 0x7, + .vref_0p8v = 0x7, + .vref_0p66v = 0x6, + .vref_0p4v = 0x3, + .vref_0p2v = 0x3, + .vref_1_1p6v = 0x6, + .vref_0_1p6v = 0x7 }, + .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ + .use_defalut_parameter = false, +}; + +static const struct type_c_cfg rtd1312c_type_c_cfg = { + .parameter_ver = PARAMETER_V0, + .cc_dfp_mode = CC_MODE_DFP_1_5, + .cc1_param = { .rp_4p7k_code = 0xe, + .rp_36k_code = 0xc, + .rp_12k_code = 0xc, + .rd_code = 0xa, + .ra_code = 0x3, + .vref_2p6v = 0xa, + .vref_1p23v = 0x7, + .vref_0p8v = 0x7, + .vref_0p66v = 0x7, + .vref_0p4v = 0x4, + .vref_0p2v = 0x4, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .cc2_param = { .rp_4p7k_code = 0xe, + .rp_36k_code = 0xc, + .rp_12k_code = 0xc, + .rd_code = 0xa, + .ra_code = 0x3, + .vref_2p6v = 0xa, + .vref_1p23v = 0x7, + .vref_0p8v = 0x7, + .vref_0p66v = 0x7, + .vref_0p4v = 0x4, + .vref_0p2v = 0x4, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ + .use_defalut_parameter = false, +}; + +static const struct type_c_cfg rtd1619b_type_c_cfg = { + .parameter_ver = PARAMETER_V1, + .cc_dfp_mode = CC_MODE_DFP_1_5, + .cc1_param = { .rp_4p7k_code = 0xf, + .rp_36k_code = 0xf, + .rp_12k_code = 0xf, + .rd_code = 0xf, + .ra_code = 0x7, + .vref_2p6v = 0x9, + .vref_1p23v = 0x7, + .vref_0p8v = 0x9, + .vref_0p66v = 0x8, + .vref_0p4v = 0x7, + .vref_0p2v = 0x9, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .cc2_param = { .rp_4p7k_code = 0xf, + .rp_36k_code = 0xf, + .rp_12k_code = 0xf, + .rd_code = 0xf, + .ra_code = 0x7, + .vref_1p23v = 0x7, + .vref_0p8v = 0x9, + .vref_0p66v = 0x8, + .vref_0p4v = 0x7, + .vref_0p2v = 0x8, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ + .use_defalut_parameter = false, +}; + +static const struct type_c_cfg rtd1319d_type_c_cfg = { + .parameter_ver = PARAMETER_V1, + .cc_dfp_mode = CC_MODE_DFP_1_5, + .cc1_param = { .rp_4p7k_code = 0xe, + .rp_36k_code = 0x3, + .rp_12k_code = 0xe, + .rd_code = 0xf, + .ra_code = 0x6, + .vref_2p6v = 0x7, + .vref_1p23v = 0x7, + .vref_0p8v = 0x8, + .vref_0p66v = 0x7, + .vref_0p4v = 0x7, + .vref_0p2v = 0x7, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .cc2_param = { .rp_4p7k_code = 0xe, + .rp_36k_code = 0x3, + .rp_12k_code = 0xe, + .rd_code = 0xf, + .ra_code = 0x6, + .vref_2p6v = 0x7, + .vref_1p23v = 0x7, + .vref_0p8v = 0x8, + .vref_0p66v = 0x7, + .vref_0p4v = 0x7, + .vref_0p2v = 0x8, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ + .use_defalut_parameter = false, +}; + +static const struct type_c_cfg rtd1315e_type_c_cfg = { + .parameter_ver = PARAMETER_V1, + .cc_dfp_mode = CC_MODE_DFP_1_5, + .cc1_param = { .rp_4p7k_code = 0xe, + .rp_36k_code = 0x3, + .rp_12k_code = 0xe, + .rd_code = 0xf, + .ra_code = 0x6, + .vref_2p6v = 0x7, + .vref_1p23v = 0x7, + .vref_0p8v = 0x8, + .vref_0p66v = 0x7, + .vref_0p4v = 0x7, + .vref_0p2v = 0x7, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .cc2_param = { .rp_4p7k_code = 0xe, + .rp_36k_code = 0x3, + .rp_12k_code = 0xe, + .rd_code = 0xf, + .ra_code = 0x6, + .vref_2p6v = 0x7, + .vref_1p23v = 0x7, + .vref_0p8v = 0x8, + .vref_0p66v = 0x7, + .vref_0p4v = 0x7, + .vref_0p2v = 0x8, + .vref_1_1p6v = 0x7, + .vref_0_1p6v = 0x7 }, + .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ + .use_defalut_parameter = false, +}; + +static const struct of_device_id extcon_rtk_type_c_match[] = { + { .compatible = "realtek,rtd1295-type-c", .data = &rtd1295_type_c_cfg }, + { .compatible = "realtek,rtd1312c-type-c", .data = &rtd1312c_type_c_cfg }, + { .compatible = "realtek,rtd1315e-type-c", .data = &rtd1315e_type_c_cfg }, + { .compatible = "realtek,rtd1319-type-c", .data = &rtd1319_type_c_cfg }, + { .compatible = "realtek,rtd1319d-type-c", .data = &rtd1319d_type_c_cfg }, + { .compatible = "realtek,rtd1395-type-c", .data = &rtd1395_type_c_cfg }, + { .compatible = "realtek,rtd1619-type-c", .data = &rtd1619_type_c_cfg }, + { .compatible = "realtek,rtd1619b-type-c", .data = &rtd1619b_type_c_cfg }, + {}, +}; +MODULE_DEVICE_TABLE(of, extcon_rtk_type_c_match); + +#ifdef CONFIG_PM_SLEEP +static int extcon_rtk_type_c_prepare(struct device *dev) +{ + struct type_c_data *type_c = dev_get_drvdata(dev); + u32 default_ctrl; + unsigned long flags; + + cancel_delayed_work_sync(&type_c->delayed_work); + flush_delayed_work(&type_c->delayed_work); + WARN_ON_ONCE(delayed_work_pending(&type_c->delayed_work)); + + spin_lock_irqsave(&type_c->lock, flags); + /* disable interrupt */ + default_ctrl = readl(type_c->reg_base + USB_TYPEC_CTRL) & + DEBOUNCE_TIME_MASK; + writel(default_ctrl, type_c->reg_base + USB_TYPEC_CTRL); + + /* disable cc detect, rp, rd */ + writel(PLR_EN, type_c->reg_base + USB_TYPEC_CTRL_CC1_0); + writel(0, type_c->reg_base + USB_TYPEC_CTRL_CC2_0); + + spin_unlock_irqrestore(&type_c->lock, flags); + + return 0; +} + +static void extcon_rtk_type_c_complete(struct device *dev) +{ + /* nothing */ +} + +static int extcon_rtk_type_c_suspend(struct device *dev) +{ + /* nothing */ + + return 0; +} + +static int extcon_rtk_type_c_resume(struct device *dev) +{ + struct type_c_data *type_c = dev_get_drvdata(dev); + int ret; + + ret = extcon_rtk_type_c_init(type_c); + if (ret) { + dev_err(dev, "%s failed to init type_c\n", __func__); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops extcon_rtk_type_c_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(extcon_rtk_type_c_suspend, extcon_rtk_type_c_resume) + .prepare = extcon_rtk_type_c_prepare, + .complete = extcon_rtk_type_c_complete, +}; + +#define DEV_PM_OPS (&extcon_rtk_type_c_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct platform_driver extcon_rtk_type_c_driver = { + .probe = extcon_rtk_type_c_probe, + .remove = extcon_rtk_type_c_remove, + .driver = { + .name = "extcon-rtk-type_c", + .of_match_table = extcon_rtk_type_c_match, + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver(extcon_rtk_type_c_driver); + +MODULE_DESCRIPTION("Realtek Extcon Type C driver"); +MODULE_AUTHOR("Stanley Chang <stanley_chang@realtek.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index 40d967a11e87..5e8ad21ad206 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c @@ -193,14 +193,12 @@ static int usb_extcon_probe(struct platform_device *pdev) return 0; } -static int usb_extcon_remove(struct platform_device *pdev) +static void usb_extcon_remove(struct platform_device *pdev) { struct usb_extcon_info *info = platform_get_drvdata(pdev); cancel_delayed_work_sync(&info->wq_detcable); device_init_wakeup(&pdev->dev, false); - - return 0; } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/extcon/extcon-usbc-cros-ec.c b/drivers/extcon/extcon-usbc-cros-ec.c index fde1db62be0d..1fb627ea8b50 100644 --- a/drivers/extcon/extcon-usbc-cros-ec.c +++ b/drivers/extcon/extcon-usbc-cros-ec.c @@ -480,14 +480,12 @@ unregister_notifier: return ret; } -static int extcon_cros_ec_remove(struct platform_device *pdev) +static void extcon_cros_ec_remove(struct platform_device *pdev) { struct cros_ec_extcon_info *info = platform_get_drvdata(pdev); blocking_notifier_chain_unregister(&info->ec->event_notifier, &info->notifier); - - return 0; } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/extcon/extcon-usbc-tusb320.c b/drivers/extcon/extcon-usbc-tusb320.c index 4d08c2123e59..2eab341de6b7 100644 --- a/drivers/extcon/extcon-usbc-tusb320.c +++ b/drivers/extcon/extcon-usbc-tusb320.c @@ -17,6 +17,7 @@ #include <linux/usb/typec.h> #include <linux/usb/typec_altmode.h> #include <linux/usb/role.h> +#include <linux/irq.h> #define TUSB320_REG8 0x8 #define TUSB320_REG8_CURRENT_MODE_ADVERTISE GENMASK(7, 6) @@ -515,6 +516,8 @@ static int tusb320_probe(struct i2c_client *client) const void *match_data; unsigned int revision; int ret; + u32 irq_trigger_type = IRQF_TRIGGER_FALLING; + struct irq_data *irq_d; priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -568,9 +571,13 @@ static int tusb320_probe(struct i2c_client *client) */ tusb320_state_update_handler(priv, true); + irq_d = irq_get_irq_data(client->irq); + if (irq_d) + irq_trigger_type = irqd_get_trigger_type(irq_d); + ret = devm_request_threaded_irq(priv->dev, client->irq, NULL, tusb320_irq_handler, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + IRQF_ONESHOT | irq_trigger_type, client->name, priv); if (ret) tusb320_typec_remove(priv); diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 6f7a60d2ed91..e7f55c021e56 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -1280,8 +1280,6 @@ int extcon_dev_register(struct extcon_dev *edev) edev->id = ret; - dev_set_name(&edev->dev, "extcon%d", edev->id); - ret = extcon_alloc_cables(edev); if (ret < 0) goto err_alloc_cables; @@ -1310,6 +1308,7 @@ int extcon_dev_register(struct extcon_dev *edev) RAW_INIT_NOTIFIER_HEAD(&edev->nh_all); dev_set_drvdata(&edev->dev, edev); + dev_set_name(&edev->dev, "extcon%d", edev->id); edev->state = 0; ret = device_register(&edev->dev); |
