diff options
Diffstat (limited to 'drivers/misc/cclogic')
-rw-r--r-- | drivers/misc/cclogic/Kconfig | 25 | ||||
-rw-r--r-- | drivers/misc/cclogic/Makefile | 9 | ||||
-rw-r--r-- | drivers/misc/cclogic/cclogic-class.c | 292 | ||||
-rw-r--r-- | drivers/misc/cclogic/cclogic-class.h | 19 | ||||
-rw-r--r-- | drivers/misc/cclogic/cclogic-core.c | 1686 | ||||
-rw-r--r-- | drivers/misc/cclogic/cclogic-core.h | 169 | ||||
-rw-r--r-- | drivers/misc/cclogic/pi5usb30216d.c | 245 | ||||
-rw-r--r-- | drivers/misc/cclogic/tusb320hai.c | 445 |
8 files changed, 2890 insertions, 0 deletions
diff --git a/drivers/misc/cclogic/Kconfig b/drivers/misc/cclogic/Kconfig new file mode 100644 index 000000000000..dfe6b585a41c --- /dev/null +++ b/drivers/misc/cclogic/Kconfig @@ -0,0 +1,25 @@ +# +# cclogic devices for type-C connector +# +menuconfig CCLOGIC + tristate "cclogic devices for Type-C connector" + depends on I2C + help + This is cclogic devices for Type-C connector. + +if CCLOGIC + +config TYPEC_CCLOGIC_PI5USBD + depends on I2C + tristate "Pericom Pi5usb30216d Support" + ---help--- + The Pericom pi5usb30216d chip is used as cc-logic of TYPEC + interface. + +config TYPEC_CCLOGIC_TUSB320HAI + depends on I2C + tristate "TI TUSB320Hai Support" + ---help--- + The TI tusb320hai chip is used as cc-logic of TYPEC + +endif # CCLOGIC diff --git a/drivers/misc/cclogic/Makefile b/drivers/misc/cclogic/Makefile new file mode 100644 index 000000000000..b0256a428144 --- /dev/null +++ b/drivers/misc/cclogic/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for cclogic device. +# + +obj-$(CONFIG_CCLOGIC) += cclogic-core.o +obj-$(CONFIG_CCLOGIC) += cclogic-class.o +obj-$(CONFIG_TYPEC_CCLOGIC_TUSB320HAI) += tusb320hai.o +obj-$(CONFIG_TYPEC_CCLOGIC_PI5USBD) += pi5usb30216d.o + diff --git a/drivers/misc/cclogic/cclogic-class.c b/drivers/misc/cclogic/cclogic-class.c new file mode 100644 index 000000000000..a58e7b6575b5 --- /dev/null +++ b/drivers/misc/cclogic/cclogic-class.c @@ -0,0 +1,292 @@ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/err.h> + +#include "cclogic-class.h" +#include "cclogic-core.h" + +struct class *cclogic_class; +static atomic_t device_count; + +/* + * USB port's supported modes. (read-only) + * Contents: "", "ufp", "dfp", or "ufp dfp". +*/ +static ssize_t cclogic_supported_modes_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_class_dev *cdev = (struct cclogic_class_dev *) dev_get_drvdata(dev); + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (cdev->support & (CCLOGIC_SUPPORT_MODE_DUAL)) + return sprintf(buf, "ufp dfp"); + else if (cdev->support & CCLOGIC_SUPPORT_MODE_UFP) + return sprintf(buf, "ufp"); + else if (cdev->support & CCLOGIC_SUPPORT_MODE_DFP) + return sprintf(buf, "dfp"); + else { + *buf = '\0'; + return 0; + } +} + +/* + * USB port's current mode. (read-write if configurable) + * Contents: "", "ufp", or "dfp". +*/ +static ssize_t cclogic_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_class_dev *cdev = (struct cclogic_class_dev *) dev_get_drvdata(dev); + struct cclogic_dev *cclogic_dev = container_of(cdev, struct cclogic_dev, cdev); + struct cclogic_state *pstate = &cclogic_dev->state; + int mode; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + mode = cclogic_get_connected_mode(pstate); + if (mode == CCLOGIC_MODE_UFP) + return sprintf(buf, "ufp"); + else if (mode == CCLOGIC_MODE_DFP) + return sprintf(buf, "dfp"); + else { + *buf = '\0'; + return 0; + } +} + +/* + * USB port's current mode. (read-write if configurable) + * Contents: "", "ufp", or "dfp". +*/ +static ssize_t cclogic_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cclogic_class_dev *cdev = (struct cclogic_class_dev *) dev_get_drvdata(dev); + struct cclogic_dev *cclogic_dev = container_of(cdev, struct cclogic_dev, cdev); + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (!strcmp(buf, "ufp")) + cclogic_set_mode(cclogic_dev, CCLOGIC_MODE_UFP); + else if (!strcmp(buf, "dfp")) + cclogic_set_mode(cclogic_dev, CCLOGIC_MODE_DFP); + + return count; +} + +/* + * USB port's current power role. (read-write if configurable) + * Contents: "", "source", or "sink". +*/ +static ssize_t cclogic_power_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_class_dev *cdev = (struct cclogic_class_dev *) dev_get_drvdata(dev); + struct cclogic_dev *cclogic_dev = container_of(cdev, struct cclogic_dev, cdev); + struct cclogic_state *pstate = &cclogic_dev->state; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (cclogic_get_power_role(pstate) == CCLOGIC_POWER_SINK) + return sprintf(buf, "sink"); + else if (cclogic_get_power_role(pstate) == CCLOGIC_POWER_SOURCE) + return sprintf(buf, "source"); + else { + *buf = '\0'; + return 0; + } +} + +/* + * USB port's current power role. (read-write if configurable) + * Contents: "", "source", or "sink". +*/ +static ssize_t cclogic_power_role_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cclogic_class_dev *cdev = (struct cclogic_class_dev *) dev_get_drvdata(dev); + struct cclogic_dev *cclogic_dev = container_of(cdev, struct cclogic_dev, cdev); + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (!strcmp(buf, "sink")) + cclogic_set_power_role(cclogic_dev, CCLOGIC_POWER_SINK); + else if (!strcmp(buf, "source")) + cclogic_set_power_role(cclogic_dev, CCLOGIC_POWER_SOURCE); + + return count; +} + +/* + * USB port's current data role. (read-write if configurable) + * Contents: "", "host", or "device". +*/ +static ssize_t cclogic_data_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_class_dev *cdev = (struct cclogic_class_dev *) dev_get_drvdata(dev); + struct cclogic_dev *cclogic_dev = container_of(cdev, struct cclogic_dev, cdev); + struct cclogic_state *pstate = &cclogic_dev->state; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (cclogic_get_data_role(pstate) == CCLOGIC_DATA_HOST) + return sprintf(buf, "host"); + else if (cclogic_get_data_role(pstate) == CCLOGIC_DATA_DEVICE) + return sprintf(buf, "device"); + else { + *buf = '\0'; + return 0; + } +} + +/* + * USB port's current data role. (read-write if configurable) + * Contents: "", "host", or "device". +*/ +static ssize_t cclogic_data_role_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cclogic_class_dev *cdev = (struct cclogic_class_dev *) dev_get_drvdata(dev); + struct cclogic_dev *cclogic_dev = container_of(cdev, struct cclogic_dev, cdev); + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (!strcmp(buf, "host")) + cclogic_set_data_role(cclogic_dev, CCLOGIC_DATA_HOST); + else if (!strcmp(buf, "device")) + cclogic_set_data_role(cclogic_dev, CCLOGIC_DATA_DEVICE); + + return count; +} + +void cclogic_class_update_state(struct cclogic_class_dev *cdev) +{ + kobject_uevent(&cdev->dev->kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL(cclogic_class_update_state); + +static int create_cclogic_class(void) +{ + if (!cclogic_class) { + cclogic_class = class_create(THIS_MODULE, "dual_role_usb"); + if (IS_ERR(cclogic_class)) + return PTR_ERR(cclogic_class); + } + + return 0; +} + +int cclogic_class_register(struct cclogic_class_dev *cdev) +{ + int ret; + struct cclogic_dev *cclogic_dev = container_of(cdev, struct cclogic_dev, cdev); + + if (!cclogic_class) { + ret = create_cclogic_class(); + if (ret < 0) + return ret; + } + + cdev->index = atomic_inc_return(&device_count); + cdev->dev = device_create(cclogic_class, NULL, + MKDEV(0, cdev->index), NULL, cdev->name); + if (IS_ERR(cdev->dev)) + return PTR_ERR(cdev->dev); + + cdev->support = cclogic_dev->ops->support; + + cdev->device_supported_modes_attr.attr.name = "supported_modes"; + cdev->device_supported_modes_attr.attr.mode = S_IRUGO; + cdev->device_supported_modes_attr.show = cclogic_supported_modes_show; + sysfs_attr_init(&cdev->device_supported_modes_attr.attr); + ret = device_create_file(cdev->dev, &cdev->device_supported_modes_attr); + if (ret < 0) + goto err_create_file_1; + + cdev->device_mode_attr.attr.name = "mode"; + cdev->device_mode_attr.attr.mode = S_IRUGO; + cdev->device_mode_attr.show = cclogic_mode_show; + if ((cdev->support & CCLOGIC_SUPPORT_MODE_DUAL) == CCLOGIC_SUPPORT_MODE_DUAL) { + cdev->device_mode_attr.attr.mode |= S_IWUSR; + cdev->device_mode_attr.store = cclogic_mode_store; + } + sysfs_attr_init(&cdev->device_mode_attr.attr); + ret = device_create_file(cdev->dev, &cdev->device_mode_attr); + if (ret < 0) + goto err_create_file_2; + + cdev->device_power_role_attr.attr.name = "power_role"; + cdev->device_power_role_attr.attr.mode = S_IRUGO; + cdev->device_power_role_attr.show = cclogic_power_role_show; + if ((cdev->support & CCLOGIC_SUPPORT_POWER_SWAP) == CCLOGIC_SUPPORT_POWER_SWAP) { + cdev->device_power_role_attr.attr.mode |= S_IWUSR; + cdev->device_power_role_attr.store = cclogic_power_role_store; + } + sysfs_attr_init(&cdev->device_power_role_attr.attr); + ret = device_create_file(cdev->dev, &cdev->device_power_role_attr); + if (ret < 0) + goto err_create_file_3; + + cdev->device_data_role_attr.attr.name = "data_role"; + cdev->device_data_role_attr.attr.mode = S_IRUGO; + cdev->device_data_role_attr.show = cclogic_data_role_show; + if ((cdev->support & CCLOGIC_SUPPORT_DATA_SWAP) == CCLOGIC_SUPPORT_DATA_SWAP) { + cdev->device_data_role_attr.attr.mode |= S_IWUSR; + cdev->device_data_role_attr.store = cclogic_data_role_store; + } + sysfs_attr_init(&cdev->device_data_role_attr.attr); + ret = device_create_file(cdev->dev, &cdev->device_data_role_attr); + if (ret < 0) + goto err_create_file_4; + + dev_set_drvdata(cdev->dev, cdev); + + return 0; + +err_create_file_4: + device_remove_file(cdev->dev, &cdev->device_power_role_attr); +err_create_file_3: + device_remove_file(cdev->dev, &cdev->device_mode_attr); +err_create_file_2: + device_remove_file(cdev->dev, &cdev->device_supported_modes_attr); +err_create_file_1: + device_destroy(cclogic_class, MKDEV(0, cdev->index)); + printk(KERN_ERR "cclogic-class: Failed to register driver %s\n", cdev->name); + + return ret; +} +EXPORT_SYMBOL(cclogic_class_register); + +void cclogic_class_unregister(struct cclogic_class_dev *cdev) +{ + device_remove_file(cdev->dev, &cdev->device_supported_modes_attr); + device_remove_file(cdev->dev, &cdev->device_mode_attr); + device_remove_file(cdev->dev, &cdev->device_power_role_attr); + device_remove_file(cdev->dev, &cdev->device_data_role_attr); + dev_set_drvdata(cdev->dev, NULL); + device_destroy(cclogic_class, MKDEV(0, cdev->index)); +} +EXPORT_SYMBOL(cclogic_class_unregister); + +static int __init cclogic_class_init(void) +{ + return create_cclogic_class(); +} + +static void __exit cclogic_class_exit(void) +{ + class_destroy(cclogic_class); +} + +module_init(cclogic_class_init); +module_exit(cclogic_class_exit); + +MODULE_AUTHOR("yangshaoying <yangsy2@zuk.com>"); +MODULE_DESCRIPTION("cclogic class driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/cclogic/cclogic-class.h b/drivers/misc/cclogic/cclogic-class.h new file mode 100644 index 000000000000..c5276839ee65 --- /dev/null +++ b/drivers/misc/cclogic/cclogic-class.h @@ -0,0 +1,19 @@ +#ifndef __CCLOGIC_CLASS_H__ +#define __CCLOGIC_CLASS_H__ + +struct cclogic_class_dev { + const char *name; + struct device *dev; + int index; + unsigned int support; + struct device_attribute device_supported_modes_attr; + struct device_attribute device_mode_attr; + struct device_attribute device_power_role_attr; + struct device_attribute device_data_role_attr; +}; + +extern int cclogic_class_register(struct cclogic_class_dev *dev); +extern void cclogic_class_unregister(struct cclogic_class_dev *dev); +extern void cclogic_class_update_state(struct cclogic_class_dev *cdev); + +#endif diff --git a/drivers/misc/cclogic/cclogic-core.c b/drivers/misc/cclogic/cclogic-core.c new file mode 100644 index 000000000000..47efe02e20f9 --- /dev/null +++ b/drivers/misc/cclogic/cclogic-core.c @@ -0,0 +1,1686 @@ +/* + * cclogic-core.c + * + * Core of CC-Logic drivers + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/miscdevice.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +#include <linux/wakelock.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/cclogic-core.h> +#include <linux/notifier.h> +#include <linux/export.h> + +#include "cclogic-core.h" +#include "cclogic-class.h" + + +static struct cclogic_dev *cclogic_priv; +static struct mutex cclogic_ops_lock; + +static int m_plug_state; + +#define DRIVER_NAME "cclogic" + +static BLOCKING_NOTIFIER_HEAD(cclogic_notifier_list); + +/* + * cclogic_register_client - register a client notifier + * @nb: notifier block to callback on events + */ +int cclogic_register_client(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&cclogic_notifier_list, nb); +} +EXPORT_SYMBOL(cclogic_register_client); + +/* + * cclogic_unregister_client - unregister a client notifier + * @nb: notifier block to callback on events + */ +int cclogic_unregister_client(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&cclogic_notifier_list, nb); +} +EXPORT_SYMBOL(cclogic_unregister_client); + +/* cclogic_notifier_call_chain - notify clients of fb_events */ +int cclogic_notifier_call_chain(unsigned long val, void *v) +{ + return blocking_notifier_call_chain(&cclogic_notifier_list, val, v); +} +EXPORT_SYMBOL_GPL(cclogic_notifier_call_chain); + +static void cclogic_patch_state(struct cclogic_dev *pdata) +{ + struct cclogic_state *state = &pdata->state; + + if (gpio_get_value(pdata->platform_data->irq_plug)) { + state->evt = CCLOGIC_EVENT_DETACHED; + state->device = CCLOGIC_NO_DEVICE; + state->vbus = false; + state->cc = CCLOGIC_CC1; + } +} + +int cclogic_vbus_power_on(struct cclogic_dev *cclogic_dev, bool enable) +{ + struct cclogic_platform *p = cclogic_dev->platform_data; + int ret; + + pr_debug("[%s][%d] enable=%d\n", __func__, __LINE__, enable); + + if (!p->vbus_reg) + return 0; + + if (enable) { + if (cclogic_dev->vbus_on) + return 0; + + ret = regulator_enable(p->vbus_reg); + if (ret) { + dev_err(&cclogic_dev->i2c_client->dev, "Failed to enable vbus_reg\n"); + return ret; + } + cclogic_dev->vbus_on = true; + } else{ + if (!cclogic_dev->vbus_on) + return 0; + ret = regulator_disable(p->vbus_reg); + if (ret) { + dev_err(&cclogic_dev->i2c_client->dev, "Failed to disable vbus_reg\n"); + return ret; + } + cclogic_dev->vbus_on = false; + } + + return 0; +} + +static int cclogic_reg_set_optimum_mode_check(struct regulator *reg, + int load_uA) +{ + pr_debug("[%s][%d]\n", __func__, __LINE__); + + return (regulator_count_voltages(reg) > 0) ? + regulator_set_load(reg, load_uA) : 0; +} + +static int cclogic_power_on(struct cclogic_dev *cclogic_dev, bool on) +{ + int ret = 0; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (cclogic_dev->platform_data->i2c_pull_up) { + if (on == false) { + if (cclogic_dev->regulator_en) { + cclogic_reg_set_optimum_mode_check( + cclogic_dev->vcc_i2c, 0); + regulator_disable(cclogic_dev->vcc_i2c); + cclogic_dev->regulator_en = false; + } + } else{ + if (!cclogic_dev->regulator_en) { + ret = cclogic_reg_set_optimum_mode_check( + cclogic_dev->vcc_i2c, CCLOGIC_I2C_LOAD_UA); + if (ret < 0) { + dev_err(&cclogic_dev->i2c_client->dev, + "%s-->Regulator vcc_i2c set_opt failed rc=%d\n", + __func__, ret); + goto error_reg_opt_i2c; + } + ret = regulator_enable(cclogic_dev->vcc_i2c); + if (ret) { + dev_err(&cclogic_dev->i2c_client->dev, + "%s-->Regulator vcc_i2c enable failed rc=%d\n", + __func__, ret); + goto error_reg_en_vcc_i2c; + } + cclogic_dev->regulator_en = true; + } + } + } + return 0; + +error_reg_en_vcc_i2c: + cclogic_reg_set_optimum_mode_check(cclogic_dev->vcc_i2c, 0); +error_reg_opt_i2c: + cclogic_dev->regulator_en = false; + return ret; +} + +static int cclogic_regulator_configure(struct cclogic_dev *cclogic_dev, bool on) +{ + int ret = 0; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (!cclogic_dev->platform_data->i2c_pull_up) + return 0; + if (on == false && cclogic_dev->vcc_i2c) { + if (regulator_count_voltages(cclogic_dev->vcc_i2c) > 0) + regulator_set_voltage(cclogic_dev->vcc_i2c, 0, + CCLOGIC_I2C_VTG_MAX_UV); + regulator_put(cclogic_dev->vcc_i2c); + cclogic_dev->vcc_i2c = NULL; + } else if (!cclogic_dev->vcc_i2c) { + cclogic_dev->vcc_i2c = + regulator_get(&cclogic_dev->i2c_client->dev, "vcc_i2c"); + if (IS_ERR(cclogic_dev->vcc_i2c)) { + dev_err(&cclogic_dev->i2c_client->dev, + "%s: Failed to get i2c regulator\n", + __func__); + ret = PTR_ERR(cclogic_dev->vcc_i2c); + goto err_get_vtg_i2c; + } + + if (regulator_count_voltages(cclogic_dev->vcc_i2c) > 0) { + ret = regulator_set_voltage(cclogic_dev->vcc_i2c, + CCLOGIC_I2C_VTG_MIN_UV, + CCLOGIC_I2C_VTG_MAX_UV); + if (ret) { + dev_err(&cclogic_dev->i2c_client->dev, + "%s-->reg set i2c vtg failed ret =%d\n", + __func__, ret); + goto err_set_vtg_i2c; + } + } + } + + return 0; + +err_set_vtg_i2c: + regulator_put(cclogic_dev->vcc_i2c); +err_get_vtg_i2c: + cclogic_dev->vcc_i2c = NULL; + return ret; + +}; + +static int cclogic_irq_enable(struct cclogic_dev *cclogic_dev, bool enable) +{ + int ret = 0; + + pr_debug("[%s][%d] enable=%d irq_enabled=%d\n", __func__, __LINE__, + enable, cclogic_dev->irq_enabled); + + if (enable) { + if (!cclogic_dev->irq_enabled) { + enable_irq(cclogic_dev->irq_working); + if (gpio_is_valid(cclogic_dev->platform_data->irq_plug)) + enable_irq(cclogic_dev->irq_plug); + cclogic_dev->irq_enabled = true; + } + } else { + if (cclogic_dev->irq_enabled) { + disable_irq(cclogic_dev->irq_working); + if (gpio_is_valid(cclogic_dev->platform_data->irq_plug)) + disable_irq(cclogic_dev->irq_plug); + cclogic_dev->irq_enabled = false; + } + } + + return ret; +} + +static int cclogic_parse_dt(struct device *dev, struct cclogic_platform *pdata) +{ + struct device_node *np = dev->of_node; + struct device_node *temp; + int idx = 0; + int ret; + unsigned int val; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + pdata->chip_num = 0; + + pdata->irq_working = of_get_named_gpio_flags(np, "cc_logic,irq-working", 0, + &pdata->irq_working_flags); + pdata->irq_plug = of_get_named_gpio_flags(np, "cc_logic,irq-plug", 0, + &pdata->irq_plug_flags); + pdata->function_switch_gpio1 = of_get_named_gpio(np, + "cc_logic,function-switch-gpio1", 0); + pdata->function_switch_gpio10 = of_get_named_gpio(np, + "cc_logic,function-switch-gpio10", 0); + pdata->function_switch_gpio2 = of_get_named_gpio(np, + "cc_logic,function-switch-gpio2", 0); + pdata->usb_ss_gpio = of_get_named_gpio(np, "cc_logic,usb-ss-gpio", 0); + pdata->enb_gpio = of_get_named_gpio(np, "cc_logic,power-control", 0); + pdata->i2c_pull_up = of_property_read_bool(np, "cc_logic,i2c-pull-up"); + + pdata->ccchip_power_gpio = of_get_named_gpio_flags(np, + "cc_logic,bypass-power-control", 0, NULL); + + if (of_get_property(np, "vcc_otg-supply", NULL)) { + pdata->vbus_reg = devm_regulator_get(dev, "vcc_otg"); + if (IS_ERR(pdata->vbus_reg)) { + dev_err(dev, "Failed to get vbus regulator\n"); + ret = PTR_ERR(pdata->vbus_reg); + return ret; + } + } + + for_each_child_of_node(np, temp) { + if (idx > CCLOGIC_MAX_SUPPORT_CHIP) { + dev_err(dev, "%s-->too many devices\n", __func__); + break; + } + ret = of_property_read_string(temp, "chip-name", + &pdata->chip[idx].chip_name); + if (ret) + return ret; + + ret = of_property_read_u32(temp, "chip-address", &val); + if (ret) + return ret; + + pdata->chip[idx].address = val; + + pdata->chip[idx].enb = of_property_read_bool(temp, "cc_logic,power-active-high"); + + pr_debug("%s--> chip:%s, address:0x%02x, enb=%d\n", __func__, + pdata->chip[idx].chip_name, val, pdata->chip[idx].enb); + + idx++; + } + + pdata->chip_num = idx; + + return 0; +} + +static irqreturn_t cclogic_irq(int irq, void *data) +{ + struct cclogic_dev *cclogic_dev = (struct cclogic_dev *)data; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (!cclogic_dev || !cclogic_dev->i2c_client) + return IRQ_HANDLED; + + if (!wake_lock_active(&cclogic_dev->wakelock)) + wake_lock(&cclogic_dev->wakelock); + cancel_delayed_work(&cclogic_dev->work); + schedule_delayed_work(&cclogic_dev->work, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t cclogic_plug_irq(int irq, void *data) +{ + struct cclogic_dev *cclogic_dev = (struct cclogic_dev *)data; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (!cclogic_dev || !cclogic_dev->i2c_client) + return IRQ_HANDLED; + + if (!wake_lock_active(&cclogic_dev->wakelock_plug)) { + pm_runtime_get(cclogic_dev->dev); + wake_lock(&cclogic_dev->wakelock_plug); + } + + m_plug_state = 1; + + schedule_delayed_work(&cclogic_dev->plug_work, 0); + + return IRQ_HANDLED; +} + +#ifdef DEV_STAGE_DEBUG +static ssize_t cclogic_show_real_status(struct cclogic_state *pdata, char *buf) +{ + char *vbus = NULL; + char *charging = NULL; + char *port = NULL; + char *polarity = NULL; + char *evt = NULL; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + switch (pdata->evt) { + case CCLOGIC_EVENT_NONE: + evt = "No Event"; + break; + case CCLOGIC_EVENT_DETACHED: + evt = "Detached Event"; + break; + case CCLOGIC_EVENT_ATTACHED: + evt = "Attached Event"; + break; + default: + evt = "unknown event"; + } + + if (pdata->vbus) + vbus = "vbus=1"; + else + vbus = "vbus=0"; + + switch (pdata->cc) { + case CCLOGIC_CC1: + polarity = "polarity=cc1"; + break; + case CCLOGIC_CC2: + polarity = "polarity=cc2"; + break; + case CCLOGIC_CC_UNKNOWN: + polarity = "polarity=unknown"; + break; + case CCLOGIC_CC1_CC2: + polarity = "polarity=cc1_cc2"; + break; + } + + switch (pdata->device) { + case CCLOGIC_NO_DEVICE: + port = "attached=nothing"; + break; + case CCLOGIC_USB_DEVICE: + port = "attached=device"; + break; + case CCLOGIC_USB_HOST: + port = "attached=host"; + break; + case CCLOGIC_DEBUG_DEVICE: + if (pdata->vbus) + port = "attached=debug_vbus"; + else + port = "attached=debug_novbus"; + break; + case CCLOGIC_AUDIO_DEVICE: + port = "attached=audio"; + break; + case CCLOGIC_DEVICE_UNKNOWN: + port = "attached=unknown"; + break; + } + + switch (pdata->charger) { + case CCLOGIC_CURRENT_NONE: + charging = "charging=none"; + break; + case CCLOGIC_CURRENT_DEFAULT: + charging = "charging=default"; + break; + case CCLOGIC_CURRENT_MEDIUM: + charging = "charging=medium"; + break; + case CCLOGIC_CURRENT_ACCESSORY: + charging = "charging=from accessory"; + break; + case CCLOGIC_CURRENT_HIGH: + charging = "charging=high"; + break; + } + + return sprintf(buf, " %15s:%8s;%25s;%23s;%15s\n", + evt, vbus, charging, port, polarity); + +} + +static ssize_t cclogic_set_chip_power_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + int value; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (gpio_is_valid(cclogic_dev->platform_data->ccchip_power_gpio)) { + if (sscanf(buf, "%d", &value) != 1) { + dev_err(dev, "Failed to parse integer: <%s>\n", buf); + return -EINVAL; + } + + gpio_set_value_cansleep(cclogic_dev->platform_data->ccchip_power_gpio, value); + } + + return count; +} + +static ssize_t cclogic_get_chip_power_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + int ret; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (gpio_is_valid(cclogic_dev->platform_data->ccchip_power_gpio)) { + ret = gpio_get_value_cansleep(cclogic_dev->platform_data->ccchip_power_gpio); + if (!ret) + return sprintf(buf, "disabled\n"); + else + return sprintf(buf, "enabled\n"); + } else + return sprintf(buf, "No chip power control pin\n"); +} + +static DEVICE_ATTR(chip_power, S_IRUGO|S_IWUSR, cclogic_get_chip_power_enable, cclogic_set_chip_power_enable); + +static ssize_t cclogic_chip_store_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + int value; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (gpio_is_valid(cclogic_dev->platform_data->enb_gpio)) { + if (sscanf(buf, "%d", &value) != 1) { + dev_err(dev, "Failed to parse integer: <%s>\n", buf); + return -EINVAL; + } + + gpio_set_value_cansleep(cclogic_dev->platform_data->enb_gpio, cclogic_dev->enb == value); + } + + return count; +} + +static ssize_t cclogic_chip_show_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + int ret; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (gpio_is_valid(cclogic_dev->platform_data->enb_gpio)) { + ret = gpio_get_value_cansleep(cclogic_dev->platform_data->enb_gpio); + if (ret == cclogic_dev->enb) + return sprintf(buf, "enabled\n"); + else + return sprintf(buf, "disabled\n"); + } else + return sprintf(buf, "No enabled pin\n"); +} + +static DEVICE_ATTR(enable, S_IRUGO|S_IWUSR, cclogic_chip_show_enable, cclogic_chip_store_enable); + +static ssize_t cclogic_show_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + int ret; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + +#ifdef CCLOGIC_UPDATE_REAL_STATUS + pm_runtime_get_sync(dev); + mutex_lock(&cclogic_ops_lock); + if (cclogic_dev->ops && cclogic_dev->ops->get_state) { + ret = cclogic_dev->ops->get_state(cclogic_dev->i2c_client, + &cclogic_dev->state); + if (ret) { + mutex_unlock(&cclogic_ops_lock); + pm_runtime_put(dev); + return sprintf(buf, "error\n"); + } + } else{ + mutex_unlock(&cclogic_ops_lock); + pm_runtime_put(dev); + return sprintf(buf, "no chip\n"); + } + mutex_unlock(&cclogic_ops_lock); + pm_runtime_put(dev); + +#endif + cclogic_patch_state(cclogic_dev); + return cclogic_show_real_status(&cclogic_dev->state, buf); +} + +static DEVICE_ATTR(status, S_IRUGO, cclogic_show_status, NULL); + +#endif + +static ssize_t cclogic_show_cc(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + return sprintf(buf, "%d\n", cclogic_dev->state.cc); + +} + +static DEVICE_ATTR(cc, S_IRUGO, cclogic_show_cc, NULL); + +static ssize_t cclogic_reg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + int size; + int reg, value; + + if (cclogic_dev->ops && cclogic_dev->ops->read) { + reg = 0x08; + value = cclogic_dev->ops->read(cclogic_dev->i2c_client, reg); + size += sprintf(&buf[size], "[0x%02x] register = 0x%02x\n", reg, value); + pr_debug("[0x%x] = [0x%x]\n", reg, value); + reg = 0x09; + value = cclogic_dev->ops->read(cclogic_dev->i2c_client, reg); + size += sprintf(&buf[size], "[0x%02x] register = 0x%02x\n", reg, value); + pr_debug("[0x%x] = [0x%x]\n", reg, value); + reg = 0x0A; + value = cclogic_dev->ops->read(cclogic_dev->i2c_client, reg); + size += sprintf(&buf[size], "[0x%02x] register = 0x%02x\n", reg, value); + pr_debug("[0x%x] = [0x%x]\n", reg, value); + reg = 0x45; + value = cclogic_dev->ops->read(cclogic_dev->i2c_client, reg); + size += sprintf(&buf[size], "[0x%02x] register = 0x%02x\n", reg, value); + pr_debug("[0x%x] = [0x%x]\n", reg, value); + reg = 0xA0; + value = cclogic_dev->ops->read(cclogic_dev->i2c_client, reg); + size += sprintf(&buf[size], "[0x%02x] register = 0x%02x\n", reg, value); + pr_debug("[0x%x] = [0x%x]\n", reg, value); + } + + return size; +} + +static ssize_t cclogic_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + int reg, value; + char rw[10]; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + gpio_set_value_cansleep(cclogic_dev->platform_data->enb_gpio, 1); + + sscanf(buf, "%s %x %x", rw, ®, &value); + if (!strcmp(rw, "write")) { + if (cclogic_dev->ops && cclogic_dev->ops->read) + cclogic_dev->ops->write(cclogic_dev->i2c_client, reg, value); + } + return size; +} + +static DEVICE_ATTR(reg, S_IRUGO | S_IWUSR, cclogic_reg_show, cclogic_reg_store); + + +static void cclogic_func_set(struct cclogic_platform *p, enum cclogic_func_type func) +{ + switch (func) { + case CCLOGIC_FUNC_HIZ: + gpio_set_value_cansleep(p->function_switch_gpio2, 0); + if (gpio_is_valid(p->function_switch_gpio1)) + gpio_set_value_cansleep(p->function_switch_gpio1, 0); + if (gpio_is_valid(p->function_switch_gpio10)) + gpio_set_value_cansleep(p->function_switch_gpio10, 1); + break; + case CCLOGIC_FUNC_USB: + gpio_set_value_cansleep(p->function_switch_gpio2, 1); + if (gpio_is_valid(p->function_switch_gpio1)) + gpio_set_value_cansleep(p->function_switch_gpio1, 0); + if (gpio_is_valid(p->function_switch_gpio10)) + gpio_set_value_cansleep(p->function_switch_gpio10, 0); + break; + case CCLOGIC_FUNC_AUDIO: + gpio_set_value_cansleep(p->function_switch_gpio2, 0); + if (gpio_is_valid(p->function_switch_gpio1)) +#ifdef CONFIG_MACH_ZUK_Z2_PLUS + gpio_set_value_cansleep(p->function_switch_gpio1, 0); +#else + gpio_set_value_cansleep(p->function_switch_gpio1, 1); +#endif + if (gpio_is_valid(p->function_switch_gpio10)) + gpio_set_value_cansleep(p->function_switch_gpio10, 0); + break; + case CCLOGIC_FUNC_UART: + gpio_set_value_cansleep(p->function_switch_gpio2, 1); + if (gpio_is_valid(p->function_switch_gpio1)) + gpio_set_value_cansleep(p->function_switch_gpio1, 0); + if (gpio_is_valid(p->function_switch_gpio10)) + gpio_set_value_cansleep(p->function_switch_gpio10, 0); + break; + } +} + +int cclogic_get_connected_mode(struct cclogic_state *state) +{ + if (state->evt == CCLOGIC_EVENT_DETACHED) + return CCLOGIC_MODE_NONE; + + if (state->device == CCLOGIC_NO_DEVICE) + return CCLOGIC_MODE_NONE; + else if (state->device == CCLOGIC_USB_DEVICE) + return CCLOGIC_MODE_DFP; + else if (state->device == CCLOGIC_USB_HOST) + return CCLOGIC_MODE_UFP; + else if (state->vbus) + return CCLOGIC_MODE_UFP; + else + return CCLOGIC_MODE_DFP; + + return CCLOGIC_MODE_NONE; +} +EXPORT_SYMBOL(cclogic_get_connected_mode); + +int cclogic_set_mode(struct cclogic_dev *pdata, int mode) +{ + int ret = 0; + + if (pdata->ops->chip_trymode) + ret = pdata->ops->chip_trymode(pdata->i2c_client, mode); + return ret; +} +EXPORT_SYMBOL(cclogic_set_mode); + +int cclogic_get_power_role(struct cclogic_state *state) +{ + if (cclogic_get_connected_mode(state) == CCLOGIC_MODE_NONE) + return CCLOGIC_POWER_NONE; + + if (state->vbus) + return CCLOGIC_POWER_SINK; + else + return CCLOGIC_POWER_SOURCE; +} +EXPORT_SYMBOL(cclogic_get_power_role); + +int cclogic_set_power_role(struct cclogic_dev *pdata, int role) +{ + return 0; +} +EXPORT_SYMBOL(cclogic_set_power_role); + +int cclogic_get_data_role(struct cclogic_state *state) +{ + if (cclogic_get_connected_mode(state) == CCLOGIC_MODE_NONE) + return CCLOGIC_DATA_NONE; + + if (state->device == CCLOGIC_USB_DEVICE) + return CCLOGIC_DATA_HOST; + else if (state->device == CCLOGIC_USB_HOST) + return CCLOGIC_DATA_DEVICE; + + return CCLOGIC_DATA_NONE; +} +EXPORT_SYMBOL(cclogic_get_data_role); + +int cclogic_set_data_role(struct cclogic_dev *pdata, int role) +{ + return 0; +} +EXPORT_SYMBOL(cclogic_set_data_role); + +static int cc_otg_state; +int cclogic_get_otg_state(void) +{ + return cc_otg_state; +} +EXPORT_SYMBOL(cclogic_get_otg_state); + +static int cclogic_do_real_work(struct cclogic_state *state, + struct cclogic_dev *pdata) +{ + int ret = 0; + struct cclogic_platform *p = pdata->platform_data; + static int cclogic_last_status; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + cc_otg_state = 0; + switch (state->evt) { + case CCLOGIC_EVENT_DETACHED: + cclogic_notifier_call_chain(CCLOGIC_EVENT_DETACHED, NULL); + pr_debug("%s-->cable detached\n", __func__); + cclogic_vbus_power_on(pdata, false); + cclogic_func_set(p, CCLOGIC_FUNC_UART); + ret = pdata->ops->chip_config(pdata->i2c_client); + goto out; + case CCLOGIC_EVENT_NONE: + pr_debug("%s-->No event\n", __func__); + break; + case CCLOGIC_EVENT_ATTACHED: + cclogic_notifier_call_chain(CCLOGIC_EVENT_ATTACHED, NULL); + pr_debug("%s-->cable attached\n", __func__); + break; + } + + if (state->device == CCLOGIC_USB_DEVICE) {/* in order to disable usb3.0 when in host mode */ + switch (state->cc) { + case CCLOGIC_CC1: + pr_debug("%s-->usb_ss signal to cc2\n", __func__); + if (gpio_is_valid(p->usb_ss_gpio)) + gpio_set_value_cansleep(p->usb_ss_gpio, 0); + break; + case CCLOGIC_CC2: + pr_debug("%s-->usb_ss signal to cc1\n", __func__); + if (gpio_is_valid(p->usb_ss_gpio)) + gpio_set_value_cansleep(p->usb_ss_gpio, 1); + break; + case CCLOGIC_CC_UNKNOWN: + default: + pr_debug("%s-->usb_ss signal to unknown\n", __func__); + ret = -1; + break; + } + + } else { + switch (state->cc) { + case CCLOGIC_CC1: + pr_debug("%s-->usb_ss signal to cc1\n", __func__); + if (gpio_is_valid(p->usb_ss_gpio)) + gpio_set_value_cansleep(p->usb_ss_gpio, 1); + break; + case CCLOGIC_CC2: + pr_debug("%s-->usb_ss signal to cc2\n", __func__); + if (gpio_is_valid(p->usb_ss_gpio)) + gpio_set_value_cansleep(p->usb_ss_gpio, 0); + break; + case CCLOGIC_CC_UNKNOWN: + default: + pr_debug("%s-->usb_ss signal to unknown\n", __func__); + ret = -1; + break; + } + } + + switch (state->device) { + case CCLOGIC_NO_DEVICE:/* nothing attached */ + pr_debug("%s-->nothing attached,switch to UART\n", __func__); + cclogic_vbus_power_on(pdata, false); + cclogic_func_set(p, CCLOGIC_FUNC_UART); + ret = pdata->ops->chip_config(pdata->i2c_client); + break; + case CCLOGIC_USB_DEVICE: + pr_debug("%s-->function switch set to usb host\n", __func__); + cc_otg_state = 1; + cclogic_func_set(p, CCLOGIC_FUNC_HIZ); + cclogic_vbus_power_on(pdata, true); + msleep(300); + cclogic_func_set(p, CCLOGIC_FUNC_USB); + break; + case CCLOGIC_USB_HOST: + pr_debug("%s-->function switch set to usb device\n", __func__); + cclogic_vbus_power_on(pdata, false); + cclogic_func_set(p, CCLOGIC_FUNC_USB); + break; + case CCLOGIC_DEBUG_DEVICE: + pr_debug("%s-->function switch set to debug device,vbus=%d\n", __func__, state->vbus); + cclogic_func_set(p, CCLOGIC_FUNC_HIZ); + break; + case CCLOGIC_AUDIO_DEVICE: + pr_debug("%s-->function switch set to audio device(HiZ)\n", __func__); + cclogic_func_set(p, CCLOGIC_FUNC_HIZ); + break; + case CCLOGIC_DEVICE_UNKNOWN: + default: + dev_err(&pdata->i2c_client->dev, "%s-->function unknown, switch to HiZ\n", + __func__); + cclogic_func_set(p, CCLOGIC_FUNC_HIZ); + ret = -1; + } + +out: + if (pdata->typec_version == 11) + cclogic_class_update_state(&pdata->cdev); + if (unlikely(state->evt == CCLOGIC_EVENT_DETACHED && + cclogic_last_status == CCLOGIC_EVENT_DETACHED)) { + msleep(20); + pr_debug("cclogic: reset driver ic, try again\n"); + ret = pdata->ops->chip_reset(pdata->i2c_client); + } + cclogic_last_status = state->evt; + + return ret; +} + +static void cclogic_do_work(struct work_struct *w) +{ + struct cclogic_dev *pdata = container_of(w, + struct cclogic_dev, work.work); + int ret = 0; + static int retries; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (pm_runtime_suspended(pdata->dev)) + return; + + mutex_lock(&cclogic_ops_lock); + + ret = pdata->ops->get_state(pdata->i2c_client, &pdata->state); + if (ret) + goto work_end; + + if (pdata->ops->ack_irq) { + ret = pdata->ops->ack_irq(pdata->i2c_client); + if (ret) + goto work_end; + } + + cclogic_patch_state(pdata); + ret = cclogic_do_real_work(&pdata->state, pdata); + +work_end: + mutex_unlock(&cclogic_ops_lock); + + if (ret || !gpio_get_value(cclogic_priv->platform_data->irq_working)) { + retries++; + if (retries <= CCLOGIC_MAX_RETRIES) { + schedule_delayed_work(&pdata->work, msecs_to_jiffies(100)); + return; + } else + pr_err("[%s][%d] still in error,more than %d retries\n", __func__, __LINE__, CCLOGIC_MAX_RETRIES); + } + + if (wake_lock_active(&pdata->wakelock)) + wake_unlock(&pdata->wakelock); + retries = 0; +} + +static void cclogic_do_plug_work(struct work_struct *w) +{ + struct cclogic_dev *pdata = container_of(w, + struct cclogic_dev, plug_work.work); + struct cclogic_platform *p = pdata->platform_data; + static int retries; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (m_plug_state) { + if (gpio_get_value(pdata->platform_data->irq_plug)) { + if (retries < 10) { + retries++; + schedule_delayed_work(&pdata->plug_work, msecs_to_jiffies(100)); + } else{ + m_plug_state = 0; + cancel_delayed_work(&cclogic_priv->work); + flush_delayed_work(&cclogic_priv->work); + cclogic_vbus_power_on(pdata, false); + cclogic_func_set(p, CCLOGIC_FUNC_UART); + retries = 0; + if (wake_lock_active(&cclogic_priv->wakelock_plug)) { + pm_runtime_put(pdata->dev); + wake_unlock(&cclogic_priv->wakelock_plug); + } + } + } else{ + retries = 0; + if (wake_lock_active(&cclogic_priv->wakelock_plug)) + wake_unlock(&cclogic_priv->wakelock_plug); + } + } else{ + retries = 0; + if (wake_lock_active(&cclogic_priv->wakelock_plug)) { + pm_runtime_put(pdata->dev); + wake_unlock(&cclogic_priv->wakelock_plug); + } + } +} + +static int cclogic_init_gpio(struct cclogic_dev *cclogic_dev) +{ + struct i2c_client *client = cclogic_dev->i2c_client; + struct cclogic_platform *pdata = cclogic_dev->platform_data; + int ret = 0; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (gpio_is_valid(pdata->function_switch_gpio1)) { + ret = gpio_request(pdata->function_switch_gpio1, + "cclogic_func_gpio1"); + if (ret) { + dev_err(&client->dev, + "%s-->unable to request gpio [%d]\n", + __func__, pdata->function_switch_gpio1); + goto err_gpio1; + } + ret = gpio_direction_output(pdata->function_switch_gpio1, 0); + if (ret) { + dev_err(&client->dev, + "%s-->unable to set direction for gpio [%d]\n", + __func__, pdata->function_switch_gpio1); + goto err_gpio1_dir; + } + } + + if (gpio_is_valid(pdata->function_switch_gpio10)) { + ret = gpio_request(pdata->function_switch_gpio10, + "cclogic_func_gpio10"); + if (ret) { + dev_err(&client->dev, + "%s-->unable to request gpio [%d]\n", + __func__, pdata->function_switch_gpio10); + goto err_gpio1_dir; + } + ret = gpio_direction_output(pdata->function_switch_gpio10, 0); + if (ret) { + dev_err(&client->dev, + "%s-->unable to set direction for gpio [%d]\n", + __func__, pdata->function_switch_gpio10); + goto err_gpio10_dir; + } + } + + if (gpio_is_valid(pdata->function_switch_gpio2)) { + ret = gpio_request(pdata->function_switch_gpio2, + "cclogic_func_gpio2"); + if (ret) { + dev_err(&client->dev, + "%s-->unable to request gpio [%d]\n", + __func__, pdata->function_switch_gpio2); + goto err_gpio10_dir; + } + ret = gpio_direction_output(pdata->function_switch_gpio2, 1); + if (ret) { + dev_err(&client->dev, + "%s-->unable to set direction for gpio [%d]\n", + __func__, pdata->function_switch_gpio2); + goto err_gpio2_dir; + } + } else { + ret = -ENODEV; + dev_err(&client->dev, + "%s-->function_switch_gpio2 not provided\n", __func__); + goto err_gpio10_dir; + } + + if (gpio_is_valid(pdata->usb_ss_gpio)) { + ret = gpio_request(pdata->usb_ss_gpio, "usb_ss_gpio"); + if (ret) { + dev_err(&client->dev, + "%s-->unable to request gpio [%d]\n", + __func__, pdata->usb_ss_gpio); + goto err_gpio2_dir; + } + ret = gpio_direction_output(pdata->usb_ss_gpio, 0); + if (ret) { + dev_err(&client->dev, + "%s-->unable to set direction for gpio [%d]\n", + __func__, pdata->usb_ss_gpio); + goto err_ss_gpio; + } + } + + if (gpio_is_valid(pdata->enb_gpio)) { + ret = gpio_request(pdata->enb_gpio, "enb_gpio"); + if (ret) { + dev_err(&client->dev, + "%s-->unable to request gpio [%d]\n", + __func__, pdata->enb_gpio); + goto err_ss_gpio; + } + ret = gpio_direction_output(pdata->enb_gpio, !cclogic_dev->enb); + if (ret) { + dev_err(&client->dev, + "%s-->unable to set direction for gpio [%d]\n", + __func__, pdata->enb_gpio); + goto err_enb_gpio; + } + } + + if (gpio_is_valid(pdata->ccchip_power_gpio)) { + ret = gpio_request(pdata->ccchip_power_gpio, "chip_power_gpio"); + if (ret) { + dev_err(&client->dev, + "%s-->unable to request gpio [%d]\n", + __func__, pdata->ccchip_power_gpio); + goto err_enb_gpio; + } + ret = gpio_direction_output(pdata->ccchip_power_gpio, 1); + if (ret) { + dev_err(&client->dev, + "%s-->unable to set direction for gpio [%d]\n", + __func__, pdata->ccchip_power_gpio); + goto err_cc_power_gpio; + } + } + + return ret; + +err_cc_power_gpio: + if (gpio_is_valid(pdata->ccchip_power_gpio)) + gpio_free(pdata->ccchip_power_gpio); + +err_enb_gpio: + if (gpio_is_valid(pdata->enb_gpio)) + gpio_free(pdata->enb_gpio); + +err_ss_gpio: + if (gpio_is_valid(pdata->usb_ss_gpio)) + gpio_free(pdata->usb_ss_gpio); + +err_gpio2_dir: + if (gpio_is_valid(pdata->function_switch_gpio2)) + gpio_free(pdata->function_switch_gpio2); + +err_gpio10_dir: + if (gpio_is_valid(pdata->function_switch_gpio10)) + gpio_free(pdata->function_switch_gpio10); + +err_gpio1_dir: + if (gpio_is_valid(pdata->function_switch_gpio1)) + gpio_free(pdata->function_switch_gpio1); +err_gpio1: + return ret; +} + +static int cclogic_remove_gpio(struct cclogic_dev *cclogic_dev) +{ + struct cclogic_platform *pdata = cclogic_dev->platform_data; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (gpio_is_valid(pdata->enb_gpio)) + gpio_free(pdata->enb_gpio); + + if (gpio_is_valid(pdata->usb_ss_gpio)) + gpio_free(pdata->usb_ss_gpio); + + if (gpio_is_valid(pdata->function_switch_gpio2)) + gpio_free(pdata->function_switch_gpio2); + + if (gpio_is_valid(pdata->function_switch_gpio1)) + gpio_free(pdata->function_switch_gpio1); + + if (gpio_is_valid(pdata->function_switch_gpio10)) + gpio_free(pdata->function_switch_gpio10); + + if (gpio_is_valid(pdata->ccchip_power_gpio)) + gpio_free(pdata->ccchip_power_gpio); + + return 0; + +} + +/* cclogic_match_id() */ +static int cclogic_match_id(const char *name) +{ + int i = 0; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (unlikely(!name)) { + pr_err("%s-->No name\n", __func__); + return -ENODEV; + } + + if (cclogic_priv && cclogic_priv->platform_data) { + for (i = 0; i < cclogic_priv->platform_data->chip_num; i++) { + struct cclogic_of_chip *chip = + &cclogic_priv->platform_data->chip[i]; + + if (!chip->chip_name) { + pr_err("%s-->%s mismatch\n", __func__, name); + return -ENODEV; + } + if (!strcmp(chip->chip_name, name)) + break; + } + } else { + pr_err("%s-->%s mismatch\n", __func__, name); + return -ENODEV; + } + + if (i >= cclogic_priv->platform_data->chip_num) { + pr_err("%s-->%s mismatch\n", __func__, name); + return -ENODEV; + } + return i; +} + +/* cclogic_register() */ +int cclogic_register(struct cclogic_chip *c) +{ + int ret = 0; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + mutex_lock(&cclogic_ops_lock); + if (cclogic_priv->ops) { + mutex_unlock(&cclogic_ops_lock); + return -EINVAL; + } + mutex_unlock(&cclogic_ops_lock); + + ret = cclogic_match_id(c->chip_name); + if (ret < 0) + return ret; + + c->addr = cclogic_priv->platform_data->chip[ret].address; + cclogic_priv->enb = cclogic_priv->platform_data->chip[ret].enb; + + if (!c->chip_check) + return -ENODEV; + + pm_runtime_get_sync(cclogic_priv->dev); + wake_lock(&cclogic_priv->wakelock_plug); + msleep(100); + + mutex_lock(&cclogic_ops_lock); + + cclogic_priv->i2c_client->addr = c->addr; + + if (c->chip_reset) { + ret = c->chip_reset(cclogic_priv->i2c_client); + if (ret) + goto err_ret; + } + ret = c->chip_check(cclogic_priv->i2c_client); + if (ret) + goto err_ret; + ret = c->chip_config(cclogic_priv->i2c_client); + if (ret) + goto err_ret; + + pr_info("%s select chip:%s\n", __func__, c->chip_name); + + cclogic_priv->ops = c; + cclogic_priv->typec_version = c->typec_version; + + mutex_unlock(&cclogic_ops_lock); + + if (cclogic_priv->typec_version == 11) { + /* cclogic_priv->cdev.name = c->chip_name; */ + cclogic_priv->cdev.name = "otg_default"; + ret = cclogic_class_register(&cclogic_priv->cdev); + if (ret) { + dev_err(cclogic_priv->dev, + "Failed to setup class dev for cclogic driver"); + goto err_ret1; + } + } + + cclogic_irq_enable(cclogic_priv, true); + + m_plug_state = 1; + schedule_delayed_work(&cclogic_priv->plug_work, 0); + + return 0; + +err_ret: + mutex_unlock(&cclogic_ops_lock); +err_ret1: + pm_runtime_put(cclogic_priv->dev); + return ret; +} +EXPORT_SYMBOL(cclogic_register); + +/*cclogic_unregister() */ +void cclogic_unregister(struct cclogic_chip *c) +{ + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (cclogic_priv->ops != c) + return; + + cancel_delayed_work(&cclogic_priv->work); + flush_delayed_work(&cclogic_priv->work); + cancel_delayed_work(&cclogic_priv->plug_work); + flush_delayed_work(&cclogic_priv->plug_work); + + cclogic_irq_enable(cclogic_priv, false); + + if (wake_lock_active(&cclogic_priv->wakelock)) + wake_unlock(&cclogic_priv->wakelock); + pm_runtime_put(cclogic_priv->dev); + + mutex_lock(&cclogic_ops_lock); + cclogic_priv->ops = NULL; + mutex_unlock(&cclogic_ops_lock); + + if (cclogic_priv->typec_version == 11) { + cclogic_priv->typec_version = 10; + cclogic_class_unregister(&cclogic_priv->cdev); + } + + return; +} +EXPORT_SYMBOL(cclogic_unregister); + +static struct attribute *cclogic_attrs[] = { +#ifdef DEV_STAGE_DEBUG + &dev_attr_chip_power.attr, + &dev_attr_status.attr, + &dev_attr_enable.attr, + &dev_attr_reg.attr, +#endif + &dev_attr_cc.attr, + NULL, +}; + +static struct attribute_group cclogic_attr_group = { + .attrs = cclogic_attrs, +}; + +/* cclogic_probe() */ +static int cclogic_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int ret = 0; + struct cclogic_dev *cclogic_dev = cclogic_priv; + struct cclogic_platform *platform_data; + + pr_info("%s start\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "%s: i2c check failed\n", __func__); + ret = -ENODEV; + goto err_i2c; + } + + if (!cclogic_dev) { + ret = -ENODEV; + goto err_i2c; + } + + memset(cclogic_dev, 0, sizeof(*cclogic_dev)); + + if (client->dev.of_node) { + platform_data = devm_kzalloc(&client->dev, + sizeof(*platform_data), GFP_KERNEL); + if (!platform_data) { + dev_err(&client->dev, "%s-->Failed to allocate memory\n", + __func__); + ret = -ENOMEM; + goto err_i2c; + } + + ret = cclogic_parse_dt(&client->dev, platform_data); + if (ret) { + dev_err(&client->dev, "%s-->Failed parse dt\n", __func__); + goto err_i2c; + } + } else + platform_data = client->dev.platform_data; + + cclogic_dev->pin = devm_pinctrl_get(&client->dev); + if (IS_ERR_OR_NULL(cclogic_dev->pin)) { + ret = -ENODEV; + goto err_i2c; + } + + cclogic_dev->pin_active = pinctrl_lookup_state(cclogic_dev->pin, "cc_active"); + if (IS_ERR(cclogic_dev->pin_active)) { + dev_err(&client->dev, "Unable find cc_active\n"); + ret = PTR_ERR(cclogic_dev->pin_active); + goto err_i2c; + } + + cclogic_dev->pin_suspend = pinctrl_lookup_state(cclogic_dev->pin, "cc_sleep"); + if (IS_ERR(cclogic_dev->pin_suspend)) { + dev_err(&client->dev, "Unable find cc_sleep\n"); + ret = PTR_ERR(cclogic_dev->pin_suspend); + goto err_i2c; + } + + ret = pinctrl_select_state(cclogic_dev->pin, cclogic_dev->pin_active); + if (ret < 0) { + dev_err(&client->dev, "Unable select active state in pinctrl\n"); + goto err_i2c; + } + + + cclogic_dev->platform_data = platform_data; + cclogic_dev->i2c_client = client; + cclogic_dev->dev = &client->dev; + + i2c_set_clientdata(client, cclogic_dev); + + pm_runtime_set_suspended(&client->dev); + + ret = cclogic_regulator_configure(cclogic_dev, true); + if (ret < 0) { + dev_err(&client->dev, "%s-->Failed to configure regulators\n", + __func__); + goto err_i2c; + } + + ret = cclogic_power_on(cclogic_dev, true); + if (ret < 0) { + dev_err(&client->dev, "%s-->Failed to power on\n", __func__); + goto err_regulator_conf; + } + + ret = cclogic_init_gpio(cclogic_dev); + if (ret) { + dev_err(&client->dev, "%s-->error in set gpio\n", __func__); + goto err_regulator_on; + } + + pm_runtime_enable(&client->dev); + + if (gpio_is_valid(platform_data->irq_working)) { + /* configure cclogic irq working */ + ret = gpio_request(platform_data->irq_working, "cclogic_irq_working"); + if (ret) { + dev_err(&client->dev, + "%s-->unable to request gpio [%d]\n", + __func__, platform_data->irq_working); + goto err_set_gpio; + } + ret = gpio_direction_input(platform_data->irq_working); + if (ret) { + dev_err(&client->dev, + "%s-->unable to set direction for gpio [%d]\n", + __func__, platform_data->irq_working); + goto err_irq_working_dir; + } + } else { + dev_err(&client->dev, "%s-->irq gpio not provided\n", __func__); + goto err_set_gpio; + } + cclogic_dev->irq_working = gpio_to_irq(platform_data->irq_working); + + if (gpio_is_valid(platform_data->irq_plug)) { + /* configure cclogic irq plug */ + ret = gpio_request(platform_data->irq_plug, "cclogic_irq_plug"); + if (ret) { + dev_err(&client->dev, + "%s-->unable to request gpio [%d]\n", + __func__, platform_data->irq_plug); + goto err_irq_working_dir; + } + ret = gpio_direction_input(platform_data->irq_plug); + if (ret) { + dev_err(&client->dev, + "%s-->unable to set direction for gpio [%d]\n", + __func__, platform_data->irq_plug); + goto err_irq_plug_dir; + } + cclogic_dev->irq_plug = gpio_to_irq(platform_data->irq_plug); + } else { + dev_err(&client->dev, "%s-->irq plug gpio not provided\n", __func__); + goto err_irq_working_dir; + } + + + wake_lock_init(&cclogic_dev->wakelock, WAKE_LOCK_SUSPEND, + "cclogic_wakelock"); + wake_lock_init(&cclogic_dev->wakelock_plug, WAKE_LOCK_SUSPEND, + "cclogic_wakelock_plug"); + + device_init_wakeup(cclogic_dev->dev, 1); + + INIT_DELAYED_WORK(&cclogic_dev->work, cclogic_do_work); + INIT_DELAYED_WORK(&cclogic_dev->plug_work, cclogic_do_plug_work); + + ret = sysfs_create_group(&client->dev.kobj, &cclogic_attr_group); + if (ret) { + dev_err(&client->dev, + "%s-->Unable to create sysfs for cclogic, errors: %d\n", + __func__, ret); + goto err_chip_check; + } + + ret = request_threaded_irq(cclogic_dev->irq_working, NULL, cclogic_irq, + cclogic_dev->platform_data->irq_working_flags | IRQF_ONESHOT, DRIVER_NAME, + cclogic_dev); + if (ret) { + dev_err(&client->dev, + "%s: Failed to create working-irq thread\n", __func__); + goto err_irq_req; + } + + if (gpio_is_valid(platform_data->irq_plug)) { + ret = request_threaded_irq(cclogic_dev->irq_plug, NULL, cclogic_plug_irq, + cclogic_dev->platform_data->irq_plug_flags | IRQF_ONESHOT, DRIVER_NAME, + cclogic_dev); + if (ret) { + dev_err(&client->dev, + "%s: Failed to create plug-irq thread\n", __func__); + goto err_irq_enable; + } + } + disable_irq(cclogic_dev->irq_working); + if (gpio_is_valid(cclogic_dev->platform_data->irq_plug)) + disable_irq(cclogic_dev->irq_plug); + + pr_info("%s Success\n", __func__); + + return 0; + + cancel_delayed_work_sync(&cclogic_dev->work); + cancel_delayed_work_sync(&cclogic_dev->plug_work); + cclogic_irq_enable(cclogic_dev, false); + + if (gpio_is_valid(platform_data->irq_plug)) + free_irq(cclogic_dev->irq_plug, cclogic_dev); + +err_irq_enable: + free_irq(cclogic_dev->irq_working, cclogic_dev); +err_irq_req: + sysfs_remove_group(&client->dev.kobj, &cclogic_attr_group); +err_chip_check: + device_init_wakeup(cclogic_dev->dev, 0); + wake_lock_destroy(&cclogic_dev->wakelock); + wake_lock_destroy(&cclogic_dev->wakelock_plug); +err_irq_plug_dir: + if (gpio_is_valid(platform_data->irq_plug)) + gpio_free(platform_data->irq_plug); +err_irq_working_dir: + if (gpio_is_valid(platform_data->irq_working)) + gpio_free(platform_data->irq_working); +err_set_gpio: + cclogic_remove_gpio(cclogic_dev); +err_regulator_on: + cclogic_power_on(cclogic_dev, false); +err_regulator_conf: + cclogic_regulator_configure(cclogic_dev, false); +err_i2c: + dev_err(&client->dev, "%s Failed\n", __func__); + + return ret; +} + +/* cclogic_remove() */ +static int cclogic_remove(struct i2c_client *client) +{ + struct cclogic_dev *cclogic_dev; + + pr_info("%s\n", __func__); + + cclogic_dev = i2c_get_clientdata(client); + + cclogic_irq_enable(cclogic_dev, false); + + cclogic_vbus_power_on(cclogic_dev, false); + + sysfs_remove_group(&client->dev.kobj, &cclogic_attr_group); + + device_init_wakeup(cclogic_dev->dev, 0); + wake_lock_destroy(&cclogic_dev->wakelock); + wake_lock_destroy(&cclogic_dev->wakelock_plug); + + cancel_delayed_work_sync(&cclogic_dev->work); + cancel_delayed_work_sync(&cclogic_dev->plug_work); + + free_irq(cclogic_dev->irq_working, cclogic_dev); + if (gpio_is_valid(cclogic_dev->platform_data->irq_plug)) + free_irq(cclogic_dev->irq_plug, cclogic_dev); + + if (gpio_is_valid(cclogic_dev->platform_data->ccchip_power_gpio)) + gpio_set_value(cclogic_dev->platform_data->ccchip_power_gpio, 0); + + disable_irq_wake(cclogic_dev->irq_working); + if (gpio_is_valid(cclogic_dev->platform_data->irq_plug)) + disable_irq_wake(cclogic_dev->irq_plug); + + cclogic_remove_gpio(cclogic_dev); + + if (gpio_is_valid(cclogic_dev->platform_data->irq_working)) + gpio_free(cclogic_dev->platform_data->irq_working); + if (gpio_is_valid(cclogic_dev->platform_data->irq_plug)) + gpio_free(cclogic_dev->platform_data->irq_plug); + + pinctrl_select_state(cclogic_dev->pin, cclogic_dev->pin_suspend); + + cclogic_power_on(cclogic_dev, false); + + cclogic_regulator_configure(cclogic_dev, false); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int cclogic_runtime_suspend(struct device *dev) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + struct cclogic_platform *pdata = cclogic_dev->platform_data; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (gpio_is_valid(pdata->enb_gpio)) + gpio_direction_output(pdata->enb_gpio, !cclogic_dev->enb); + + return 0; +} + +static int cclogic_runtime_resume(struct device *dev) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + struct cclogic_platform *pdata = cclogic_dev->platform_data; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + if (gpio_is_valid(pdata->enb_gpio)) + gpio_direction_output(pdata->enb_gpio, cclogic_dev->enb); + + return 0; +} + +#endif + +#ifdef CONFIG_PM_SLEEP +static int cclogic_suspend(struct device *dev) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + struct cclogic_platform *pdata = cclogic_dev->platform_data; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + disable_irq(cclogic_dev->irq_working); + enable_irq_wake(cclogic_dev->irq_working); + if (gpio_is_valid(pdata->irq_plug)) { + disable_irq(cclogic_dev->irq_plug); + enable_irq_wake(cclogic_dev->irq_plug); + } + + cclogic_power_on(cclogic_dev, false); + pinctrl_select_state(cclogic_dev->pin, cclogic_dev->pin_suspend); + + return 0; +} + +static int cclogic_resume(struct device *dev) +{ + struct cclogic_dev *cclogic_dev = dev_get_drvdata(dev); + struct cclogic_platform *pdata = cclogic_dev->platform_data; + + pr_debug("[%s][%d]\n", __func__, __LINE__); + + pinctrl_select_state(cclogic_dev->pin, cclogic_dev->pin_active); + + cclogic_power_on(cclogic_dev, true); + + disable_irq_wake(cclogic_dev->irq_working); + enable_irq(cclogic_dev->irq_working); + if (gpio_is_valid(pdata->irq_plug)) { + disable_irq_wake(cclogic_dev->irq_plug); + enable_irq(cclogic_dev->irq_plug); + } + + return 0; +} +#endif + +void cclogic_shutdown(struct i2c_client *client) +{ + cclogic_remove(client); +} + +static const struct dev_pm_ops cclogic_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cclogic_suspend, cclogic_resume) + SET_RUNTIME_PM_OPS(cclogic_runtime_suspend, cclogic_runtime_resume, NULL) +}; + +static const struct i2c_device_id cclogic_id_table[] = { + {DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, cclogic_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id cclogic_match_table[] = { + { .compatible = "typec,cclogic", }, + { }, +}; +MODULE_DEVICE_TABLE(of, cclogic_match_table); +#else +#define cclogic_match_table NULL +#endif + +static struct i2c_driver cclogic_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &cclogic_pm_ops, + .of_match_table = cclogic_match_table, + }, + .probe = cclogic_probe, + .remove = cclogic_remove, + .shutdown = cclogic_shutdown, + .id_table = cclogic_id_table, +}; + +static int __init cclogic_init(void) +{ + pr_debug("[%s][%d]\n", __func__, __LINE__); + + mutex_init(&cclogic_ops_lock); + + cclogic_priv = kzalloc(sizeof(*cclogic_priv), GFP_KERNEL); + if (cclogic_priv == NULL) { + pr_err("%s-->failed to allocate memory for module data\n", + __func__); + return -ENOMEM; + } + + return i2c_add_driver(&cclogic_driver); +} + +static void __exit cclogic_exit(void) +{ + pr_debug("[%s][%d]\n", __func__, __LINE__); + + i2c_del_driver(&cclogic_driver); + + kfree(cclogic_priv); + cclogic_priv = NULL; + + return; +} + +module_init(cclogic_init); +module_exit(cclogic_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Yang ShaoYing <yangsy2@lenovo.com>"); +MODULE_DESCRIPTION("Drivers core for CC-Logic"); +MODULE_ALIAS("platform:cc-logic"); diff --git a/drivers/misc/cclogic/cclogic-core.h b/drivers/misc/cclogic/cclogic-core.h new file mode 100644 index 000000000000..7be98e4a619e --- /dev/null +++ b/drivers/misc/cclogic/cclogic-core.h @@ -0,0 +1,169 @@ +#ifndef __CCLOGIC_COMMON_H +#define __CCLOGIC_COMMON_H + +#include <linux/wakelock.h> +#include "cclogic-class.h" + +#define DEV_STAGE_DEBUG +#define CCLOGIC_UPDATE_REAL_STATUS + +#ifdef DEBUG +#undef pr_debug +#undef pr_info +#define pr_debug(fmt, args...) pr_err(fmt, ##args) +#define pr_info(fmt, args...) pr_err(fmt, ##args) +#endif + + +#define CCLOGIC_I2C_VTG_MIN_UV 1800000 +#define CCLOGIC_I2C_VTG_MAX_UV 1800000 +#define CCLOGIC_I2C_LOAD_UA 1800 + +#define CCLOGIC_MAX_SUPPORT_CHIP 2 +/* #define CCLOGIC_MAX_RETRIES 100 */ +#define CCLOGIC_MAX_RETRIES 5 + +struct cclogic_of_chip { + const char *chip_name; + int enb; + int address; +}; + +#define CCLOGIC_SUPPORT_MODE_DFP (1<<0) +#define CCLOGIC_SUPPORT_MODE_UFP (1<<1) +#define CCLOGIC_SUPPORT_MODE_DUAL ((1<<0)|(1<<1)) +#define CCLOGIC_SUPPORT_POWER_SOURCE (1<<2) +#define CCLOGIC_SUPPORT_POWER_SINK (1<<3) +#define CCLOGIC_SUPPORT_POWER_SWAP ((1<<2)|(1<<3)) +#define CCLOGIC_SUPPORT_DATA_HOST (1<<4) +#define CCLOGIC_SUPPORT_DATA_DEVICE (1<<5) +#define CCLOGIC_SUPPORT_DATA_SWAP ((1<<4)|(1<<5)) + +#define CCLOGIC_MODE_NONE 0 +#define CCLOGIC_MODE_DFP 1 +#define CCLOGIC_MODE_UFP 2 +#define CCLOGIC_MODE_DRP 3 +#define CCLOGIC_POWER_NONE 0 +#define CCLOGIC_POWER_SOURCE 1 +#define CCLOGIC_POWER_SINK 2 +#define CCLOGIC_DATA_NONE 0 +#define CCLOGIC_DATA_HOST 1 +#define CCLOGIC_DATA_DEVICE 2 + +struct cclogic_platform { + unsigned int irq_working_flags; + unsigned int irq_working; + unsigned int irq_plug_flags; + unsigned int irq_plug; + bool i2c_pull_up; + unsigned int function_switch_gpio1; + unsigned int function_switch_gpio10; + unsigned int function_switch_gpio2; + unsigned int usb_ss_gpio; + unsigned int enb_gpio; + unsigned int ccchip_power_gpio; + int chip_num; + struct regulator *vbus_reg; + struct cclogic_of_chip chip[CCLOGIC_MAX_SUPPORT_CHIP]; +}; + + +enum cclogic_attached_type { + CCLOGIC_DEVICE_UNKNOWN, + CCLOGIC_NO_DEVICE, + CCLOGIC_USB_DEVICE, + CCLOGIC_USB_HOST, + CCLOGIC_DEBUG_DEVICE, + CCLOGIC_AUDIO_DEVICE, +}; + +enum cclogic_current_type { + CCLOGIC_CURRENT_NONE, + CCLOGIC_CURRENT_DEFAULT, + CCLOGIC_CURRENT_MEDIUM, + CCLOGIC_CURRENT_ACCESSORY, + CCLOGIC_CURRENT_HIGH, +}; + +enum cclogic_event_type { + CCLOGIC_EVENT_NONE, + CCLOGIC_EVENT_DETACHED, + CCLOGIC_EVENT_ATTACHED, +}; + +enum cclogic_cc_type { + CCLOGIC_CC_UNKNOWN, + CCLOGIC_CC1, + CCLOGIC_CC2, + CCLOGIC_CC1_CC2, +}; + +struct cclogic_state { + bool vbus; + enum cclogic_cc_type cc; + enum cclogic_event_type evt; + enum cclogic_attached_type device; + enum cclogic_current_type charger; +}; + +struct cclogic_chip; +struct cclogic_dev { + struct device *dev; + struct i2c_client *i2c_client; + unsigned int irq_working; + unsigned int irq_plug; + bool irq_enabled; + struct regulator *vcc_i2c; + bool regulator_en; + struct delayed_work work; + struct delayed_work plug_work; + struct cclogic_platform *platform_data; + struct wake_lock wakelock; + struct wake_lock wakelock_plug; + bool vbus_on; + struct cclogic_chip *ops; + struct cclogic_state state; + struct pinctrl *pin; + struct pinctrl_state *pin_active; + struct pinctrl_state *pin_suspend; + struct cclogic_class_dev cdev; + int enb; + unsigned int typec_version; +}; + +struct cclogic_chip { + const char *chip_name; + unsigned char addr; + unsigned int typec_version; + unsigned int support; + int (*get_state)(struct i2c_client *client, struct cclogic_state *result); + int (*ack_irq)(struct i2c_client *client); + int (*chip_config)(struct i2c_client *client); + int (*chip_reset)(struct i2c_client *client); + int (*chip_check)(struct i2c_client *client); + int (*chip_trymode)(struct i2c_client *client, int mode); + struct list_head chip_list; + int (*read)(struct i2c_client *client, u8 reg); + int (*write)(struct i2c_client *client, u8 reg, u8 data); +}; + +enum cclogic_func_type { + CCLOGIC_FUNC_HIZ, + CCLOGIC_FUNC_USB, + CCLOGIC_FUNC_AUDIO, + CCLOGIC_FUNC_UART, +}; + + + +extern int cclogic_register(struct cclogic_chip *c); +extern void cclogic_unregister(struct cclogic_chip *c); + +extern int cclogic_get_connected_mode(struct cclogic_state *state); +extern int cclogic_set_mode(struct cclogic_dev *pdata, int mode); +extern int cclogic_get_power_role(struct cclogic_state *state); +extern int cclogic_set_power_role(struct cclogic_dev *pdata, int role); +extern int cclogic_get_data_role(struct cclogic_state *state); +extern int cclogic_set_data_role(struct cclogic_dev *pdata, int role); + +#endif diff --git a/drivers/misc/cclogic/pi5usb30216d.c b/drivers/misc/cclogic/pi5usb30216d.c new file mode 100644 index 000000000000..ba7d6449c736 --- /dev/null +++ b/drivers/misc/cclogic/pi5usb30216d.c @@ -0,0 +1,245 @@ +/* + * pi5usb30216d.c + * + * Drivers for usb type-C interface's CC-Logic chip of Pericom + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/miscdevice.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +#include <linux/wakelock.h> +#include "cclogic-core.h" + + +#define DRIVER_NAME "pericom,pi5usb30216d" + + +/* + * pi5usb30216d_write_i2c() + * only modified 0x2 register value. + */ +static int pi5usb30216d_write_i2c(struct i2c_client *client, u8 reg, u8 data) +{ + int ret = 0; + char val[4] = {0}; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + val[reg-1] = data; + + ret = i2c_master_send(client, val, 2); + if (ret != 2) { + dev_err(&client->dev, "cclogic:%s-->i2c send error\n", __func__); + return ret; + } + + return 0; + +} +/* pi5usb30216d_parse_cclogic_state() */ +static void pi5usb30216d_parse_cclogic_state(int reg3, int reg4, + struct cclogic_state *result) +{ + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + result->vbus = false; + result->cc = CCLOGIC_CC_UNKNOWN; + result->evt = CCLOGIC_EVENT_NONE; + result->device = CCLOGIC_DEVICE_UNKNOWN; + result->charger = CCLOGIC_CURRENT_NONE; + + if (reg3 == 0x3) { + pr_err("cclogic:%s-->detach and attach in the same time\n", + __func__); + } else{ + if (reg3&0x2) + result->evt = CCLOGIC_EVENT_DETACHED; + if (reg3&0x1) + result->evt = CCLOGIC_EVENT_ATTACHED; + } + + if (reg4&0x80) + result->vbus = true; + + switch (reg4&0x60) { + case 0x00: + result->charger = CCLOGIC_CURRENT_NONE; + break; + case 0x20: + result->charger = CCLOGIC_CURRENT_DEFAULT; + break; + case 0x40: + result->charger = CCLOGIC_CURRENT_MEDIUM; + break; + case 0x60: + result->charger = CCLOGIC_CURRENT_HIGH; + break; + } + + switch (reg4&0x3) { + case 0x01: + result->cc = CCLOGIC_CC1; + break; + case 0x02: + result->cc = CCLOGIC_CC2; + break; + default: + break; + } + + switch (reg4&0x1C) { + case 0x00: + result->device = CCLOGIC_NO_DEVICE; + break; + case 0x04: + result->device = CCLOGIC_USB_DEVICE; + break; + case 0x08: + result->device = CCLOGIC_USB_HOST; + break; + case 0x0C: + result->device = CCLOGIC_AUDIO_DEVICE; + break; + case 0x10: + result->device = CCLOGIC_DEBUG_DEVICE; + break; + default: + result->device = CCLOGIC_DEVICE_UNKNOWN; + break; + } + +} + +/* pi5usb30216d_get_state() */ +static int pi5usb30216d_get_state(struct i2c_client *client, + struct cclogic_state *state) +{ + char reg[4] = {0}; + int ret = 0; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + ret = i2c_master_recv(client, reg, 4); + if (ret != 4) { + dev_err(&client->dev, "cclogic:%s-->i2c recv error\n", __func__); + return ret; + } + pr_debug("cclogic:%s-->i2c reg value: 0x%02x 0x%02x 0x%02x 0x%02x\n", + __func__, reg[0], reg[1], reg[2], reg[3]); + + pi5usb30216d_parse_cclogic_state(reg[2], reg[3], state); + + return 0; +} + +/* pi5usb30216d_check_chip() */ +static int pi5usb30216d_check_chip(struct i2c_client *client) +{ + int ret; + char reg_test_r = {0}; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + /* ID check */ + ret = i2c_master_recv(client, ®_test_r, 1); + if (ret != 1) { + dev_err(&client->dev, "cclogic:%s: i2c read error\n", __func__); + return ret; + } + + if (reg_test_r != 0x20) { + dev_err(&client->dev, "cclogic:%s: devid mismatch (0x%02x!=0x20)\n", + __func__, reg_test_r); + return -ENODEV; + } + + return 0; +} +/* pi5usb30216d_trymode() */ +static int pi5usb30216d_trymode(struct i2c_client *client, int mode) +{ + int ret; + char reg; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + if (mode == CCLOGIC_MODE_UFP) { + reg = 0x60; + pr_debug("cclogic:trymode sink\n"); + } else{ + reg = 0x62; + pr_debug("cclogic:trymode source\n"); + } + ret = pi5usb30216d_write_i2c(client, 0x2, reg); + if (ret) { + dev_err(&client->dev, "cclogic:%s: i2c write error\n", __func__); + return ret; + } + + return ret; + +} +/* pi5usb30216d_config_chip() */ +static int pi5usb30216d_config_chip(struct i2c_client *client) +{ + int ret; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + ret = pi5usb30216d_write_i2c(client, 0x2, 0x66); + if (ret) { + dev_err(&client->dev, "cclogic:%s: i2c write error\n", __func__); + return ret; + } + + return 0; +} + +static struct cclogic_chip pi5usb30216d_chip = { + .chip_name = DRIVER_NAME, + .get_state = pi5usb30216d_get_state, + .ack_irq = NULL, + .chip_config = pi5usb30216d_config_chip, + .chip_reset = NULL, + .chip_check = pi5usb30216d_check_chip, + .chip_trymode = pi5usb30216d_trymode, + .typec_version = 11,/* spec 1.1 */ + .support = CCLOGIC_SUPPORT_MODE_DUAL, +}; + +/* pi5usb30216d_init() */ +static int __init pi5usb30216d_init(void) +{ + pr_info("cclogic:[%s][%d]\n", __func__, __LINE__); + + return cclogic_register(&pi5usb30216d_chip); +} + +/* pi5usb30216d_exit() */ +static void __exit pi5usb30216d_exit(void) +{ + pr_info("cclogic:[%s][%d]\n", __func__, __LINE__); + + cclogic_unregister(&pi5usb30216d_chip); + return; +} + +late_initcall(pi5usb30216d_init); +module_exit(pi5usb30216d_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Yang ShaoYing <yangsy2@lenovo.com>"); +MODULE_DESCRIPTION("Drivers for Type-C CC-Logic chip of Pericom pi5usb30216dd"); +MODULE_ALIAS("platform:cc-logic"); diff --git a/drivers/misc/cclogic/tusb320hai.c b/drivers/misc/cclogic/tusb320hai.c new file mode 100644 index 000000000000..d7f9c4f60bb9 --- /dev/null +++ b/drivers/misc/cclogic/tusb320hai.c @@ -0,0 +1,445 @@ +/* + * tusb320hai.c + * + * Drivers for usb type-C interface's CC-Logic chip of TI + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <asm/uaccess.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/miscdevice.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +#include <linux/wakelock.h> +#include "cclogic-core.h" + + +#define DRIVER_NAME "ti,tusb320hai" + +#define MAX_REG_NUM 11 + +#define TUSB320_DEVID "023BSUT" + +/* tusb320hai_read_i2c() */ +static int tusb320hai_read_i2c(struct i2c_client *client, u8 reg) +{ + struct i2c_msg msg[2]; + int data = 0; + int ret; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = ® + msg[0].len = sizeof(reg); + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = (char *)&data; + msg[1].len = 1; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret != 2) { + dev_err(&client->dev, "cclogic:%s-->i2c_transfer error\n", + __func__); + return ret; + } + + return data; +} + +/* tusb320hai_recv_i2c() */ +static int tusb320hai_recv_i2c(struct i2c_client *client, char baseaddr, + char *buf, int len) +{ + struct i2c_msg msg[2]; + int ret; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = &baseaddr; + msg[0].len = 1; + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = buf; + msg[1].len = len; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret != 2) { + dev_err(&client->dev, "cclogic:%s-->i2c_transfer error\n", + __func__); + return ret; + } + + return 0; +} +/* tusb320hai_write_i2c() */ +static int tusb320hai_write_i2c(struct i2c_client *client, u8 reg, u8 data) +{ + int ret = 0; + struct i2c_msg msg; + u8 buf[2]; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + if (!client->adapter) + return -ENODEV; + + buf[0] = reg; + buf[1] = data; + msg.addr = client->addr; + msg.flags = 0; + msg.buf = buf; + msg.len = sizeof(buf); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) { + dev_err(&client->dev, "cclogic:%s-->i2c_transfer error\n", + __func__); + return ret; + } + + return 0; + +} + +/* tusb320hai_parse_cclogic_state() */ +static void tusb320hai_parse_cclogic_state(int reg8, int reg9, + struct cclogic_state *result) +{ + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + result->vbus = false; + result->cc = CCLOGIC_CC_UNKNOWN; + result->evt = CCLOGIC_EVENT_NONE; + result->device = CCLOGIC_NO_DEVICE; + result->charger = CCLOGIC_CURRENT_NONE; + + if (!(reg9&0x10)) {/* No interrupt */ + result->evt = CCLOGIC_EVENT_NONE; + } else{ + result->evt = CCLOGIC_EVENT_ATTACHED; + } + + if (reg9&0x20) + result->cc = CCLOGIC_CC2; + else + result->cc = CCLOGIC_CC1; + + switch (reg9&0xc0) { + case 0x00:/* nothing attached */ + if (reg9&0x10) + result->evt = CCLOGIC_EVENT_DETACHED; + result->vbus = false; + result->device = CCLOGIC_NO_DEVICE; + break; + case 0x40: + result->device = CCLOGIC_USB_DEVICE; + result->vbus = false; + break; + case 0x80: + result->device = CCLOGIC_USB_HOST; + result->vbus = true; + break; + case 0xC0:/* accessory */ + switch (reg8&0x0e) { + case 0x00: /* No Accessory attached */ + if (reg9&0x10) + result->evt = CCLOGIC_EVENT_DETACHED; + result->vbus = false; + result->device = CCLOGIC_NO_DEVICE; + break; + case 0x08: /* Audio Accessory DFP */ + result->vbus = false; + result->device = CCLOGIC_AUDIO_DEVICE; + break; + case 0x0a: /* Audio Accessory UFP */ + result->vbus = true; + result->device = CCLOGIC_AUDIO_DEVICE; + break; + case 0x0e:/* Debug Accessory UFP */ + result->vbus = true; + result->device = CCLOGIC_DEBUG_DEVICE; + break; + case 0x0c:/* Debug Accessory DFP set to Hi-Z */ + result->vbus = false; + result->device = CCLOGIC_DEBUG_DEVICE; + break; + default:/* Reserve */ + result->vbus = false; + result->device = CCLOGIC_NO_DEVICE; + break; + } + + break; + } + + + switch (reg8&0x30) { + case 0x00: + result->charger = CCLOGIC_CURRENT_DEFAULT; + break; + case 0x10: + result->charger = CCLOGIC_CURRENT_MEDIUM; + break; + case 0x20: + result->charger = CCLOGIC_CURRENT_ACCESSORY; + /* result->vbus = false; */ + break; + case 0x30: + result->charger = CCLOGIC_CURRENT_HIGH; + break; + } + +} + +/* tusb320hai_reset_chip() */ +static int tusb320hai_reset_chip(struct i2c_client *client) +{ + int retries = 10; + int regval; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + regval = tusb320hai_write_i2c(client, 0xa, 0x8); + if (regval) { + dev_err(&client->dev, "cclogic:%s: i2c write error\n", __func__); + return regval; + } + + while (retries--) { + regval = tusb320hai_read_i2c(client, 0xa); + if (regval < 0) { + dev_err(&client->dev, "cclogic:%s: i2c read error\n", + __func__); + return regval; + } + if (!(regval&0x08)) + return 0; + msleep(100); + } + if (regval&0x08) + return -1; + return 0; +} + +/* tusb320hai_trymode() */ +static int tusb320hai_trymode(struct i2c_client *client, int mode) +{ + int ret; + int regval; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + ret = tusb320hai_write_i2c(client, 0xa, 0x33); + if (ret) { + dev_err(&client->dev, "cclogic:%s: i2c write error\n", __func__); + return ret; + } + + if (mode == CCLOGIC_MODE_UFP) { + regval = 0x13; + pr_debug("cclogic:trymode sink\n"); + } else{ + regval = 0x23; + pr_debug("cclogic:trymode source\n"); + } + ret = tusb320hai_write_i2c(client, 0xa, regval); + if (ret) { + dev_err(&client->dev, "cclogic:%s: i2c write error\n", __func__); + return ret; + } + + msleep(6); + + ret = tusb320hai_write_i2c(client, 0xa, regval & 0xfe); + if (ret) { + dev_err(&client->dev, "cclogic:%s: i2c write error\n", __func__); + return ret; + } + + return ret; + +} + +/* tusb320hai_config_chip() */ +static int tusb320hai_config_chip(struct i2c_client *client) +{ + int ret; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + ret = tusb320hai_write_i2c(client, 0x8, 0); + if (ret) { + dev_err(&client->dev, "cclogic:%s: i2c write error\n", __func__); + return ret; + } + ret = tusb320hai_write_i2c(client, 0xa, 0x32); + if (ret) { + dev_err(&client->dev, "cclogic:%s: i2c write error\n", __func__); + return ret; + } + + return ret; + +} + +/* tusb320hai_check_chip() */ +static int tusb320hai_check_chip(struct i2c_client *client) +{ + char buf[8]; + int ret; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + /* ID check */ + ret = tusb320hai_recv_i2c(client, 0, buf, 8); + if (ret) { + dev_err(&client->dev, "cclogic:%s: i2c recv error\n", __func__); + return -ENODEV; + } + + if (memcmp(buf, TUSB320_DEVID, sizeof(TUSB320_DEVID))) { + dev_err(&client->dev, "cclogic:%s: devid mismatch (%s != %s)\n", + __func__, buf, TUSB320_DEVID); + return -ENODEV; + } + + return 0; +} + +/* tusb320hai_ack_irq() */ +static int tusb320hai_ack_irq(struct i2c_client *client) +{ + int reg9, ret; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + reg9 = tusb320hai_read_i2c(client, 0x9); + if (reg9 < 0) { + dev_err(&client->dev, "cclogic:%s-->i2c read error\n", __func__); + return reg9; + } + + /* clear interrupt */ + ret = tusb320hai_write_i2c(client, 0x9, reg9); + if (ret) { + dev_err(&client->dev, "cclogic:%s-->i2c write error\n", __func__); + return ret; + } + + return 0; +} + +/* tusb320hai_get_state() */ +static int tusb320hai_get_state(struct i2c_client *client, + struct cclogic_state *state) +{ + int reg8, reg9, rega; + static int flag; + + pr_debug("cclogic:[%s][%d]\n", __func__, __LINE__); + + reg9 = tusb320hai_read_i2c(client, 0x9); + if (reg9 < 0) { + dev_err(&client->dev, "cclogic:%s-->i2c read error,reg9=%d\n", + __func__, reg9); + return reg9; + } + + reg8 = tusb320hai_read_i2c(client, 0x8); + if (reg8 < 0) { + dev_err(&client->dev, "cclogic:%s-->i2c read error,reg8=%d\n", + __func__, reg8); + return reg8; + } + + pr_debug("cclogic:%s-->i2c register value: reg8=0x%02x reg9=0x%02x\n", + __func__, reg8, reg9); + + tusb320hai_parse_cclogic_state(reg8, reg9, state); + +#if 1 /* for chip bug */ + if (state->evt == CCLOGIC_EVENT_DETACHED) {/* skip detach event */ + return 0; + } + + rega = tusb320hai_read_i2c(client, 0xa); + if (rega < 0) { + dev_err(&client->dev, "cclogic:%s: i2c read error\n", + __func__); + return rega; + } + + if ((rega & 0x30) != 0x30) + return 0; + + if (!flag && (state->device == CCLOGIC_USB_DEVICE)) { + tusb320hai_reset_chip(client); + tusb320hai_config_chip(client); + flag = 1; + return -1; + } + + flag = 0; +#endif + + return 0; +} + + +static struct cclogic_chip tusb320hai_chip = { + .chip_name = DRIVER_NAME, + .get_state = tusb320hai_get_state, + .ack_irq = tusb320hai_ack_irq, + .chip_config = tusb320hai_config_chip, + .chip_reset = tusb320hai_reset_chip, + .chip_check = tusb320hai_check_chip, + .chip_trymode = tusb320hai_trymode, + .typec_version = 11,/* spec 1.1 */ + .support = CCLOGIC_SUPPORT_MODE_DUAL, + .read = tusb320hai_read_i2c, + .write = tusb320hai_write_i2c, +}; + +/* tusb320hai_init() */ +static int __init tusb320hai_init(void) +{ + pr_info("cclogic:[%s][%d]\n", __func__, __LINE__); + + return cclogic_register(&tusb320hai_chip); +} + +/* tusb320hai_exit() */ +static void __exit tusb320hai_exit(void) +{ + pr_info("cclogic:[%s][%d]\n", __func__, __LINE__); + + cclogic_unregister(&tusb320hai_chip); + return; +} + +late_initcall(tusb320hai_init); +module_exit(tusb320hai_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Yang ShaoYing <yangsy2@lenovo.com>"); +MODULE_DESCRIPTION("Drivers for usb type-C CC-Logic chip of TI Tusb320"); +MODULE_ALIAS("platform:cc-logic"); |