blob: 30bcac463fd3708c32150fb00e4d86c108f445c1 [file] [log] [blame]
/*
* extcon-smsc375x.c - SMSC375x extcon driver
*
* Copyright (C) 2013 Intel Corporation
* Ramakrishna Pallala <ramakrishna.pallala@intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/usb/otg.h>
#include <linux/notifier.h>
#include <linux/extcon.h>
#include <linux/pm_runtime.h>
#include <linux/acpi.h>
#include <linux/acpi_gpio.h>
#include <linux/power_supply.h>
#include <linux/wakelock.h>
#include <linux/extcon/extcon-smsc375x.h>
/* SMSC375x I2C registers */
#define SMSC375X_REG_STAT 0x00
#define SMSC375X_REG_CFG 0x01
#define SMSC375X_REG_CHRG_CFG 0x02
#define SMSC375X_REG_CHRG_STAT 0x03
/* Status */
#define STAT_OVLO_STAT (1 << 0)
#define STAT_OVLO_LATCH (1 << 1)
#define STAT_OVP_SWTCH_STAT (1 << 2)
#define STAT_CUR_LMT_STAT (1 << 3)
#define STAT_CHRG_DET_DONE (1 << 4)
#define STAT_CHRG_TYPE_MASK (7 << 5)
#define STAT_CHRG_TYPE_DCP (1 << 5)
#define STAT_CHRG_TYPE_CDP (2 << 5)
#define STAT_CHRG_TYPE_SDP (3 << 5)
#define STAT_CHRG_TYPE_SE1L (4 << 5)
#define STAT_CHRG_TYPE_SE1H (5 << 5)
/* Config */
#define CFG_EN_OVP_SWITCH (1 << 0)
#define CFG_EN_CUR_LMT (1 << 1)
#define CFG_OVERRIDE_VBUS (1 << 2)
#define CFG_OVERRIDE_CUR_LMT (1 << 3)
#define CFG_EN_MUX1 (1 << 5)
#define CFG_EN_MUX2 (1 << 6)
#define CFG_SOFT_POR (1 << 7)
/* Charger Config */
#define CHRG_CFG_EN_SNG_RX (1 << 0)
#define CHRG_CFG_EN_CON_DET (1 << 1)
#define CHRG_CFG_EN_VDAT_SRC (1 << 2)
#define CHRG_CFG_EN_HOST_CHRG (1 << 3)
#define CHRG_CFG_EN_IDAT_SINK (1 << 4)
#define CHRG_CFG_EN_DP_PDOWN (1 << 5)
#define CHRG_CFG_EN_DM_PDOWN (1 << 6)
#define CHRG_CFG_I2C_CNTL (1 << 7)
/* Charger Config */
#define CHRG_STAT_VDAT_DET (1 << 0)
#define CHRG_STAT_DP_SNG_RX (1 << 1)
#define CHRG_STAT_DM_SNG_RX (1 << 2)
#define CHRG_STAT_RX_HIGH_CUR (1 << 3)
#define SMSC_CHARGE_CUR_DCP 2000
#define SMSC_CHARGE_CUR_CDP 1500
#define SMSC_CHARGE_CUR_SDP_100 100
#define SMSC_CHARGE_CUR_SDP_500 500
#define SMSC375X_EXTCON_USB "USB"
#define SMSC375X_EXTCON_SDP "CHARGER_USB_SDP"
#define SMSC375X_EXTCON_DCP "CHARGER_USB_DCP"
#define SMSC375X_EXTCON_CDP "CHARGER_USB_CDP"
static const char *smsc375x_extcon_cable[] = {
SMSC375X_EXTCON_SDP,
SMSC375X_EXTCON_DCP,
SMSC375X_EXTCON_CDP,
NULL,
};
struct smsc375x_chip {
struct i2c_client *client;
struct smsc375x_pdata *pdata;
struct usb_phy *otg;
struct work_struct otg_work;
struct notifier_block id_nb;
bool id_short;
struct extcon_specific_cable_nb cable_obj;
struct notifier_block vbus_nb;
struct work_struct vbus_work;
struct extcon_dev *edev;
struct wake_lock wakelock;
bool is_sdp;
};
static struct smsc375x_chip *chip_ptr;
extern void *smsc375x_platform_data(void);
static int smsc375x_write_reg(struct i2c_client *client,
int reg, int value)
{
int ret;
ret = i2c_smbus_write_byte_data(client, reg, value);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
static int smsc375x_read_reg(struct i2c_client *client, int reg)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
static int smsc375x_detect_dev(struct smsc375x_chip *chip)
{
struct i2c_client *client = chip->client;
static bool notify_otg, notify_charger;
static char *cable;
static struct power_supply_cable_props cable_props;
int stat, cfg, ret, vbus_mask = 0;
u8 chrg_type;
bool vbus_attach = false;
dev_info(&chip->client->dev, "%s\n", __func__);
/*
* get VBUS status from external IC like
* PMIC or Charger as SMSC375x chip can not
* be accessed with out VBUS.
*/
ret = chip->pdata->is_vbus_online();
if (ret < 0) {
dev_info(&chip->client->dev, "get vbus stat error\n");
return ret;
}
if (ret) {
dev_info(&chip->client->dev, "VBUS present\n");
vbus_attach = true;
} else {
dev_info(&chip->client->dev, "VBUS NOT present\n");
vbus_attach = false;
cable_props.ma = 0;
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
goto notify_otg_em;
}
/* dont proceed with charger detection in host mode */
if (chip->id_short) {
/*
* only after reading the status register
* MUX path is being closed. And by default
* MUX is to connected Host mode path.
*/
ret = smsc375x_read_reg(client, SMSC375X_REG_STAT);
return ret;
}
/* check charger detection completion status */
ret = smsc375x_read_reg(client, SMSC375X_REG_STAT);
if (ret < 0)
goto dev_det_i2c_failed;
else
stat = ret;
if (!(stat & STAT_CHRG_DET_DONE)) {
dev_info(&chip->client->dev, "DET failed");
return -EOPNOTSUPP;
}
ret = smsc375x_read_reg(client, SMSC375X_REG_CFG);
if (ret < 0)
goto dev_det_i2c_failed;
else
cfg = ret;
dev_info(&client->dev, "Stat:%x, Cfg:%x\n", stat, cfg);
chrg_type = stat & STAT_CHRG_TYPE_MASK;
chip->is_sdp = false;
/* Enabling the OVP switch on VBUS to draw maximum current */
ret = smsc375x_write_reg(client, SMSC375X_REG_CFG,
(cfg | (CFG_OVERRIDE_VBUS | CFG_EN_OVP_SWITCH)));
if (ret < 0)
goto dev_det_i2c_failed;
if (chrg_type == STAT_CHRG_TYPE_SDP) {
dev_info(&chip->client->dev,
"SDP cable connecetd\n");
notify_otg = true;
vbus_mask = 1;
notify_charger = true;
chip->is_sdp = true;
cable = SMSC375X_EXTCON_SDP;
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
if (chip->pdata->charging_compliance_override)
cable_props.ma = SMSC_CHARGE_CUR_SDP_500;
else
cable_props.ma = SMSC_CHARGE_CUR_SDP_100;
} else if (chrg_type == STAT_CHRG_TYPE_CDP) {
dev_info(&chip->client->dev,
"CDP cable connecetd\n");
notify_otg = true;
vbus_mask = 1;
notify_charger = true;
cable = SMSC375X_EXTCON_CDP;
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_CDP;
cable_props.ma = SMSC_CHARGE_CUR_CDP;
} else if ((chrg_type == STAT_CHRG_TYPE_DCP) ||
(chrg_type == STAT_CHRG_TYPE_SE1L) ||
(chrg_type == STAT_CHRG_TYPE_SE1H)) {
dev_info(&chip->client->dev,
"DCP/SE1 cable connecetd\n");
/* Driving Vdat_src pin as the PET expects the voltage on DP
* to remain >0.5V for the duration of the time VBUS is valid
*/
ret = smsc375x_write_reg(client, SMSC375X_REG_CHRG_CFG,
(CHRG_CFG_I2C_CNTL | CHRG_CFG_EN_VDAT_SRC));
if (ret < 0)
goto dev_det_i2c_failed;
notify_charger = true;
cable = SMSC375X_EXTCON_DCP;
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP;
cable_props.ma = SMSC_CHARGE_CUR_DCP;
if (!wake_lock_active(&chip->wakelock))
wake_lock(&chip->wakelock);
} else {
dev_warn(&chip->client->dev,
"disconnect or unknown or ID event\n");
cable_props.ma = 0;
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
}
notify_otg_em:
if (!vbus_attach) { /* disconnevt event */
if (notify_otg) {
atomic_notifier_call_chain(&chip->otg->notifier,
USB_EVENT_VBUS, &vbus_mask);
notify_otg = false;
}
if (notify_charger) {
/*
* not supporting extcon events currently.
* extcon_set_cable_state(chip->edev, cable, false);
*/
atomic_notifier_call_chain(&power_supply_notifier,
POWER_SUPPLY_CABLE_EVENT, &cable_props);
notify_charger = false;
cable = NULL;
}
if (wake_lock_active(&chip->wakelock))
wake_unlock(&chip->wakelock);
} else {
if (notify_otg) {
/* close mux path to enable device mode */
ret = smsc375x_write_reg(client, SMSC375X_REG_CFG,
(cfg & ~CFG_EN_MUX1) | CFG_EN_MUX2);
if (ret < 0)
goto dev_det_i2c_failed;
atomic_notifier_call_chain(&chip->otg->notifier,
USB_EVENT_VBUS, &vbus_mask);
}
if (notify_charger) {
/*
* not supporting extcon events currently.
* extcon_set_cable_state(chip->edev, cable, true);
*/
atomic_notifier_call_chain(&power_supply_notifier,
POWER_SUPPLY_CABLE_EVENT, &cable_props);
}
}
return 0;
dev_det_i2c_failed:
if (chip->pdata->is_vbus_online())
dev_err(&chip->client->dev,
"vbus present: i2c read failed:%d\n", ret);
else
dev_info(&chip->client->dev,
"vbus removed: i2c read failed:%d\n", ret);
return ret;
}
static irqreturn_t smsc375x_irq_handler(int irq, void *data)
{
struct smsc375x_chip *chip = data;
pm_runtime_get_sync(&chip->client->dev);
dev_info(&chip->client->dev, "SMSC USB INT!\n");
smsc375x_detect_dev(chip);
pm_runtime_put_sync(&chip->client->dev);
return IRQ_HANDLED;
}
static void smsc375x_otg_event_worker(struct work_struct *work)
{
struct smsc375x_chip *chip =
container_of(work, struct smsc375x_chip, otg_work);
int ret;
pm_runtime_get_sync(&chip->client->dev);
if (chip->id_short)
ret = chip->pdata->enable_vbus();
else
ret = chip->pdata->disable_vbus();
if (ret < 0)
dev_warn(&chip->client->dev, "id vbus control failed\n");
/*
* As we are not getting SMSC INT in case
* 5V boost enablement.
* Follwoing WA is added to enable Host mode
* on CR V2.1 by invoking the VBUS worker.
*/
msleep(5000);
schedule_work(&chip->vbus_work);
pm_runtime_put_sync(&chip->client->dev);
}
static int smsc375x_handle_otg_notification(struct notifier_block *nb,
unsigned long event, void *param)
{
struct smsc375x_chip *chip =
container_of(nb, struct smsc375x_chip, id_nb);
struct power_supply_cable_props cable_props;
int *val = (int *)param;
int ret;
if (!val || ((event != USB_EVENT_ID) &&
(event != USB_EVENT_ENUMERATED)))
return NOTIFY_DONE;
dev_info(&chip->client->dev,
"[OTG notification]evt:%lu val:%d\n", event, *val);
switch (event) {
case USB_EVENT_ID:
/*
* in case of ID short(*id = 0)
* enable vbus else disable vbus.
*/
if (*val)
chip->id_short = false;
else
chip->id_short = true;
schedule_work(&chip->otg_work);
break;
case USB_EVENT_ENUMERATED:
/*
* ignore cable plug/unplug events as SMSC
* had already send those event notifications.
* Also only handle notifications for SDP case.
*/
/* No need to change SDP inlimit based on enumeration status
* if platform can voilate charging_compliance.
*/
if (chip->pdata->charging_compliance_override ||
!chip->is_sdp ||
(*val == SMSC_CHARGE_CUR_SDP_100))
break;
/*
* if current limit is < 100mA
* treat it as suspend event.
*/
if (*val < SMSC_CHARGE_CUR_SDP_100)
cable_props.chrg_evt =
POWER_SUPPLY_CHARGER_EVENT_SUSPEND;
else
cable_props.chrg_evt =
POWER_SUPPLY_CHARGER_EVENT_CONNECT;
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
cable_props.ma = *val;
atomic_notifier_call_chain(&power_supply_notifier,
POWER_SUPPLY_CABLE_EVENT, &cable_props);
break;
default:
dev_warn(&chip->client->dev, "invalid OTG event\n");
}
return NOTIFY_OK;
}
static void smsc375x_pwrsrc_event_worker(struct work_struct *work)
{
struct smsc375x_chip *chip =
container_of(work, struct smsc375x_chip, vbus_work);
int ret;
pm_runtime_get_sync(&chip->client->dev);
/*
* Sometimes SMSC INT triggering is only
* happening after reading the status bits.
* So we are reading the status register as WA
* to invoke teh MUX INT in case of connect events.
*/
if (!chip->pdata->is_vbus_online()) {
ret = smsc375x_detect_dev(chip);
} else {
/**
* To guarantee SDP detection in SMSC, need 75mSec delay before
* sending an I2C command. So added 50mSec delay here.
*/
mdelay(50);
ret = smsc375x_read_reg(chip->client, SMSC375X_REG_STAT);
}
if (ret < 0)
dev_warn(&chip->client->dev, "pwrsrc evt error\n");
pm_runtime_put_sync(&chip->client->dev);
}
static int smsc375x_handle_pwrsrc_notification(struct notifier_block *nb,
unsigned long event, void *param)
{
struct smsc375x_chip *chip =
container_of(nb, struct smsc375x_chip, vbus_nb);
dev_info(&chip->client->dev, "[PWRSRC notification]: %lu\n", event);
schedule_work(&chip->vbus_work);
return NOTIFY_OK;
}
static int smsc375x_irq_init(struct smsc375x_chip *chip)
{
struct i2c_client *client = chip->client;
int ret, gpio_num;
struct acpi_gpio_info gpio_info;
/* get kernel GPIO number */
gpio_num = acpi_get_gpio_by_index(&client->dev, 0, &gpio_info);
if (gpio_num < 0) {
gpio_num = acpi_get_gpio("\\_SB.GPO2", 0x1);
if (gpio_num < 0)
dev_err(&client->dev, "failed to get GPIO\n");
}
/* get irq number */
chip->client->irq = gpio_to_irq(gpio_num);
if (client->irq) {
ret = request_threaded_irq(client->irq, NULL,
smsc375x_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"smsc375x", chip);
if (ret) {
dev_err(&client->dev, "failed to reqeust IRQ\n");
return ret;
}
enable_irq_wake(client->irq);
} else {
dev_err(&client->dev, "IRQ not set\n");
return -EINVAL;
}
return 0;
}
static int smsc375x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct smsc375x_chip *chip;
int ret = 0, id_val = -1;
chip = kzalloc(sizeof(struct smsc375x_chip), GFP_KERNEL);
if (!chip) {
dev_err(&client->dev, "failed to allocate driver data\n");
return -ENOMEM;
}
chip->client = client;
#ifdef CONFIG_ACPI
chip->pdata = smsc375x_platform_data();
#else
chip->pdata = dev->platform_data;
#endif
i2c_set_clientdata(client, chip);
wake_lock_init(&chip->wakelock, WAKE_LOCK_SUSPEND,
"smsc375x_wakelock");
/* register with extcon */
chip->edev = kzalloc(sizeof(struct extcon_dev), GFP_KERNEL);
if (!chip->edev) {
dev_err(&client->dev, "mem alloc failed\n");
ret = -ENOMEM;
goto extcon_mem_failed;
}
chip->edev->name = "smsc375x";
chip->edev->supported_cable = smsc375x_extcon_cable;
ret = extcon_dev_register(chip->edev, &client->dev);
if (ret) {
dev_err(&client->dev, "extcon registration failed!!\n");
goto extcon_reg_failed;
}
/* register for EXTCON USB notification */
INIT_WORK(&chip->vbus_work, smsc375x_pwrsrc_event_worker);
chip->vbus_nb.notifier_call = smsc375x_handle_pwrsrc_notification;
ret = extcon_register_interest(&chip->cable_obj, NULL,
SMSC375X_EXTCON_USB, &chip->vbus_nb);
/* OTG notification */
chip->otg = usb_get_phy(USB_PHY_TYPE_USB2);
if (!chip->otg) {
dev_warn(&client->dev, "Failed to get otg transceiver!!\n");
goto otg_reg_failed;
}
INIT_WORK(&chip->otg_work, smsc375x_otg_event_worker);
chip->id_nb.notifier_call = smsc375x_handle_otg_notification;
ret = usb_register_notifier(chip->otg, &chip->id_nb);
if (ret) {
dev_err(&chip->client->dev,
"failed to register otg notifier\n");
goto id_reg_failed;
}
ret = smsc375x_irq_init(chip);
if (ret)
goto intr_reg_failed;
chip_ptr = chip;
if (chip->otg->get_id_status) {
ret = chip->otg->get_id_status(chip->otg, &id_val);
if (ret < 0) {
dev_warn(&client->dev,
"otg get ID status failed:%d\n", ret);
ret = 0;
}
}
if (!id_val && !chip->id_short)
atomic_notifier_call_chain(&chip->otg->notifier,
USB_EVENT_ID, &id_val);
else
smsc375x_detect_dev(chip);
/* Init Runtime PM State */
pm_runtime_put_noidle(&chip->client->dev);
pm_schedule_suspend(&chip->client->dev, MSEC_PER_SEC);
return 0;
intr_reg_failed:
usb_unregister_notifier(chip->otg, &chip->id_nb);
id_reg_failed:
usb_put_phy(chip->otg);
otg_reg_failed:
extcon_dev_unregister(chip->edev);
extcon_reg_failed:
kfree(chip->edev);
extcon_mem_failed:
kfree(chip);
return ret;
}
static int smsc375x_remove(struct i2c_client *client)
{
struct smsc375x_chip *chip = i2c_get_clientdata(client);
free_irq(client->irq, chip);
usb_put_phy(chip->otg);
extcon_dev_unregister(chip->edev);
kfree(chip->edev);
pm_runtime_get_noresume(&chip->client->dev);
kfree(chip);
return 0;
}
static void smsc375x_shutdown(struct i2c_client *client)
{
dev_dbg(&client->dev, "smsc375x shutdown\n");
if (client->irq > 0)
disable_irq(client->irq);
return;
}
static int smsc375x_suspend(struct device *dev)
{
struct smsc375x_chip *chip = dev_get_drvdata(dev);
if (chip->client->irq > 0)
disable_irq(chip->client->irq);
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int smsc375x_resume(struct device *dev)
{
struct smsc375x_chip *chip = dev_get_drvdata(dev);
if (chip->client->irq > 0)
enable_irq(chip->client->irq);
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int smsc375x_runtime_suspend(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int smsc375x_runtime_resume(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int smsc375x_runtime_idle(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static const struct dev_pm_ops smsc375x_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(smsc375x_suspend,
smsc375x_resume)
SET_RUNTIME_PM_OPS(smsc375x_runtime_suspend,
smsc375x_runtime_resume,
smsc375x_runtime_idle)
};
static const struct i2c_device_id smsc375x_id[] = {
{"smsc375x", },
{"SMSC3750", },
{}
};
MODULE_DEVICE_TABLE(i2c, smsc375x_id);
static const struct acpi_device_id acpi_smsc375x_id[] = {
{"SMSC3750", },
{}
};
MODULE_DEVICE_TABLE(acpi, acpi_smsc375x_id);
static struct i2c_driver smsc375x_i2c_driver = {
.driver = {
.name = "smsc375x",
.owner = THIS_MODULE,
.pm = &smsc375x_pm_ops,
.acpi_match_table = ACPI_PTR(acpi_smsc375x_id),
},
.probe = smsc375x_probe,
.remove = smsc375x_remove,
.id_table = smsc375x_id,
.shutdown = smsc375x_shutdown,
};
/*
* Module stuff
*/
static int __init smsc375x_extcon_init(void)
{
int ret = i2c_add_driver(&smsc375x_i2c_driver);
return ret;
}
late_initcall(smsc375x_extcon_init);
static void __exit smsc375x_extcon_exit(void)
{
i2c_del_driver(&smsc375x_i2c_driver);
}
module_exit(smsc375x_extcon_exit);
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
MODULE_DESCRIPTION("SMSC375x extcon driver");
MODULE_LICENSE("GPL");