mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
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: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> Link: https://patch.msgid.link/20240809110122.137761-1-krzysztof.kozlowski@linaro.org Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
parent
7817eb1ad3
commit
6b99dc62d9
1 changed files with 201 additions and 0 deletions
|
@ -5,11 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/bitfield.h>
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/cleanup.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/gpio/consumer.h>
|
#include <linux/gpio/consumer.h>
|
||||||
|
#include <linux/hwmon.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
|
@ -301,8 +304,28 @@
|
||||||
#define WSA884X_PA_FSM_MSK1 (WSA884X_DIG_CTRL0_BASE + 0x3b)
|
#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_BYP_CTL (WSA884X_DIG_CTRL0_BASE + 0x3c)
|
||||||
#define WSA884X_PA_FSM_BYP0 (WSA884X_DIG_CTRL0_BASE + 0x3d)
|
#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_PA_FSM_BYP1 (WSA884X_DIG_CTRL0_BASE + 0x3e)
|
||||||
#define WSA884X_TADC_VALUE_CTL (WSA884X_DIG_CTRL0_BASE + 0x50)
|
#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_DETECT_CTL (WSA884X_DIG_CTRL0_BASE + 0x51)
|
||||||
#define WSA884X_TEMP_DIN_MSB (WSA884X_DIG_CTRL0_BASE + 0x52)
|
#define WSA884X_TEMP_DIN_MSB (WSA884X_DIG_CTRL0_BASE + 0x52)
|
||||||
#define WSA884X_TEMP_DIN_LSB (WSA884X_DIG_CTRL0_BASE + 0x53)
|
#define WSA884X_TEMP_DIN_LSB (WSA884X_DIG_CTRL0_BASE + 0x53)
|
||||||
|
@ -691,6 +714,17 @@
|
||||||
SNDRV_PCM_FMTBIT_S24_LE |\
|
SNDRV_PCM_FMTBIT_S24_LE |\
|
||||||
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_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 wsa884x_priv {
|
||||||
struct regmap *regmap;
|
struct regmap *regmap;
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
|
@ -706,6 +740,13 @@ struct wsa884x_priv {
|
||||||
int active_ports;
|
int active_ports;
|
||||||
int dev_mode;
|
int dev_mode;
|
||||||
bool hw_init;
|
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 {
|
enum {
|
||||||
|
@ -1660,6 +1701,10 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w,
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case SND_SOC_DAPM_POST_PMU:
|
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);
|
wsa884x_spkr_post_pmu(component, wsa884x);
|
||||||
|
|
||||||
snd_soc_component_write_field(component, WSA884X_PDM_WD_CTL,
|
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,
|
snd_soc_component_write_field(component, WSA884X_PDM_WD_CTL,
|
||||||
WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK,
|
WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK,
|
||||||
0x0);
|
0x0);
|
||||||
|
|
||||||
|
mutex_lock(&wsa884x->sp_lock);
|
||||||
|
wsa884x->pa_on = false;
|
||||||
|
mutex_unlock(&wsa884x->sp_lock);
|
||||||
break;
|
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)
|
static void wsa884x_reset_powerdown(void *data)
|
||||||
{
|
{
|
||||||
struct wsa884x_priv *wsa884x = data;
|
struct wsa884x_priv *wsa884x = data;
|
||||||
|
@ -1866,6 +2053,8 @@ static int wsa884x_probe(struct sdw_slave *pdev,
|
||||||
if (!wsa884x)
|
if (!wsa884x)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
mutex_init(&wsa884x->sp_lock);
|
||||||
|
|
||||||
for (i = 0; i < WSA884X_SUPPLIES_NUM; i++)
|
for (i = 0; i < WSA884X_SUPPLIES_NUM; i++)
|
||||||
wsa884x->supplies[i].supply = wsa884x_supply_name[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);
|
regcache_cache_only(wsa884x->regmap, true);
|
||||||
wsa884x->hw_init = 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_set_autosuspend_delay(dev, 3000);
|
||||||
pm_runtime_use_autosuspend(dev);
|
pm_runtime_use_autosuspend(dev);
|
||||||
pm_runtime_mark_last_busy(dev);
|
pm_runtime_mark_last_busy(dev);
|
||||||
|
|
Loading…
Add table
Reference in a new issue