diff options
Diffstat (limited to 'drivers/misc/cclogic/tusb320hai.c')
-rw-r--r-- | drivers/misc/cclogic/tusb320hai.c | 445 |
1 files changed, 445 insertions, 0 deletions
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"); |