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:
Manu Gautam 2015-06-23 17:04:39 +05:30 committed by Gerrit - the friendly Code Review server
parent bd2078f611
commit 09b109818e
7 changed files with 308 additions and 0 deletions

View File

@ -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>;
};

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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,

View File

@ -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;