1979 lines
60 KiB
C
Executable File
1979 lines
60 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/usb/typec.h>
|
|
#include <linux/usb/ucsi_glink.h>
|
|
#include <linux/soc/qcom/wcd939x-i2c.h>
|
|
#include <linux/qti-regmap-debugfs.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/nvmem-consumer.h>
|
|
#include "wcd-usbss-priv.h"
|
|
#include "wcd-usbss-reg-masks.h"
|
|
#include "wcd-usbss-reg-shifts.h"
|
|
|
|
#define WCD_USBSS_I2C_NAME "wcd-usbss-i2c-driver"
|
|
|
|
#define DEFAULT_SURGE_TIMER_PERIOD_MS 15000
|
|
#define SEC_TO_MS 1000
|
|
#define NUM_RCO_MISC2_READ 10
|
|
#define MIN_SURGE_TIMER_PERIOD_SEC 3
|
|
#define MAX_SURGE_TIMER_PERIOD_SEC 20
|
|
#define PM_RUNTIME_RESUME_CNT 8
|
|
#define PM_RUNTIME_RESUME_WAIT_US_MIN 5000
|
|
|
|
enum {
|
|
WCD_USBSS_AUDIO_MANUAL,
|
|
WCD_USBSS_AUDIO_FSM,
|
|
};
|
|
|
|
enum {
|
|
WCD_USBSS_1_X,
|
|
WCD_USBSS_2_0,
|
|
};
|
|
|
|
enum {
|
|
WCD_USBSS_LPD_USB_MODE_CLEAR = 0,
|
|
WCD_USBSS_LPD_MODE_SET,
|
|
WCD_USBSS_USB_MODE_SET,
|
|
WCD_USBSS_LPD_USB_MODE_SET,
|
|
WCD_USBSS_LPD_CONTINUOUS_1 = 5,
|
|
WCD_USBSS_LPD_CONTINUOUS_2 = 7,
|
|
WCD_USBSS_SDAM_MODE_MAX = WCD_USBSS_LPD_CONTINUOUS_2,
|
|
WCD_USBSS_AUDIO_MODE_SET = WCD_USBSS_SDAM_MODE_MAX + 1,
|
|
};
|
|
|
|
struct wcd_usbss_reg_mask_val {
|
|
u16 reg;
|
|
u8 mask;
|
|
u8 val;
|
|
};
|
|
|
|
/* regulator power supply names */
|
|
static const char * const supply_names[] = {
|
|
"vdd-usb-cp",
|
|
};
|
|
|
|
static int audio_fsm_mode = WCD_USBSS_AUDIO_MANUAL;
|
|
|
|
/* Linearlizer coefficients for 32ohm load */
|
|
static const struct wcd_usbss_reg_mask_val coeff_init[] = {
|
|
{WCD_USBSS_AUD_COEF_L_K5_0, 0xFF, 0x39},
|
|
{WCD_USBSS_AUD_COEF_R_K5_0, 0xFF, 0x39},
|
|
{WCD_USBSS_GND_COEF_L_K2_0, 0xFF, 0xE8},
|
|
{WCD_USBSS_GND_COEF_L_K4_0, 0xFF, 0x73},
|
|
{WCD_USBSS_GND_COEF_R_K2_0, 0xFF, 0xE8},
|
|
{WCD_USBSS_GND_COEF_R_K4_0, 0xFF, 0x73},
|
|
{WCD_USBSS_RATIO_SPKR_REXT_L_LSB, 0xFF, 0x00},
|
|
{WCD_USBSS_RATIO_SPKR_REXT_L_MSB, 0x7F, 0x04},
|
|
{WCD_USBSS_RATIO_SPKR_REXT_R_LSB, 0xFF, 0x00},
|
|
{WCD_USBSS_RATIO_SPKR_REXT_R_MSB, 0x7F, 0x04},
|
|
};
|
|
|
|
static struct wcd_usbss_ctxt *wcd_usbss_ctxt_;
|
|
|
|
/* Required for kobj_attributes */
|
|
static ssize_t wcd_usbss_surge_enable_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count);
|
|
|
|
static ssize_t wcd_usbss_surge_period_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count);
|
|
|
|
static ssize_t wcd_usbss_standby_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count);
|
|
|
|
static struct kobj_attribute wcd_usbss_surge_enable_attribute =
|
|
__ATTR(surge_enable, 0220, NULL, wcd_usbss_surge_enable_store);
|
|
|
|
static struct kobj_attribute wcd_usbss_surge_period_attribute =
|
|
__ATTR(surge_period, 0220, NULL, wcd_usbss_surge_period_store);
|
|
|
|
static struct kobj_attribute wcd_usbss_standby_enable_attribute =
|
|
__ATTR(standby_mode, 0220, NULL, wcd_usbss_standby_store);
|
|
|
|
|
|
static int acquire_runtime_env(struct wcd_usbss_ctxt *priv)
|
|
{
|
|
int rc = 0, retry = 0;
|
|
|
|
mutex_lock(&priv->runtime_env_counter_lock);
|
|
|
|
priv->runtime_env_counter++;
|
|
if (priv->runtime_env_counter == 1) {
|
|
pm_stay_awake(priv->dev);
|
|
|
|
do {
|
|
rc = pm_runtime_resume_and_get(priv->dev);
|
|
if (rc >= 0)
|
|
break;
|
|
|
|
if (rc == -EACCES) {
|
|
usleep_range(PM_RUNTIME_RESUME_WAIT_US_MIN,
|
|
PM_RUNTIME_RESUME_WAIT_US_MIN + 500);
|
|
} else {
|
|
dev_err(priv->dev, "%s: pm_runtime_resume_and_get failed: %i\n",
|
|
__func__, rc);
|
|
}
|
|
} while (++retry < PM_RUNTIME_RESUME_CNT);
|
|
|
|
if (rc == -EACCES)
|
|
dev_err(priv->dev, "%s: pm runtime in disabled state\n", __func__);
|
|
|
|
if (rc < 0) {
|
|
pm_relax(priv->dev);
|
|
priv->runtime_env_counter--;
|
|
}
|
|
} else if (priv->runtime_env_counter <= 0) {
|
|
dev_err(priv->dev, "%s: priv->runtime_env_counter %d underrun\n", __func__,
|
|
priv->runtime_env_counter);
|
|
priv->runtime_env_counter = 0;
|
|
}
|
|
|
|
mutex_unlock(&priv->runtime_env_counter_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void release_runtime_env(struct wcd_usbss_ctxt *priv)
|
|
{
|
|
mutex_lock(&priv->runtime_env_counter_lock);
|
|
|
|
priv->runtime_env_counter--;
|
|
if (priv->runtime_env_counter == 0) {
|
|
pm_runtime_mark_last_busy(priv->dev);
|
|
pm_runtime_put_autosuspend(priv->dev);
|
|
pm_relax(priv->dev);
|
|
} else if (priv->runtime_env_counter < 0) {
|
|
dev_err(priv->dev, "%s: priv->runtime_env_counter %d underrun\n", __func__,
|
|
priv->runtime_env_counter);
|
|
priv->runtime_env_counter = 0;
|
|
}
|
|
|
|
mutex_unlock(&priv->runtime_env_counter_lock);
|
|
}
|
|
|
|
/**
|
|
* wcd_usbss_sbu_switch_orientation() - Determine SBU switch orientation based on switch settings.
|
|
*
|
|
* This function is used to determine SBU switch orientation of the WCD USBSS. INVALID_ORIENTATION
|
|
* in enum wcd_usbss_sbu_switch_orientation represents an error state where none of the defined
|
|
* orientations can be inferred by the switch settings.
|
|
*
|
|
* Return: Returns an enum wcd_usbss_sbu_switch_orientation to client. INVALID_ORIENTATION is
|
|
* returned if the driver is not probed or if undefined switch settings are discovered.
|
|
*/
|
|
enum wcd_usbss_sbu_switch_orientation wcd_usbss_get_sbu_switch_orientation(void)
|
|
{
|
|
unsigned int read_val = 0;
|
|
int ret = 0;
|
|
|
|
/* check if driver is probed and private context is init'ed */
|
|
if (wcd_usbss_ctxt_ == NULL)
|
|
return INVALID_ORIENTATION;
|
|
|
|
if (!wcd_usbss_ctxt_->regmap)
|
|
return INVALID_ORIENTATION;
|
|
|
|
ret = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (ret < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
regmap_read(wcd_usbss_ctxt_->regmap, WCD_USBSS_SWITCH_SELECT0, &read_val);
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
if ((read_val & 0x3) == 0x1)
|
|
return GND_SBU1_ORIENTATION_B;
|
|
if ((read_val & 0x3) == 0x2)
|
|
return GND_SBU2_ORIENTATION_A;
|
|
return INVALID_ORIENTATION;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_get_sbu_switch_orientation);
|
|
|
|
/*
|
|
* wcd_usbss_set_switch_settings_enable() - Configure a specified WCD USBSS switch.
|
|
* @switch_type: Switch to be enabled/disabled.
|
|
* @switch_setting: Enable or disable.
|
|
*
|
|
* This function will set or reset a specific bit in the WCD_USBSS_SWITCH_SETTINGS_ENABLE register.
|
|
* There is a check that switch_type represents a bit in this register. Update the definition of
|
|
* enum wcd_usbss_switch_type switch_type if the bits in WCD_USBSS_SWITCH_SETTINGS_ENABLE change.
|
|
*
|
|
* Return : Returns int on whether the switch configuration happened or not. -ENODEV is returned if
|
|
* the driver is not probed.
|
|
*/
|
|
int wcd_usbss_set_switch_settings_enable(enum wcd_usbss_switch_type switch_type,
|
|
enum wcd_usbss_switch_state switch_state)
|
|
{
|
|
int ret = 0;
|
|
/* check if driver is probed and private context is initialized */
|
|
if (wcd_usbss_ctxt_ == NULL)
|
|
return -ENODEV;
|
|
|
|
if ((!wcd_usbss_ctxt_->regmap) || (switch_type < MIN_SWITCH_TYPE_NUM) ||
|
|
(switch_type > MAX_SWITCH_TYPE_NUM) ||
|
|
(switch_state != USBSS_SWITCH_DISABLE && switch_state != USBSS_SWITCH_ENABLE))
|
|
return -EINVAL;
|
|
|
|
ret = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (ret < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
|
|
1 << switch_type, switch_state << switch_type);
|
|
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_set_switch_settings_enable);
|
|
|
|
/*
|
|
* wcd_usbss_linearizer_rdac_cal_code_select() - Configure the linearizer calibration codes source.
|
|
*
|
|
* @source: HW (hardware) or SW (software).
|
|
*
|
|
* This function configures the linearizer to use SW or HW as the sources for the calibration codes.
|
|
*
|
|
* Return: Returns int on whether the switch configuration happened or not. -ENODEV is returned if
|
|
* the driver is not probed.
|
|
*/
|
|
int wcd_usbss_linearizer_rdac_cal_code_select(enum linearizer_rdac_cal_code_select source)
|
|
{
|
|
int ret = 0;
|
|
/* check if driver is probed and private context is initialized */
|
|
if (wcd_usbss_ctxt_ == NULL)
|
|
return -ENODEV;
|
|
|
|
if ((!wcd_usbss_ctxt_->regmap) || (source != LINEARIZER_SOURCE_HW &&
|
|
source != LINEARIZER_SOURCE_SW))
|
|
return -EINVAL;
|
|
|
|
ret = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (ret < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_FUNCTION_ENABLE, 0x4, source << 2);
|
|
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_linearizer_rdac_cal_code_select);
|
|
|
|
/*
|
|
* wcd_usbss_set_linearizer_sw_tap() - Configure linearizer audio and ground software tap values.
|
|
*
|
|
* @aud_tap: 10-bit tap code for the L and R audio software tap registers.
|
|
* @gnd_tap: 10-bit tap code for the L and R ground software tap registers.
|
|
*
|
|
* This function writes tap values to the left and right tap registers for the audio and ground
|
|
* FETs. Note that the tap values are 10 bits and cannot exceed 0x3FF, but they can be 0.
|
|
*
|
|
* Return: Returns int on whether the switch configuration happened or not. -ENODEV is returned if
|
|
* the driver is not probed.
|
|
*/
|
|
int wcd_usbss_set_linearizer_sw_tap(uint32_t aud_tap, uint32_t gnd_tap)
|
|
{
|
|
int ret = 0;
|
|
uint32_t lsb_mask = 0xFF, msb_shift = 8;
|
|
|
|
/* check if driver is probed and private context is initialized */
|
|
if (wcd_usbss_ctxt_ == NULL)
|
|
return -ENODEV;
|
|
|
|
if ((!wcd_usbss_ctxt_->regmap) || aud_tap > 0x3FF || gnd_tap > 0x3FF)
|
|
return -EINVAL;
|
|
|
|
ret = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (ret < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Audio left */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_TAP_AUD_L_LSB, 0xFF,
|
|
aud_tap & lsb_mask);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_TAP_AUD_L_MSB, 0x3,
|
|
aud_tap >> msb_shift);
|
|
/* Audio right */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_TAP_AUD_R_LSB, 0xFF,
|
|
aud_tap & lsb_mask);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_TAP_AUD_R_MSB, 0x3,
|
|
aud_tap >> msb_shift);
|
|
/* Ground left */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_TAP_GND_L_LSB, 0xFF,
|
|
gnd_tap & lsb_mask);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_TAP_GND_L_MSB, 0x3,
|
|
gnd_tap >> msb_shift);
|
|
/* Ground right */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_TAP_GND_R_LSB, 0xFF,
|
|
gnd_tap & lsb_mask);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_TAP_GND_R_MSB, 0x3,
|
|
gnd_tap >> msb_shift);
|
|
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_set_linearizer_sw_tap);
|
|
|
|
static bool wcd_usbss_readable_register(struct device *dev, unsigned int reg)
|
|
{
|
|
if (reg <= (WCD_USBSS_BASE + 1))
|
|
return false;
|
|
|
|
if ((wcd_usbss_ctxt_ && wcd_usbss_ctxt_->version == WCD_USBSS_1_X) &&
|
|
(reg >= WCD_USBSS_EFUSE_CTL &&
|
|
reg <= WCD_USBSS_ANA_CSR_DBG_CTL))
|
|
return false;
|
|
|
|
return wcd_usbss_reg_access[WCD_USBSS_REG(reg)] & RD_REG;
|
|
}
|
|
|
|
/*
|
|
* wcd_usbss_register_update() - Write or read multiple USB-SS registers.
|
|
*
|
|
* @reg_arr: Array of {register address, register value} pairs.
|
|
* @write: Bool selecting whether to write values from reg_arr or read values to store in reg_arr.
|
|
* @arr_size: Number of {register address, register value} pairs in reg_arr.
|
|
*
|
|
* This function writes or reads arr_size number of register values, specified in reg_arr. If write
|
|
* is true, this function will write all the values specified in reg_arr to corresponding USB-SS
|
|
* registers. If write is false, this function will read the USB-SS registers specified in reg_arr
|
|
* and write those values to the corresponding register values in reg_arr. If any register write or
|
|
* read fails, this function prints an error message and exits.
|
|
*
|
|
* Return: Returns int on whether the register writes/reads were successful. -ENODEV is
|
|
* returned if the driver is not probed.
|
|
*/
|
|
int wcd_usbss_register_update(uint32_t reg_arr[][2], bool write, size_t arr_size)
|
|
{
|
|
size_t i;
|
|
int rc = 0;
|
|
uint32_t reg_mask = 0xFF;
|
|
|
|
/* check if driver is probed and private context is initialized */
|
|
if (wcd_usbss_ctxt_ == NULL)
|
|
return -ENODEV;
|
|
|
|
if (!wcd_usbss_ctxt_->regmap)
|
|
return -EINVAL;
|
|
|
|
rc = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (rc < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < arr_size; i++) {
|
|
if (write) {
|
|
rc = regmap_write(wcd_usbss_ctxt_->regmap, reg_arr[i][0],
|
|
reg_arr[i][1] & reg_mask);
|
|
if (rc != 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev,
|
|
"%s: USB-SS register 0x%x (value of 0x%x) write failed\n",
|
|
__func__, reg_arr[i][0], reg_arr[i][1]);
|
|
goto err;
|
|
}
|
|
} else {
|
|
rc = regmap_read(wcd_usbss_ctxt_->regmap, reg_arr[i][0], ®_arr[i][1]);
|
|
if (rc != 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev,
|
|
"%s: USB-SS register 0x%x read failed\n", __func__,
|
|
reg_arr[i][0]);
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
err:
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_register_update);
|
|
|
|
/*
|
|
* wcd_usbss_is_in_reset_state() - Check whether a negative surge ESD event has occurred.
|
|
*
|
|
* This function has a series of three checks to determine whether a negative surge ESD event has
|
|
* occurred. If any of the three check conditions is met, it is concluded that a negative surge
|
|
* ESD event has occurred. The checks include the following:
|
|
* 1. Register WCD_USBSS_CPLDO_CTL2 reads 0xFF
|
|
* 2. Register WCD_USBSS_RCO_MISC2 Bit<1> reads 0 at least once in NUM_RCO_MISC2_READ reads
|
|
* 3. Register 0x06 Bit<0> reads 1 after toggling register WCD_USBSS_PMP_MISC1 Bit<0> from
|
|
* 0 --> 1 --> 0
|
|
*
|
|
* Return: Returns true if any check(s) fail, false otherwise.
|
|
*/
|
|
static bool wcd_usbss_is_in_reset_state(void)
|
|
{
|
|
bool ret = false;
|
|
int i = 0;
|
|
int rc = 0;
|
|
unsigned int read_val = 0;
|
|
|
|
/* Check 1: Read WCD_USBSS_CPLDO_CTL2 */
|
|
rc = regmap_read(wcd_usbss_ctxt_->regmap, WCD_USBSS_CPLDO_CTL2, &read_val);
|
|
if (rc != 0)
|
|
goto done;
|
|
|
|
if (read_val != 0xFF) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: Surge check #1 failed\n", __func__);
|
|
ret = true;
|
|
goto done;
|
|
}
|
|
|
|
/* Check 2: Read WCD_USBSS_RCO_MISC2 */
|
|
for (i = 0; i < NUM_RCO_MISC2_READ; i++) {
|
|
rc = regmap_read(wcd_usbss_ctxt_->regmap, WCD_USBSS_RCO_MISC2, &read_val);
|
|
if (rc != 0)
|
|
goto done;
|
|
|
|
if ((read_val & 0x2) == 0)
|
|
break;
|
|
if (i == (NUM_RCO_MISC2_READ - 1)) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: Surge check #2 failed\n", __func__);
|
|
ret = true;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
if (!wcd_usbss_ctxt_->is_in_standby) {
|
|
/* Toggle WCD_USBSS_PMP_MISC1 bit<0>: 0 --> 1 --> 0 */
|
|
rc = rc | regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_MISC1,
|
|
0x1, 0x0);
|
|
rc = rc | regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_MISC1,
|
|
0x1, 0x1);
|
|
rc = rc | regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_MISC1,
|
|
0x1, 0x0);
|
|
|
|
/* Check 3: Read WCD_USBSS_PMP_MISC2 */
|
|
rc = rc | regmap_read(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_MISC2, &read_val);
|
|
|
|
if (rc != 0) {
|
|
mutex_unlock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
goto done;
|
|
}
|
|
|
|
if ((read_val & 0x1) == 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: Surge check #3 failed\n", __func__);
|
|
ret = true;
|
|
}
|
|
}
|
|
mutex_unlock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
|
|
done:
|
|
/* All checks passed, so a negative surge ESD event has not occurred */
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* wcd_usbss_reset_routine - Uses cached state to restore USB-SS registers after a negative surge.
|
|
*
|
|
* Return: Returns int return value from wcd_usbss_switch_update()
|
|
*/
|
|
static int wcd_usbss_reset_routine(void)
|
|
{
|
|
/* Mark the cache as dirty to force a flush */
|
|
regcache_mark_dirty(wcd_usbss_ctxt_->regmap);
|
|
regcache_sync(wcd_usbss_ctxt_->regmap);
|
|
/* Write 0xFF to WCD_USBSS_CPLDO_CTL2 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_CPLDO_CTL2, 0xFF, 0xFF);
|
|
|
|
/* Set RCO_EN: WCD_USBSS_USB_SS_CNTL Bit<3> --> 0x0 --> 0x1 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_USB_SS_CNTL, 0x8, 0x0);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_USB_SS_CNTL, 0x8, 0x8);
|
|
|
|
/* If in audio mode reset codec registers */
|
|
if ((wcd_usbss_ctxt_->cable_status & (BIT(WCD_USBSS_AATC) |
|
|
BIT(WCD_USBSS_GND_MIC_SWAP_AATC) |
|
|
BIT(WCD_USBSS_HSJ_CONNECT) |
|
|
BIT(WCD_USBSS_GND_MIC_SWAP_HSJ))))
|
|
blocking_notifier_call_chain(&wcd_usbss_ctxt_->wcd_usbss_notifier,
|
|
WCD_USBSS_SURGE_RESET_EVENT, NULL);
|
|
|
|
return 0;
|
|
}
|
|
/* Called with switch_update_lock mutex locked */
|
|
static void wcd_usbss_standby_control_locked(bool enter_standby)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (wcd_usbss_ctxt_->is_in_standby == enter_standby)
|
|
return;
|
|
|
|
if (enter_standby) {
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "%s: Enabling standby mode\n",
|
|
__func__);
|
|
rc = regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_USB_SS_CNTL,
|
|
0x10, 0x10);
|
|
if (rc < 0)
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: enter standby failed\n", __func__);
|
|
else
|
|
wcd_usbss_ctxt_->is_in_standby = true;
|
|
} else {
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "%s: Disabling standby mode\n",
|
|
__func__);
|
|
rc = regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_USB_SS_CNTL,
|
|
0x10, 0x00);
|
|
if (rc < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: exit standby failed\n", __func__);
|
|
} else {
|
|
/* 10ms wait recommended to get WCD USBSS out of standby */
|
|
usleep_range(10000, 10100);
|
|
wcd_usbss_ctxt_->is_in_standby = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int wcd_usbss_standby_control(bool enter_standby)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!wcd_usbss_ctxt_->standby_enable)
|
|
return 0;
|
|
|
|
mutex_lock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
|
|
ret = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (ret < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, ret);
|
|
goto done;
|
|
}
|
|
|
|
wcd_usbss_standby_control_locked(enter_standby);
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
done:
|
|
mutex_unlock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t wcd_usbss_surge_enable_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned int enable = 0;
|
|
|
|
if (kstrtouint(buf, 10, &enable) < 0)
|
|
return -EINVAL;
|
|
|
|
/* Return if period is 0ms */
|
|
if (!wcd_usbss_ctxt_->surge_timer_period_ms)
|
|
wcd_usbss_ctxt_->surge_timer_period_ms = DEFAULT_SURGE_TIMER_PERIOD_MS;
|
|
|
|
wcd_usbss_ctxt_->surge_enable = enable;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t wcd_usbss_surge_period_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned int period_sec = 0;
|
|
|
|
if (kstrtouint(buf, 10, &period_sec) < 0)
|
|
return -EINVAL;
|
|
|
|
/* Constrain period */
|
|
if (period_sec >= MIN_SURGE_TIMER_PERIOD_SEC && period_sec <= MAX_SURGE_TIMER_PERIOD_SEC)
|
|
wcd_usbss_ctxt_->surge_timer_period_ms = SEC_TO_MS * period_sec;
|
|
|
|
if (!wcd_usbss_ctxt_->surge_thread)
|
|
return count;
|
|
|
|
/* Wake up thread if usb is connected and surge is enabled */
|
|
if (wcd_usbss_ctxt_->cable_status && wcd_usbss_ctxt_->surge_enable)
|
|
wake_up_process(wcd_usbss_ctxt_->surge_thread);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t wcd_usbss_standby_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned int enable = 0;
|
|
|
|
if (kstrtouint(buf, 10, &enable) < 0)
|
|
return -EINVAL;
|
|
|
|
/* temporarily enabling standby to force proper state update */
|
|
wcd_usbss_ctxt_->standby_enable = true;
|
|
|
|
if (enable) {
|
|
if (!wcd_usbss_ctxt_->cable_status)
|
|
wcd_usbss_standby_control(true);
|
|
else
|
|
wcd_usbss_standby_control(false);
|
|
} else {
|
|
wcd_usbss_standby_control(false);
|
|
}
|
|
|
|
wcd_usbss_ctxt_->standby_enable = enable;
|
|
|
|
return count;
|
|
}
|
|
/*
|
|
* wcd_usbss_surge_kthread_fn - checks for a negative surge reset at a given period interval
|
|
*
|
|
* Returns 0
|
|
*/
|
|
static int wcd_usbss_surge_kthread_fn(void *p)
|
|
{
|
|
while (!kthread_should_stop()) {
|
|
if (acquire_runtime_env(wcd_usbss_ctxt_) >= 0) {
|
|
|
|
if (wcd_usbss_ctxt_->surge_enable &&
|
|
wcd_usbss_is_in_reset_state())
|
|
wcd_usbss_reset_routine();
|
|
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
}
|
|
|
|
msleep_interruptible(wcd_usbss_ctxt_->surge_timer_period_ms);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* wcd_usbss_enable_surge_kthread - routine for creating and deploying a kthread to handle surge
|
|
* protection.
|
|
*/
|
|
static void wcd_usbss_enable_surge_kthread(void)
|
|
{
|
|
|
|
if (!wcd_usbss_ctxt_->surge_enable)
|
|
return;
|
|
|
|
if (!wcd_usbss_ctxt_->surge_thread)
|
|
wcd_usbss_ctxt_->surge_thread = kthread_run(wcd_usbss_surge_kthread_fn,
|
|
NULL, "Surge kthread");
|
|
|
|
if (!wcd_usbss_ctxt_->surge_thread)
|
|
pr_err("%s, Unable to create WCD USBSS surge kthread.\n", __func__);
|
|
}
|
|
|
|
/*
|
|
* wcd_usbss_disable_surge_kthread - routine for stopping a kthread that handles surge
|
|
* protection.
|
|
*/
|
|
static void wcd_usbss_disable_surge_kthread(void)
|
|
{
|
|
if (!wcd_usbss_ctxt_->surge_enable)
|
|
return;
|
|
|
|
if (!wcd_usbss_ctxt_->surge_thread)
|
|
return;
|
|
|
|
kthread_stop(wcd_usbss_ctxt_->surge_thread);
|
|
wcd_usbss_ctxt_->surge_thread = NULL;
|
|
}
|
|
|
|
static int wcd_usbss_sysfs_init(struct wcd_usbss_ctxt *priv)
|
|
{
|
|
int rc = 0;
|
|
|
|
priv->surge_kobject = kobject_create_and_add("wcd_usbss", kernel_kobj);
|
|
|
|
if (!(priv->surge_kobject)) {
|
|
dev_err(priv->dev, "%s: sysfs failed, surge kobj not created\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
rc = sysfs_create_file(priv->surge_kobject, &wcd_usbss_surge_enable_attribute.attr);
|
|
if (rc < 0) {
|
|
dev_err(priv->dev,
|
|
"%s: sysfs failed, unable to register surge enable attribute. rc: %d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = sysfs_create_file(priv->surge_kobject, &wcd_usbss_surge_period_attribute.attr);
|
|
if (rc < 0) {
|
|
dev_err(priv->dev,
|
|
"%s: sysfs failed, unable to register surge period attribute. rc: %d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = sysfs_create_file(priv->surge_kobject, &wcd_usbss_standby_enable_attribute.attr);
|
|
if (rc < 0) {
|
|
dev_err(priv->dev,
|
|
"%s: sysfs failed, unable to register standby enable attribute. rc: %d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wcd_usbss_usbc_event_changed(struct notifier_block *nb,
|
|
unsigned long evt, void *ptr)
|
|
{
|
|
struct wcd_usbss_ctxt *priv =
|
|
container_of(nb, struct wcd_usbss_ctxt, ucsi_nb);
|
|
struct device *dev;
|
|
enum typec_accessory acc = ((struct ucsi_glink_constat_info *)ptr)->acc;
|
|
|
|
if (!priv)
|
|
return -EINVAL;
|
|
|
|
dev = priv->dev;
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
dev_dbg(dev, "%s: USB change event received, supply mode %d, usbc mode %d, expected %d\n",
|
|
__func__, acc, priv->usbc_mode.counter,
|
|
TYPEC_ACCESSORY_AUDIO);
|
|
|
|
switch (acc) {
|
|
case TYPEC_ACCESSORY_AUDIO:
|
|
case TYPEC_ACCESSORY_NONE:
|
|
if (atomic_read(&(priv->usbc_mode)) == acc)
|
|
break; /* filter notifications received before */
|
|
atomic_set(&(priv->usbc_mode), acc);
|
|
|
|
dev_dbg(dev, "%s: queueing usbc_analog_work\n",
|
|
__func__);
|
|
pm_stay_awake(priv->dev);
|
|
queue_work(system_freezable_wq, &priv->usbc_analog_work);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wcd_usbss_usbc_analog_setup_switches(struct wcd_usbss_ctxt *priv)
|
|
{
|
|
int rc = 0;
|
|
int mode;
|
|
struct device *dev;
|
|
bool cable_status_cache = false;
|
|
|
|
if (!priv)
|
|
return -EINVAL;
|
|
|
|
dev = priv->dev;
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&priv->notification_lock);
|
|
/* get latest mode again within locked context */
|
|
mode = atomic_read(&(priv->usbc_mode));
|
|
|
|
dev_dbg(dev, "%s: setting GPIOs active = %d cable_status = %d mode = %d\n",
|
|
__func__, mode != TYPEC_ACCESSORY_NONE, priv->cable_status, mode);
|
|
|
|
switch (mode) {
|
|
/* add all modes WCD USBSS should notify for in here */
|
|
case TYPEC_ACCESSORY_AUDIO:
|
|
/*
|
|
* If cable_type is already decided, update the cable_status to
|
|
* avoid reconfiguration of AATC switch settings again
|
|
*/
|
|
if (priv->cable_status & (BIT(WCD_USBSS_AATC) |
|
|
BIT(WCD_USBSS_GND_MIC_SWAP_AATC) |
|
|
BIT(WCD_USBSS_HSJ_CONNECT) |
|
|
BIT(WCD_USBSS_GND_MIC_SWAP_HSJ)))
|
|
cable_status_cache = true;
|
|
/* notify call chain on event */
|
|
blocking_notifier_call_chain(&priv->wcd_usbss_notifier,
|
|
mode, &cable_status_cache);
|
|
break;
|
|
case TYPEC_ACCESSORY_NONE:
|
|
/* notify call chain on event */
|
|
blocking_notifier_call_chain(&priv->wcd_usbss_notifier,
|
|
TYPEC_ACCESSORY_NONE, NULL);
|
|
break;
|
|
default:
|
|
/* ignore other usb connection modes */
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&priv->notification_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int wcd_usbss_validate_display_port_settings(struct wcd_usbss_ctxt *priv,
|
|
enum wcd_usbss_cable_types ctype)
|
|
{
|
|
unsigned int sts;
|
|
int rc;
|
|
|
|
rc = regmap_read(priv->regmap, WCD_USBSS_SWITCH_STATUS1, &sts);
|
|
if (rc)
|
|
return rc;
|
|
|
|
sts &= 0xCC;
|
|
pr_info("DPAUX switch status (MG1/2): %08x\n", sts);
|
|
|
|
if (ctype == WCD_USBSS_DP_AUX_CC1 && sts == 0x48)
|
|
return 0;
|
|
|
|
if (ctype == WCD_USBSS_DP_AUX_CC2 && sts == 0x84)
|
|
return 0;
|
|
|
|
pr_err("Failed to update switch for display port\n");
|
|
rc = -EINVAL;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int wcd_usbss_switch_update_defaults(struct wcd_usbss_ctxt *priv)
|
|
{
|
|
dev_dbg(priv->dev, "restoring defaults\n");
|
|
/* Disable all switches */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x07, 0x00);
|
|
/* Select MG1 for AGND_SWITCHES */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SELECT1, 0x01, 0x00);
|
|
/* Select GSBU1 and MG1 for MIC_SWITCHES */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SELECT0, 0x03, 0x00);
|
|
/* Enable OVP_MG1_BIAS PCOMP_DYN_BST_EN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG1_BIAS, 0x08, 0x08);
|
|
/* Enable OVP_MG2_BIAS PCOMP_DYN_BST_EN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG2_BIAS, 0x08, 0x08);
|
|
regmap_update_bits_base(priv->regmap, WCD_USBSS_AUDIO_FSM_START, 0x01,
|
|
0x01, NULL, false, true);
|
|
/* Select DN for DNL_SWITHCES and DP for DPR_SWITCHES */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SELECT0, 0x3C, 0x14);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_USB_SS_CNTL, 0x07, 0x05); /* Mode5: USB*/
|
|
regmap_write(priv->regmap, WCD_USBSS_PMP_EN, 0x0);
|
|
if (wcd_usbss_ctxt_->version == WCD_USBSS_2_0)
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_OUT1,
|
|
0x40, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_EXT_SW_CTRL_1, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_EXT_LIN_EN, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_CLK, 0x10);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_LIN_CTRL_1, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_2, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_3, 0x00);
|
|
|
|
/* Once plug-out done, restore to MANUAL mode */
|
|
audio_fsm_mode = WCD_USBSS_AUDIO_MANUAL;
|
|
return 0;
|
|
}
|
|
|
|
static void wcd_usbss_update_reg_init(struct regmap *regmap)
|
|
{
|
|
if (audio_fsm_mode == WCD_USBSS_AUDIO_FSM)
|
|
regmap_update_bits(regmap, WCD_USBSS_FUNCTION_ENABLE, 0x03,
|
|
0x02); /* AUDIO_FSM mode */
|
|
else
|
|
regmap_update_bits(regmap, WCD_USBSS_FUNCTION_ENABLE, 0x03,
|
|
0x01); /* AUDIO_MANUAL mode */
|
|
|
|
/* Enable dynamic boosting for DP and DN */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_DP_DN_MISC1, 0x09, 0x09);
|
|
/* Enable dynamic boosting for MG1 OVP */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_MG1_MISC, 0x24, 0x24);
|
|
/* Enable dynamic boosting for MG2 OVP */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_MG2_MISC, 0x24, 0x24);
|
|
|
|
/* Disable Equalizer */
|
|
regmap_update_bits(regmap, WCD_USBSS_EQUALIZER1,
|
|
WCD_USBSS_EQUALIZER1_EQ_EN_MASK, 0x00);
|
|
/* For surge reset routine: Write WCD_USBSS_CPLDO_CTL2 --> 0xFF */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_CPLDO_CTL2, 0xFF, 0xFF);
|
|
}
|
|
|
|
#define AUXP_M_EN_MASK (WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES_MASK |\
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES_MASK)
|
|
|
|
static int wcd_usbss_display_port_switch_update(struct wcd_usbss_ctxt *priv,
|
|
enum wcd_usbss_cable_types ctype)
|
|
{
|
|
pr_info("Configuring display port for ctype %d\n", ctype);
|
|
|
|
/* Disable AUX switches */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, AUXP_M_EN_MASK, 0x00);
|
|
|
|
/* Select MG1 for AUXP and MG2 for AUXM */
|
|
if (ctype == WCD_USBSS_DP_AUX_CC1)
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SELECT0, 0xC0, 0x40);
|
|
/* Select MG2 for AUXP and MG1 for AUXM */
|
|
else
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SELECT0, 0xC0, 0x80);
|
|
|
|
/* Enable DP_AUXP_TO_MGX and DP_AUXM_TO_MGX switches */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, AUXP_M_EN_MASK, 0x60);
|
|
return wcd_usbss_validate_display_port_settings(priv, ctype);
|
|
}
|
|
|
|
static void wcd_usbss_dpdm_switch_connect(struct wcd_usbss_ctxt *priv, bool connect)
|
|
{
|
|
if (connect)
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
|
|
0x18, 0x18);
|
|
else
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
|
|
0x18, 0x00);
|
|
}
|
|
|
|
static const char *status_to_str(int status)
|
|
{
|
|
switch (status) {
|
|
case WCD_USBSS_LPD_USB_MODE_CLEAR:
|
|
return "STANDBY";
|
|
case WCD_USBSS_LPD_MODE_SET:
|
|
return "LPD";
|
|
case WCD_USBSS_USB_MODE_SET:
|
|
return "USB";
|
|
case WCD_USBSS_LPD_USB_MODE_SET:
|
|
return "LPD_USB";
|
|
case WCD_USBSS_AUDIO_MODE_SET:
|
|
return "AUDIO";
|
|
case WCD_USBSS_LPD_CONTINUOUS_1:
|
|
fallthrough;
|
|
case WCD_USBSS_LPD_CONTINUOUS_2:
|
|
return "LPD_CONTINUOUS";
|
|
default:
|
|
return "UNDEFINED";
|
|
}
|
|
}
|
|
|
|
static void wcd_usbss_pd_pu_enable(void)
|
|
{
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_OUT1, 0x20, 0x00);
|
|
/* Enable D+/D- 1M & 400K PLDN */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_BIAS_TOP, 0x20, 0x00);
|
|
|
|
/* Enable DP/DN 2K PLDN */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_DP_BIAS, 0x01, 0x01);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_DN_BIAS, 0x01, 0x01);
|
|
|
|
if (!wcd_usbss_ctxt_->usb_sbu_compliance) {
|
|
/* Enable SBU1/2 2K PLDN */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_MG1_BIAS, 0x01, 0x01);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_MG2_BIAS, 0x01, 0x01);
|
|
}
|
|
}
|
|
|
|
/* to use with DPDM switch selection */
|
|
#define DPDM_SEL_MASK (WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES_MASK |\
|
|
WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES_MASK)
|
|
#define DPDM_SEL_ENABLE ((0x1 << WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES_SHIFT) |\
|
|
(0x1 << WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES_SHIFT))
|
|
#define DPDM_SEL_DISABLE 0x0
|
|
|
|
/* to use with DPDM switch enable/disable*/
|
|
#define DPDM_SW_EN_MASK (WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES_MASK |\
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES_MASK)
|
|
#define DPDM_SW_ENABLE ((0x1 << WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES_SHIFT) |\
|
|
(0x1 << WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES_SHIFT))
|
|
#define DPDM_SW_DISABLE 0x0
|
|
|
|
/*
|
|
* wcd_usbss_dpdm_switch_update - configure WCD USBSS DP/DM switch position
|
|
*
|
|
* @sw_en: enable or disable DP/DM switches.
|
|
* @eq_en: enable or disable equalizer. Usually true in case of USB high-speed.
|
|
*
|
|
* Returns zero for success, a negative number on error.
|
|
*/
|
|
int wcd_usbss_dpdm_switch_update(bool sw_en, bool eq_en)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* check if driver is probed and private context is initialized */
|
|
if (wcd_usbss_ctxt_ == NULL)
|
|
return -ENODEV;
|
|
|
|
if (!wcd_usbss_ctxt_->regmap)
|
|
return -EINVAL;
|
|
|
|
ret = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (ret < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
ret = regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
|
|
DPDM_SW_EN_MASK, (sw_en ? DPDM_SW_ENABLE : DPDM_SW_DISABLE));
|
|
if (ret)
|
|
pr_err("%s(): Failed to write dpdm_en_value ret:%d\n", __func__, ret);
|
|
|
|
ret = regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_EQUALIZER1,
|
|
WCD_USBSS_EQUALIZER1_EQ_EN_MASK,
|
|
(eq_en ? WCD_USBSS_EQUALIZER1_EQ_EN_MASK : 0x0));
|
|
if (ret)
|
|
pr_err("%s(): Failed to write equalizer1_en ret:%d\n", __func__, ret);
|
|
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_dpdm_switch_update);
|
|
|
|
static int wcd_usbss_dpdm_switch_update_from_handler(bool sw_en, bool eq_en)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
|
|
DPDM_SW_EN_MASK, (sw_en ? DPDM_SW_ENABLE : DPDM_SW_DISABLE));
|
|
if (ret)
|
|
pr_err("%s(): Failed to write dpdm_en_value ret:%d\n", __func__, ret);
|
|
|
|
ret = regmap_update_bits(wcd_usbss_ctxt_->regmap, WCD_USBSS_EQUALIZER1,
|
|
WCD_USBSS_EQUALIZER1_EQ_EN_MASK,
|
|
(eq_en ? WCD_USBSS_EQUALIZER1_EQ_EN_MASK : 0x0));
|
|
if (ret)
|
|
pr_err("%s(): Failed to write equalizer1_en ret:%d\n", __func__, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* wcd_usbss_audio_config - configure audio for power mode and Impedance calculations
|
|
*
|
|
* @enable: enable/disable switch settings for MIC and SENSE for impedance readings
|
|
* @config_type: Config type to configure audio
|
|
* @power_mode: power mode type to config
|
|
*
|
|
* Returns int on whether the config happened or not. -ENODEV is returned
|
|
* in case if the driver is not probed.
|
|
*/
|
|
|
|
int wcd_usbss_audio_config(bool enable, enum wcd_usbss_config_type config_type,
|
|
unsigned int power_mode)
|
|
{
|
|
|
|
int rc = 0;
|
|
unsigned int current_power_mode;
|
|
|
|
/* check if driver is probed and private context is init'ed */
|
|
if (wcd_usbss_ctxt_ == NULL)
|
|
return -ENODEV;
|
|
|
|
if (!wcd_usbss_ctxt_->regmap)
|
|
return -EINVAL;
|
|
|
|
pr_info("%s: connect_status = 0x%x, power mode = %d\n",
|
|
__func__, wcd_usbss_ctxt_->cable_status, power_mode);
|
|
|
|
if (!(wcd_usbss_ctxt_->cable_status & (BIT(WCD_USBSS_AATC) |
|
|
BIT(WCD_USBSS_GND_MIC_SWAP_AATC) |
|
|
BIT(WCD_USBSS_HSJ_CONNECT) |
|
|
BIT(WCD_USBSS_GND_MIC_SWAP_HSJ))))
|
|
return 0;
|
|
|
|
rc = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (rc < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
regmap_read(wcd_usbss_ctxt_->regmap, WCD_USBSS_USB_SS_CNTL, ¤t_power_mode);
|
|
if ((current_power_mode & 0x07) == power_mode)
|
|
goto exit;
|
|
|
|
switch (config_type) {
|
|
case WCD_USBSS_CONFIG_TYPE_POWER_MODE:
|
|
/* switching to MBHC mode */
|
|
if (power_mode == 0x1) {
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_EN, 0xF);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_EXT_LIN_EN, 0xC4);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_USB_SS_CNTL, 0x07, power_mode);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_CLK, 0x10);
|
|
} else { /* switching to ULP/HiFi/Std */
|
|
if (power_mode == 0x2) /* ULP */
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_CLK, 0x1C);
|
|
else
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_CLK, 0x10);
|
|
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_USB_SS_CNTL, 0x07, power_mode);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_EN, 0x0);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_EXT_LIN_EN, 0x00);
|
|
}
|
|
break;
|
|
default:
|
|
pr_err("%s Invalid config type %d\n", __func__, config_type);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
exit:
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_audio_config);
|
|
|
|
/*
|
|
* wcd_usbss_switch_update - configure WCD USBSS switch position based on
|
|
* cable type and status
|
|
*
|
|
* @ctype - cable type
|
|
* @connect_status - cable connected/disconnected status
|
|
*
|
|
* Returns int on whether the switch happened or not. -ENODEV is returned
|
|
* in case if the driver is not probed
|
|
*/
|
|
int wcd_usbss_switch_update(enum wcd_usbss_cable_types ctype,
|
|
enum wcd_usbss_cable_status connect_status)
|
|
{
|
|
int i = 0, ret = 0;
|
|
bool audio_switch = false;
|
|
|
|
/* check if driver is probed and private context is init'ed */
|
|
if (wcd_usbss_ctxt_ == NULL)
|
|
return -ENODEV;
|
|
|
|
if (!wcd_usbss_ctxt_->regmap)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
|
|
pr_info("%s: ctype = %d, connect_status = %d\n",
|
|
__func__, ctype, connect_status);
|
|
|
|
ret = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (ret < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, ret);
|
|
mutex_unlock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
if (connect_status == WCD_USBSS_CABLE_DISCONNECT) {
|
|
wcd_usbss_ctxt_->cable_status &= ~BIT(ctype);
|
|
|
|
switch (ctype) {
|
|
case WCD_USBSS_USB:
|
|
/* Keep DP/DM switch on but disable EQ */
|
|
if (wcd_usbss_ctxt_->standby_enable && wcd_usbss_ctxt_->is_in_standby)
|
|
wcd_usbss_dpdm_switch_update(false, false);
|
|
else
|
|
wcd_usbss_dpdm_switch_update(true, false);
|
|
break;
|
|
case WCD_USBSS_DP_AUX_CC1:
|
|
fallthrough;
|
|
case WCD_USBSS_DP_AUX_CC2:
|
|
/* Disable AUX switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT0, 0xC0, 0x00);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE,
|
|
AUXP_M_EN_MASK, 0x00);
|
|
wcd_usbss_ctxt_->cable_status &= ~BIT(WCD_USBSS_DP_AUX_CC1);
|
|
wcd_usbss_ctxt_->cable_status &= ~BIT(WCD_USBSS_DP_AUX_CC2);
|
|
break;
|
|
case WCD_USBSS_AATC:
|
|
wcd_usbss_ctxt_->cable_status &= ~BIT(WCD_USBSS_GND_MIC_SWAP_AATC);
|
|
audio_switch = true;
|
|
break;
|
|
case WCD_USBSS_GND_MIC_SWAP_AATC:
|
|
wcd_usbss_ctxt_->cable_status &= ~BIT(WCD_USBSS_AATC);
|
|
audio_switch = true;
|
|
break;
|
|
case WCD_USBSS_HSJ_CONNECT:
|
|
wcd_usbss_ctxt_->cable_status &= ~BIT(WCD_USBSS_GND_MIC_SWAP_HSJ);
|
|
audio_switch = true;
|
|
break;
|
|
case WCD_USBSS_GND_MIC_SWAP_HSJ:
|
|
wcd_usbss_ctxt_->cable_status &= ~BIT(WCD_USBSS_HSJ_CONNECT);
|
|
audio_switch = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* reset to defaults when all cable types are disconnected */
|
|
if (!wcd_usbss_ctxt_->cable_status && audio_switch) {
|
|
wcd_usbss_switch_update_defaults(wcd_usbss_ctxt_);
|
|
if (wcd_usbss_ctxt_->standby_enable) {
|
|
wcd_usbss_dpdm_switch_connect(wcd_usbss_ctxt_, false);
|
|
wcd_usbss_standby_control_locked(true);
|
|
wcd_usbss_ctxt_->wcd_standby_status = WCD_USBSS_LPD_USB_MODE_CLEAR;
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "wcd state transition to %s complete\n",
|
|
status_to_str(wcd_usbss_ctxt_->wcd_standby_status));
|
|
} else {
|
|
wcd_usbss_dpdm_switch_connect(wcd_usbss_ctxt_, true);
|
|
wcd_usbss_ctxt_->wcd_standby_status = WCD_USBSS_USB_MODE_SET;
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "wcd state transition to %s complete\n",
|
|
status_to_str(wcd_usbss_ctxt_->wcd_standby_status));
|
|
}
|
|
}
|
|
} else if (connect_status == WCD_USBSS_CABLE_CONNECT) {
|
|
wcd_usbss_ctxt_->cable_status |= BIT(ctype);
|
|
|
|
wcd_usbss_pd_pu_enable();
|
|
wcd_usbss_standby_control_locked(false);
|
|
|
|
switch (ctype) {
|
|
case WCD_USBSS_USB:
|
|
wcd_usbss_dpdm_switch_update(true, true);
|
|
break;
|
|
case WCD_USBSS_AATC:
|
|
/* Update power mode to mode 1 for AATC */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_USB_SS_CNTL, 0x07, 0x01);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_EN, 0xF);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_EXT_SW_CTRL_1, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_EXT_LIN_EN, 0xC4);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_LIN_CTRL_1, 0x01);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_2, 0x3C);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_3, 0x0F);
|
|
if (wcd_usbss_ctxt_->version == WCD_USBSS_2_0)
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_PMP_OUT1, 0x40, 0x40);
|
|
/* for AATC plug-in, change mode to FSM */
|
|
audio_fsm_mode = WCD_USBSS_AUDIO_FSM;
|
|
/* Disable all switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x7F, 0x00);
|
|
if (audio_fsm_mode == WCD_USBSS_AUDIO_FSM) {
|
|
regmap_update_bits_base(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_AUDIO_FSM_START, 0x01, 0x01, NULL, false, true);
|
|
}
|
|
/* Select L, R, GSBU2, MG1 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT0, 0x3F, 0x02);
|
|
/* Disable OVP_MG2_BIAS PCOMP_DYN_BST_EN */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_MG2_BIAS, 0x08, 0x00);
|
|
/* Enable SENSE, MIC switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x06, 0x06);
|
|
/* Select MG2 for AGND_SWITCHES */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT1, 0x01, 0x01);
|
|
/* Enable AGND switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x01, 0x01);
|
|
/* Enable DPR, DNL */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x18, 0x18);
|
|
/* Set DELAY_L_SW to CYL_1K */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_DELAY_L_SW, 0xFF, 0x02);
|
|
/* Set DELAY_R_SW to CYL_1K */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_DELAY_R_SW, 0xFF, 0x02);
|
|
/* Set DELAY_MIC_SW to CYL_1K */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_DELAY_MIC_SW, 0xFF, 0x01);
|
|
if (audio_fsm_mode == WCD_USBSS_AUDIO_FSM) {
|
|
regmap_update_bits_base(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_AUDIO_FSM_START, 0x01, 0x01, NULL, false, true);
|
|
}
|
|
for (i = 0; i < ARRAY_SIZE(coeff_init); ++i)
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap, coeff_init[i].reg,
|
|
coeff_init[i].mask, coeff_init[i].val);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_USB_SS_CNTL, 0x08, 0x00);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_USB_SS_CNTL, 0x08, 0x08);
|
|
usleep_range(10000, 10100);
|
|
break;
|
|
case WCD_USBSS_GND_MIC_SWAP_AATC:
|
|
dev_info(wcd_usbss_ctxt_->dev,
|
|
"%s: GND MIC Swap register updates..\n", __func__);
|
|
/* Update power mode to mode 1 for AATC */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_USB_SS_CNTL, 0x07, 0x01);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_EN, 0xF);
|
|
if (wcd_usbss_ctxt_->version == WCD_USBSS_2_0)
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_PMP_OUT1, 0x40, 0x40);
|
|
|
|
/* Disable MIC switch */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x02, 0x00);
|
|
/* Disable OVP_MG1_BIAS PCOMP_DYN_BST_EN */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_MG1_BIAS, 0x08, 0x00);
|
|
/* Select MG1 for AGND_SWITCHES */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT1, 0x01, 0x00);
|
|
/* Select L, R, GSBU1, MG2 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT0, 0x3F, 0x01);
|
|
/* Enable MIC switch */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x02, 0x02);
|
|
break;
|
|
case WCD_USBSS_HSJ_CONNECT:
|
|
/* Update power mode to mode 1 for AATC */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_USB_SS_CNTL, 0x07, 0x01);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_PMP_EN, 0xF);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_EXT_SW_CTRL_1, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_EXT_LIN_EN, 0xC4);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_LIN_CTRL_1, 0x01);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_2, 0x3C);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_3, 0x0F);
|
|
if (wcd_usbss_ctxt_->version == WCD_USBSS_2_0)
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_PMP_OUT1, 0x40, 0x40);
|
|
/* Select MG2, GSBU1 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT0, 0x03, 0x1);
|
|
/* Select AGND MG2 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT1, 0x01, 0x0);
|
|
/* Disable OVP_MG1_BIAS PCOMP_DYN_BST_EN */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_MG1_BIAS, 0x08, 0x00);
|
|
/* Enable SENSE, MIC, AGND switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x07, 0x07);
|
|
break;
|
|
case WCD_USBSS_GND_MIC_SWAP_HSJ:
|
|
/* Disable SENSE, MIC, AGND switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x07, 0x00);
|
|
/* Select MG1, GSBU2 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT0, 0x03, 0x2);
|
|
/* Select AGND MG2 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT1, 0x01, 0x1);
|
|
/* Enable SENSE, MIC, AGND switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x07, 0x07);
|
|
break;
|
|
case WCD_USBSS_CHARGER:
|
|
/* Disable DN DP Switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x18, 0x00);
|
|
/* Select DN2 DP2 */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SELECT0, 0x3C, 0x28);
|
|
/* Enable DN DP Switches */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_SWITCH_SETTINGS_ENABLE, 0x18, 0x18);
|
|
break;
|
|
case WCD_USBSS_DP_AUX_CC1:
|
|
fallthrough;
|
|
case WCD_USBSS_DP_AUX_CC2:
|
|
/* Update Leakage Canceller Coefficient for AUXP pins */
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_DISP_AUXP_CTL, 0x07, 0x01);
|
|
regmap_update_bits(wcd_usbss_ctxt_->regmap,
|
|
WCD_USBSS_DISP_AUXP_THRESH, 0xE0, 0xE0);
|
|
ret = wcd_usbss_display_port_switch_update(wcd_usbss_ctxt_, ctype);
|
|
if (ret) /* clear DP AUX bit if DP switch update fails */
|
|
wcd_usbss_ctxt_->cable_status &= ~BIT(ctype);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if ((wcd_usbss_ctxt_->cable_status & (BIT(WCD_USBSS_AATC) |
|
|
BIT(WCD_USBSS_GND_MIC_SWAP_AATC) |
|
|
BIT(WCD_USBSS_HSJ_CONNECT) |
|
|
BIT(WCD_USBSS_GND_MIC_SWAP_HSJ)))) {
|
|
wcd_usbss_ctxt_->wcd_standby_status = WCD_USBSS_AUDIO_MODE_SET;
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "wcd state transition to %s complete\n",
|
|
status_to_str(wcd_usbss_ctxt_->wcd_standby_status));
|
|
}
|
|
}
|
|
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
mutex_unlock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_switch_update);
|
|
|
|
/*
|
|
* wcd_usbss_reg_notifier - register notifier block with wcd usbss driver
|
|
*
|
|
* @nb - notifier block of wcd_usbss
|
|
* @node - phandle node to wcd_usbss device
|
|
*
|
|
* Returns 0 on success, or error code
|
|
*/
|
|
int wcd_usbss_reg_notifier(struct notifier_block *nb,
|
|
struct device_node *node)
|
|
{
|
|
int rc = 0;
|
|
struct i2c_client *client = of_find_i2c_device_by_node(node);
|
|
struct wcd_usbss_ctxt *priv;
|
|
|
|
if (!client)
|
|
return -EINVAL;
|
|
|
|
priv = (struct wcd_usbss_ctxt *)i2c_get_clientdata(client);
|
|
if (!priv)
|
|
return -EINVAL;
|
|
|
|
rc = blocking_notifier_chain_register
|
|
(&priv->wcd_usbss_notifier, nb);
|
|
|
|
dev_dbg(priv->dev, "%s: registered notifier for %s\n",
|
|
__func__, node->name);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/*
|
|
* as part of the init sequence check if there is a connected
|
|
* USB C analog adapter
|
|
*/
|
|
if (atomic_read(&(priv->usbc_mode)) == TYPEC_ACCESSORY_AUDIO) {
|
|
dev_dbg(priv->dev, "%s: analog adapter already inserted\n",
|
|
__func__);
|
|
rc = wcd_usbss_usbc_analog_setup_switches(priv);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_reg_notifier);
|
|
|
|
/*
|
|
* wcd_usbss_unreg_notifier - unregister notifier block with wcd usbss driver
|
|
*
|
|
* @nb - notifier block of wcd_usbss
|
|
* @node - phandle node to wcd_usbss device
|
|
*
|
|
* Returns 0 on pass, or error code
|
|
*/
|
|
int wcd_usbss_unreg_notifier(struct notifier_block *nb,
|
|
struct device_node *node)
|
|
{
|
|
struct i2c_client *client = of_find_i2c_device_by_node(node);
|
|
struct wcd_usbss_ctxt *priv;
|
|
|
|
if (!client)
|
|
return -EINVAL;
|
|
|
|
priv = (struct wcd_usbss_ctxt *)i2c_get_clientdata(client);
|
|
if (!priv)
|
|
return -EINVAL;
|
|
|
|
return blocking_notifier_chain_unregister
|
|
(&priv->wcd_usbss_notifier, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_unreg_notifier);
|
|
|
|
|
|
/*
|
|
* wcd_usbss_update_default_trim - update default trim for TP < 3
|
|
*
|
|
* Returns 0 on pass, or error code
|
|
*/
|
|
int wcd_usbss_update_default_trim(void)
|
|
{
|
|
int ret = 0;
|
|
if (!wcd_usbss_ctxt_)
|
|
return -ENODEV;
|
|
|
|
if (!wcd_usbss_ctxt_->regmap)
|
|
return -EINVAL;
|
|
|
|
ret = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (ret < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_SW_LIN_CTRL_1, 0x01);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_1, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_2, 0x00);
|
|
regmap_write(wcd_usbss_ctxt_->regmap, WCD_USBSS_DC_TRIMCODE_3, 0x00);
|
|
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_update_default_trim);
|
|
|
|
static void wcd_usbss_usbc_analog_work_fn(struct work_struct *work)
|
|
{
|
|
struct wcd_usbss_ctxt *priv =
|
|
container_of(work, struct wcd_usbss_ctxt, usbc_analog_work);
|
|
|
|
if (!priv) {
|
|
pr_err("%s: wcd usbss container invalid\n", __func__);
|
|
return;
|
|
}
|
|
wcd_usbss_usbc_analog_setup_switches(priv);
|
|
pm_relax(priv->dev);
|
|
}
|
|
|
|
static int wcd_usbss_init_optional_reset_pins(struct wcd_usbss_ctxt *priv)
|
|
{
|
|
priv->rst_pins = devm_pinctrl_get(priv->dev);
|
|
if (IS_ERR_OR_NULL(priv->rst_pins)) {
|
|
dev_dbg(priv->dev, "Cannot get wcd usbss reset pinctrl:%ld\n",
|
|
PTR_ERR(priv->rst_pins));
|
|
return PTR_ERR(priv->rst_pins);
|
|
}
|
|
|
|
priv->rst_pins_active = pinctrl_lookup_state(
|
|
priv->rst_pins, "active");
|
|
if (IS_ERR_OR_NULL(priv->rst_pins_active)) {
|
|
dev_dbg(priv->dev, "Cannot get active pinctrl state:%ld\n",
|
|
PTR_ERR(priv->rst_pins_active));
|
|
return PTR_ERR(priv->rst_pins_active);
|
|
}
|
|
|
|
if (priv->rst_pins_active)
|
|
return pinctrl_select_state(priv->rst_pins,
|
|
priv->rst_pins_active);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* called from switch_update_lock mutex locked */
|
|
static int wcd_usbss_sdam_handle_events_locked(int req_state)
|
|
{
|
|
struct wcd_usbss_ctxt *priv = wcd_usbss_ctxt_;
|
|
int rc = 0;
|
|
|
|
switch (req_state) {
|
|
case WCD_USBSS_LPD_USB_MODE_CLEAR:
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_PMP_OUT1, 0x20, 0x00);
|
|
/* Enable D+/D- 1M & 400K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_BIAS_TOP, 0x20, 0x00);
|
|
|
|
/* Enable DP/DN 2K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DP_BIAS, 0x01, 0x01);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DN_BIAS, 0x01, 0x01);
|
|
|
|
if (!wcd_usbss_ctxt_->usb_sbu_compliance) {
|
|
/* Enable SBU1/2 2K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG1_BIAS, 0x01, 0x01);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG2_BIAS, 0x01, 0x01);
|
|
}
|
|
/* Disconnect D+/D- switch */
|
|
wcd_usbss_dpdm_switch_update_from_handler(false, false);
|
|
|
|
/* Enter standby */
|
|
wcd_usbss_standby_control_locked(true);
|
|
break;
|
|
case WCD_USBSS_LPD_CONTINUOUS_1:
|
|
fallthrough;
|
|
case WCD_USBSS_LPD_CONTINUOUS_2:
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_PMP_OUT1, 0x20, 0x20);
|
|
/* Disable SBU1/2 2K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG1_BIAS, 0x01, 0x00);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG2_BIAS, 0x01, 0x00);
|
|
/* USB Mode : Connect D+/D- switch */
|
|
wcd_usbss_dpdm_switch_connect(priv, true);
|
|
|
|
/* Exit from standby */
|
|
wcd_usbss_standby_control_locked(false);
|
|
break;
|
|
case WCD_USBSS_LPD_MODE_SET:
|
|
fallthrough;
|
|
case WCD_USBSS_LPD_USB_MODE_SET:
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_PMP_OUT1, 0x20, 0x20);
|
|
/* Disable DP/DN 2K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DP_BIAS, 0x01, 0x00);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DN_BIAS, 0x01, 0x00);
|
|
/* Disable D+/D- 1M & 400K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_BIAS_TOP, 0x20, 0x20);
|
|
|
|
if (!wcd_usbss_ctxt_->usb_sbu_compliance) {
|
|
/* Disable SBU1/2 2K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG1_BIAS, 0x01, 0x00);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG2_BIAS, 0x01, 0x00);
|
|
}
|
|
/* USB Mode : Connect D+/D- switch */
|
|
wcd_usbss_dpdm_switch_connect(priv, true);
|
|
|
|
/* Exit from standby */
|
|
wcd_usbss_standby_control_locked(false);
|
|
break;
|
|
case WCD_USBSS_USB_MODE_SET:
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_PMP_OUT1, 0x20, 0x00);
|
|
/* Enable D+/D- 1M & 400K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_BIAS_TOP, 0x20, 0x00);
|
|
/* Enable DP/DN 2K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DP_BIAS, 0x01, 0x01);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DN_BIAS, 0x01, 0x01);
|
|
|
|
if (!wcd_usbss_ctxt_->usb_sbu_compliance) {
|
|
/* Enable SBU1/2 2K PLDN */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG1_BIAS, 0x01, 0x01);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG2_BIAS, 0x01, 0x01);
|
|
}
|
|
|
|
/* Connect D+/D- switch */
|
|
wcd_usbss_dpdm_switch_connect(priv, true);
|
|
|
|
/* Exit from standby */
|
|
wcd_usbss_standby_control_locked(false);
|
|
break;
|
|
default:
|
|
dev_err(priv->dev, "unexpected state:%d\n", req_state);
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static irqreturn_t wcd_usbss_sdam_notifier_handler(int irq, void *data)
|
|
{
|
|
struct wcd_usbss_ctxt *priv = data;
|
|
u8 *buf;
|
|
size_t len = 0;
|
|
int rc = 0;
|
|
|
|
buf = nvmem_cell_read(priv->nvmem_cell, &len);
|
|
if (IS_ERR(buf)) {
|
|
rc = PTR_ERR(buf);
|
|
dev_err(priv->dev, "nvmem cell read failed, rc:%d\n", rc);
|
|
return rc;
|
|
}
|
|
buf[0] &= 0x7;
|
|
dev_dbg(priv->dev, "sdam notifier request:%d\n", buf[0]);
|
|
|
|
mutex_lock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
if (buf[0] == priv->wcd_standby_status) {
|
|
dev_info(priv->dev, "%s: wcd already in %s mode:\n", __func__,
|
|
status_to_str(priv->wcd_standby_status));
|
|
goto unlock_mutex;
|
|
}
|
|
|
|
rc = acquire_runtime_env(wcd_usbss_ctxt_);
|
|
if (rc == -EACCES) {
|
|
dev_dbg(priv->dev, "%s: acquire_runtime_env failed: %d, check suspend\n",
|
|
__func__, rc);
|
|
} else if (rc < 0) {
|
|
dev_err(priv->dev, "%s: acquire_runtime_env failed: %d\n",
|
|
__func__, rc);
|
|
goto unlock_mutex;
|
|
}
|
|
|
|
if (wcd_usbss_ctxt_->suspended) {
|
|
wcd_usbss_ctxt_->defer_writes = true;
|
|
wcd_usbss_ctxt_->req_state = buf[0];
|
|
dev_dbg(priv->dev, "i2c in suspend, deferring %s transition to resume\n",
|
|
status_to_str(wcd_usbss_ctxt_->req_state));
|
|
goto release_runtime;
|
|
}
|
|
|
|
dev_dbg(priv->dev, "executing wcd state transition from %s to %s\n",
|
|
status_to_str(priv->wcd_standby_status), status_to_str(buf[0]));
|
|
|
|
rc = wcd_usbss_sdam_handle_events_locked(buf[0]);
|
|
if (rc == 0) {
|
|
priv->wcd_standby_status = buf[0];
|
|
dev_dbg(priv->dev, "wcd state transition to %s complete\n",
|
|
status_to_str(priv->wcd_standby_status));
|
|
}
|
|
release_runtime:
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
unlock_mutex:
|
|
mutex_unlock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
kfree(buf);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int wcd_usbss_sdam_registration(struct wcd_usbss_ctxt *priv)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!priv)
|
|
return -EINVAL;
|
|
|
|
priv->wcd_standby_status = WCD_USBSS_USB_MODE_SET;
|
|
priv->nvmem_cell = devm_nvmem_cell_get(priv->dev, "usb_mode");
|
|
if (IS_ERR(priv->nvmem_cell)) {
|
|
rc = PTR_ERR(priv->nvmem_cell);
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(priv->dev, "nvmem cell get failed, rc:%d\n", rc);
|
|
goto exit;
|
|
}
|
|
/* client->irq = of_get_irq( ); not required i2c_client->irq is populated */
|
|
rc = devm_request_threaded_irq(priv->dev, priv->client->irq, NULL,
|
|
wcd_usbss_sdam_notifier_handler, IRQF_ONESHOT,
|
|
"wcd-usbss-sdam", priv);
|
|
if (rc) {
|
|
dev_err(priv->dev, "sdam registration failed, standby not supported, rc:%d\n",
|
|
rc);
|
|
} else {
|
|
enable_irq_wake(priv->client->irq);
|
|
}
|
|
|
|
exit:
|
|
if (rc == 0)
|
|
dev_info(priv->dev, "sdam registration successful\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int wcd_usbss_probe(struct i2c_client *i2c)
|
|
{
|
|
struct wcd_usbss_ctxt *priv;
|
|
struct device *dev = &i2c->dev;
|
|
int rc = 0, i;
|
|
unsigned int ver = 0;
|
|
|
|
priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->dev = &i2c->dev;
|
|
priv->client = i2c;
|
|
priv->runtime_env_counter = 0;
|
|
mutex_init(&priv->io_lock);
|
|
mutex_init(&priv->switch_update_lock);
|
|
mutex_init(&priv->runtime_env_counter_lock);
|
|
i2c_set_clientdata(i2c, priv);
|
|
|
|
pm_runtime_enable(dev);
|
|
pm_runtime_use_autosuspend(dev);
|
|
pm_runtime_set_autosuspend_delay(dev, 600);
|
|
device_init_wakeup(priv->dev, true);
|
|
rc = acquire_runtime_env(priv);
|
|
if (rc < 0) {
|
|
dev_err(wcd_usbss_ctxt_->dev, "%s: acquire_runtime_env failed: %i\n",
|
|
__func__, rc);
|
|
goto err_data;
|
|
}
|
|
|
|
if (ARRAY_SIZE(supply_names) >= WCD_USBSS_SUPPLY_MAX) {
|
|
dev_err(priv->dev, "Unsupported number of supplies: %ld\n",
|
|
ARRAY_SIZE(supply_names));
|
|
rc = -EINVAL;
|
|
goto err_data;
|
|
}
|
|
for (i = 0; i < ARRAY_SIZE(supply_names); ++i)
|
|
priv->supplies[i].supply = supply_names[i];
|
|
|
|
rc = devm_regulator_bulk_get(priv->dev, ARRAY_SIZE(supply_names),
|
|
priv->supplies);
|
|
if (rc < 0) {
|
|
dev_err(priv->dev, "Failed to get supplies: %d\n", rc);
|
|
goto err_data;
|
|
}
|
|
|
|
rc = regulator_bulk_enable(ARRAY_SIZE(supply_names), priv->supplies);
|
|
if (rc) {
|
|
dev_err(priv->dev, "Failed to enable supplies: %d\n", rc);
|
|
goto err_data;
|
|
}
|
|
|
|
rc = wcd_usbss_init_optional_reset_pins(priv);
|
|
if (rc) {
|
|
dev_dbg(priv->dev, "%s: Optional reset pin reset failed\n",
|
|
__func__);
|
|
rc = 0;
|
|
}
|
|
wcd_usbss_regmap_config.readable_reg = wcd_usbss_readable_register;
|
|
priv->regmap = wcd_usbss_regmap_init(priv->dev, &wcd_usbss_regmap_config);
|
|
if (IS_ERR_OR_NULL(priv->regmap)) {
|
|
rc = PTR_ERR(priv->regmap);
|
|
if (!priv->regmap)
|
|
rc = -EINVAL;
|
|
|
|
dev_err(priv->dev, "Failed to initialize regmap: %d\n", rc);
|
|
goto err_data;
|
|
}
|
|
if (of_find_property(i2c->dev.of_node, "wcd-usb-sbu-compliance", NULL)) {
|
|
dev_dbg(priv->dev, "disabling SBU pulldowns for USB compliance\n");
|
|
priv->usb_sbu_compliance = true;
|
|
/*
|
|
* external zener diode installed,
|
|
* disable OVP protections on SBUx. SBUx pull-downs are disabled.
|
|
* This is needed for USB compliance requirement (related to
|
|
* impedance) on SBUx
|
|
*/
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG1_BIAS, 0x01, 0x00);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG2_BIAS, 0x01, 0x00);
|
|
}
|
|
|
|
/* OVP-Fuse settings recommended from HW */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_FSM_OVERRIDE, 0x77, 0x77);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DP_EN, 0x0E, 0x08);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DN_EN, 0x0E, 0x08);
|
|
|
|
/* Display common mode and OVP 4V updates */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DISP_AUXP_CTL, 0x07, 0x01);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DISP_AUXP_THRESH, 0xE0, 0xE0);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_DISP_AUXM_THRESH, 0xE0, 0xE0);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG1_EN, 0x0C, 0x0C);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG2_EN, 0x0C, 0x0C);
|
|
|
|
regmap_read(priv->regmap, WCD_USBSS_CHIP_ID1, &ver);
|
|
if (ver == 0x1) { /* Harmonium 2.0 */
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG1_EN, 0x2, 0x0);
|
|
regmap_update_bits(priv->regmap, WCD_USBSS_MG2_EN, 0x2, 0x0);
|
|
}
|
|
priv->version = ver;
|
|
|
|
devm_regmap_qti_debugfs_register(priv->dev, priv->regmap);
|
|
|
|
wcd_usbss_ctxt_ = priv;
|
|
|
|
i2c_set_clientdata(i2c, priv);
|
|
|
|
rc = wcd_usbss_sdam_registration(priv);
|
|
if (rc == 0)
|
|
priv->standby_enable = true;
|
|
else
|
|
dev_info(priv->dev, "wcd standby feature not enabled\n");
|
|
|
|
priv->ucsi_nb.notifier_call = wcd_usbss_usbc_event_changed;
|
|
priv->ucsi_nb.priority = 0;
|
|
rc = register_ucsi_glink_notifier(&priv->ucsi_nb);
|
|
if (rc) {
|
|
dev_err(priv->dev, "%s: ucsi glink notifier registration failed: %d\n",
|
|
__func__, rc);
|
|
goto err_data;
|
|
}
|
|
|
|
mutex_init(&priv->notification_lock);
|
|
|
|
wcd_usbss_update_reg_init(priv->regmap);
|
|
INIT_WORK(&priv->usbc_analog_work,
|
|
wcd_usbss_usbc_analog_work_fn);
|
|
BLOCKING_INIT_NOTIFIER_HEAD(&priv->wcd_usbss_notifier);
|
|
|
|
rc = wcd_usbss_sysfs_init(priv);
|
|
if (rc == 0) {
|
|
priv->surge_timer_period_ms = DEFAULT_SURGE_TIMER_PERIOD_MS;
|
|
priv->surge_enable = true;
|
|
wcd_usbss_enable_surge_kthread();
|
|
}
|
|
|
|
release_runtime_env(wcd_usbss_ctxt_);
|
|
dev_info(priv->dev, "Probe completed!\n");
|
|
return 0;
|
|
err_data:
|
|
device_init_wakeup(priv->dev, false);
|
|
pm_runtime_dont_use_autosuspend(wcd_usbss_ctxt_->dev);
|
|
pm_runtime_disable(wcd_usbss_ctxt_->dev);
|
|
return rc;
|
|
}
|
|
|
|
static void wcd_usbss_remove(struct i2c_client *i2c)
|
|
{
|
|
int error;
|
|
struct wcd_usbss_ctxt *priv =
|
|
(struct wcd_usbss_ctxt *)i2c_get_clientdata(i2c);
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
error = pm_runtime_resume_and_get(priv->dev);
|
|
if (error < 0)
|
|
dev_err(priv->dev, "%s: pm_runtime_resume_and_get failed: %i\n",
|
|
__func__, error);
|
|
|
|
wcd_usbss_disable_surge_kthread();
|
|
unregister_ucsi_glink_notifier(&priv->ucsi_nb);
|
|
cancel_work_sync(&priv->usbc_analog_work);
|
|
pm_relax(priv->dev);
|
|
mutex_destroy(&priv->notification_lock);
|
|
mutex_destroy(&priv->io_lock);
|
|
mutex_destroy(&priv->switch_update_lock);
|
|
mutex_destroy(&priv->runtime_env_counter_lock);
|
|
if (error >= 0)
|
|
pm_runtime_put_sync(priv->dev);
|
|
pm_runtime_dont_use_autosuspend(priv->dev);
|
|
pm_runtime_disable(priv->dev);
|
|
device_init_wakeup(priv->dev, false);
|
|
dev_set_drvdata(&i2c->dev, NULL);
|
|
wcd_usbss_ctxt_ = NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int wcd_usbss_pm_suspend(struct device *dev)
|
|
{
|
|
if (!wcd_usbss_ctxt_)
|
|
return 0;
|
|
|
|
mutex_lock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
wcd_usbss_ctxt_->suspended = true;
|
|
mutex_unlock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "wcd usbss pm suspended");
|
|
return 0;
|
|
}
|
|
|
|
static int wcd_usbss_pm_resume(struct device *dev)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!wcd_usbss_ctxt_)
|
|
return 0;
|
|
|
|
mutex_lock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
if (wcd_usbss_ctxt_->defer_writes) {
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "wcd defer writes in progress");
|
|
rc = wcd_usbss_sdam_handle_events_locked(wcd_usbss_ctxt_->req_state);
|
|
wcd_usbss_ctxt_->defer_writes = false;
|
|
if (rc == 0) {
|
|
wcd_usbss_ctxt_->wcd_standby_status = wcd_usbss_ctxt_->req_state;
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "wcd state transition to %s complete\n",
|
|
status_to_str(wcd_usbss_ctxt_->wcd_standby_status));
|
|
}
|
|
}
|
|
wcd_usbss_ctxt_->suspended = false;
|
|
mutex_unlock(&wcd_usbss_ctxt_->switch_update_lock);
|
|
|
|
dev_dbg(wcd_usbss_ctxt_->dev, "wcd usbss pm resume completed");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct of_device_id wcd_usbss_i2c_dt_match[] = {
|
|
{
|
|
.compatible = "qcom,wcd939x-i2c",
|
|
},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, wcd_usbss_i2c_dt_match);
|
|
|
|
static const struct i2c_device_id wcd_usbss_id_i2c[] = {
|
|
{ "wcd939x", 0 },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, wcd_usbss_id_i2c);
|
|
|
|
static const struct dev_pm_ops wcd_usbss_pm_ops = {
|
|
.suspend_late = wcd_usbss_pm_suspend,
|
|
.resume_early = wcd_usbss_pm_resume,
|
|
};
|
|
|
|
static struct i2c_driver wcd_usbss_i2c_driver = {
|
|
.driver = {
|
|
.name = WCD_USBSS_I2C_NAME,
|
|
.of_match_table = wcd_usbss_i2c_dt_match,
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = &wcd_usbss_pm_ops,
|
|
#endif
|
|
},
|
|
.id_table = wcd_usbss_id_i2c,
|
|
.probe = wcd_usbss_probe,
|
|
.remove = wcd_usbss_remove,
|
|
};
|
|
module_i2c_driver(wcd_usbss_i2c_driver);
|
|
|
|
MODULE_DESCRIPTION("WCD USBSS I2C driver");
|
|
MODULE_LICENSE("GPL");
|