usb: type_c: Add support for type-c detection using Pericom chip
Type-C defines new USB cables, connectors and plugs to allow connecting USB cables without worrying about orientation. This also allows detecting type-c enabled host/chargers enabling phone to draw up to 3A current for charging. Change-Id: Icdf913dcfdbd5c3d74c8bb3c7584be61e01d0056 Signed-off-by: Manu Gautam <mgautam@codeaurora.org>
This commit is contained in:
parent
bd2078f611
commit
09b109818e
|
@ -0,0 +1,27 @@
|
|||
USB Type-C Detection Chip
|
||||
|
||||
Required properties :
|
||||
- compatible : Must be "pericom,usb-type-c"
|
||||
- reg: 7bit I2C slave address
|
||||
- interrupt-parent: Should be phandle for the interrupt controller
|
||||
that services interrupts for this device.
|
||||
- interrupt: IRQ line
|
||||
|
||||
Optional properties:
|
||||
- pinctrl-names : This should be defined if a target uses pinctrl framework
|
||||
for INT_N pin. See "pinctrl" in
|
||||
Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt.
|
||||
It should specify the names of the configs that pinctrl can install in driver.
|
||||
Following are the pinctrl config that can be installed:
|
||||
"usbc_int_default" : Default configuration of INT_N pin.
|
||||
|
||||
|
||||
Example :
|
||||
pericom-type-c@3d {
|
||||
compatible = "pericom,usb-type-c";
|
||||
reg = <0x3d>;
|
||||
interrupt-parent = <&msm_gpio>;
|
||||
interrupts = <102 0>; /* MSM GPIO 102 */
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&usbc_int_default>;
|
||||
};
|
|
@ -56,6 +56,7 @@ nvidia NVIDIA
|
|||
nxp NXP Semiconductors
|
||||
onnn ON Semiconductor Corp.
|
||||
ovti OmniVision Technologies, Inc.
|
||||
pericom Pericom Semiconductor Corp.
|
||||
picochip Picochip Ltd
|
||||
powervr PowerVR (deprecated, use img)
|
||||
qca Qualcomm Atheros, Inc.
|
||||
|
|
|
@ -601,6 +601,16 @@ config USB_HSIC_SMSC_HUB
|
|||
This adds support for connecting devices like mouse in HSIC
|
||||
Host mode.
|
||||
|
||||
config USB_EXT_TYPE_C
|
||||
tristate "USB Type-C charger detection support using external chip"
|
||||
depends on I2C && POWER_SUPPLY
|
||||
help
|
||||
Enables support for the USB Type-C chargers using external
|
||||
chips connected using I2C.
|
||||
|
||||
This adds support for detecting USB Type-C chargers that
|
||||
can supply upto 3A Vbus current for charging.
|
||||
|
||||
config TI_DRV2667
|
||||
tristate "TI's DRV2667 haptic controller support"
|
||||
depends on I2C
|
||||
|
|
|
@ -67,3 +67,4 @@ obj-$(CONFIG_TI_DRV2667) += ti_drv2667.o
|
|||
obj-$(CONFIG_QPNP_MISC) += qpnp-misc.o
|
||||
obj-$(CONFIG_QCOM_LIQUID_DOCK) += qcom_liquid_dock.o
|
||||
obj-y += qcom/
|
||||
obj-$(CONFIG_USB_EXT_TYPE_C) += type-c-pericom.o
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/power_supply.h>
|
||||
|
||||
#define PERICOM_I2C_NAME "usb-type-c-pericom"
|
||||
|
||||
#define CCD_DEFAULT 0x1
|
||||
#define CCD_MEDIUM 0x2
|
||||
#define CCD_HIGH 0x3
|
||||
|
||||
#define MAX_CURRENT_BC1P2 500
|
||||
#define MAX_CURRENT_MEDIUM 1500
|
||||
#define MAX_CURRENT_HIGH 3000
|
||||
|
||||
struct piusb_regs {
|
||||
u8 dev_id;
|
||||
u8 control;
|
||||
u8 intr_status;
|
||||
#define INTS_ATTACH 0x1
|
||||
#define INTS_DETACH 0x2
|
||||
#define INTS_ATTACH_MASK 0x3 /* current attach state interrupt */
|
||||
|
||||
u8 port_status;
|
||||
#define STS_PORT_MASK (0x1c) /* attached port status - device/host */
|
||||
#define STS_CCD_MASK (0x60) /* charging current status */
|
||||
#define STS_VBUS_MASK (0x80) /* vbus status */
|
||||
|
||||
} __packed;
|
||||
|
||||
struct pi_usb_type_c {
|
||||
struct i2c_client *client;
|
||||
struct piusb_regs reg_data;
|
||||
struct power_supply *usb_psy;
|
||||
int max_current;
|
||||
bool attach_state;
|
||||
};
|
||||
static struct pi_usb_type_c *pi_usb;
|
||||
|
||||
static int piusb_read_regdata(struct i2c_client *i2c)
|
||||
{
|
||||
int rc;
|
||||
int data_length = sizeof(pi_usb->reg_data);
|
||||
uint16_t saddr = i2c->addr;
|
||||
u8 attach_state;
|
||||
struct i2c_msg msgs[] = {
|
||||
{
|
||||
.addr = saddr,
|
||||
.flags = I2C_M_RD,
|
||||
.len = data_length,
|
||||
.buf = (u8 *)&pi_usb->reg_data,
|
||||
}
|
||||
};
|
||||
|
||||
rc = i2c_transfer(i2c->adapter, msgs, 1);
|
||||
if (rc < 0) {
|
||||
/* i2c read may fail if type-c plug removed, treat as detach */
|
||||
dev_dbg(&i2c->dev, "i2c read from 0x%x failed %d\n", saddr, rc);
|
||||
pi_usb->attach_state = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_dbg(&i2c->dev, "i2c read from 0x%x-[%x %x %x %x]\n", saddr,
|
||||
pi_usb->reg_data.dev_id, pi_usb->reg_data.control,
|
||||
pi_usb->reg_data.intr_status, pi_usb->reg_data.port_status);
|
||||
|
||||
if (!pi_usb->reg_data.intr_status) {
|
||||
dev_err(&i2c->dev, "intr_status is 0!, ignore interrupt\n");
|
||||
pi_usb->attach_state = false;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
attach_state = pi_usb->reg_data.intr_status & INTS_ATTACH_MASK;
|
||||
pi_usb->attach_state = (attach_state == INTS_ATTACH) ? true : false;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int piusb_update_power_supply(struct power_supply *psy, int limit)
|
||||
{
|
||||
const union power_supply_propval ret = {limit,};
|
||||
|
||||
/* Update USB of max charging current (500 corresponds to bc1.2 */
|
||||
if (psy->set_property)
|
||||
return psy->set_property(psy,
|
||||
POWER_SUPPLY_PROP_INPUT_CURRENT_MAX, &ret);
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static void piusb_update_max_current(struct pi_usb_type_c *pi_usb)
|
||||
{
|
||||
u8 mask = STS_CCD_MASK;
|
||||
u8 shift = find_first_bit((void *)&mask, 8);
|
||||
u8 chg_mode = pi_usb->reg_data.port_status & mask;
|
||||
|
||||
chg_mode >>= shift;
|
||||
|
||||
/* update to 0 if type-c detached */
|
||||
if (!pi_usb->attach_state) {
|
||||
pi_usb->max_current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (chg_mode) {
|
||||
case CCD_DEFAULT:
|
||||
pi_usb->max_current = MAX_CURRENT_BC1P2;
|
||||
break;
|
||||
case CCD_MEDIUM:
|
||||
pi_usb->max_current = MAX_CURRENT_MEDIUM;
|
||||
break;
|
||||
case CCD_HIGH:
|
||||
pi_usb->max_current = MAX_CURRENT_HIGH;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(&pi_usb->client->dev, "wrong chg mode %x\n", chg_mode);
|
||||
pi_usb->max_current = MAX_CURRENT_BC1P2;
|
||||
}
|
||||
|
||||
dev_dbg(&pi_usb->client->dev, "chg mode: %x, mA:%u\n", chg_mode,
|
||||
pi_usb->max_current);
|
||||
}
|
||||
|
||||
static irqreturn_t piusb_irq(int irq, void *data)
|
||||
{
|
||||
int ret;
|
||||
struct pi_usb_type_c *pi_usb = (struct pi_usb_type_c *)data;
|
||||
|
||||
/* i2c register update takes time, 30msec sleep required as per HPG */
|
||||
msleep(30);
|
||||
|
||||
ret = piusb_read_regdata(pi_usb->client);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
piusb_update_max_current(pi_usb);
|
||||
|
||||
ret = piusb_update_power_supply(pi_usb->usb_psy, pi_usb->max_current);
|
||||
if (ret < 0)
|
||||
dev_err(&pi_usb->client->dev, "failed to notify USB-%d\n", ret);
|
||||
|
||||
out:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int piusb_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct power_supply *usb_psy;
|
||||
|
||||
usb_psy = power_supply_get_by_name("usb");
|
||||
if (!usb_psy) {
|
||||
dev_dbg(&i2c->dev, "USB power_supply not found, defer probe\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
pi_usb = devm_kzalloc(&i2c->dev, sizeof(struct pi_usb_type_c),
|
||||
GFP_KERNEL);
|
||||
if (!pi_usb)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(i2c, pi_usb);
|
||||
pi_usb->client = i2c;
|
||||
pi_usb->usb_psy = usb_psy;
|
||||
|
||||
if (i2c->irq < 0) {
|
||||
dev_err(&i2c->dev, "irq not defined (%d)\n", i2c->irq);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Update initial state to USB */
|
||||
piusb_irq(i2c->irq, pi_usb);
|
||||
|
||||
ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, piusb_irq,
|
||||
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
|
||||
PERICOM_I2C_NAME, pi_usb);
|
||||
if (ret) {
|
||||
dev_err(&i2c->dev, "irq(%d) req failed-%d\n", i2c->irq, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_dbg(&i2c->dev, "%s finished, addr:%d\n", __func__, i2c->addr);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int piusb_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct pi_usb_type_c *pi_usb = i2c_get_clientdata(i2c);
|
||||
|
||||
devm_kfree(&i2c->dev, pi_usb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id piusb_id[] = {
|
||||
{ PERICOM_I2C_NAME, 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, piusb_id);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id piusb_of_match[] = {
|
||||
{ .compatible = "pericom,usb-type-c", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, piusb_of_match);
|
||||
#endif
|
||||
|
||||
static struct i2c_driver piusb_driver = {
|
||||
.driver = {
|
||||
.name = PERICOM_I2C_NAME,
|
||||
.of_match_table = of_match_ptr(piusb_of_match),
|
||||
},
|
||||
.probe = piusb_probe,
|
||||
.remove = piusb_remove,
|
||||
.id_table = piusb_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(piusb_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Pericom TypeC Detection driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -2040,6 +2040,15 @@ static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA)
|
|||
mA > IDEV_ACA_CHG_LIMIT)
|
||||
mA = IDEV_ACA_CHG_LIMIT;
|
||||
|
||||
dev_dbg(motg->phy.dev, "Requested curr from USB = %u, max-type-c:%u\n",
|
||||
mA, motg->typec_current_max);
|
||||
/* Save bc1.2 max_curr if type-c charger later moves to diff mode */
|
||||
motg->bc1p2_current_max = mA;
|
||||
|
||||
/* Override mA if type-c charger used (use hvdcp/bc1.2 if it is 500) */
|
||||
if (motg->typec_current_max > 500 && mA < motg->typec_current_max)
|
||||
mA = motg->typec_current_max;
|
||||
|
||||
if (msm_otg_notify_chg_type(motg))
|
||||
dev_err(motg->phy.dev,
|
||||
"Failed notifying %d charger type to PMIC\n",
|
||||
|
@ -4788,6 +4797,9 @@ static int otg_power_get_property_usb(struct power_supply *psy,
|
|||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
val->intval = motg->current_max;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
||||
val->intval = motg->typec_current_max;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = !!test_bit(B_SESS_VLD, &motg->inputs);
|
||||
break;
|
||||
|
@ -4846,6 +4858,16 @@ static int otg_power_set_property_usb(struct power_supply *psy,
|
|||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
motg->current_max = val->intval;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
||||
motg->typec_current_max = val->intval;
|
||||
msm_otg_dbg_log_event(&motg->phy, "type-c charger",
|
||||
val->intval, motg->bc1p2_current_max);
|
||||
/* Update chg_current as per type-c charger detection on VBUS */
|
||||
if (motg->chg_type != USB_INVALID_CHARGER) {
|
||||
dev_dbg(motg->phy.dev, "update type-c charger\n");
|
||||
msm_otg_notify_charger(motg, motg->bc1p2_current_max);
|
||||
}
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TYPE:
|
||||
psy->type = val->intval;
|
||||
|
||||
|
@ -4917,6 +4939,7 @@ static int otg_power_property_is_writeable_usb(struct power_supply *psy,
|
|||
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
case POWER_SUPPLY_PROP_DP_DM:
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
||||
case POWER_SUPPLY_PROP_USB_OTG:
|
||||
return 1;
|
||||
default:
|
||||
|
@ -4936,6 +4959,7 @@ static enum power_supply_property otg_pm_power_props_usb[] = {
|
|||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
||||
POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_SCOPE,
|
||||
POWER_SUPPLY_PROP_TYPE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
|
|
|
@ -427,6 +427,8 @@ struct msm_otg_platform_data {
|
|||
* @bus_clks_enabled: indicates pcnoc/snoc/bimc clocks are on or not.
|
||||
* @chg_check_timer: The timer used to implement the workaround to detect
|
||||
* very slow plug in of wall charger.
|
||||
* @bc1p2_current_max: Max charging current allowed as per bc1.2 chg detection
|
||||
* @typec_current_max: Max charging current allowed as per type-c chg detection
|
||||
* @is_ext_chg_dcp: To indicate whether charger detected by external entity
|
||||
SMB hardware is DCP charger or not.
|
||||
* @pm_done: It is used to increment the pm counter using pm_runtime_get_sync.
|
||||
|
@ -570,6 +572,8 @@ struct msm_otg {
|
|||
unsigned int host_mode;
|
||||
unsigned int voltage_max;
|
||||
unsigned int current_max;
|
||||
unsigned int bc1p2_current_max;
|
||||
unsigned int typec_current_max;
|
||||
unsigned int usbin_health;
|
||||
|
||||
dev_t ext_chg_dev;
|
||||
|
|
Loading…
Reference in New Issue