// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2020 Linaro Limited * * Based on original driver: * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. * * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
/* * Thermal monitoring block consists of 8 (ADC_TM5_NUM_CHANNELS) channels. Each * channel is programmed to use one of ADC channels for voltage comparison. * Voltages are programmed using ADC codes, so we have to convert temp to * voltage and then to ADC code value. * * Configuration of TM channels must match configuration of corresponding ADC * channels.
*/
struct adc_tm5_data { const u32 full_scale_code_volt; unsignedint *decimation; unsignedint *hw_settle; int (*disable_channel)(struct adc_tm5_channel *channel); int (*configure)(struct adc_tm5_channel *channel, int low, int high);
irqreturn_t (*isr)(int irq, void *data); int (*init)(struct adc_tm5_chip *chip); char *irq_name; int gen;
};
/** * struct adc_tm5_channel - ADC Thermal Monitoring channel data. * @channel: channel number. * @adc_channel: corresponding ADC channel number. * @cal_method: calibration method. * @prescale: channel scaling performed on the input signal. * @hw_settle_time: the time between AMUX being configured and the * start of conversion. * @decimation: sampling rate supported for the channel. * @avg_samples: ability to provide single result from the ADC * that is an average of multiple measurements. * @high_thr_en: channel upper voltage threshold enable state. * @low_thr_en: channel lower voltage threshold enable state. * @meas_en: recurring measurement enable state * @iio: IIO channel instance used by this channel. * @chip: ADC TM chip instance. * @tzd: thermal zone device used by this channel.
*/ struct adc_tm5_channel { unsignedint channel; unsignedint adc_channel; enum adc_tm5_cal_method cal_method; unsignedint prescale; unsignedint hw_settle_time; unsignedint decimation; /* For Gen2 ADC_TM */ unsignedint avg_samples; /* For Gen2 ADC_TM */ bool high_thr_en; /* For Gen2 ADC_TM */ bool low_thr_en; /* For Gen2 ADC_TM */ bool meas_en; /* For Gen2 ADC_TM */ struct iio_channel *iio; struct adc_tm5_chip *chip; struct thermal_zone_device *tzd;
};
/** * struct adc_tm5_chip - ADC Thermal Monitoring properties * @regmap: SPMI ADC5 Thermal Monitoring peripheral register map field. * @dev: SPMI ADC5 device. * @data: software configuration data. * @channels: array of ADC TM channel data. * @nchannels: amount of channels defined/allocated * @decimation: sampling rate supported for the channel. * Applies to all channels, used only on Gen1 ADC_TM. * @avg_samples: ability to provide single result from the ADC * that is an average of multiple measurements. Applies to all * channels, used only on Gen1 ADC_TM. * @base: base address of TM registers. * @adc_mutex_lock: ADC_TM mutex lock, used only on Gen2 ADC_TM. * It is used to ensure only one ADC channel configuration * is done at a time using the shared set of configuration * registers.
*/ struct adc_tm5_chip { struct regmap *regmap; struct device *dev; conststruct adc_tm5_data *data; struct adc_tm5_channel *channels; unsignedint nchannels; unsignedint decimation; unsignedint avg_samples;
u16 base; struct mutex adc_mutex_lock;
};
ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, sizeof(status_low)); if (unlikely(ret)) {
dev_err(chip->dev, "read status low failed: %d\n", ret); return IRQ_HANDLED;
}
ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, sizeof(status_high)); if (unlikely(ret)) {
dev_err(chip->dev, "read status high failed: %d\n", ret); return IRQ_HANDLED;
}
for (i = 0; i < chip->nchannels; i++) { bool upper_set = false, lower_set = false; unsignedint ch = chip->channels[i].channel;
/* No TZD, we warned at the boot time */ if (!chip->channels[i].tzd) continue;
ret = adc_tm5_read(chip, ADC_TM5_M_EN(ch), &ctl, sizeof(ctl)); if (unlikely(ret)) {
dev_err(chip->dev, "ctl read failed: %d, channel %d\n", ret, i); continue;
}
ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low)); if (ret) {
dev_err(chip->dev, "read status_low failed: %d\n", ret); return IRQ_HANDLED;
}
ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high)); if (ret) {
dev_err(chip->dev, "read status_high failed: %d\n", ret); return IRQ_HANDLED;
}
ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low)); if (ret < 0) {
dev_err(chip->dev, "clear status low failed with %d\n", ret); return IRQ_HANDLED;
}
ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high)); if (ret < 0) {
dev_err(chip->dev, "clear status high failed with %d\n", ret); return IRQ_HANDLED;
}
for (i = 0; i < chip->nchannels; i++) { bool upper_set = false, lower_set = false; unsignedint ch = chip->channels[i].channel;
/* No TZD, we warned at the boot time */ if (!chip->channels[i].tzd) continue;
/* High temperature corresponds to low voltage threshold */ if (high != INT_MAX) {
u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
chip->data->full_scale_code_volt, high);
/* Low temperature corresponds to high voltage threshold */ if (low != -INT_MAX) {
u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
chip->data->full_scale_code_volt, low);
for (i = 0; i < adc_tm->nchannels; i++) {
adc_tm->channels[i].chip = adc_tm;
tzd = devm_thermal_of_zone_register(adc_tm->dev,
adc_tm->channels[i].channel,
&adc_tm->channels[i],
&adc_tm5_thermal_ops); if (IS_ERR(tzd)) { if (PTR_ERR(tzd) == -ENODEV) {
dev_dbg(adc_tm->dev, "thermal sensor on channel %d is not used\n",
adc_tm->channels[i].channel); continue;
}
dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n",
adc_tm->channels[i].channel, PTR_ERR(tzd)); return PTR_ERR(tzd);
}
adc_tm->channels[i].tzd = tzd;
devm_thermal_add_hwmon_sysfs(adc_tm->dev, tzd);
}
ret = of_property_read_u32(node, "reg", &chan); if (ret) {
dev_err(dev, "%s: invalid channel number %d\n", name, ret); return ret;
}
if (chan >= ADC_TM5_NUM_CHANNELS) {
dev_err(dev, "%s: channel number too big: %d\n", name, chan); return -EINVAL;
}
channel->channel = chan;
/* * We are tied to PMIC's ADC controller, which always use single * argument for channel number. So don't bother parsing * #io-channel-cells, just enforce cell_count = 1.
*/
ret = of_parse_phandle_with_fixed_args(node, "io-channels", 1, 0, &args); if (ret < 0) {
dev_err(dev, "%s: error parsing ADC channel number %d: %d\n", name, chan, ret); return ret;
}
of_node_put(args.np);
if (args.args_count != 1) {
dev_err(dev, "%s: invalid args count for ADC channel %d\n", name, chan); return -EINVAL;
}
adc_channel = args.args[0]; if (adc_tm->data->gen == ADC_TM5_GEN2)
adc_channel &= 0xff;
if (adc_channel >= ADC5_MAX_CHANNEL) {
dev_err(dev, "%s: invalid ADC channel number %d\n", name, chan); return -EINVAL;
}
channel->adc_channel = args.args[0];
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.