Hi Alexey, Thank you for this! On Tue, Jan 7, 2025 at 5:45 AM Alexey Klimov <alexey.klimov@xxxxxxxxxx> wrote: > > Read temperature of the amplifier and expose it via hwmon interface, which > will be later used during calibration of speaker protection algorithms. > The method is the same as for wsa884x and therefore this is based on > Krzysztof Kozlowski's approach implemented in commit 6b99dc62d940 ("ASoC: > codecs: wsa884x: Implement temperature reading and hwmon"). > > Cc: Krzysztof Kozlowski <krzysztof.kozlowski@xxxxxxxxxx> > Cc: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxx> > Signed-off-by: Alexey Klimov <alexey.klimov@xxxxxxxxxx> > --- > sound/soc/codecs/wsa883x.c | 195 +++++++++++++++++++++++++++++++++++++ > 1 file changed, 195 insertions(+) > > diff --git a/sound/soc/codecs/wsa883x.c b/sound/soc/codecs/wsa883x.c > index 47da5674d7c9..848a68bb4335 100644 > --- a/sound/soc/codecs/wsa883x.c > +++ b/sound/soc/codecs/wsa883x.c > @@ -6,6 +6,7 @@ > #include <linux/bitops.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> > @@ -156,8 +157,28 @@ > #define WSA883X_PA_FSM_ERR_COND (WSA883X_DIG_CTRL_BASE + 0x0014) > #define WSA883X_PA_FSM_MSK (WSA883X_DIG_CTRL_BASE + 0x0015) > #define WSA883X_PA_FSM_BYP (WSA883X_DIG_CTRL_BASE + 0x0016) > +#define WSA883X_PA_FSM_BYP_DC_CAL_EN_MASK 0x01 > +#define WSA883X_PA_FSM_BYP_DC_CAL_EN_SHIFT 0 > +#define WSA883X_PA_FSM_BYP_CLK_WD_EN_MASK 0x02 > +#define WSA883X_PA_FSM_BYP_CLK_WD_EN_SHIFT 1 > +#define WSA883X_PA_FSM_BYP_BG_EN_MASK 0x04 > +#define WSA883X_PA_FSM_BYP_BG_EN_SHIFT 2 > +#define WSA883X_PA_FSM_BYP_BOOST_EN_MASK 0x08 > +#define WSA883X_PA_FSM_BYP_BOOST_EN_SHIFT 3 > +#define WSA883X_PA_FSM_BYP_PA_EN_MASK 0x10 > +#define WSA883X_PA_FSM_BYP_PA_EN_SHIFT 4 > +#define WSA883X_PA_FSM_BYP_D_UNMUTE_MASK 0x20 > +#define WSA883X_PA_FSM_BYP_D_UNMUTE_SHIFT 5 > +#define WSA883X_PA_FSM_BYP_SPKR_PROT_EN_MASK 0x40 > +#define WSA883X_PA_FSM_BYP_SPKR_PROT_EN_SHIFT 6 > +#define WSA883X_PA_FSM_BYP_TSADC_EN_MASK 0x80 > +#define WSA883X_PA_FSM_BYP_TSADC_EN_SHIFT 7 > #define WSA883X_PA_FSM_DBG (WSA883X_DIG_CTRL_BASE + 0x0017) > #define WSA883X_TADC_VALUE_CTL (WSA883X_DIG_CTRL_BASE + 0x0020) > +#define WSA883X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK 0x01 > +#define WSA883X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_SHIFT 0 > +#define WSA883X_TADC_VALUE_CTL_VBAT_VALUE_RD_EN_MASK 0x02 > +#define WSA883X_TADC_VALUE_CTL_VBAT_VALUE_RD_EN_SHIFT 1 > #define WSA883X_TEMP_DETECT_CTL (WSA883X_DIG_CTRL_BASE + 0x0021) > #define WSA883X_TEMP_MSB (WSA883X_DIG_CTRL_BASE + 0x0022) > #define WSA883X_TEMP_LSB (WSA883X_DIG_CTRL_BASE + 0x0023) > @@ -427,6 +448,17 @@ > SNDRV_PCM_FMTBIT_S24_LE |\ > SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) > > +/* Two-point trimming for temperature calibration */ > +#define WSA883X_T1_TEMP -10L > +#define WSA883X_T2_TEMP 150L > + > +/* > + * Device will report senseless data in many cases, so discard any measurements > + * outside of valid range. > + */ > +#define WSA883X_LOW_TEMP_THRESHOLD 5 > +#define WSA883X_HIGH_TEMP_THRESHOLD 45 > + > struct wsa883x_priv { > struct regmap *regmap; > struct device *dev; > @@ -441,6 +473,13 @@ struct wsa883x_priv { > int active_ports; > int dev_mode; > int comp_offset; > + /* > + * 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 { > @@ -1186,6 +1225,10 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, > > switch (event) { > case SND_SOC_DAPM_POST_PMU: > + mutex_lock(&wsa883x->sp_lock); > + wsa883x->pa_on = true; > + mutex_unlock(&wsa883x->sp_lock); > + > switch (wsa883x->dev_mode) { > case RECEIVER: > snd_soc_component_write_field(component, WSA883X_CDC_PATH_MODE, > @@ -1235,6 +1278,9 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, > WSA883X_GLOBAL_PA_EN_MASK, 0); > snd_soc_component_write_field(component, WSA883X_PDM_WD_CTL, > WSA883X_PDM_EN_MASK, 0); > + mutex_lock(&wsa883x->sp_lock); > + wsa883x->pa_on = false; > + mutex_unlock(&wsa883x->sp_lock); > break; > } > return 0; > @@ -1367,6 +1413,141 @@ static struct snd_soc_dai_driver wsa883x_dais[] = { > }, > }; > > + > +static int wsa883x_get_temp(struct wsa883x_priv *wsa883x, 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; > + int ret, range; > + long val; > + > + guard(mutex)(&wsa883x->sp_lock); > + > + if (wsa883x->pa_on) { > + /* > + * Reading temperature is possible only when Power Amplifier is > + * off. Report last cached data. > + */ > + *temp = wsa883x->temperature; > + return 0; > + } > + > + ret = pm_runtime_resume_and_get(wsa883x->dev); > + if (ret < 0) > + return ret; > + > + mask = WSA883X_PA_FSM_BYP_DC_CAL_EN_MASK | > + WSA883X_PA_FSM_BYP_CLK_WD_EN_MASK | > + WSA883X_PA_FSM_BYP_BG_EN_MASK | > + WSA883X_PA_FSM_BYP_D_UNMUTE_MASK | > + WSA883X_PA_FSM_BYP_SPKR_PROT_EN_MASK | > + WSA883X_PA_FSM_BYP_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(wsa883x->regmap, WSA883X_PA_FSM_BYP, mask, mask); > + > + regmap_update_bits(wsa883x->regmap, WSA883X_TADC_VALUE_CTL, > + WSA883X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, > + FIELD_PREP(WSA883X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, 0x0)); > + > + regmap_read(wsa883x->regmap, WSA883X_TEMP_MSB, &dmeas_msb); > + regmap_read(wsa883x->regmap, WSA883X_TEMP_LSB, &dmeas_lsb); > + > + regmap_update_bits(wsa883x->regmap, WSA883X_TADC_VALUE_CTL, > + WSA883X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, > + FIELD_PREP(WSA883X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, 0x1)); > + > + regmap_read(wsa883x->regmap, WSA883X_OTP_REG_1, &d1_msb); > + regmap_read(wsa883x->regmap, WSA883X_OTP_REG_2, &d1_lsb); > + regmap_read(wsa883x->regmap, WSA883X_OTP_REG_3, &d2_msb); > + regmap_read(wsa883x->regmap, WSA883X_OTP_REG_4, &d2_lsb); > + > + regmap_update_bits(wsa883x->regmap, WSA883X_PA_FSM_BYP, 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 = WSA883X_T1_TEMP + (((dmeas - d1) * (WSA883X_T2_TEMP - WSA883X_T1_TEMP))/(d2 - d1)); > + range = WSA883X_HIGH_TEMP_THRESHOLD - WSA883X_LOW_TEMP_THRESHOLD; > + if (in_range(val, WSA883X_LOW_TEMP_THRESHOLD, range)) { > + wsa883x->temperature = val; > + *temp = val * 1000; > + ret = 0; > + } else > + ret = -EAGAIN; > + > +out: > + pm_runtime_mark_last_busy(wsa883x->dev); > + pm_runtime_put_autosuspend(wsa883x->dev); > + > + return ret; > +} > + > +static umode_t wsa883x_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 wsa883x_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 = wsa883x_get_temp(dev_get_drvdata(dev), temp); > + break; > + default: > + ret = -EOPNOTSUPP; > + break; > + } > + > + return ret; > +} > + > +static const struct hwmon_channel_info *const wsa883x_hwmon_info[] = { > + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), > + NULL > +}; > + > +static const struct hwmon_ops wsa883x_hwmon_ops = { > + .is_visible = wsa883x_hwmon_is_visible, > + .read = wsa883x_hwmon_read, > +}; > + > +static const struct hwmon_chip_info wsa883x_hwmon_chip_info = { > + .ops = &wsa883x_hwmon_ops, > + .info = wsa883x_hwmon_info, > +}; > + > static int wsa883x_probe(struct sdw_slave *pdev, > const struct sdw_device_id *id) > { > @@ -1402,6 +1583,7 @@ static int wsa883x_probe(struct sdw_slave *pdev, > wsa883x->sconfig.bps = 1; > wsa883x->sconfig.direction = SDW_DATA_DIR_RX; > wsa883x->sconfig.type = SDW_STREAM_PDM; > + mutex_init(&wsa883x->sp_lock); > > /** > * Port map index starts with 0, however the data port for this codec > @@ -1424,6 +1606,19 @@ static int wsa883x_probe(struct sdw_slave *pdev, > "regmap_init failed\n"); > goto err; > } > + > + if (IS_REACHABLE(CONFIG_HWMON)) { > + struct device *hwmon; > + > + hwmon = devm_hwmon_device_register_with_info(dev, "wsa883x", > + wsa883x, > + &wsa883x_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); > -- > 2.47.1 > > I've tested this on my Thinkpad X13s which has the wsa883x, and here, when idle, I see an entry, sdw:1:0:0217:0202:00:1 which shows ~26-28C when idle, as well as sdw:1:0:0217:0202:00:2 which has ~22-24C when idle, however if I play audio, both of them drop to 1C and do not move from that while audio is playing. Is this expected behaviour currently? (out of laziness to repeat what I am doing here - I have bottom (https://github.com/clementtsang/bottom) running in 1 terminal, and then in a second terminal I either play an audio file with canbetrra-gtk-play or mpv a video with audio and for the entire time that there is audio playback, the temperature sits at 1C. -- steev