diff options
Diffstat (limited to 'drivers/misc/usb3503.c')
-rw-r--r-- | drivers/misc/usb3503.c | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/drivers/misc/usb3503.c b/drivers/misc/usb3503.c new file mode 100644 index 0000000..c7b88d7 --- /dev/null +++ b/drivers/misc/usb3503.c @@ -0,0 +1,519 @@ +/* + * drivers/misc/usb3503.c - usb3503 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/platform_device.h> +#include <linux/platform_data/usb3503.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> + +static int usb3503_register_write(struct i2c_client *i2c_dev, char reg, + char data) +{ + int ret; + char buf[2]; + struct i2c_msg msg[] = { + { + .addr = i2c_dev->addr, + .flags = 0, + .len = 2, + .buf = buf, + }, + }; + + buf[0] = reg; + buf[1] = data; + + ret = i2c_transfer(i2c_dev->adapter, msg, 1); + if (ret < 0) + pr_err(HUB_TAG "%s: reg: %x data: %x write failed\n", + __func__, reg, data); + + return ret; +} + +static int usb3503_register_read(struct i2c_client *i2c_dev, char reg, + char *data) +{ + int ret; + struct i2c_msg msgs[] = { + { + .addr = i2c_dev->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = i2c_dev->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = data, + }, + }; + + ret = i2c_transfer(i2c_dev->adapter, msgs, 2); + if (ret < 0) + pr_err(HUB_TAG "%s: reg: %x read failed\n", __func__, reg); + + return ret; +} + +void s5pv210_hsic_port1_power(int enable) +{ + /*TODO:*/ +} + +static int reg_write(struct i2c_client *i2c_dev, char reg, char req, int retry) +{ + int cnt = retry, err; + char data = 0; + + pr_debug(HUB_TAG "%s: write %02X, data: %02x\n", __func__, reg, req); + do { + err = usb3503_register_write(i2c_dev, reg, req); + if (err < 0) { + pr_err(HUB_TAG "%s: usb3503_register_write failed" + " - retry(%d)", __func__, cnt); + continue; + } + + err = usb3503_register_read(i2c_dev, reg, &data); + if (err < 0) + pr_err(HUB_TAG "%s: usb3503_register_read failed" + " - retry(%d)", __func__, cnt); + } while (data != req && cnt--); +exit: + pr_info(HUB_TAG "%s: write %02X, req:%02x, val:%02x\n", __func__, reg, + req, data); + + return err; +} + +static int reg_update(struct i2c_client *i2c_dev, char reg, char req, int retry) +{ + int cnt = retry, err; + char data; + + pr_debug(HUB_TAG "%s: update %02X, data: %02x\n", __func__, reg, req); + do { + err = usb3503_register_read(i2c_dev, reg, &data); + if (err < 0) { + pr_err(HUB_TAG "%s: usb3503_register_read failed" + " - retry(%d)", __func__, cnt); + continue; + } + + pr_debug(HUB_TAG "%s: read %02X, data: %02x\n", __func__, reg, + data); + if ((data & req) == req) { + pr_debug(HUB_TAG "%s: aleady set data: %02x\n", + __func__, data); + break; + } + err = usb3503_register_write(i2c_dev, reg, data | req); + if (err < 0) + pr_err(HUB_TAG "%s: usb3503_register_write failed" + " - retry(%d)", __func__, cnt); + } while (cnt--); +exit: + pr_info(HUB_TAG "%s: update %02X, req:%02x, val:%02x\n", __func__, reg, + req, data); + return err; +} + +static int reg_clear(struct i2c_client *i2c_dev, char reg, char req, int retry) +{ + int cnt = retry, err; + char data; + + pr_debug(HUB_TAG "%s: clear %X, data %x\n", __func__, reg, req); + do { + err = usb3503_register_read(i2c_dev, reg, &data); + if (err < 0) + goto exit; + pr_debug(HUB_TAG "%s: read %02X, data %02x\n", __func__, reg, + data); + if (!(data & req)) { + pr_err(HUB_TAG "%s: aleady cleared data = %02x\n", + __func__, data); + break; + } + err = usb3503_register_write(i2c_dev, reg, data & ~req); + if (err < 0) + goto exit; + } while (cnt--); +exit: + pr_info(HUB_TAG "%s: clear %02X, req:%02x, val:%02x\n", __func__, reg, + req, data); + return err; +} + +static int usb3503_set_mode(struct usb3503_hubctl *hc, int mode) +{ + int err = 0; + struct i2c_client *i2c_dev = hc->i2c_dev; + + pr_info(HUB_TAG "%s: mode = %d\n", __func__, mode); + + switch (mode) { + case USB3503_MODE_HUB: + hc->reset_n(1); + + /* SP_ILOCK: set connect_n, config_n for config */ + err = reg_write(i2c_dev, SP_ILOCK_REG, + (SPILOCK_CONNECT_N | SPILOCK_CONFIG_N), 3); + if (err < 0) { + pr_err(HUB_TAG "SP_ILOCK write fail err = %d\n", err); + goto exit; + } +#ifdef USB3503_ES_VER +/* ES version issue + * USB3503 can't PLL power up under cold circumstance, so enable + * the Force suspend clock bit + */ + err = reg_update(i2c_dev, CFGP_REG, CFGP_CLKSUSP, 1); + if (err < 0) { + pr_err(HUB_TAG "CFGP update fail err = %d\n", err); + goto exit; + } +#endif + /* PDS : Port2,3 Disable For Self Powered Operation */ + err = reg_update(i2c_dev, PDS_REG, (PDS_PORT2 | PDS_PORT3), 1); + if (err < 0) { + pr_err(HUB_TAG "PDS update fail err = %d\n", err); + goto exit; + } + /* CFG1 : SELF_BUS_PWR -> Self-Powerd operation */ + err = reg_update(i2c_dev, CFG1_REG, CFG1_SELF_BUS_PWR, 1); + if (err < 0) { + pr_err(HUB_TAG "CFG1 update fail err = %d\n", err); + goto exit; + } + /* SP_LOCK: clear connect_n, config_n for hub connect */ + err = reg_clear(i2c_dev, SP_ILOCK_REG, + (SPILOCK_CONNECT_N | SPILOCK_CONFIG_N), 1); + if (err < 0) { + pr_err(HUB_TAG "SP_ILOCK clear err = %d\n", err); + goto exit; + } + hc->mode = mode; + + /* Should be enable the HSIC port1 */ + + break; + + case USB3503_MODE_STANDBY: + hc->reset_n(0); + hc->mode = mode; + break; + + default: + pr_err(HUB_TAG "%s: Invalid mode %d\n", __func__, mode); + err = -EINVAL; + goto exit; + break; + } +exit: + return err; +} + +/* sysfs for control */ +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb3503_hubctl *hc = dev_get_drvdata(dev); + + if (hc->mode == USB3503_MODE_HUB) + return sprintf(buf, "%s", "hub"); + else if (hc->mode == USB3503_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) +{ + struct usb3503_hubctl *hc = dev_get_drvdata(dev); + + if (!strncmp(buf, "hub", 3)) { + /*usb3503_set_mode(hc, USB3503_MODE_HUB);*/ + if (hc->port_enable) + hc->port_enable(2, 1); + pr_debug(HUB_TAG "mode set to hub\n"); + } else if (!strncmp(buf, "standby", 7)) { + /*usb3503_set_mode(hc, USB3503_MODE_STANDBY);*/ + if (hc->port_enable) + hc->port_enable(2, 0); + pr_debug(HUB_TAG "mode set to standby\n"); + } + return size; +} +static DEVICE_ATTR(mode, 0664, mode_show, mode_store); + +#ifdef USB3503_SYSFS_DEBUG +static ssize_t read_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned addr; + char data; + int err; + struct usb3503_hubctl *hc = dev_get_drvdata(dev); + + err = sscanf(buf, "%x", &addr); + + err = usb3503_register_read(hc->i2c_dev, addr, &data); + if (err < 0) { + pr_err(HUB_TAG "register read fail\n"); + goto exit; + } + pr_info(HUB_TAG "%s: read 0x%x = 0x%x\n", __func__, addr, data); +exit: + return size; +} +static DEVICE_ATTR(read, 0664, NULL, read_store); + +static ssize_t write_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned addr, data; + int err; + struct usb3503_hubctl *hc = dev_get_drvdata(dev); + + err = sscanf(buf, "%x %x", &addr, &data); + pr_debug(HUB_TAG "%s: addr=%x, data=%x\n", __func__, addr, data); + + err = usb3503_register_write(hc->i2c_dev, addr, data); + if (err < 0) { + pr_err(HUB_TAG "register write fail\n"); + goto exit; + } + + err = usb3503_register_read(hc->i2c_dev, addr, (char *)&data); + if (err < 0) { + pr_err(HUB_TAG "register read fail\n"); + goto exit; + } + pr_info(HUB_TAG "%s: write 0x%x = 0x%x\n", __func__, addr, data); +exit: + return size; +} +static DEVICE_ATTR(write, 0664, NULL, write_store); + +static ssize_t reset_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned val; + int err; + struct usb3503_hubctl *hc = dev_get_drvdata(dev); + + err = sscanf(buf, "%x", &val); + pr_info(HUB_TAG "%s: val=%x\n", __func__, val); + + hc->reset_n(val); + + return size; +} +static DEVICE_ATTR(reset, 0664, NULL, reset_store); +#endif /* end of USB3503_SYSFS_DEBUG */ + +int usb3503_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct usb3503_hubctl *hc = i2c_get_clientdata(client); +#if defined(CONFIG_MACH_C1) + struct regulator *regulator; +#endif + + /* Should be disable the HSIC port1 */ + + hc->reset_n(0); + pr_info(HUB_TAG "suspended\n"); + +#if defined(CONFIG_MACH_C1) + + if (system_rev >= 0x6) { + regulator = regulator_get(NULL, "vusbhub_osc_1.8v"); + if (IS_ERR(regulator)) { + pr_err(HUB_TAG "%s:Get VUSBHUBOSC Fail\n", __func__); + return 0; + } + regulator_disable(regulator); + regulator_put(regulator); + } +#endif + + return 0; +} + +int usb3503_resume(struct i2c_client *client) +{ + struct usb3503_hubctl *hc = i2c_get_clientdata(client); + +#if defined(CONFIG_MACH_M0_CTC) + return 0; +#endif + +#if defined(CONFIG_MACH_C1) + + struct regulator *regulator; + + if (system_rev >= 0x6) { + regulator = regulator_get(NULL, "vusbhub_osc_1.8v"); + if (IS_ERR(regulator)) { + pr_err(HUB_TAG "%s:Get VUSBHUBOSC Fail\n", __func__); + return 0; + } + regulator_enable(regulator); + regulator_put(regulator); + + mdelay(3); + } +#endif + + if (hc->mode == USB3503_MODE_HUB) + usb3503_set_mode(hc, USB3503_MODE_HUB); + + pr_info(HUB_TAG "resume mode=%s", (hc->mode == USB3503_MODE_HUB) ? + "hub" : "standny"); + + return 0; +} + +int usb3503_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int err = 0; + struct usb3503_hubctl *hc; + struct usb3503_platform_data *pdata; + +#if defined(CONFIG_MACH_C1) + + struct regulator *regulator; + + if (system_rev >= 0x6) { + regulator = regulator_get(NULL, "vusbhub_osc_1.8v"); + if (IS_ERR(regulator)) { + pr_err(HUB_TAG "%s:Get VUSBHUBOSC Fail\n", __func__); + return 0; + } + regulator_enable(regulator); + regulator_put(regulator); + } +#endif + + pr_info(HUB_TAG "%s:%d\n", __func__, __LINE__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + err = -ENODEV; + goto exit; + } + + pdata = client->dev.platform_data; + if (pdata == NULL) { + pr_err(HUB_TAG "device's platform data is NULL!\n"); + err = -ENODEV; + goto exit; + } + + hc = kzalloc(sizeof(struct usb3503_hubctl), GFP_KERNEL); + if (!hc) { + pr_err(HUB_TAG "private data alloc fail\n"); + err = -ENOMEM; + goto exit; + } + hc->i2c_dev = client; + hc->reset_n = pdata->reset_n; + hc->port_enable = pdata->port_enable; + if (pdata->initial_mode) { + usb3503_set_mode(hc, pdata->initial_mode); + hc->mode = pdata->initial_mode; + } + /* For HSIC to USB brige with CMC221 + * export the hub_set_mode and private data to board modem + * it will be handled by PM scenaio. + */ + if (pdata->register_hub_handler) + pdata->register_hub_handler((void (*)(void))usb3503_set_mode, + (void *)hc); + + i2c_set_clientdata(client, hc); + + err = device_create_file(&client->dev, &dev_attr_mode); +#ifdef USB3503_SYSFS_DEBUG + err = device_create_file(&client->dev, &dev_attr_read); + err = device_create_file(&client->dev, &dev_attr_write); + err = device_create_file(&client->dev, &dev_attr_reset); +#endif + pr_info(HUB_TAG "%s: probed on %s mode\n", __func__, + (hc->mode == USB3503_MODE_HUB) ? "hub" : "standby"); +exit: + return err; +} + +static int usb3503_remove(struct i2c_client *client) +{ + struct usb3503_hubctl *hc = i2c_get_clientdata(client); + + pr_debug(HUB_TAG "%s\n", __func__); + kfree(hc); + + return 0; +} + +static const struct i2c_device_id usb3503_id[] = { + { USB3503_I2C_NAME, 0 }, + { } +}; + +static void usb3503_shutdown(struct i2c_client *client) +{ + struct usb3503_hubctl *hc = i2c_get_clientdata(client); + + pr_err(HUB_TAG "%s:\n", __func__); + mdelay(10); + usb3503_set_mode(hc, USB3503_MODE_STANDBY); +} + +static struct i2c_driver usb3503_driver = { + .probe = usb3503_probe, + .remove = usb3503_remove, + .suspend = usb3503_suspend, + .resume = usb3503_resume, + .shutdown = usb3503_shutdown, + .id_table = usb3503_id, + .driver = { + .name = USB3503_I2C_NAME, + }, +}; + +static int __init usb3503_init(void) +{ + pr_info(HUB_TAG "USB HUB driver init\n"); + return i2c_add_driver(&usb3503_driver); +} + +static void __exit usb3503_exit(void) +{ + pr_info(HUB_TAG "USB HUB driver exit\n"); + i2c_del_driver(&usb3503_driver); +} +module_init(usb3503_init); +module_exit(usb3503_exit); + +MODULE_DESCRIPTION("USB3503 USB HUB driver"); +MODULE_LICENSE("GPL"); |