#include <linux/io.h>
#include <linux/iio/iio.h>
#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#define MESON_SAR_ADC_MAX_FIFO_SIZE 32
#define MESON_SAR_ADC_TIMEOUT 100 /* ms */
+#define MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL 6
+#define MESON_SAR_ADC_TEMP_OFFSET 27
+
+/* temperature sensor calibration information in eFuse */
+#define MESON_SAR_ADC_EFUSE_BYTES 4
+#define MESON_SAR_ADC_EFUSE_BYTE3_UPPER_ADC_VAL GENMASK(6, 0)
+#define MESON_SAR_ADC_EFUSE_BYTE3_IS_CALIBRATED BIT(7)
+
/* for use with IIO_VAL_INT_PLUS_MICRO */
#define MILLION 1000000
.address = _chan, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
- .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
- BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
BIT(IIO_CHAN_INFO_CALIBSCALE), \
.datasheet_name = "SAR_ADC_CH"#_chan, \
}
-/*
- * TODO: the hardware supports IIO_TEMP for channel 6 as well which is
- * currently not supported by this driver.
- */
+#define MESON_SAR_ADC_TEMP_CHAN(_chan) { \
+ .type = IIO_TEMP, \
+ .channel = _chan, \
+ .address = MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE), \
+ .datasheet_name = "TEMP_SENSOR", \
+}
+
static const struct iio_chan_spec meson_sar_adc_iio_channels[] = {
MESON_SAR_ADC_CHAN(0),
MESON_SAR_ADC_CHAN(1),
IIO_CHAN_SOFT_TIMESTAMP(8),
};
+static const struct iio_chan_spec meson_sar_adc_and_temp_iio_channels[] = {
+ MESON_SAR_ADC_CHAN(0),
+ MESON_SAR_ADC_CHAN(1),
+ MESON_SAR_ADC_CHAN(2),
+ MESON_SAR_ADC_CHAN(3),
+ MESON_SAR_ADC_CHAN(4),
+ MESON_SAR_ADC_CHAN(5),
+ MESON_SAR_ADC_CHAN(6),
+ MESON_SAR_ADC_CHAN(7),
+ MESON_SAR_ADC_TEMP_CHAN(8),
+ IIO_CHAN_SOFT_TIMESTAMP(9),
+};
+
enum meson_sar_adc_avg_mode {
NO_AVERAGING = 0x0,
MEAN_AVERAGING = 0x1,
u32 bandgap_reg;
unsigned int resolution;
const struct regmap_config *regmap_config;
+ u8 temperature_trimming_bits;
+ unsigned int temperature_multiplier;
+ unsigned int temperature_divider;
};
struct meson_sar_adc_data {
struct completion done;
int calibbias;
int calibscale;
+ bool temperature_sensor_calibrated;
+ u8 temperature_sensor_coefficient;
+ u16 temperature_sensor_adc_val;
};
static const struct regmap_config meson_sar_adc_regmap_config_gxbb = {
MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK,
regval);
- if (chan->address == 6)
- regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
- MESON_SAR_ADC_DELTA_10_TEMP_SEL, 0);
+ if (chan->address == MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL) {
+ if (chan->type == IIO_TEMP)
+ regval = MESON_SAR_ADC_DELTA_10_TEMP_SEL;
+ else
+ regval = 0;
+
+ regmap_update_bits(priv->regmap,
+ MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TEMP_SEL, regval);
+ }
}
static void meson_sar_adc_set_chan7_mux(struct iio_dev *indio_dev,
enum meson_sar_adc_num_samples avg_samples,
int *val)
{
+ struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
int ret;
+ if (chan->type == IIO_TEMP && !priv->temperature_sensor_calibrated)
+ return -ENOTSUPP;
+
ret = meson_sar_adc_lock(indio_dev);
if (ret)
return ret;
break;
case IIO_CHAN_INFO_SCALE:
- ret = regulator_get_voltage(priv->vref);
- if (ret < 0) {
- dev_err(indio_dev->dev.parent,
- "failed to get vref voltage: %d\n", ret);
- return ret;
+ if (chan->type == IIO_VOLTAGE) {
+ ret = regulator_get_voltage(priv->vref);
+ if (ret < 0) {
+ dev_err(indio_dev->dev.parent,
+ "failed to get vref voltage: %d\n",
+ ret);
+ return ret;
+ }
+
+ *val = ret / 1000;
+ *val2 = priv->param->resolution;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ } else if (chan->type == IIO_TEMP) {
+ /* SoC specific multiplier and divider */
+ *val = priv->param->temperature_multiplier;
+ *val2 = priv->param->temperature_divider;
+
+ /* celsius to millicelsius */
+ *val *= 1000;
+
+ return IIO_VAL_FRACTIONAL;
+ } else {
+ return -EINVAL;
}
- *val = ret / 1000;
- *val2 = priv->param->resolution;
- return IIO_VAL_FRACTIONAL_LOG2;
-
case IIO_CHAN_INFO_CALIBBIAS:
*val = priv->calibbias;
return IIO_VAL_INT;
*val2 = priv->calibscale % MILLION;
return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = DIV_ROUND_CLOSEST(MESON_SAR_ADC_TEMP_OFFSET *
+ priv->param->temperature_divider,
+ priv->param->temperature_multiplier);
+ *val -= priv->temperature_sensor_adc_val;
+ return IIO_VAL_INT;
+
default:
return -EINVAL;
}
return 0;
}
+static int meson_sar_adc_temp_sensor_init(struct iio_dev *indio_dev)
+{
+ struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+ u8 *buf, trimming_bits, trimming_mask, upper_adc_val;
+ struct nvmem_cell *temperature_calib;
+ size_t read_len;
+ int ret;
+
+ temperature_calib = devm_nvmem_cell_get(&indio_dev->dev,
+ "temperature_calib");
+ if (IS_ERR(temperature_calib)) {
+ ret = PTR_ERR(temperature_calib);
+
+ /*
+ * leave the temperature sensor disabled if no calibration data
+ * was passed via nvmem-cells.
+ */
+ if (ret == -ENODEV)
+ return 0;
+
+ if (ret != -EPROBE_DEFER)
+ dev_err(indio_dev->dev.parent,
+ "failed to get temperature_calib cell\n");
+
+ return ret;
+ }
+
+ read_len = MESON_SAR_ADC_EFUSE_BYTES;
+ buf = nvmem_cell_read(temperature_calib, &read_len);
+ if (IS_ERR(buf)) {
+ dev_err(indio_dev->dev.parent,
+ "failed to read temperature_calib cell\n");
+ return PTR_ERR(buf);
+ } else if (read_len != MESON_SAR_ADC_EFUSE_BYTES) {
+ kfree(buf);
+ dev_err(indio_dev->dev.parent,
+ "invalid read size of temperature_calib cell\n");
+ return -EINVAL;
+ }
+
+ trimming_bits = priv->param->temperature_trimming_bits;
+ trimming_mask = BIT(trimming_bits) - 1;
+
+ priv->temperature_sensor_calibrated =
+ buf[3] & MESON_SAR_ADC_EFUSE_BYTE3_IS_CALIBRATED;
+ priv->temperature_sensor_coefficient = buf[2] & trimming_mask;
+
+ upper_adc_val = FIELD_GET(MESON_SAR_ADC_EFUSE_BYTE3_UPPER_ADC_VAL,
+ buf[3]);
+
+ priv->temperature_sensor_adc_val = buf[2];
+ priv->temperature_sensor_adc_val |= upper_adc_val << BITS_PER_BYTE;
+ priv->temperature_sensor_adc_val >>= trimming_bits;
+
+ kfree(buf);
+
+ return 0;
+}
+
static int meson_sar_adc_init(struct iio_dev *indio_dev)
{
struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
meson_sar_adc_stop_sample_engine(indio_dev);
- /* update the channel 6 MUX to select the temperature sensor */
+ /*
+ * disable this bit as seems to be only relevant for Meson6 (based
+ * on the vendor driver), which we don't support at the moment.
+ */
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
- MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL,
- MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL);
+ MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL, 0);
/* disable all channels by default */
regmap_write(priv->regmap, MESON_SAR_ADC_CHAN_LIST, 0x0);
regval |= MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW;
regmap_write(priv->regmap, MESON_SAR_ADC_AUX_SW, regval);
+ if (priv->temperature_sensor_calibrated) {
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_REVE1,
+ MESON_SAR_ADC_DELTA_10_TS_REVE1);
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_REVE0,
+ MESON_SAR_ADC_DELTA_10_TS_REVE0);
+
+ /*
+ * set bits [3:0] of the TSC (temperature sensor coefficient)
+ * to get the correct values when reading the temperature.
+ */
+ regval = FIELD_PREP(MESON_SAR_ADC_DELTA_10_TS_C_MASK,
+ priv->temperature_sensor_coefficient);
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_C_MASK, regval);
+ } else {
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_REVE1, 0);
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_REVE0, 0);
+ }
+
ret = clk_set_parent(priv->adc_sel_clk, priv->clkin);
if (ret) {
dev_err(indio_dev->dev.parent,
.bandgap_reg = MESON_SAR_ADC_DELTA_10,
.regmap_config = &meson_sar_adc_regmap_config_meson8,
.resolution = 10,
+ .temperature_trimming_bits = 4,
+ .temperature_multiplier = 18 * 10000,
+ .temperature_divider = 1024 * 10 * 85,
+};
+
+static const struct meson_sar_adc_param meson_sar_adc_meson8b_param = {
+ .has_bl30_integration = false,
+ .clock_rate = 1150000,
+ .bandgap_reg = MESON_SAR_ADC_DELTA_10,
+ .regmap_config = &meson_sar_adc_regmap_config_meson8,
+ .resolution = 10,
};
static const struct meson_sar_adc_param meson_sar_adc_gxbb_param = {
};
static const struct meson_sar_adc_data meson_sar_adc_meson8b_data = {
- .param = &meson_sar_adc_meson8_param,
+ .param = &meson_sar_adc_meson8b_param,
.name = "meson-meson8b-saradc",
};
static const struct meson_sar_adc_data meson_sar_adc_meson8m2_data = {
- .param = &meson_sar_adc_meson8_param,
+ .param = &meson_sar_adc_meson8b_param,
.name = "meson-meson8m2-saradc",
};
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &meson_sar_adc_iio_info;
- indio_dev->channels = meson_sar_adc_iio_channels;
- indio_dev->num_channels = ARRAY_SIZE(meson_sar_adc_iio_channels);
-
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
priv->calibscale = MILLION;
+ if (priv->param->temperature_trimming_bits) {
+ ret = meson_sar_adc_temp_sensor_init(indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ if (priv->temperature_sensor_calibrated) {
+ indio_dev->channels = meson_sar_adc_and_temp_iio_channels;
+ indio_dev->num_channels =
+ ARRAY_SIZE(meson_sar_adc_and_temp_iio_channels);
+ } else {
+ indio_dev->channels = meson_sar_adc_iio_channels;
+ indio_dev->num_channels =
+ ARRAY_SIZE(meson_sar_adc_iio_channels);
+ }
+
ret = meson_sar_adc_init(indio_dev);
if (ret)
goto err;