|
@@ -20,6 +20,8 @@
|
|
|
#include <sound/soc-dapm.h>
|
|
|
#include <asoc/wcdcal-hwdep.h>
|
|
|
#include <asoc/wcd-mbhc-v2-api.h>
|
|
|
+#include <linux/sysfs.h>
|
|
|
+#include <linux/kobject.h>
|
|
|
#include "wcd939x-registers.h"
|
|
|
#include "internal.h"
|
|
|
#if IS_ENABLED(CONFIG_QCOM_WCD_USBSS_I2C)
|
|
@@ -952,6 +954,310 @@ err_data:
|
|
|
*gnd_tap = 0;
|
|
|
}
|
|
|
|
|
|
+struct usbcss_hs_attr {
|
|
|
+ struct wcd939x_priv *priv;
|
|
|
+ struct kobj_attribute attr;
|
|
|
+ int index;
|
|
|
+};
|
|
|
+
|
|
|
+static char *usbcss_sysfs_files[] = {
|
|
|
+ "rdson",
|
|
|
+ "r2",
|
|
|
+ "r3",
|
|
|
+ "r4",
|
|
|
+ "r5",
|
|
|
+ "r6",
|
|
|
+ "r7",
|
|
|
+ "lin-k-aud",
|
|
|
+ "lin-k-gnd",
|
|
|
+ "xtalk_config",
|
|
|
+};
|
|
|
+
|
|
|
+static ssize_t usbcss_sysfs_store(struct kobject *kobj,
|
|
|
+ struct kobj_attribute *attr, const char *buf,
|
|
|
+ size_t count)
|
|
|
+{
|
|
|
+ struct usbcss_hs_attr *usbc_attr;
|
|
|
+ struct wcd939x_priv *wcd939x;
|
|
|
+ struct wcd939x_pdata *pdata;
|
|
|
+ struct wcd939x_usbcss_hs_params *usbcss_hs;
|
|
|
+ long val;
|
|
|
+ int rc;
|
|
|
+ u32 aud_tap = 0, gnd_tap = 0;
|
|
|
+ bool update_xtalk = false, update_linearizer = false;
|
|
|
+
|
|
|
+ usbc_attr = container_of(attr, struct usbcss_hs_attr, attr);
|
|
|
+ wcd939x = usbc_attr->priv;
|
|
|
+ pdata = dev_get_platdata(wcd939x->dev);
|
|
|
+
|
|
|
+ if (!wcd939x || !pdata)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ usbcss_hs = &pdata->usbcss_hs;
|
|
|
+
|
|
|
+ rc = kstrtol(buf, 0, &val);
|
|
|
+ if (rc)
|
|
|
+ return rc;
|
|
|
+
|
|
|
+ if (strcmp(attr->attr.name, "rdson") == 0) {
|
|
|
+ if (val > MAX_USBCSS_HS_IMPEDANCE_MOHMS) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of HS impedance range %d\n",
|
|
|
+ __func__, val, MAX_USBCSS_HS_IMPEDANCE_MOHMS);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->r_gnd_ext_fet_customer_mohms = val;
|
|
|
+ update_linearizer = usbcss_hs->xtalk_config == XTALK_ANALOG;
|
|
|
+ } else if (strcmp(attr->attr.name, "r2") == 0) {
|
|
|
+ if (val > MAX_USBCSS_HS_IMPEDANCE_MOHMS) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of HS impedance range %d\n",
|
|
|
+ __func__, val, MAX_USBCSS_HS_IMPEDANCE_MOHMS);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->r_conn_par_load_pos_mohms = val;
|
|
|
+ } else if (strcmp(attr->attr.name, "r3") == 0) {
|
|
|
+ if (val > MAX_USBCSS_HS_IMPEDANCE_MOHMS) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of HS impedance range %d\n",
|
|
|
+ __func__, val, MAX_USBCSS_HS_IMPEDANCE_MOHMS);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->r3 = val;
|
|
|
+ update_linearizer = true;
|
|
|
+ } else if (strcmp(attr->attr.name, "r4") == 0) {
|
|
|
+ if (val > MAX_USBCSS_HS_IMPEDANCE_MOHMS) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of HS impedance range %d\n",
|
|
|
+ __func__, val, MAX_USBCSS_HS_IMPEDANCE_MOHMS);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->r4 = val;
|
|
|
+ update_xtalk = true;
|
|
|
+ update_linearizer = true;
|
|
|
+
|
|
|
+ switch (usbcss_hs->xtalk_config) {
|
|
|
+ case XTALK_DIGITAL:
|
|
|
+ usbcss_hs->r_gnd_par_route2_mohms = usbcss_hs->r6 + val;
|
|
|
+ break;
|
|
|
+ case XTALK_ANALOG:
|
|
|
+ usbcss_hs->r_gnd_par_route1_mohms = usbcss_hs->r5 + val;
|
|
|
+ break;
|
|
|
+ case XTALK_NONE:
|
|
|
+ fallthrough;
|
|
|
+ default:
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ } else if (strcmp(attr->attr.name, "r5") == 0) {
|
|
|
+ if (val > MAX_USBCSS_HS_IMPEDANCE_MOHMS) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of HS impedance range %d\n",
|
|
|
+ __func__, val, MAX_USBCSS_HS_IMPEDANCE_MOHMS);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->r5 = val;
|
|
|
+
|
|
|
+ switch (usbcss_hs->xtalk_config) {
|
|
|
+ case XTALK_ANALOG:
|
|
|
+ update_xtalk = true;
|
|
|
+ update_linearizer = true;
|
|
|
+ usbcss_hs->r_gnd_par_route1_mohms = val + usbcss_hs->r4;
|
|
|
+ break;
|
|
|
+ case XTALK_DIGITAL:
|
|
|
+ fallthrough;
|
|
|
+ case XTALK_NONE:
|
|
|
+ fallthrough;
|
|
|
+ default:
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ } else if (strcmp(attr->attr.name, "r6") == 0) {
|
|
|
+ if (val > MAX_USBCSS_HS_IMPEDANCE_MOHMS) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of HS impedance range %d\n",
|
|
|
+ __func__, val, MAX_USBCSS_HS_IMPEDANCE_MOHMS);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->r6 = val;
|
|
|
+
|
|
|
+ switch (usbcss_hs->xtalk_config) {
|
|
|
+ case XTALK_DIGITAL:
|
|
|
+ update_xtalk = true;
|
|
|
+ update_linearizer = true;
|
|
|
+ usbcss_hs->r_gnd_par_route2_mohms = val + usbcss_hs->r4;
|
|
|
+ break;
|
|
|
+ case XTALK_ANALOG:
|
|
|
+ fallthrough;
|
|
|
+ case XTALK_NONE:
|
|
|
+ fallthrough;
|
|
|
+ default:
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ } else if (strcmp(attr->attr.name, "r7") == 0) {
|
|
|
+ if (val > MAX_USBCSS_HS_IMPEDANCE_MOHMS) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of HS impedance range %d\n",
|
|
|
+ __func__, val, MAX_USBCSS_HS_IMPEDANCE_MOHMS);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->r7 = val;
|
|
|
+
|
|
|
+ switch (usbcss_hs->xtalk_config) {
|
|
|
+ case XTALK_DIGITAL:
|
|
|
+ update_xtalk = true;
|
|
|
+ update_linearizer = true;
|
|
|
+ usbcss_hs->r_gnd_par_route1_mohms = val;
|
|
|
+ break;
|
|
|
+ case XTALK_ANALOG:
|
|
|
+ fallthrough;
|
|
|
+ case XTALK_NONE:
|
|
|
+ fallthrough;
|
|
|
+ default:
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ } else if (strcmp(attr->attr.name, "lin-k-aud") == 0) {
|
|
|
+ if (val < MIN_K_TIMES_100 || val > MAX_K_TIMES_100) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of bounds. Min: %d, Max: %d\n",
|
|
|
+ __func__, val, MIN_K_TIMES_100, MAX_K_TIMES_100);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->k_aud_times_100 = val;
|
|
|
+ update_linearizer = true;
|
|
|
+ } else if (strcmp(attr->attr.name, "lin-k-gnd") == 0) {
|
|
|
+ if (val < MIN_K_TIMES_100 || val > MAX_K_TIMES_100) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Value %d out of bounds. Min: %d, Max: %d\n",
|
|
|
+ __func__, val, MIN_K_TIMES_100, MAX_K_TIMES_100);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ usbcss_hs->k_gnd_times_100 = val;
|
|
|
+ update_linearizer = true;
|
|
|
+ } else if (strcmp(attr->attr.name, "xtalk_config") == 0) {
|
|
|
+ pdata->usbcss_hs.xtalk_config = val;
|
|
|
+ update_xtalk = true;
|
|
|
+
|
|
|
+ switch (val) {
|
|
|
+ case XTALK_NONE:
|
|
|
+ usbcss_hs->scale_l = MAX_XTALK_SCALE;
|
|
|
+ usbcss_hs->scale_r = MAX_XTALK_SCALE;
|
|
|
+ usbcss_hs->alpha_l = MIN_XTALK_ALPHA;
|
|
|
+ usbcss_hs->alpha_r = MIN_XTALK_ALPHA;
|
|
|
+ break;
|
|
|
+ case XTALK_DIGITAL:
|
|
|
+ usbcss_hs->r_gnd_par_route2_mohms = usbcss_hs->r6 + usbcss_hs->r4;
|
|
|
+ usbcss_hs->r_gnd_par_route1_mohms = usbcss_hs->r7;
|
|
|
+ update_linearizer = true;
|
|
|
+ break;
|
|
|
+ case XTALK_ANALOG:
|
|
|
+ usbcss_hs->r_gnd_par_route1_mohms = usbcss_hs->r5 + usbcss_hs->r4;
|
|
|
+ usbcss_hs->r_gnd_par_route2_mohms = 1;
|
|
|
+ update_linearizer = true;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (update_xtalk) {
|
|
|
+ update_xtalk_scale_and_alpha(pdata, wcd939x->regmap);
|
|
|
+ regmap_update_bits(wcd939x->regmap, WCD939X_HPHL_RX_PATH_SEC0,
|
|
|
+ 0x1F, pdata->usbcss_hs.scale_l);
|
|
|
+ regmap_update_bits(wcd939x->regmap, WCD939X_HPHL_RX_PATH_SEC1,
|
|
|
+ 0xFF, pdata->usbcss_hs.alpha_l);
|
|
|
+ regmap_update_bits(wcd939x->regmap, WCD939X_HPHL_RX_PATH_SEC0 + 1,
|
|
|
+ 0x1F, pdata->usbcss_hs.scale_r);
|
|
|
+ regmap_update_bits(wcd939x->regmap, WCD939X_HPHL_RX_PATH_SEC1 + 1,
|
|
|
+ 0xFF, pdata->usbcss_hs.alpha_r);
|
|
|
+ dev_err(wcd939x->dev, "%s: Updated xtalk thru sysfs\n",
|
|
|
+ __func__);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (update_linearizer) {
|
|
|
+ get_linearizer_taps(pdata, &aud_tap, &gnd_tap);
|
|
|
+ wcd_usbss_set_linearizer_sw_tap(aud_tap, gnd_tap);
|
|
|
+ dev_err(wcd939x->dev, "%s: Updated linearizer thru sysfs\n",
|
|
|
+ __func__);
|
|
|
+ }
|
|
|
+
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t usbcss_sysfs_show(struct kobject *kobj,
|
|
|
+ struct kobj_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct usbcss_hs_attr *usbc_attr;
|
|
|
+ struct wcd939x_priv *wcd939x;
|
|
|
+ struct wcd939x_pdata *pdata;
|
|
|
+
|
|
|
+ usbc_attr = container_of(attr, struct usbcss_hs_attr, attr);
|
|
|
+ wcd939x = usbc_attr->priv;
|
|
|
+ pdata = dev_get_platdata(wcd939x->dev);
|
|
|
+
|
|
|
+ if (strcmp(attr->attr.name, "rdson") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.r_gnd_ext_fet_customer_mohms);
|
|
|
+ else if (strcmp(attr->attr.name, "r2") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.r_conn_par_load_pos_mohms);
|
|
|
+ else if (strcmp(attr->attr.name, "r3") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.r3);
|
|
|
+ else if (strcmp(attr->attr.name, "r4") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.r4);
|
|
|
+ else if (strcmp(attr->attr.name, "r5") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.r5);
|
|
|
+ else if (strcmp(attr->attr.name, "r6") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.r6);
|
|
|
+ else if (strcmp(attr->attr.name, "r7") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.r7);
|
|
|
+ else if (strcmp(attr->attr.name, "lin-k-aud") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.k_aud_times_100);
|
|
|
+ else if (strcmp(attr->attr.name, "lin-k-gnd") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.k_gnd_times_100);
|
|
|
+ else if (strcmp(attr->attr.name, "xtalk_config") == 0)
|
|
|
+ return scnprintf(buf, 10, "%d\n", pdata->usbcss_hs.xtalk_config);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int create_sysfs_entry_file(struct wcd939x_priv *wcd939x, char *name, int mode,
|
|
|
+ int index, struct kobject *parent)
|
|
|
+{
|
|
|
+ struct usbcss_hs_attr *usbc_attr;
|
|
|
+ char *name_copy;
|
|
|
+
|
|
|
+ usbc_attr = devm_kmalloc(wcd939x->dev, sizeof(*usbc_attr), GFP_KERNEL);
|
|
|
+ if (!usbc_attr)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ name_copy = devm_kstrdup(wcd939x->dev, name, GFP_KERNEL);
|
|
|
+ if (!name_copy)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ usbc_attr->priv = wcd939x;
|
|
|
+ usbc_attr->index = index;
|
|
|
+ usbc_attr->attr.attr.name = name_copy;
|
|
|
+ usbc_attr->attr.attr.mode = mode;
|
|
|
+ usbc_attr->attr.show = usbcss_sysfs_show;
|
|
|
+ usbc_attr->attr.store = usbcss_sysfs_store;
|
|
|
+ sysfs_attr_init(&usbc_attr->attr.attr);
|
|
|
+
|
|
|
+ return sysfs_create_file(parent, &usbc_attr->attr.attr);
|
|
|
+}
|
|
|
+
|
|
|
+static int usbcss_hs_sysfs_init(struct wcd939x_priv *wcd939x)
|
|
|
+{
|
|
|
+ int rc = 0;
|
|
|
+ int i = 0;
|
|
|
+ struct kobject *kobj = NULL;
|
|
|
+
|
|
|
+ if (!wcd939x || !wcd939x->dev) {
|
|
|
+ pr_err("%s: Invalid wcd939x private data.\n", __func__);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ kobj = kobject_create_and_add("usbcss_hs", kernel_kobj);
|
|
|
+ if (!kobj) {
|
|
|
+ dev_err(wcd939x->dev, "%s: Could not create the USBC-SS HS kobj.\n", __func__);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(usbcss_sysfs_files); i++) {
|
|
|
+ rc = create_sysfs_entry_file(wcd939x, usbcss_sysfs_files[i],
|
|
|
+ 0644, i, kobj);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static void wcd939x_wcd_mbhc_calc_impedance(struct wcd_mbhc *mbhc, uint32_t *zl, uint32_t *zr)
|
|
|
{
|
|
|
struct snd_soc_component *component = mbhc->component;
|
|
@@ -1607,6 +1913,7 @@ int wcd939x_mbhc_init(struct wcd939x_mbhc **mbhc,
|
|
|
struct wcd_mbhc *wcd_mbhc = NULL;
|
|
|
int ret = 0;
|
|
|
struct wcd939x_pdata *pdata;
|
|
|
+ struct wcd939x_priv *wcd939x;
|
|
|
|
|
|
if (!component) {
|
|
|
pr_err("%s: component is NULL\n", __func__);
|
|
@@ -1659,6 +1966,14 @@ int wcd939x_mbhc_init(struct wcd939x_mbhc **mbhc,
|
|
|
snd_soc_add_component_controls(component, hph_type_detect_controls,
|
|
|
ARRAY_SIZE(hph_type_detect_controls));
|
|
|
|
|
|
+ wcd939x = dev_get_drvdata(component->dev);
|
|
|
+ if (!wcd939x) {
|
|
|
+ dev_err(component->dev, "%s: wcd939x pointer is NULL\n", __func__);
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ usbcss_hs_sysfs_init(wcd939x);
|
|
|
+
|
|
|
return 0;
|
|
|
err:
|
|
|
if (wcd939x_mbhc)
|