diff options
Diffstat (limited to 'drivers/misc/usb3803.c')
-rw-r--r-- | drivers/misc/usb3803.c | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/drivers/misc/usb3803.c b/drivers/misc/usb3803.c new file mode 100644 index 0000000..5a05b38 --- /dev/null +++ b/drivers/misc/usb3803.c @@ -0,0 +1,390 @@ +/* + * drivers/misc/usb3803.c - usb3803 usb hub driver + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/i2c.h> +#include <linux/usb3803.h> +#include <linux/delay.h> + +static struct i2c_client *this_client; +static struct usb3803_platform_data *pdata; +static int current_mode; + +/* DEFINE RESISTERs */ +#define CFG1_REG 0x06 +#define SP_ILOCK_REG 0xE7 +#define CFGP_REG 0xEE +#define PDS_REG 0x0A + +/* + * 0x06; only AP (disable CP and WiMax) + * 0x0A; only CP (disable AP and Wimax) + * 0x00; enable all port + * + * bit : 3 2 1 0 + * AP, CP, WIMAX, RESERVED + */ +static int hub_port = 0x0; + +static ssize_t mode_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size); +static ssize_t port_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t port_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size); +static int usb3803_register_write(char reg, char data); +static int usb3803_register_read(char reg, char *data); + + +static DEVICE_ATTR(mode, 0664, mode_show, mode_store); + +static ssize_t mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (current_mode == USB_3803_MODE_HUB) + return sprintf(buf, "%s", "hub"); + else if (current_mode == USB_3803_MODE_BYPASS) + return sprintf(buf, "%s", "bypass"); + else if (current_mode == USB_3803_MODE_STANDBY) + return sprintf(buf, "%s", "standby"); + + return 0; +} + +static ssize_t mode_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + if (!strncmp(buf, "hub", 3)) { + usb3803_set_mode(USB_3803_MODE_HUB); + pr_debug("usb3803 mode set to hub\n"); + } else if (!strncmp(buf, "bypass", 6)) { + usb3803_set_mode(USB_3803_MODE_BYPASS); + pr_debug("usb3803 mode set to bypass\n"); + } else if (!strncmp(buf, "standby", 7)) { + usb3803_set_mode(USB_3803_MODE_STANDBY); + pr_debug("usb3803 mode set to standby\n"); + } + return size; +} + +static DEVICE_ATTR(port, 0664, port_show, port_store); + +static ssize_t port_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "hub_port=0x%x", hub_port); +} + +static ssize_t port_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + if (!strncmp(buf, "disable_cp", 10)) { + hub_port = (0x1 << 2) | hub_port; + pr_debug("usb3803 port disable cp\n"); + } else if (!strncmp(buf, "disable_wimax", 13)) { + hub_port = (0x1 << 1) | hub_port; + pr_debug("usb3803 port disable wimaxb\n"); + } else if (!strncmp(buf, "disable_ap", 10)) { + hub_port = (0x1 << 3) | hub_port; + pr_debug("usb3803 port disable ap\n"); + } else if (!strncmp(buf, "enable_cp", 9)) { + hub_port = ~(0x1 << 2) & hub_port; + pr_debug("usb3803 port enable cp\n"); + } else if (!strncmp(buf, "enable_wimax", 12)) { + hub_port = ~(0x1 << 1) & hub_port; + pr_debug("usb3803 port enable wimaxb\n"); + } else if (!strncmp(buf, "enable_ap", 9)) { + hub_port = ~(0x1 << 3) & hub_port; + pr_debug("usb3803 port enable ap\n"); + } + + if (current_mode == USB_3803_MODE_HUB) + usb3803_set_mode(USB_3803_MODE_HUB); + pr_debug("usb3803 mode setting (%s)\n", buf); + + return size; +} + +static int usb3803_register_write(char reg, char data) +{ + int ret; + char buf[2]; + struct i2c_msg msg[] = { + { + .addr = this_client->addr, + .flags = 0, + .len = 2, + .buf = buf, + }, + }; + + buf[0] = reg; + buf[1] = data; + + ret = i2c_transfer(this_client->adapter, msg, 1); + + if (ret < 0) + pr_err("[%s] reg:0x%x data:0x%x write failed\n", + __func__, reg, data); + + return ret; +} + +static int usb3803_register_read(char reg, char *data) +{ + int ret; + + struct i2c_msg msgs[] = { + { + .addr = this_client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = this_client->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = data, + }, + }; + + ret = i2c_transfer(this_client->adapter, msgs, 2); + + if (ret < 0) + pr_err("[%s] 0x%x read failed\n", __func__, reg); + + return ret; +} + +int usb3803_set_mode(int mode) +{ + int ret = 0; + + switch (mode) { + case USB_3803_MODE_HUB: + pdata->reset_n(0); + msleep(20); + + /* enable clock */ + pdata->clock_en(1); + /* reset assert */ + pdata->reset_n(1); + /* bypass mode disable */ + pdata->bypass_n(1); + + /* there is a major glitch in the ES version of USB3803 */ + /* below is the work-around. CS version doesn't + need this code! */ + if (pdata->es_ver) { + int i; + char data; + usleep_range(5000, 5000); + + /* Step 1. Write value 0x03 to register + 0xE7 after RESET_N transitioning to high. */ + for (i = 0; i < 3; i++) { + pr_info("[%s] write SP_ILOCK_REG (attempt %d)\n", + __func__, i); + usb3803_register_write(SP_ILOCK_REG, 0x03); + usb3803_register_read(SP_ILOCK_REG, &data); + pr_info("[%s] read SP_ILOCK_REG : 0x%x\n", + __func__, data); + + if (data == 0x3) + break; + } + if (i >= 3) { + pr_crit("[%s] SP_ILOCK_REG set failed! aborted!\n", + __func__); + ret = -1; + break; + } + + /* Step 2. Set bit 7 (ClkSusp) of register 0xEE + to high. */ + usb3803_register_read(CFGP_REG, &data); + pr_info("[%s] read CFGP_REG : 0x%x (default)\n", + __func__, data); + data |= 0x80; + usb3803_register_write(CFGP_REG, data); + usb3803_register_read(CFGP_REG, &data); + pr_info("[%s] read CFGP_REG : 0x%x\n", __func__, data); + + /* Step 2.5 + * Port Disable For Self Powered Operation + */ + usb3803_register_read(PDS_REG, &data); + pr_info("[%s] read PDS_REG : 0x%x (default)\n" + , __func__, data); + usb3803_register_write(PDS_REG, hub_port); + usb3803_register_read(PDS_REG, &data); + pr_info("[%s] hub_port : 0x%x\n" + , __func__, hub_port); + pr_info("[%s] read PDS_REG : 0x%x\n" + , __func__, data); + + /* Step 3. Set bit 7 (SELF_BUS_PWR) of + register 0x06 to high.*/ + usb3803_register_read(CFG1_REG, &data); + pr_info("[%s] read CFG1_REG : 0x%x (default)\n", + __func__, data); + data |= 0x80; + usb3803_register_write(CFG1_REG, data); + usb3803_register_read(CFG1_REG, &data); + pr_info("[%s] read CFG1_REG : 0x%x\n", __func__, data); + + /* Step 4. Clear bit 0 (config_n) & bit 1 + (connect_n) of register 0xE7. */ + usb3803_register_write(SP_ILOCK_REG, 0x00); + usb3803_register_read(SP_ILOCK_REG, &data); + pr_info("[%s] read SP_ILOCK_REG : 0x%x\n", + __func__, data); + current_mode = mode; + } + break; + + case USB_3803_MODE_BYPASS: + /* disable clock */ + pdata->clock_en(0); + /* reset assert */ + pdata->reset_n(1); + /* bypass mode enable */ + pdata->bypass_n(0); + current_mode = mode; + break; + + case USB_3803_MODE_STANDBY: + pdata->reset_n(0); + /* disable clock */ + pdata->clock_en(0); + pdata->bypass_n(0); + current_mode = mode; + break; + + default: + pr_err("[%s] Invalid mode %d\n", __func__, mode); + break; + } + + return ret; +} +EXPORT_SYMBOL(usb3803_set_mode); + +int usb3803_suspend(struct i2c_client *client, pm_message_t mesg) +{ + pdata->reset_n(0); + pdata->clock_en(0); + + pr_info("USB3803 suspended\n"); + + return 0; +} + +int usb3803_resume(struct i2c_client *client) +{ + if (current_mode == USB_3803_MODE_HUB) + pr_info("USB3803 skip hub mode setting\n"); + else + usb3803_set_mode(current_mode); + if (current_mode == USB_3803_MODE_HUB) + pr_info("USB3803 resumed to mode %s\n", "hub"); + else if (current_mode == USB_3803_MODE_BYPASS) + pr_info("USB3803 resumed to mode %s\n", "bypass"); + else if (current_mode == USB_3803_MODE_STANDBY) + pr_info("USB3803 resumed to mode %s\n", "standby"); + return 0; +} + + +int usb3803_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int err = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + err = -ENODEV; + goto exit_check_functionality_failed; + } + + pdata = client->dev.platform_data; + if (pdata == NULL) { + pr_err("USB3803 device's platform data is NULL!\n"); + err = -ENODEV; + goto exit_pdata_null; + } + + pdata->hw_config(); + + this_client = client; + + if (pdata->init_needed) + usb3803_set_mode(pdata->inital_mode); + + current_mode = pdata->inital_mode; + + err = device_create_file(&client->dev, &dev_attr_mode); + err = device_create_file(&client->dev, &dev_attr_port); + + if (current_mode == USB_3803_MODE_HUB) + pr_info("USB3803 probed on mode %s\n", "hub"); + else if (current_mode == USB_3803_MODE_BYPASS) + pr_info("USB3803 probed on mode %s\n", "bypass"); + else if (current_mode == USB_3803_MODE_STANDBY) + pr_info("USB3803 probed on mode %s\n", "standby"); + + exit_pdata_null: + exit_check_functionality_failed: + return err; + +} + +static int usb3803_remove(struct i2c_client *client) +{ + return 0; +} +static const struct i2c_device_id usb3803_id[] = { + { USB3803_I2C_NAME, 0 }, + { } +}; + +static struct i2c_driver usb3803_driver = { + .probe = usb3803_probe, + .remove = usb3803_remove, + .suspend = usb3803_suspend, + .resume = usb3803_resume, + .id_table = usb3803_id, + .driver = { + .name = USB3803_I2C_NAME, + }, +}; + +static int __init usb3803_init(void) +{ + pr_info("USB3803 USB HUB driver init\n"); + return i2c_add_driver(&usb3803_driver); +} + +static void __exit usb3803_exit(void) +{ + pr_info("USB3803 USB HUB driver exit\n"); + i2c_del_driver(&usb3803_driver); +} + +module_init(usb3803_init); +module_exit(usb3803_exit); + +MODULE_DESCRIPTION("USB3803 USB HUB driver"); +MODULE_LICENSE("GPL"); |