Message ID | 20200914154809.192174-1-dmitry.baryshkov@linaro.org |
---|---|
Headers | show |
Series | qcom: pm8150: add support for thermal monitoring | expand |
Hi Dmitry, Recently I was testing the ADC_TM driver added with these changes on SC7180 and I could see that this patch needs a few changes, which I'll mention below. On 9/14/2020 9:18 PM, Dmitry Baryshkov wrote: > Add support for Thermal Monitoring part of PMIC5. This part is closely > coupled with ADC, using it's channels directly. ADC-TM support > generating interrupts on ADC value crossing low or high voltage bounds, > which is used to support thermal trip points. > > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> > Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> > --- > drivers/iio/adc/qcom-vadc-common.c | 62 +++ > drivers/iio/adc/qcom-vadc-common.h | 3 + > drivers/thermal/qcom/Kconfig | 11 + > drivers/thermal/qcom/Makefile | 1 + > drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 583 +++++++++++++++++++++++ > 5 files changed, 660 insertions(+) > create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5.c > > diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c > index 40d77b3af1bb..e58e393b8713 100644 > --- a/drivers/iio/adc/qcom-vadc-common.c > +++ b/drivers/iio/adc/qcom-vadc-common.c > @@ -377,6 +377,42 @@ static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts, > return 0; > } > > +static irqreturn_t adc_tm5_isr(int irq, void *data) > +{ > + struct adc_tm5_chip *chip = data; > + u8 status_low, status_high, ctl; > + int ret = 0, i = 0; > + > + ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, 1); > + if (ret) { > + dev_err(chip->dev, "read status low failed with %d\n", ret); > + return IRQ_HANDLED; > + } > + > + ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, 1); > + if (ret) { > + dev_err(chip->dev, "read status high failed with %d\n", ret); > + return IRQ_HANDLED; > + } > + > + for (i = 0; i < chip->nchannels; i++) { > + bool upper_set = false, lower_set = false; > + unsigned int ch = chip->channels[i].channel; > + > + if (!chip->channels[i].tzd) { > + dev_err_once(chip->dev, "thermal device not found\n"); > + continue; > + } > + > + ret = adc_tm5_read(chip, ADC_TM5_M_EN(ch), &ctl, 1); > + > + if (ret) { > + dev_err(chip->dev, "ctl read failed with %d\n", ret); > + continue; > + } > + > + lower_set = (status_low & BIT(ch)) && > + (ctl & ADC_TM5_M_MEAS_EN) && > + (ctl & ADC_TM5_M_LOW_THR_INT_EN); > + > + upper_set = (status_high & BIT(ch)) && > + (ctl & ADC_TM5_M_MEAS_EN) && > + (ctl & ADC_TM5_M_HIGH_THR_INT_EN); > + > + if (upper_set || lower_set) > + thermal_zone_device_update(chip->channels[i].tzd, > + THERMAL_EVENT_UNSPECIFIED); When using thermal_zone_device_update here, it internally calls tz->ops->get_temp, which maps to adc_tm5_get_temp defined just below. This in turn calls iio_read_channel_processed, which internally calls a mutex and this results in a mutex being called from atomic context. To avoid this, the interrupt should be requested as a threaded IRQ. > + } > + > + return IRQ_HANDLED; > +} > + > +static int adc_tm5_get_temp(void *data, int *temp) > +{ > + struct adc_tm5_channel *channel = data; > + int ret, milli_celsius; > + > + if (!channel || !channel->iio) > + return -EINVAL; > + > + ret = iio_read_channel_processed(channel->iio, &milli_celsius); > + if (ret < 0) > + return ret; > + > + *temp = milli_celsius; > + > + return 0; > +} > + > +static int adc_tm5_disable_channel(struct adc_tm5_channel *channel) > +{ > + struct adc_tm5_chip *chip = channel->chip; > + unsigned int reg = ADC_TM5_M_EN(channel->channel); > + > + return adc_tm5_reg_update(chip, reg, > + ADC_TM5_M_MEAS_EN | ADC_TM5_M_HIGH_THR_INT_EN | ADC_TM5_M_LOW_THR_INT_EN, > + 0); > +} > + > +static int adc_tm5_configure(struct adc_tm5_channel *channel, int low_temp, int high_temp) > +{ > + struct adc_tm5_chip *chip = channel->chip; > + u8 buf[8]; > + u16 reg = ADC_TM5_M_ADC_CH_SEL_CTL(channel->channel); > + int ret = 0; > + > + ret = adc_tm5_read(chip, reg, buf, sizeof(buf)); > + if (ret) { > + dev_err(chip->dev, "block read failed with %d\n", ret); > + return ret; > + } > + > + /* Update ADC channel select */ > + buf[0] = channel->adc_channel; > + > + /* Warm temperature corresponds to low voltage threshold */ > + if (high_temp != INT_MAX) { > + u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale, > + chip->data->full_scale_code_volt, high_temp); > + > + buf[1] = adc_code & 0xff; > + buf[2] = adc_code >> 8; > + buf[7] |= ADC_TM5_M_LOW_THR_INT_EN; > + } > + > + /* Cool temperature corresponds to high voltage threshold */ > + if (low_temp != -INT_MAX) { > + u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale, > + chip->data->full_scale_code_volt, low_temp); > + > + buf[3] = adc_code & 0xff; > + buf[4] = adc_code >> 8; > + buf[7] |= ADC_TM5_M_HIGH_THR_INT_EN; > + } > + > + /* Update timer select */ > + buf[5] = ADC5_TIMER_SEL_2; > + > + /* Set calibration select, hw_settle delay */ > + buf[6] &= ~ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK; > + buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK, channel->hw_settle_time); > + buf[6] &= ~ADC_TM5_M_CTL_CAL_SEL_MASK; > + buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_CAL_SEL_MASK, channel->cal_method); > + > + buf[7] |= ADC_TM5_M_MEAS_EN; > + > + ret = adc_tm5_write(chip, reg, buf, sizeof(buf)); > + if (ret) > + dev_err(chip->dev, "buf write failed\n"); > + > + return ret; > +} > + > +static int adc_tm5_set_trips(void *data, int low_temp, int high_temp) > +{ > + struct adc_tm5_channel *channel = data; > + struct adc_tm5_chip *chip; > + int ret; > + > + if (!channel) > + return -EINVAL; > + > + chip = channel->chip; > + dev_dbg(chip->dev, "%d:low_temp(mdegC):%d, high_temp(mdegC):%d\n", > + channel->channel, low_temp, high_temp); > + > + if (high_temp == INT_MAX && low_temp <= -INT_MAX) > + ret = adc_tm5_disable_channel(channel); > + else > + ret = adc_tm5_configure(channel, low_temp, high_temp); In addition to the configurations done in adc_tm5_configure, you also need to write to the registers at 0x3546 (write 0x80 to enable the ADC_TM peripheral overall) and 0x3547 (this is the conversion request strobe, you need to write to bit 7 here too, to initiate the recurring measurements). > + > + return ret; > +} > + > +static int adc_tm5_probe(struct platform_device *pdev) > +{ > + struct device_node *node = pdev->dev.of_node; > + struct device *dev = &pdev->dev; > + struct adc_tm5_chip *adc_tm; > + struct regmap *regmap; > + int ret, irq; > + u32 reg; > + > + regmap = dev_get_regmap(dev->parent, NULL); > + if (!regmap) > + return -ENODEV; > + > + ret = of_property_read_u32(node, "reg", ®); > + if (ret) > + return ret; > + > + adc_tm = devm_kzalloc(&pdev->dev, sizeof(*adc_tm), GFP_KERNEL); > + if (!adc_tm) > + return -ENOMEM; > + > + adc_tm->regmap = regmap; > + adc_tm->dev = dev; > + adc_tm->base = reg; > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) { > + dev_err(dev, "get_irq failed: %d\n", irq); > + return irq; > + } > + > + ret = adc_tm5_get_dt_data(adc_tm, node); > + if (ret) { > + dev_err(dev, "get dt data failed: %d\n", ret); > + return ret; > + } > + > + ret = adc_tm5_init(adc_tm); > + if (ret) { > + dev_err(dev, "adc-tm init failed\n"); > + return ret; > + } > + > + ret = adc_tm5_register_tzd(adc_tm); > + if (ret) { > + dev_err(dev, "tzd register failed\n"); > + return ret; > + } > + > + return devm_request_irq(dev, irq, adc_tm5_isr, 0, "pm-adc-tm5", adc_tm); The interrupt should be requested with devm_request_threaded_irq, with the existing interrupt handler being given as the threaded function, for the reason mentioned above earlier. > +} > + >