blob: 8fc721fc825888f0fa369e7055697c7d43d9d681 [file] [log] [blame]
/*
* Copyright (c) 2015-2016, 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.
*
*/
#define pr_fmt(fmt) "TYPEC: %s: " fmt, __func__
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/power_supply.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/machine.h>
#include <linux/slab.h>
#include <linux/spmi.h>
#include <linux/usb/class-dual-role.h>
#define CREATE_MASK(NUM_BITS, POS) \
((unsigned char) (((1 << (NUM_BITS)) - 1) << (POS)))
#define TYPEC_MASK(MSB_BIT, LSB_BIT) \
CREATE_MASK(MSB_BIT - LSB_BIT + 1, LSB_BIT)
/* Interrupt offsets */
#define INT_RT_STS_REG(base) (base + 0x10)
#define DFP_DETECT_BIT BIT(3)
#define UFP_DETECT_BIT BIT(1)
#define TYPEC_UFP_STATUS_REG(base) (base + 0x08)
#define TYPEC_CCOUT_BIT BIT(7)
#define TYPEC_CCOUT_OPEN_BIT BIT(6)
#define TYPEC_CURRENT_MASK TYPEC_MASK(2, 0)
#define TYPEC_RDSTD_BIT BIT(2)
#define TYPEC_RD1P5_BIT BIT(1)
#define TYPEC_DFP_STATUS_REG(base) (base + 0x09)
#define VALID_DFP_MASK TYPEC_MASK(6, 4)
#define TYPEC_SW_CTL_REG(base) (base + 0x52)
#define TYPEC_STD_MA 900
#define TYPEC_MED_MA 1500
#define TYPEC_HIGH_MA 3000
#define QPNP_TYPEC_DEV_NAME "qcom,qpnp-typec"
#define TYPEC_PSY_NAME "typec"
#define DUAL_ROLE_DESC_NAME "otg_default"
enum cc_line_state {
CC_1,
CC_2,
OPEN,
};
struct typec_wakeup_source {
struct wakeup_source source;
unsigned long enabled;
};
static void typec_stay_awake(struct typec_wakeup_source *source)
{
if (!__test_and_set_bit(0, &source->enabled)) {
__pm_stay_awake(&source->source);
pr_debug("enabled source %s\n", source->source.name);
}
}
static void typec_relax(struct typec_wakeup_source *source)
{
if (__test_and_clear_bit(0, &source->enabled)) {
__pm_relax(&source->source);
pr_debug("disabled source %s\n", source->source.name);
}
}
struct qpnp_typec_chip {
struct device *dev;
struct spmi_device *spmi;
struct power_supply *batt_psy;
struct power_supply type_c_psy;
struct regulator *ss_mux_vreg;
struct mutex typec_lock;
spinlock_t rw_lock;
u16 base;
/* IRQs */
int vrd_changed;
int ufp_detach;
int ufp_detect;
int dfp_detach;
int dfp_detect;
int vbus_err;
int vconn_oc;
/* Configurations */
int cc_line_state;
int current_ma;
int ssmux_gpio;
enum of_gpio_flags gpio_flag;
int typec_state;
/* Dual role support */
bool role_reversal_supported;
bool in_force_mode;
int force_mode;
struct dual_role_phy_instance *dr_inst;
struct dual_role_phy_desc dr_desc;
struct delayed_work role_reversal_check;
struct typec_wakeup_source role_reversal_wakeup_source;
};
/* current mode */
static char *mode_text[] = {
"ufp", "dfp", "none"
};
/* SPMI operations */
static int __qpnp_typec_read(struct spmi_device *spmi, u8 *val, u16 addr,
int count)
{
int rc;
rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, addr, val, count);
if (rc)
pr_err("spmi read failed addr=0x%02x sid=0x%02x rc=%d\n",
addr, spmi->sid, rc);
return rc;
}
static int __qpnp_typec_write(struct spmi_device *spmi, u8 *val, u16 addr,
int count)
{
int rc;
rc = spmi_ext_register_writel(spmi->ctrl, spmi->sid, addr, val, count);
if (rc)
pr_err("spmi write failed addr=0x%02x sid=0x%02x rc=%d\n",
addr, spmi->sid, rc);
return rc;
}
static int qpnp_typec_read(struct qpnp_typec_chip *chip, u8 *val, u16 addr,
int count)
{
int rc;
unsigned long flags;
struct spmi_device *spmi = chip->spmi;
if (addr == 0) {
pr_err("addr cannot be zero addr=0x%02x sid=0x%02x rc=%d\n",
addr, spmi->sid, rc);
return -EINVAL;
}
spin_lock_irqsave(&chip->rw_lock, flags);
rc = __qpnp_typec_read(spmi, val, addr, count);
spin_unlock_irqrestore(&chip->rw_lock, flags);
return rc;
}
static int qpnp_typec_masked_write(struct qpnp_typec_chip *chip, u16 base,
u8 mask, u8 val)
{
u8 reg;
int rc;
unsigned long flags;
struct spmi_device *spmi = chip->spmi;
spin_lock_irqsave(&chip->rw_lock, flags);
rc = __qpnp_typec_read(spmi, &reg, base, 1);
if (rc) {
pr_err("spmi read failed: addr=%03X, rc=%d\n", base, rc);
goto out;
}
reg &= ~mask;
reg |= val & mask;
pr_debug("addr = 0x%x writing 0x%x\n", base, reg);
rc = __qpnp_typec_write(spmi, &reg, base, 1);
if (rc) {
pr_err("spmi write failed: addr=%03X, rc=%d\n", base, rc);
goto out;
}
out:
spin_unlock_irqrestore(&chip->rw_lock, flags);
return rc;
}
static int set_property_on_battery(struct qpnp_typec_chip *chip,
enum power_supply_property prop)
{
int rc = 0;
union power_supply_propval ret = {0, };
if (!chip->batt_psy) {
chip->batt_psy = power_supply_get_by_name("battery");
if (!chip->batt_psy) {
pr_err("no batt psy found\n");
return -ENODEV;
}
}
switch (prop) {
case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
ret.intval = chip->current_ma;
rc = chip->batt_psy->set_property(chip->batt_psy,
POWER_SUPPLY_PROP_CURRENT_CAPABILITY, &ret);
if (rc)
pr_err("failed to set current max rc=%d\n", rc);
break;
case POWER_SUPPLY_PROP_TYPEC_MODE:
/*
* Notify the typec mode to charger. This is useful in the DFP
* case where there is no notification of OTG insertion to the
* charger driver.
*/
ret.intval = chip->typec_state;
rc = chip->batt_psy->set_property(chip->batt_psy,
POWER_SUPPLY_PROP_TYPEC_MODE, &ret);
if (rc)
pr_err("failed to set typec mode rc=%d\n", rc);
break;
default:
pr_err("invalid request\n");
rc = -EINVAL;
}
return rc;
}
static int get_max_current(u8 reg)
{
if (!reg)
return 0;
return (reg & TYPEC_RDSTD_BIT) ? TYPEC_STD_MA :
(reg & TYPEC_RD1P5_BIT) ? TYPEC_MED_MA : TYPEC_HIGH_MA;
}
static int qpnp_typec_configure_ssmux(struct qpnp_typec_chip *chip,
enum cc_line_state cc_line)
{
int rc = 0;
if (cc_line != chip->cc_line_state) {
switch (cc_line) {
case OPEN:
if (chip->ss_mux_vreg) {
rc = regulator_disable(chip->ss_mux_vreg);
if (rc) {
pr_err("failed to disable ssmux regulator rc=%d\n",
rc);
return rc;
}
}
if (chip->ssmux_gpio) {
rc = gpio_direction_input(chip->ssmux_gpio);
if (rc) {
pr_err("failed to configure ssmux gpio rc=%d\n",
rc);
return rc;
}
}
break;
case CC_1:
case CC_2:
if (chip->ss_mux_vreg) {
rc = regulator_enable(chip->ss_mux_vreg);
if (rc) {
pr_err("failed to enable ssmux regulator rc=%d\n",
rc);
return rc;
}
}
if (chip->ssmux_gpio) {
rc = gpio_direction_output(chip->ssmux_gpio,
(chip->gpio_flag == OF_GPIO_ACTIVE_LOW)
? !cc_line : cc_line);
if (rc) {
pr_err("failed to configure ssmux gpio rc=%d\n",
rc);
return rc;
}
}
break;
}
}
return 0;
}
#define UFP_EN_BIT BIT(5)
#define DFP_EN_BIT BIT(4)
#define FORCE_MODE_MASK TYPEC_MASK(5, 4)
static int qpnp_typec_force_mode(struct qpnp_typec_chip *chip, int mode)
{
int rc = 0;
u8 reg = (mode == DUAL_ROLE_PROP_MODE_UFP) ? UFP_EN_BIT
: (mode == DUAL_ROLE_PROP_MODE_DFP) ? DFP_EN_BIT : 0x0;
if (chip->force_mode != mode) {
rc = qpnp_typec_masked_write(chip,
TYPEC_SW_CTL_REG(chip->base), FORCE_MODE_MASK, reg);
if (rc) {
pr_err("Failed to force typeC mode rc=%d\n", rc);
} else {
chip->force_mode = mode;
pr_debug("Forced mode: %s\n",
mode_text[chip->force_mode]);
}
}
return rc;
}
static int qpnp_typec_handle_usb_insertion(struct qpnp_typec_chip *chip, u8 reg)
{
int rc;
enum cc_line_state cc_line_state;
cc_line_state = (reg & TYPEC_CCOUT_OPEN_BIT) ?
OPEN : (reg & TYPEC_CCOUT_BIT) ? CC_2 : CC_1;
rc = qpnp_typec_configure_ssmux(chip, cc_line_state);
if (rc) {
pr_err("failed to configure ss-mux rc=%d\n", rc);
return rc;
}
chip->cc_line_state = cc_line_state;
pr_debug("CC_line state = %d\n", cc_line_state);
return 0;
}
static int qpnp_typec_handle_detach(struct qpnp_typec_chip *chip)
{
int rc;
rc = qpnp_typec_configure_ssmux(chip, OPEN);
if (rc) {
pr_err("failed to configure SSMUX rc=%d\n", rc);
return rc;
}
chip->cc_line_state = OPEN;
chip->current_ma = 0;
chip->typec_state = POWER_SUPPLY_TYPE_UNKNOWN;
chip->type_c_psy.type = POWER_SUPPLY_TYPE_UNKNOWN;
rc = set_property_on_battery(chip, POWER_SUPPLY_PROP_TYPEC_MODE);
if (rc)
pr_err("failed to set TYPEC MODE on battery psy rc=%d\n", rc);
pr_debug("CC_line state = %d current_ma = %d in_force_mode = %d\n",
chip->cc_line_state, chip->current_ma,
chip->in_force_mode);
/* handle role reversal */
if (chip->role_reversal_supported && !chip->in_force_mode) {
rc = qpnp_typec_force_mode(chip, DUAL_ROLE_PROP_MODE_NONE);
if (rc)
pr_err("Failed to set DRP mode rc=%d\n", rc);
}
if (chip->dr_inst)
dual_role_instance_changed(chip->dr_inst);
return rc;
}
/* Interrupt handlers */
static irqreturn_t vrd_changed_handler(int irq, void *_chip)
{
int rc, old_current;
u8 reg;
struct qpnp_typec_chip *chip = _chip;
pr_debug("vrd changed triggered\n");
mutex_lock(&chip->typec_lock);
rc = qpnp_typec_read(chip, &reg, TYPEC_UFP_STATUS_REG(chip->base), 1);
if (rc) {
pr_err("failed to read status reg rc=%d\n", rc);
goto out;
}
old_current = chip->current_ma;
chip->current_ma = get_max_current(reg & TYPEC_CURRENT_MASK);
/* only notify if current is valid and changed at runtime */
if (chip->current_ma && (old_current != chip->current_ma)) {
rc = set_property_on_battery(chip,
POWER_SUPPLY_PROP_CURRENT_CAPABILITY);
if (rc)
pr_err("failed to set INPUT CURRENT MAX on battery psy rc=%d\n",
rc);
}
pr_debug("UFP status reg = 0x%x old current = %dma new current = %dma\n",
reg, old_current, chip->current_ma);
out:
mutex_unlock(&chip->typec_lock);
return IRQ_HANDLED;
}
static irqreturn_t vconn_oc_handler(int irq, void *_chip)
{
pr_warn("vconn oc triggered\n");
return IRQ_HANDLED;
}
static irqreturn_t ufp_detect_handler(int irq, void *_chip)
{
int rc;
u8 reg;
struct qpnp_typec_chip *chip = _chip;
pr_debug("ufp detect triggered\n");
mutex_lock(&chip->typec_lock);
rc = qpnp_typec_read(chip, &reg, TYPEC_UFP_STATUS_REG(chip->base), 1);
if (rc) {
pr_err("failed to read status reg rc=%d\n", rc);
goto out;
}
rc = qpnp_typec_handle_usb_insertion(chip, reg);
if (rc) {
pr_err("failed to handle USB insertion rc=%d\n", rc);
goto out;
}
chip->current_ma = get_max_current(reg & TYPEC_CURRENT_MASK);
/* device in UFP state */
chip->typec_state = POWER_SUPPLY_TYPE_UFP;
chip->type_c_psy.type = POWER_SUPPLY_TYPE_UFP;
rc = set_property_on_battery(chip, POWER_SUPPLY_PROP_TYPEC_MODE);
if (rc)
pr_err("failed to set TYPEC MODE on battery psy rc=%d\n", rc);
if (chip->dr_inst)
dual_role_instance_changed(chip->dr_inst);
pr_debug("UFP status reg = 0x%x current = %dma\n",
reg, chip->current_ma);
out:
mutex_unlock(&chip->typec_lock);
return IRQ_HANDLED;
}
static irqreturn_t ufp_detach_handler(int irq, void *_chip)
{
int rc;
struct qpnp_typec_chip *chip = _chip;
pr_debug("ufp detach triggered\n");
mutex_lock(&chip->typec_lock);
rc = qpnp_typec_handle_detach(chip);
if (rc)
pr_err("failed to handle UFP detach rc=%d\n", rc);
mutex_unlock(&chip->typec_lock);
return IRQ_HANDLED;
}
static irqreturn_t dfp_detect_handler(int irq, void *_chip)
{
int rc;
u8 reg[2];
struct qpnp_typec_chip *chip = _chip;
pr_debug("dfp detect trigerred\n");
mutex_lock(&chip->typec_lock);
rc = qpnp_typec_read(chip, reg, TYPEC_UFP_STATUS_REG(chip->base), 2);
if (rc) {
pr_err("failed to read status reg rc=%d\n", rc);
goto out;
}
if (reg[1] & VALID_DFP_MASK) {
rc = qpnp_typec_handle_usb_insertion(chip, reg[0]);
if (rc) {
pr_err("failed to handle USB insertion rc=%d\n", rc);
goto out;
}
chip->typec_state = POWER_SUPPLY_TYPE_DFP;
chip->type_c_psy.type = POWER_SUPPLY_TYPE_DFP;
chip->current_ma = 0;
rc = set_property_on_battery(chip,
POWER_SUPPLY_PROP_TYPEC_MODE);
if (rc)
pr_err("failed to set TYPEC MODE on battery psy rc=%d\n",
rc);
}
if (chip->dr_inst)
dual_role_instance_changed(chip->dr_inst);
pr_debug("UFP status reg = 0x%x DFP status reg = 0x%x\n",
reg[0], reg[1]);
out:
mutex_unlock(&chip->typec_lock);
return IRQ_HANDLED;
}
static irqreturn_t dfp_detach_handler(int irq, void *_chip)
{
int rc;
struct qpnp_typec_chip *chip = _chip;
pr_debug("dfp detach triggered\n");
mutex_lock(&chip->typec_lock);
rc = qpnp_typec_handle_detach(chip);
if (rc)
pr_err("failed to handle DFP detach rc=%d\n", rc);
mutex_unlock(&chip->typec_lock);
return IRQ_HANDLED;
}
static irqreturn_t vbus_err_handler(int irq, void *_chip)
{
int rc;
struct qpnp_typec_chip *chip = _chip;
pr_debug("vbus_err triggered\n");
mutex_lock(&chip->typec_lock);
rc = qpnp_typec_handle_detach(chip);
if (rc)
pr_err("failed to handle VBUS_ERR rc==%d\n", rc);
mutex_unlock(&chip->typec_lock);
return IRQ_HANDLED;
}
static int qpnp_typec_parse_dt(struct qpnp_typec_chip *chip)
{
int rc;
struct device_node *node = chip->dev->of_node;
/* SS-Mux configuration gpio */
if (of_find_property(node, "qcom,ssmux-gpio", NULL)) {
chip->ssmux_gpio = of_get_named_gpio_flags(node,
"qcom,ssmux-gpio", 0, &chip->gpio_flag);
if (!gpio_is_valid(chip->ssmux_gpio)) {
if (chip->ssmux_gpio != -EPROBE_DEFER)
pr_err("failed to get ss-mux config gpio=%d\n",
chip->ssmux_gpio);
return chip->ssmux_gpio;
}
rc = devm_gpio_request(chip->dev, chip->ssmux_gpio,
"typec_mux_config_gpio");
if (rc) {
pr_err("failed to request ss-mux gpio rc=%d\n", rc);
return rc;
}
}
/* SS-Mux regulator */
if (of_find_property(node, "ss-mux-supply", NULL)) {
chip->ss_mux_vreg = devm_regulator_get(chip->dev, "ss-mux");
if (IS_ERR(chip->ss_mux_vreg))
return PTR_ERR(chip->ss_mux_vreg);
}
chip->role_reversal_supported = of_property_read_bool(node,
"qcom,role-reversal-supported");
return 0;
}
static int qpnp_typec_determine_initial_status(struct qpnp_typec_chip *chip)
{
int rc;
u8 rt_reg;
rc = qpnp_typec_read(chip, &rt_reg, INT_RT_STS_REG(chip->base), 1);
if (rc) {
pr_err("failed to read RT status reg rc=%d\n", rc);
return rc;
}
pr_debug("RT status reg = 0x%x\n", rt_reg);
chip->cc_line_state = OPEN;
chip->typec_state = POWER_SUPPLY_TYPE_UNKNOWN;
chip->type_c_psy.type = POWER_SUPPLY_TYPE_UNKNOWN;
if (rt_reg & DFP_DETECT_BIT) {
/* we are in DFP state*/
dfp_detect_handler(0, chip);
} else if (rt_reg & UFP_DETECT_BIT) {
/* we are in UFP state */
ufp_detect_handler(0, chip);
}
return 0;
}
#define REQUEST_IRQ(chip, irq, irq_name, irq_handler, flags, wake, rc) \
do { \
irq = spmi_get_irq_byname(chip->spmi, NULL, irq_name); \
if (irq < 0) { \
pr_err("Unable to get " irq_name " irq\n"); \
rc |= -ENXIO; \
} \
rc = devm_request_threaded_irq(chip->dev, \
irq, NULL, irq_handler, flags, irq_name, \
chip); \
if (rc < 0) { \
pr_err("Unable to request " irq_name " irq: %d\n", rc); \
rc |= -ENXIO; \
} \
\
if (wake) \
enable_irq_wake(irq); \
} while (0)
static int qpnp_typec_request_irqs(struct qpnp_typec_chip *chip)
{
int rc = 0;
unsigned long flags = IRQF_TRIGGER_RISING | IRQF_ONESHOT;
REQUEST_IRQ(chip, chip->vrd_changed, "vrd-change", vrd_changed_handler,
flags, true, rc);
REQUEST_IRQ(chip, chip->ufp_detach, "ufp-detach", ufp_detach_handler,
flags, true, rc);
REQUEST_IRQ(chip, chip->ufp_detect, "ufp-detect", ufp_detect_handler,
flags, true, rc);
REQUEST_IRQ(chip, chip->dfp_detach, "dfp-detach", dfp_detach_handler,
flags, true, rc);
REQUEST_IRQ(chip, chip->dfp_detect, "dfp-detect", dfp_detect_handler,
flags, true, rc);
REQUEST_IRQ(chip, chip->vbus_err, "vbus-err", vbus_err_handler,
flags, true, rc);
REQUEST_IRQ(chip, chip->vconn_oc, "vconn-oc", vconn_oc_handler,
flags, true, rc);
return rc;
}
static enum power_supply_property qpnp_typec_properties[] = {
POWER_SUPPLY_PROP_CURRENT_CAPABILITY,
POWER_SUPPLY_PROP_TYPE,
};
static int qpnp_typec_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct qpnp_typec_chip *chip = container_of(psy,
struct qpnp_typec_chip, type_c_psy);
switch (prop) {
case POWER_SUPPLY_PROP_TYPE:
val->intval = chip->typec_state;
break;
case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
val->intval = chip->current_ma;
break;
default:
return -EINVAL;
}
return 0;
}
#define ROLE_REVERSAL_DELAY_MS 500
static void qpnp_typec_role_check_work(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct qpnp_typec_chip *chip = container_of(dwork,
struct qpnp_typec_chip, role_reversal_check);
int rc;
mutex_lock(&chip->typec_lock);
switch (chip->force_mode) {
case DUAL_ROLE_PROP_MODE_UFP:
if (chip->typec_state != POWER_SUPPLY_TYPE_UFP) {
pr_debug("Role-reversal not latched to UFP in %d msec resetting to DRP mode\n",
ROLE_REVERSAL_DELAY_MS);
rc = qpnp_typec_force_mode(chip,
DUAL_ROLE_PROP_MODE_NONE);
if (rc)
pr_err("Failed to set DRP mode rc=%d\n", rc);
}
chip->in_force_mode = false;
break;
case DUAL_ROLE_PROP_MODE_DFP:
if (chip->typec_state != POWER_SUPPLY_TYPE_DFP) {
pr_debug("Role-reversal not latched to DFP in %d msec resetting to DRP mode\n",
ROLE_REVERSAL_DELAY_MS);
rc = qpnp_typec_force_mode(chip,
DUAL_ROLE_PROP_MODE_NONE);
if (rc)
pr_err("Failed to set DRP mode rc=%d\n", rc);
}
chip->in_force_mode = false;
break;
default:
pr_debug("Already in DRP mode\n");
break;
}
mutex_unlock(&chip->typec_lock);
typec_relax(&chip->role_reversal_wakeup_source);
}
enum dual_role_property qpnp_typec_dr_properties[] = {
DUAL_ROLE_PROP_SUPPORTED_MODES,
DUAL_ROLE_PROP_MODE,
DUAL_ROLE_PROP_PR,
DUAL_ROLE_PROP_DR,
};
static int qpnp_typec_dr_is_writeable(struct dual_role_phy_instance *dual_role,
enum dual_role_property prop)
{
int rc;
switch (prop) {
case DUAL_ROLE_PROP_MODE:
rc = 1;
break;
default:
rc = 0;
}
return rc;
}
static int qpnp_typec_dr_set_property(struct dual_role_phy_instance *dual_role,
enum dual_role_property prop,
const unsigned int *val)
{
int rc = 0;
struct qpnp_typec_chip *chip = dual_role_get_drvdata(dual_role);
if (!chip || (chip->typec_state == POWER_SUPPLY_TYPE_UNKNOWN))
return -EINVAL;
switch (prop) {
case DUAL_ROLE_PROP_MODE:
/* Force role */
mutex_lock(&chip->typec_lock);
if (chip->in_force_mode) {
pr_debug("Already in mode transition skipping request\n");
mutex_unlock(&chip->typec_lock);
return 0;
}
switch (*val) {
case DUAL_ROLE_PROP_MODE_UFP:
rc = qpnp_typec_force_mode(chip,
DUAL_ROLE_PROP_MODE_UFP);
if (rc)
pr_err("Failed to force UFP mode rc=%d\n", rc);
else
chip->in_force_mode = true;
break;
case DUAL_ROLE_PROP_MODE_DFP:
rc = qpnp_typec_force_mode(chip,
DUAL_ROLE_PROP_MODE_DFP);
if (rc)
pr_err("Failed to force DFP mode rc=%d\n", rc);
else
chip->in_force_mode = true;
break;
default:
pr_debug("Invalid role(not DFP/UFP) %d\n", *val);
rc = -EINVAL;
}
mutex_unlock(&chip->typec_lock);
/*
* schedule delayed work to check if device latched to the
* requested mode.
*/
if (!rc && chip->in_force_mode) {
cancel_delayed_work_sync(&chip->role_reversal_check);
typec_stay_awake(&chip->role_reversal_wakeup_source);
schedule_delayed_work(&chip->role_reversal_check,
msecs_to_jiffies(ROLE_REVERSAL_DELAY_MS));
}
break;
default:
pr_debug("Invalid DUAL ROLE request %d\n", prop);
rc = -EINVAL;
}
return rc;
}
static int qpnp_typec_dr_get_property(struct dual_role_phy_instance *dual_role,
enum dual_role_property prop,
unsigned int *val)
{
struct qpnp_typec_chip *chip = dual_role_get_drvdata(dual_role);
unsigned int mode, power_role, data_role;
if (!chip)
return -EINVAL;
switch (chip->typec_state) {
case POWER_SUPPLY_TYPE_UFP:
mode = DUAL_ROLE_PROP_MODE_UFP;
power_role = DUAL_ROLE_PROP_PR_SNK;
data_role = DUAL_ROLE_PROP_DR_DEVICE;
break;
case POWER_SUPPLY_TYPE_DFP:
mode = DUAL_ROLE_PROP_MODE_DFP;
power_role = DUAL_ROLE_PROP_PR_SRC;
data_role = DUAL_ROLE_PROP_DR_HOST;
break;
default:
mode = DUAL_ROLE_PROP_MODE_NONE;
power_role = DUAL_ROLE_PROP_PR_NONE;
data_role = DUAL_ROLE_PROP_DR_NONE;
};
switch (prop) {
case DUAL_ROLE_PROP_SUPPORTED_MODES:
*val = chip->dr_desc.supported_modes;
break;
case DUAL_ROLE_PROP_MODE:
*val = mode;
break;
case DUAL_ROLE_PROP_PR:
*val = power_role;
break;
case DUAL_ROLE_PROP_DR:
*val = data_role;
break;
default:
return -EINVAL;
}
return 0;
}
static int qpnp_typec_probe(struct spmi_device *spmi)
{
int rc;
struct resource *resource;
struct qpnp_typec_chip *chip;
resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
if (!resource) {
pr_err("Unable to get spmi resource for TYPEC\n");
return -EINVAL;
}
chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_typec_chip),
GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->dev = &spmi->dev;
chip->spmi = spmi;
/* parse DT */
rc = qpnp_typec_parse_dt(chip);
if (rc) {
pr_err("failed to parse DT rc=%d\n", rc);
return rc;
}
chip->base = resource->start;
dev_set_drvdata(&spmi->dev, chip);
device_init_wakeup(&spmi->dev, 1);
mutex_init(&chip->typec_lock);
spin_lock_init(&chip->rw_lock);
/* determine initial status */
rc = qpnp_typec_determine_initial_status(chip);
if (rc) {
pr_err("failed to determine initial state rc=%d\n", rc);
goto out;
}
chip->type_c_psy.name = TYPEC_PSY_NAME;
chip->type_c_psy.get_property = qpnp_typec_get_property;
chip->type_c_psy.properties = qpnp_typec_properties;
chip->type_c_psy.num_properties = ARRAY_SIZE(qpnp_typec_properties);
rc = power_supply_register(chip->dev, &chip->type_c_psy);
if (rc < 0) {
pr_err("Unable to register type_c_psy rc=%d\n", rc);
goto out;
}
if (chip->role_reversal_supported) {
chip->force_mode = DUAL_ROLE_PROP_MODE_NONE;
wakeup_source_init(&chip->role_reversal_wakeup_source.source,
"typec_role_reversal_wake");
INIT_DELAYED_WORK(&chip->role_reversal_check,
qpnp_typec_role_check_work);
/* Register for android TypeC dual role framework */
chip->dr_desc.name = DUAL_ROLE_DESC_NAME;
chip->dr_desc.properties = qpnp_typec_dr_properties;
chip->dr_desc.get_property = qpnp_typec_dr_get_property;
chip->dr_desc.set_property = qpnp_typec_dr_set_property;
chip->dr_desc.property_is_writeable =
qpnp_typec_dr_is_writeable;
chip->dr_desc.supported_modes =
DUAL_ROLE_SUPPORTED_MODES_DFP_AND_UFP;
chip->dr_desc.num_properties =
ARRAY_SIZE(qpnp_typec_dr_properties);
chip->dr_inst = devm_dual_role_instance_register(chip->dev,
&chip->dr_desc);
if (IS_ERR(chip->dr_inst)) {
pr_err("Failed to initialize dual role\n");
rc = PTR_ERR(chip->dr_inst);
goto unregister_psy;
} else {
chip->dr_inst->drv_data = chip;
}
}
/* All irqs */
rc = qpnp_typec_request_irqs(chip);
if (rc) {
pr_err("failed to request irqs rc=%d\n", rc);
goto unregister_psy;
}
pr_info("TypeC successfully probed state=%d CC-line-state=%d\n",
chip->typec_state, chip->cc_line_state);
return 0;
unregister_psy:
power_supply_unregister(&chip->type_c_psy);
out:
mutex_destroy(&chip->typec_lock);
if (chip->role_reversal_supported)
wakeup_source_trash(&chip->role_reversal_wakeup_source.source);
return rc;
}
static int qpnp_typec_remove(struct spmi_device *spmi)
{
int rc;
struct qpnp_typec_chip *chip = dev_get_drvdata(&spmi->dev);
if (chip->role_reversal_supported) {
cancel_delayed_work_sync(&chip->role_reversal_check);
wakeup_source_trash(&chip->role_reversal_wakeup_source.source);
}
rc = qpnp_typec_configure_ssmux(chip, OPEN);
if (rc)
pr_err("failed to configure SSMUX rc=%d\n", rc);
mutex_destroy(&chip->typec_lock);
dev_set_drvdata(chip->dev, NULL);
return 0;
}
static struct of_device_id qpnp_typec_match_table[] = {
{ .compatible = QPNP_TYPEC_DEV_NAME, },
{}
};
static struct spmi_driver qpnp_typec_driver = {
.probe = qpnp_typec_probe,
.remove = qpnp_typec_remove,
.driver = {
.name = QPNP_TYPEC_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = qpnp_typec_match_table,
},
};
/*
* qpnp_typec_init() - register spmi driver for qpnp-typec
*/
static int __init qpnp_typec_init(void)
{
return spmi_driver_register(&qpnp_typec_driver);
}
module_init(qpnp_typec_init);
static void __exit qpnp_typec_exit(void)
{
spmi_driver_unregister(&qpnp_typec_driver);
}
module_exit(qpnp_typec_exit);
MODULE_DESCRIPTION("QPNP type-C driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" QPNP_TYPEC_DEV_NAME);