On Tue, 28 Jun 2022 18:16:29 +0300 Claudiu Beznea <claudiu.beznea@xxxxxxxxxxxxx> wrote: > The ADC on SAMA7G5 has a dedicated channel (channel 31) for measuring > in-SoC temperature. 2 inputs are multiplexed on channel 31, VTEMP and > VBG as follows: > > ` > | \ +-----+ > VBG --->| | ch31 | | > Vtemp --->| |----->| ADC | > | / | | > | / +-----+ > . > > where: > - VTEMP is proportional to the absolute temperature voltage > - VBG is a quasi-temperature independent voltage > > Both VBG and VTEMP are needed to determine the correct in-SoC > temperature. At a moment of time only one of these could be measured, the > selection being done with bit SRCLCH bit of ACR register. The formula to > calculate the temperature is as follows: > > P1 + (Vref * (VTEMP - P6 - P4 * VBG)) / (VBG * VTEMP_DT) > > where: > - P1, P4, P6 are calibration data retrieved from OTP memory > - Vref is the reference voltage for ADC > - VTEMP_DT is the voltage sensitivity to temperature and is constant > - VTEMP, VBG are the measured values from channel 31 > > For better resolution before reading the temperature certain settings > for oversampling ratio, sample frequency, EMR.TRACKX, MR.TRACKTIM are > applied. The initial settings are reapplied at the end of temperature > reading. > > Current support is not integrated with trigger buffers channel 31 not > being enabled/disabled in functions at91_adc_buffer_prepare(), > at91_adc_buffer_postdisable() thus the conversion for channel 31 is not > done in case trigger buffers are enabled. In case of trigger buffers are > enabled and temperature requests are received in the driver though > at91_adc_read_temp() the at91_adc_read_temp() will return with an error > code. > > Signed-off-by: Claudiu Beznea <claudiu.beznea@xxxxxxxxxxxxx> One really minor suggestion inline. If nothing else comes up I can probably just tweak that whilst applying. Thanks, Jonathan > --- > drivers/iio/adc/at91-sama5d2_adc.c | 255 ++++++++++++++++++++++++++++- > 1 file changed, 248 insertions(+), 7 deletions(-) > > diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c > index 04c44b2d574c..0e2dab544311 100644 > --- a/drivers/iio/adc/at91-sama5d2_adc.c > +++ b/drivers/iio/adc/at91-sama5d2_adc.c > @@ -26,9 +26,12 @@ > #include <linux/iio/trigger.h> > #include <linux/iio/trigger_consumer.h> > #include <linux/iio/triggered_buffer.h> > +#include <linux/nvmem-consumer.h> > #include <linux/pinctrl/consumer.h> > #include <linux/regulator/consumer.h> > > +#include <dt-bindings/iio/adc/at91-sama5d2_adc.h> > + > struct at91_adc_reg_layout { > /* Control Register */ > u16 CR; > @@ -73,10 +76,13 @@ struct at91_adc_reg_layout { > /* Startup Time */ > #define AT91_SAMA5D2_MR_STARTUP(v) ((v) << 16) > #define AT91_SAMA5D2_MR_STARTUP_MASK GENMASK(19, 16) > +/* Minimum startup time for temperature sensor */ > +#define AT91_SAMA5D2_MR_STARTUP_TS_MIN (50) > /* Analog Change */ > #define AT91_SAMA5D2_MR_ANACH BIT(23) > /* Tracking Time */ > #define AT91_SAMA5D2_MR_TRACKTIM(v) ((v) << 24) > +#define AT91_SAMA5D2_MR_TRACKTIM_TS 6 > #define AT91_SAMA5D2_MR_TRACKTIM_MAX 0xf > /* Transfer Time */ > #define AT91_SAMA5D2_MR_TRANSFER(v) ((v) << 28) > @@ -149,6 +155,9 @@ struct at91_adc_reg_layout { > #define AT91_SAMA5D2_TRACKX_MASK GENMASK(23, 22) > #define AT91_SAMA5D2_TRACKX(x) (((x) << 22) & \ > AT91_SAMA5D2_TRACKX_MASK) > +/* TRACKX for temperature sensor. */ > +#define AT91_SAMA5D2_TRACKX_TS (1) > + > /* Extended Mode Register - Averaging on single trigger event */ > #define AT91_SAMA5D2_EMR_ASTE(V) ((V) << 20) > > @@ -164,6 +173,8 @@ struct at91_adc_reg_layout { > u16 ACR; > /* Analog Control Register - Pen detect sensitivity mask */ > #define AT91_SAMA5D2_ACR_PENDETSENS_MASK GENMASK(1, 0) > +/* Analog Control Register - Source last channel */ > +#define AT91_SAMA5D2_ACR_SRCLCH BIT(16) > > /* Touchscreen Mode Register */ > u16 TSMR; > @@ -231,6 +242,10 @@ struct at91_adc_reg_layout { > u16 WPSR; > /* Version Register */ > u16 VERSION; > +/* Temperature Sensor Mode Register */ > + u16 TEMPMR; > +/* Temperature Sensor Mode - Temperature sensor on */ > +#define AT91_SAMA5D2_TEMPMR_TEMPON BIT(0) > }; > > static const struct at91_adc_reg_layout sama5d2_layout = { > @@ -285,6 +300,7 @@ static const struct at91_adc_reg_layout sama7g5_layout = { > .EOC_IDR = 0x38, > .EOC_IMR = 0x3c, > .EOC_ISR = 0x40, > + .TEMPMR = 0x44, > .OVER = 0x4c, > .EMR = 0x50, > .CWR = 0x54, > @@ -391,6 +407,21 @@ static const struct at91_adc_reg_layout sama7g5_layout = { > .datasheet_name = name, \ > } > > +#define AT91_SAMA5D2_CHAN_TEMP(num, name, addr) \ > + { \ > + .type = IIO_TEMP, \ > + .channel = num, \ > + .address = addr, \ > + .scan_index = num, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ > + .info_mask_shared_by_all = \ > + BIT(IIO_CHAN_INFO_PROCESSED) | \ > + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ > + .info_mask_shared_by_all_available = \ > + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ > + .datasheet_name = name, \ > + } > + > #define at91_adc_readl(st, reg) \ > readl_relaxed((st)->base + (st)->soc_info.platform->layout->reg) > #define at91_adc_read_chan(st, reg) \ > @@ -415,6 +446,8 @@ static const struct at91_adc_reg_layout sama7g5_layout = { > * @oversampling_avail: available oversampling values > * @oversampling_avail_no: number of available oversampling values > * @chan_realbits: realbits for registered channels > + * @temp_chan: temperature channel index > + * @temp_sensor: temperature sensor supported > */ > struct at91_adc_platform { > const struct at91_adc_reg_layout *layout; > @@ -430,20 +463,54 @@ struct at91_adc_platform { > unsigned int oversampling_avail[5]; > unsigned int oversampling_avail_no; > unsigned int chan_realbits; > + unsigned int temp_chan; > + bool temp_sensor; > }; > > +/** > + * struct at91_adc_temp_sensor_clb - at91-sama5d2 temperature sensor > + * calibration data structure > + * @p1: P1 calibration temperature > + * @p4: P4 calibration voltage > + * @p6: P6 calibration voltage > + */ > +struct at91_adc_temp_sensor_clb { > + u32 p1; > + u32 p4; > + u32 p6; > +}; > + > +/** > + * enum at91_adc_ts_clb_idx - calibration indexes in NVMEM buffer > + * @AT91_ADC_TS_CLB_IDX_P1: index for P1 > + * @AT91_ADC_TS_CLB_IDX_P4: index for P4 > + * @AT91_ADC_TS_CLB_IDX_P6: index for P6 > + * @AT91_ADC_TS_CLB_IDX_MAX: max index for temperature calibration packet in OTP > + */ > +enum at91_adc_ts_clb_idx { > + AT91_ADC_TS_CLB_IDX_P1 = 2, > + AT91_ADC_TS_CLB_IDX_P4 = 5, > + AT91_ADC_TS_CLB_IDX_P6 = 7, > + AT91_ADC_TS_CLB_IDX_MAX = 19, > +}; > + > +/* Temperature sensor calibration - Vtemp voltage sensitivity to temperature. */ > +#define AT91_ADC_TS_VTEMP_DT (2080U) > + > /** > * struct at91_adc_soc_info - at91-sama5d2 soc information struct > * @startup_time: device startup time > * @min_sample_rate: minimum sample rate in Hz > * @max_sample_rate: maximum sample rate in Hz > * @platform: pointer to the platform structure > + * @temp_sensor_clb: temperature sensor calibration data structure > */ > struct at91_adc_soc_info { > unsigned startup_time; > unsigned min_sample_rate; > unsigned max_sample_rate; > const struct at91_adc_platform *platform; > + struct at91_adc_temp_sensor_clb temp_sensor_clb; > }; > > struct at91_adc_trigger { > @@ -491,6 +558,18 @@ struct at91_adc_touch { > struct work_struct workq; > }; > > +/** > + * struct at91_adc_temp - at91-sama5d2 temperature information structure > + * @sample_period_val: sample period value > + * @saved_sample_rate: saved sample rate > + * @saved_oversampling: saved oversampling > + */ > +struct at91_adc_temp { > + u16 sample_period_val; > + u16 saved_sample_rate; > + u16 saved_oversampling; > +}; > + > /* > * Buffer size requirements: > * No channels * bytes_per_channel(2) + timestamp bytes (8) > @@ -518,6 +597,7 @@ struct at91_adc_state { > wait_queue_head_t wq_data_available; > struct at91_adc_dma dma_st; > struct at91_adc_touch touch_st; > + struct at91_adc_temp temp_st; > struct iio_dev *indio_dev; > /* Ensure naturally aligned timestamp */ > u16 buffer[AT91_BUFFER_MAX_HWORDS] __aligned(8); > @@ -607,6 +687,7 @@ static const struct iio_chan_spec at91_sama7g5_adc_channels[] = { > AT91_SAMA5D2_CHAN_DIFF(22, 12, 13, 0x90), > AT91_SAMA5D2_CHAN_DIFF(23, 14, 15, 0x98), > IIO_CHAN_SOFT_TIMESTAMP(24), > + AT91_SAMA5D2_CHAN_TEMP(AT91_SAMA7G5_ADC_TEMP_CHANNEL, "temp", 0xdc), > }; > > static const struct at91_adc_platform sama5d2_platform = { > @@ -639,10 +720,13 @@ static const struct at91_adc_platform sama7g5_platform = { > .adc_channels = &at91_sama7g5_adc_channels, > #define AT91_SAMA7G5_SINGLE_CHAN_CNT 16 > #define AT91_SAMA7G5_DIFF_CHAN_CNT 8 > +#define AT91_SAMA7G5_TEMP_CHAN_CNT 1 > .nr_channels = AT91_SAMA7G5_SINGLE_CHAN_CNT + > - AT91_SAMA7G5_DIFF_CHAN_CNT, > + AT91_SAMA7G5_DIFF_CHAN_CNT + > + AT91_SAMA7G5_TEMP_CHAN_CNT, > #define AT91_SAMA7G5_MAX_CHAN_IDX (AT91_SAMA7G5_SINGLE_CHAN_CNT + \ > - AT91_SAMA7G5_DIFF_CHAN_CNT) > + AT91_SAMA7G5_DIFF_CHAN_CNT + \ > + AT91_SAMA7G5_TEMP_CHAN_CNT) > .max_channels = ARRAY_SIZE(at91_sama7g5_adc_channels), > .max_index = AT91_SAMA7G5_MAX_CHAN_IDX, > #define AT91_SAMA7G5_HW_TRIG_CNT 3 > @@ -651,6 +735,8 @@ static const struct at91_adc_platform sama7g5_platform = { > .oversampling_avail = { 1, 4, 16, 64, 256, }, > .oversampling_avail_no = 5, > .chan_realbits = 16, > + .temp_sensor = true, > + .temp_chan = AT91_SAMA7G5_ADC_TEMP_CHANNEL, > }; > > static int at91_adc_chan_xlate(struct iio_dev *indio_dev, int chan) > @@ -1190,7 +1276,8 @@ static int at91_adc_buffer_prepare(struct iio_dev *indio_dev) > continue; > /* these channel types cannot be handled by this trigger */ > if (chan->type == IIO_POSITIONRELATIVE || > - chan->type == IIO_PRESSURE) > + chan->type == IIO_PRESSURE || > + chan->type == IIO_TEMP) > continue; > > at91_adc_cor(st, chan); > @@ -1232,7 +1319,8 @@ static int at91_adc_buffer_postdisable(struct iio_dev *indio_dev) > continue; > /* these channel types are virtual, no need to do anything */ > if (chan->type == IIO_POSITIONRELATIVE || > - chan->type == IIO_PRESSURE) > + chan->type == IIO_PRESSURE || > + chan->type == IIO_TEMP) > continue; > > at91_adc_writel(st, CHDR, BIT(chan->channel)); > @@ -1610,12 +1698,19 @@ static int at91_adc_read_info_raw(struct iio_dev *indio_dev, > return ret; > } > > - /* in this case we have a voltage channel */ > + /* in this case we have a voltage or temperature channel */ > > st->chan = chan; > > at91_adc_cor(st, chan); > at91_adc_writel(st, CHER, BIT(chan->channel)); > + /* > + * TEMPMR.TEMPON needs to update after CHER otherwise if none > + * of the channels are enabled and TEMPMR.TEMPON = 1 will > + * trigger DRDY interruption while preparing for temperature read. > + */ > + if (chan->type == IIO_TEMP) > + at91_adc_writel(st, TEMPMR, AT91_SAMA5D2_TEMPMR_TEMPON); > at91_adc_eoc_ena(st, chan->channel); > at91_adc_writel(st, CR, AT91_SAMA5D2_CR_START); > > @@ -1635,6 +1730,8 @@ static int at91_adc_read_info_raw(struct iio_dev *indio_dev, > } > > at91_adc_eoc_dis(st, st->chan->channel); > + if (chan->type == IIO_TEMP) > + at91_adc_writel(st, TEMPMR, 0U); > at91_adc_writel(st, CHDR, BIT(chan->channel)); > > /* Needed to ACK the DRDY interruption */ > @@ -1662,6 +1759,89 @@ static int at91_adc_read_info_locked(struct iio_dev *indio_dev, > return ret; > } > > +static void at91_adc_temp_sensor_configure(struct at91_adc_state *st, > + bool start) > +{ > + u32 sample_rate, oversampling_ratio; > + u32 startup_time, tracktim, trackx; > + > + if (start) { > + /* > + * Configure the sensor for best accuracy: 10MHz frequency, > + * oversampling rate of 256, tracktim=0xf and trackx=1. > + */ > + sample_rate = 10000000U; Use the multipliers in units.h e.g. sample_rate = 10 * MEGA; > + oversampling_ratio = 256; > + startup_time = AT91_SAMA5D2_MR_STARTUP_TS_MIN; > + tracktim = AT91_SAMA5D2_MR_TRACKTIM_TS; > + trackx = AT91_SAMA5D2_TRACKX_TS; > + > + st->temp_st.saved_sample_rate = st->current_sample_rate; > + st->temp_st.saved_oversampling = st->oversampling_ratio; > + } else { > + /* Go back to previous settings. */ > + sample_rate = st->temp_st.saved_sample_rate; > + oversampling_ratio = st->temp_st.saved_oversampling; > + startup_time = st->soc_info.startup_time; > + tracktim = 0; > + trackx = 0; > + } > + > + at91_adc_setup_samp_freq(st->indio_dev, sample_rate, startup_time, > + tracktim); > + at91_adc_config_emr(st, oversampling_ratio, trackx); > +} > + > +static int at91_adc_read_temp(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int *val) > +{ > + struct at91_adc_state *st = iio_priv(indio_dev); > + struct at91_adc_temp_sensor_clb *clb = &st->soc_info.temp_sensor_clb; > + u64 div1, div2; > + u32 tmp; > + int ret, vbg, vtemp; > + > + ret = iio_device_claim_direct_mode(indio_dev); > + if (ret) > + return ret; > + mutex_lock(&st->lock); > + > + at91_adc_temp_sensor_configure(st, true); > + > + /* Read VBG. */ > + tmp = at91_adc_readl(st, ACR); > + tmp |= AT91_SAMA5D2_ACR_SRCLCH; > + at91_adc_writel(st, ACR, tmp); > + ret = at91_adc_read_info_raw(indio_dev, chan, &vbg); > + if (ret < 0) > + goto restore_config; > + > + /* Read VTEMP. */ > + tmp &= ~AT91_SAMA5D2_ACR_SRCLCH; > + at91_adc_writel(st, ACR, tmp); > + ret = at91_adc_read_info_raw(indio_dev, chan, &vtemp); > + > +restore_config: > + /* Revert previous settings. */ > + at91_adc_temp_sensor_configure(st, false); > + mutex_unlock(&st->lock); > + iio_device_release_direct_mode(indio_dev); > + if (ret < 0) > + return ret; > + > + /* > + * Temp[milli] = p1[milli] + (vtemp * clb->p6 - clb->p4 * vbg)/ > + * (vbg * AT91_ADC_TS_VTEMP_DT) > + */ > + div1 = DIV_ROUND_CLOSEST_ULL(((u64)vtemp * clb->p6), vbg); > + div1 = DIV_ROUND_CLOSEST_ULL((div1 * 1000), AT91_ADC_TS_VTEMP_DT); > + div2 = DIV_ROUND_CLOSEST_ULL((u64)clb->p4, AT91_ADC_TS_VTEMP_DT); > + div2 *= 1000; > + *val = clb->p1 + (int)div1 - (int)div2; > + > + return ret; > +} > + > static int at91_adc_read_raw(struct iio_dev *indio_dev, > struct iio_chan_spec const *chan, > int *val, int *val2, long mask) > @@ -1679,6 +1859,11 @@ static int at91_adc_read_raw(struct iio_dev *indio_dev, > *val2 = chan->scan_type.realbits; > return IIO_VAL_FRACTIONAL_LOG2; > > + case IIO_CHAN_INFO_PROCESSED: > + if (chan->type != IIO_TEMP) > + return -EINVAL; > + return at91_adc_read_temp(indio_dev, chan, val); > + > case IIO_CHAN_INFO_SAMP_FREQ: > *val = at91_adc_get_sample_freq(st); > return IIO_VAL_INT; > @@ -1994,12 +2179,61 @@ static int at91_adc_buffer_and_trigger_init(struct device *dev, > return 0; > } > > +static int at91_adc_temp_sensor_init(struct at91_adc_state *st, > + struct device *dev) > +{ > + struct at91_adc_temp_sensor_clb *clb = &st->soc_info.temp_sensor_clb; > + struct nvmem_cell *temp_calib; > + u32 *buf; > + size_t len; > + int ret = 0; > + > + if (!st->soc_info.platform->temp_sensor) > + return 0; > + > + /* Get the calibration data from NVMEM. */ > + temp_calib = devm_nvmem_cell_get(dev, "temperature_calib"); > + if (IS_ERR(temp_calib)) { > + ret = PTR_ERR(temp_calib); > + if (ret != -ENOENT) > + dev_err(dev, "Failed to get temperature_calib cell!\n"); > + return ret; > + } > + > + buf = nvmem_cell_read(temp_calib, &len); > + if (IS_ERR(buf)) { > + dev_err(dev, "Failed to read calibration data!\n"); > + return PTR_ERR(buf); > + } > + if (len < AT91_ADC_TS_CLB_IDX_MAX * 4) { > + dev_err(dev, "Invalid calibration data!\n"); > + ret = -EINVAL; > + goto free_buf; > + } > + > + /* Store calibration data for later use. */ > + clb->p1 = buf[AT91_ADC_TS_CLB_IDX_P1]; > + clb->p4 = buf[AT91_ADC_TS_CLB_IDX_P4]; > + clb->p6 = buf[AT91_ADC_TS_CLB_IDX_P6]; > + > + /* > + * We prepare here the conversion to milli and also add constant > + * factor (5 degrees Celsius) to p1 here to avoid doing it on > + * hotpath. > + */ > + clb->p1 = clb->p1 * 1000 + 5000; > + > +free_buf: > + kfree(buf); > + return ret; > +} > + > static int at91_adc_probe(struct platform_device *pdev) > { > struct iio_dev *indio_dev; > struct at91_adc_state *st; > struct resource *res; > - int ret, i; > + int ret, i, num_channels; > u32 edge_type = IRQ_TYPE_NONE; > > indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); > @@ -2011,11 +2245,18 @@ static int at91_adc_probe(struct platform_device *pdev) > > st->soc_info.platform = of_device_get_match_data(&pdev->dev); > > + ret = at91_adc_temp_sensor_init(st, &pdev->dev); > + /* Don't register temperature channel if initialization failed. */ > + if (ret) > + num_channels = st->soc_info.platform->max_channels - 1; > + else > + num_channels = st->soc_info.platform->max_channels; > + > indio_dev->name = dev_name(&pdev->dev); > indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; > indio_dev->info = &at91_adc_info; > indio_dev->channels = *st->soc_info.platform->adc_channels; > - indio_dev->num_channels = st->soc_info.platform->max_channels; > + indio_dev->num_channels = num_channels; > > bitmap_set(&st->touch_st.channels_bitmask, > st->soc_info.platform->touch_chan_x, 1);