Unverified Commit 6b99dc62 authored by Krzysztof Kozlowski's avatar Krzysztof Kozlowski Committed by Mark Brown
Browse files

ASoC: codecs: wsa884x: Implement temperature reading and hwmon



Read temperature of the speaker and expose it via hwmon interface, which
will be later used during calibration of speaker protection algorithms.

Signed-off-by: default avatarKrzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Link: https://patch.msgid.link/20240809110122.137761-1-krzysztof.kozlowski@linaro.org


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 7817eb1a
Loading
Loading
Loading
Loading
+201 −0
Original line number Diff line number Diff line
@@ -5,11 +5,14 @@
 */

#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -301,8 +304,28 @@
#define WSA884X_PA_FSM_MSK1		(WSA884X_DIG_CTRL0_BASE + 0x3b)
#define WSA884X_PA_FSM_BYP_CTL		(WSA884X_DIG_CTRL0_BASE + 0x3c)
#define WSA884X_PA_FSM_BYP0		(WSA884X_DIG_CTRL0_BASE + 0x3d)
#define WSA884X_PA_FSM_BYP0_DC_CAL_EN_MASK		0x01
#define WSA884X_PA_FSM_BYP0_DC_CAL_EN_SHIFT		0
#define WSA884X_PA_FSM_BYP0_CLK_WD_EN_MASK		0x02
#define WSA884X_PA_FSM_BYP0_CLK_WD_EN_SHIFT		1
#define WSA884X_PA_FSM_BYP0_BG_EN_MASK			0x04
#define WSA884X_PA_FSM_BYP0_BG_EN_SHIFT			2
#define WSA884X_PA_FSM_BYP0_BOOST_EN_MASK		0x08
#define WSA884X_PA_FSM_BYP0_BOOST_EN_SHIFT		3
#define WSA884X_PA_FSM_BYP0_PA_EN_MASK			0x10
#define WSA884X_PA_FSM_BYP0_PA_EN_SHIFT			4
#define WSA884X_PA_FSM_BYP0_D_UNMUTE_MASK		0x20
#define WSA884X_PA_FSM_BYP0_D_UNMUTE_SHIFT		5
#define WSA884X_PA_FSM_BYP0_SPKR_PROT_EN_MASK		0x40
#define WSA884X_PA_FSM_BYP0_SPKR_PROT_EN_SHIFT		6
#define WSA884X_PA_FSM_BYP0_TSADC_EN_MASK		0x80
#define WSA884X_PA_FSM_BYP0_TSADC_EN_SHIFT		7
#define WSA884X_PA_FSM_BYP1		(WSA884X_DIG_CTRL0_BASE + 0x3e)
#define WSA884X_TADC_VALUE_CTL		(WSA884X_DIG_CTRL0_BASE + 0x50)
#define WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK	0x01
#define WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_SHIFT	0
#define WSA884X_TADC_VALUE_CTL_VBAT_VALUE_RD_EN_MASK	0x02
#define WSA884X_TADC_VALUE_CTL_VBAT_VALUE_RD_EN_SHIFT	1
#define WSA884X_TEMP_DETECT_CTL		(WSA884X_DIG_CTRL0_BASE + 0x51)
#define WSA884X_TEMP_DIN_MSB		(WSA884X_DIG_CTRL0_BASE + 0x52)
#define WSA884X_TEMP_DIN_LSB		(WSA884X_DIG_CTRL0_BASE + 0x53)
@@ -691,6 +714,17 @@
		SNDRV_PCM_FMTBIT_S24_LE |\
		SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)

/* Two-point trimming for temperature calibration */
#define WSA884X_T1_TEMP			-10L
#define WSA884X_T2_TEMP			150L

/*
 * Device will report senseless data in many cases, so discard any measurements
 * outside of valid range.
 */
#define WSA884X_LOW_TEMP_THRESHOLD	5
#define WSA884X_HIGH_TEMP_THRESHOLD	45

struct wsa884x_priv {
	struct regmap *regmap;
	struct device *dev;
@@ -706,6 +740,13 @@ struct wsa884x_priv {
	int active_ports;
	int dev_mode;
	bool hw_init;
	/*
	 * Protects temperature reading code (related to speaker protection) and
	 * fields: temperature and pa_on.
	 */
	struct mutex sp_lock;
	unsigned int temperature;
	bool pa_on;
};

enum {
@@ -1660,6 +1701,10 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w,

	switch (event) {
	case SND_SOC_DAPM_POST_PMU:
		mutex_lock(&wsa884x->sp_lock);
		wsa884x->pa_on = true;
		mutex_unlock(&wsa884x->sp_lock);

		wsa884x_spkr_post_pmu(component, wsa884x);

		snd_soc_component_write_field(component, WSA884X_PDM_WD_CTL,
@@ -1671,6 +1716,10 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w,
		snd_soc_component_write_field(component, WSA884X_PDM_WD_CTL,
					      WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK,
					      0x0);

		mutex_lock(&wsa884x->sp_lock);
		wsa884x->pa_on = false;
		mutex_unlock(&wsa884x->sp_lock);
		break;
	}

@@ -1810,6 +1859,144 @@ static struct snd_soc_dai_driver wsa884x_dais[] = {
	},
};

static int wsa884x_get_temp(struct wsa884x_priv *wsa884x, long *temp)
{
	unsigned int d1_msb = 0, d1_lsb = 0, d2_msb = 0, d2_lsb = 0;
	unsigned int dmeas_msb = 0, dmeas_lsb = 0;
	int d1, d2, dmeas;
	unsigned int mask;
	long val;
	int ret;

	guard(mutex)(&wsa884x->sp_lock);

	if (wsa884x->pa_on) {
		/*
		 * Reading temperature is possible only when Power Amplifier is
		 * off. Report last cached data.
		 */
		*temp = wsa884x->temperature;
		return 0;
	}

	ret = pm_runtime_resume_and_get(wsa884x->dev);
	if (ret < 0)
		return ret;

	mask = WSA884X_PA_FSM_BYP0_DC_CAL_EN_MASK |
	       WSA884X_PA_FSM_BYP0_CLK_WD_EN_MASK |
	       WSA884X_PA_FSM_BYP0_BG_EN_MASK |
	       WSA884X_PA_FSM_BYP0_D_UNMUTE_MASK |
	       WSA884X_PA_FSM_BYP0_SPKR_PROT_EN_MASK |
	       WSA884X_PA_FSM_BYP0_TSADC_EN_MASK;
	/*
	 * Here and further do not care about read or update failures.
	 * For example, before turning on Power Amplifier for the first
	 * time, reading WSA884X_TEMP_DIN_MSB will always return 0.
	 * Instead, check if returned value is within reasonable
	 * thresholds.
	 */
	regmap_update_bits(wsa884x->regmap, WSA884X_PA_FSM_BYP0, mask, mask);

	regmap_update_bits(wsa884x->regmap, WSA884X_TADC_VALUE_CTL,
			   WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK,
			   FIELD_PREP(WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, 0x0));

	regmap_read(wsa884x->regmap, WSA884X_TEMP_DIN_MSB, &dmeas_msb);
	regmap_read(wsa884x->regmap, WSA884X_TEMP_DIN_LSB, &dmeas_lsb);

	regmap_update_bits(wsa884x->regmap, WSA884X_TADC_VALUE_CTL,
			   WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK,
			   FIELD_PREP(WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, 0x1));

	regmap_read(wsa884x->regmap, WSA884X_OTP_REG_1, &d1_msb);
	regmap_read(wsa884x->regmap, WSA884X_OTP_REG_2, &d1_lsb);
	regmap_read(wsa884x->regmap, WSA884X_OTP_REG_3, &d2_msb);
	regmap_read(wsa884x->regmap, WSA884X_OTP_REG_4, &d2_lsb);

	regmap_update_bits(wsa884x->regmap, WSA884X_PA_FSM_BYP0, mask, 0x0);

	dmeas = (((dmeas_msb & 0xff) << 0x8) | (dmeas_lsb & 0xff)) >> 0x6;
	d1 = (((d1_msb & 0xff) << 0x8) | (d1_lsb & 0xff)) >> 0x6;
	d2 = (((d2_msb & 0xff) << 0x8) | (d2_lsb & 0xff)) >> 0x6;

	if (d1 == d2) {
		/* Incorrect data in OTP? */
		ret = -EINVAL;
		goto out;
	}

	val = WSA884X_T1_TEMP + (((dmeas - d1) * (WSA884X_T2_TEMP - WSA884X_T1_TEMP))/(d2 - d1));

	dev_dbg(wsa884x->dev, "Measured temp %ld (dmeas=%d, d1=%d, d2=%d)\n",
		val, dmeas, d1, d2);

	if ((val > WSA884X_LOW_TEMP_THRESHOLD) &&
	    (val < WSA884X_HIGH_TEMP_THRESHOLD)) {
		wsa884x->temperature = val;
		*temp = val;
		ret = 0;
	} else {
		ret = -EAGAIN;
	}

out:
	pm_runtime_mark_last_busy(wsa884x->dev);
	pm_runtime_put_autosuspend(wsa884x->dev);

	return ret;
}

static umode_t wsa884x_hwmon_is_visible(const void *data,
					enum hwmon_sensor_types type, u32 attr,
					int channel)
{
	if (type != hwmon_temp)
		return 0;

	switch (attr) {
	case hwmon_temp_input:
		return 0444;
	default:
		break;
	}

	return 0;
}

static int wsa884x_hwmon_read(struct device *dev,
			      enum hwmon_sensor_types type,
			      u32 attr, int channel, long *temp)
{
	int ret;

	switch (attr) {
	case hwmon_temp_input:
		ret = wsa884x_get_temp(dev_get_drvdata(dev), temp);
		break;
	default:
		ret = -EOPNOTSUPP;
		break;
	}

	return ret;
}

static const struct hwmon_channel_info *const wsa884x_hwmon_info[] = {
	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
	NULL
};

static const struct hwmon_ops wsa884x_hwmon_ops = {
	.is_visible	= wsa884x_hwmon_is_visible,
	.read		= wsa884x_hwmon_read,
};

static const struct hwmon_chip_info wsa884x_hwmon_chip_info = {
	.ops	= &wsa884x_hwmon_ops,
	.info	= wsa884x_hwmon_info,
};

static void wsa884x_reset_powerdown(void *data)
{
	struct wsa884x_priv *wsa884x = data;
@@ -1866,6 +2053,8 @@ static int wsa884x_probe(struct sdw_slave *pdev,
	if (!wsa884x)
		return -ENOMEM;

	mutex_init(&wsa884x->sp_lock);

	for (i = 0; i < WSA884X_SUPPLIES_NUM; i++)
		wsa884x->supplies[i].supply = wsa884x_supply_name[i];

@@ -1923,6 +2112,18 @@ static int wsa884x_probe(struct sdw_slave *pdev,
	regcache_cache_only(wsa884x->regmap, true);
	wsa884x->hw_init = true;

	if (IS_REACHABLE(CONFIG_HWMON)) {
		struct device *hwmon;

		hwmon = devm_hwmon_device_register_with_info(dev, "wsa884x",
							     wsa884x,
							     &wsa884x_hwmon_chip_info,
							     NULL);
		if (IS_ERR(hwmon))
			return dev_err_probe(dev, PTR_ERR(hwmon),
					     "Failed to register hwmon sensor\n");
	}

	pm_runtime_set_autosuspend_delay(dev, 3000);
	pm_runtime_use_autosuspend(dev);
	pm_runtime_mark_last_busy(dev);