// SPDX-License-Identifier: GPL-2.0
//
// CS42L43 CODEC driver jack handling
//
// Copyright (C) 2022-2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
#include <linux/build_bug.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/irq.h>
#include <linux/jiffies.h>
#include <linux/mfd/cs42l43.h>
#include <linux/mfd/cs42l43-regs.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/time.h>
#include <linux/workqueue.h>
#include <sound/control.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc-component.h>
#include <sound/soc-jack.h>
#include <sound/soc.h>
#include "cs42l43.h"
static const unsigned int cs42l43_accdet_us[] = {
20, 100, 1000, 10000, 50000, 75000, 100000, 200000,
};
static const unsigned int cs42l43_accdet_db_ms[] = {
0, 125, 250, 500, 750, 1000, 1250, 1500,
};
static const unsigned int cs42l43_accdet_ramp_ms[] = { 10, 40, 90, 170 };
static const unsigned int cs42l43_accdet_bias_sense[] = {
14, 24, 43, 52, 61, 71, 90, 99, 0,
};
static int cs42l43_find_index(struct cs42l43_codec *priv, const char * const prop,
unsigned int defval, unsigned int *val,
const unsigned int *values, const int nvalues)
{
struct cs42l43 *cs42l43 = priv->core;
int i, ret;
ret = device_property_read_u32(cs42l43->dev, prop, &defval);
if (ret != -EINVAL && ret < 0) {
dev_err(priv->dev, "Property %s malformed: %d\n", prop, ret);
return ret;
}
if (val)
*val = defval;
for (i = 0; i < nvalues; i++)
if (defval == values[i])
return i;
dev_err(priv->dev, "Invalid value for property %s: %d\n", prop, defval);
return -EINVAL;
}
int cs42l43_set_jack(struct snd_soc_component *component,
struct snd_soc_jack *jack, void *d)
{
struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component);
struct cs42l43 *cs42l43 = priv->core;
/* This tip sense invert is always set, HW wants an inverted signal */
unsigned int tip_deb = CS42L43_TIPSENSE_INV_MASK;
unsigned int hs2 = 0x2 << CS42L43_HSDET_MODE_SHIFT;
unsigned int autocontrol = 0, pdncntl = 0;
int ret;
dev_dbg(priv->dev, "Configure accessory detect\n");
ret = pm_runtime_resume_and_get(priv->dev);
if (ret) {
dev_err(priv->dev, "Failed to resume for jack config: %d\n", ret);
return ret;
}
mutex_lock(&priv->jack_lock);
priv->jack_hp = jack;
if (!jack)
goto done;
ret = device_property_count_u32(cs42l43->dev, "cirrus,buttons-ohms");
if (ret != -EINVAL) {
if (ret < 0) {
dev_err(priv->dev, "Property cirrus,buttons-ohms malformed: %d\n",
ret);
goto error;
}
if (ret > CS42L43_N_BUTTONS) {
ret = -EINVAL;
dev_err(priv->dev, "Property cirrus,buttons-ohms too many entries\n");
goto error;
}
ret = device_property_read_u32_array(cs42l43->dev, "cirrus,buttons-ohms",
priv->buttons, ret);
if (ret < 0) {
dev_err(priv->dev, "Property cirrus,button-ohms malformed: %d\n",
ret);
goto error;
}
} else {
priv->buttons[0] = 70;
priv->buttons[1] = 185;
priv->buttons[2] = 355;
priv->buttons[3] = 735;
}
ret = cs42l43_find_index(priv, "cirrus,detect-us", 50000, &priv->detect_us,
cs42l43_accdet_us, ARRAY_SIZE(cs42l43_accdet_us));
if (ret < 0)
goto error;
hs2 |= ret << CS42L43_AUTO_HSDET_TIME_SHIFT;
priv->bias_low = device_property_read_bool(cs42l43->dev, "cirrus,bias-low");
ret = cs42l43_find_index(priv, "cirrus,bias-ramp-ms", 170,
&priv->bias_ramp_ms, cs42l43_accdet_ramp_ms,
ARRAY_SIZE(cs42l43_accdet_ramp_ms));
if (ret < 0)
goto error;
hs2 |= ret << CS42L43_HSBIAS_RAMP_SHIFT;
ret = cs42l43_find_index(priv, "cirrus,bias-sense-microamp", 14,
&priv->bias_sense_ua, cs42l43_accdet_bias_sense,
ARRAY_SIZE(cs42l43_accdet_bias_sense));
if (ret < 0)
goto error;
if (priv->bias_sense_ua)
autocontrol |= ret << CS42L43_HSBIAS_SENSE_TRIP_SHIFT;
if (!device_property_read_bool(cs42l43->dev, "cirrus,button-automute"))
autocontrol |= CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK;
ret = device_property_read_u32(cs42l43->dev, "cirrus,tip-debounce-ms",
&priv->tip_debounce_ms);
if (ret < 0 && ret != -EINVAL) {
dev_err(priv->dev, "Property cirrus,tip-debounce-ms malformed: %d\n", ret);
goto error;
}
/* This tip sense invert is set normally, as TIPSENSE_INV already inverted */
if (device_property_read_bool(cs42l43->dev, "cirrus,tip-invert"))
autocontrol |= 0x1 << CS42L43_JACKDET_INV_SHIFT;
if (device_property_read_bool(cs42l43->dev, "cirrus,tip-disable-pullup"))
autocontrol |= 0x1 << CS42L43_JACKDET_MODE_SHIFT;
else