// SPDX-License-Identifier: GPL-2.0-only /* * TWL6030 GPADC module driver * * Copyright (C) 2009-2013 Texas Instruments Inc. * Nishant Kamat <nskamat@ti.com> * Balaji T K <balajitk@ti.com> * Graeme Gregory <gg@slimlogic.co.uk> * Girish S Ghongdemath <girishsg@ti.com> * Ambresh K <ambresh@ti.com> * Oleksandr Kozaruk <oleksandr.kozaruk@ti.com * * Based on twl4030-madc.c * Copyright (C) 2008 Nokia Corporation * Mikko Ylinen <mikko.k.ylinen@nokia.com>
*/ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/mfd/twl.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h>
#define DRIVER_NAME "twl6030_gpadc"
/* * twl6030 per TRM has 17 channels, and twl6032 has 19 channels * 2 test network channels are not used, * 2 die temperature channels are not used either, as it is not * defined how to convert ADC value to temperature
*/ #define TWL6030_GPADC_USED_CHANNELS 13 #define TWL6030_GPADC_MAX_CHANNELS 15 #define TWL6032_GPADC_USED_CHANNELS 15 #define TWL6032_GPADC_MAX_CHANNELS 19 #define TWL6030_GPADC_NUM_TRIM_REGS 16
/** * struct twl6030_chnl_calib - channel calibration * @gain: slope coefficient for ideal curve * @gain_error: gain error * @offset_error: offset of the real curve
*/ struct twl6030_chnl_calib {
s32 gain;
s32 gain_error;
s32 offset_error;
};
/** * struct twl6030_ideal_code - GPADC calibration parameters * GPADC is calibrated in two points: close to the beginning and * to the and of the measurable input range * * @channel: channel number * @code1: ideal code for the input at the beginning * @code2: ideal code for at the end of the range * @volt1: voltage input at the beginning(low voltage) * @volt2: voltage input at the end(high voltage)
*/ struct twl6030_ideal_code { int channel;
u16 code1;
u16 code2;
u16 volt1;
u16 volt2;
};
struct twl6030_gpadc_data;
/** * struct twl6030_gpadc_platform_data - platform specific data * @nchannels: number of GPADC channels * @iio_channels: iio channels * @ideal: pointer to calibration parameters * @start_conversion: pointer to ADC start conversion function * @channel_to_reg: pointer to ADC function to convert channel to * register address for reading conversion result * @calibrate: pointer to calibration function
*/ struct twl6030_gpadc_platform_data { constint nchannels; conststruct iio_chan_spec *iio_channels; conststruct twl6030_ideal_code *ideal; int (*start_conversion)(int channel);
u8 (*channel_to_reg)(int channel); int (*calibrate)(struct twl6030_gpadc_data *gpadc);
};
/** * struct twl6030_gpadc_data - GPADC data * @dev: device pointer * @lock: mutual exclusion lock for the structure * @irq_complete: completion to signal end of conversion * @twl6030_cal_tbl: pointer to calibration data for each * channel with gain error and offset * @pdata: pointer to device specific data
*/ struct twl6030_gpadc_data { struct device *dev; struct mutex lock; struct completion irq_complete; struct twl6030_chnl_calib *twl6030_cal_tbl; conststruct twl6030_gpadc_platform_data *pdata;
};
static u8 twl6032_channel_to_reg(int channel)
{ /* * for any prior chosen channel, when the conversion is ready * the result is avalable in GPCH0_LSB, GPCH0_MSB.
*/
return TWL6032_GPADC_GPCH0_LSB;
}
staticint twl6030_gpadc_lookup(conststruct twl6030_ideal_code *ideal, int channel, int size)
{ int i;
for (i = 0; i < size; i++) if (ideal[i].channel == channel) break;
return i;
}
staticint twl6030_channel_calibrated(conststruct twl6030_gpadc_platform_data
*pdata, int channel)
{ conststruct twl6030_ideal_code *ideal = pdata->ideal; int i;
i = twl6030_gpadc_lookup(ideal, channel, pdata->nchannels); /* not calibrated channels have 0 in all structure members */ return pdata->ideal[i].code2;
}
staticint twl6030_gpadc_make_correction(struct twl6030_gpadc_data *gpadc, int channel, int raw_code)
{ conststruct twl6030_ideal_code *ideal = gpadc->pdata->ideal; int corrected_code; int i;
staticint twl6030_gpadc_get_raw(struct twl6030_gpadc_data *gpadc, int channel, int *res)
{
u8 reg = gpadc->pdata->channel_to_reg(channel);
__le16 val; int raw_code; int ret;
ret = twl6030_gpadc_read(reg, (u8 *)&val); if (ret) {
dev_dbg(gpadc->dev, "unable to read register 0x%X\n", reg); return ret;
}
raw_code = le16_to_cpu(val);
dev_dbg(gpadc->dev, "GPADC raw code: %d", raw_code);
staticint twl6030_gpadc_get_processed(struct twl6030_gpadc_data *gpadc, int channel, int *val)
{ conststruct twl6030_ideal_code *ideal = gpadc->pdata->ideal; int corrected_code; int channel_value; int i; int ret;
ret = twl6030_gpadc_get_raw(gpadc, channel, &corrected_code); if (ret) return ret;
i = twl6030_gpadc_lookup(ideal, channel, gpadc->pdata->nchannels);
channel_value = corrected_code *
gpadc->twl6030_cal_tbl[i].gain;
/* Shift back into mV range */
channel_value /= 1000;
staticint twl6030_gpadc_read_raw(struct iio_dev *indio_dev, conststruct iio_chan_spec *chan, int *val, int *val2, long mask)
{ struct twl6030_gpadc_data *gpadc = iio_priv(indio_dev); int ret; long time_left;
mutex_lock(&gpadc->lock);
ret = gpadc->pdata->start_conversion(chan->channel); if (ret) {
dev_err(gpadc->dev, "failed to start conversion\n"); goto err;
} /* wait for conversion to complete */
time_left = wait_for_completion_interruptible_timeout(
&gpadc->irq_complete, msecs_to_jiffies(5000)); if (time_left == 0) {
ret = -ETIMEDOUT; goto err;
} elseif (time_left < 0) {
ret = -EINTR; goto err;
}
switch (mask) { case IIO_CHAN_INFO_RAW:
ret = twl6030_gpadc_get_raw(gpadc, chan->channel, val);
ret = ret ? -EIO : IIO_VAL_INT; break;
case IIO_CHAN_INFO_PROCESSED:
ret = twl6030_gpadc_get_processed(gpadc, chan->channel, val);
ret = ret ? -EIO : IIO_VAL_INT; break;
/* * The GPADC channels are calibrated using a two point calibration method. * The channels measured with two known values: volt1 and volt2, and * ideal corresponding output codes are known: code1, code2. * The difference(d1, d2) between ideal and measured codes stored in trim * registers. * The goal is to find offset and gain of the real curve for each calibrated * channel. * gain: k = 1 + ((d2 - d1) / (x2 - x1)) * offset: b = d1 + (k - 1) * x1
*/ staticvoid twl6030_calibrate_channel(struct twl6030_gpadc_data *gpadc, int channel, int d1, int d2)
{ int b, k, gain, x1, x2, i; conststruct twl6030_ideal_code *ideal = gpadc->pdata->ideal;
i = twl6030_gpadc_lookup(ideal, channel, gpadc->pdata->nchannels);
/* Gain */
gain = ((ideal[i].volt2 - ideal[i].volt1) * 1000) /
(ideal[i].code2 - ideal[i].code1);
x1 = ideal[i].code1;
x2 = ideal[i].code2;
/* k - real curve gain */
k = 1000 + (((d2 - d1) * 1000) / (x2 - x1));
/* b - offset of the real curve gain */
b = (d1 * 1000) - (k - 1000) * x1;
dev_dbg(gpadc->dev, "GPADC d1 for Chn: %d = %d\n", channel, d1);
dev_dbg(gpadc->dev, "GPADC d2 for Chn: %d = %d\n", channel, d2);
dev_dbg(gpadc->dev, "GPADC x1 for Chn: %d = %d\n", channel, x1);
dev_dbg(gpadc->dev, "GPADC x2 for Chn: %d = %d\n", channel, x2);
dev_dbg(gpadc->dev, "GPADC Gain for Chn: %d = %d\n", channel, gain);
dev_dbg(gpadc->dev, "GPADC k for Chn: %d = %d\n", channel, k);
dev_dbg(gpadc->dev, "GPADC b for Chn: %d = %d\n", channel, b);
}
staticinlineint twl6030_gpadc_get_trim_offset(s8 d)
{ /* * XXX NOTE! * bit 0 - sign, bit 7 - reserved, 6..1 - trim value * though, the documentation states that trim value * is absolute value, the correct conversion results are * obtained if the value is interpreted as 2's complement.
*/
__u32 temp = ((d & 0x7f) >> 1) | ((d & 1) << 6);
return sign_extend32(temp, 6);
}
staticint twl6030_calibration(struct twl6030_gpadc_data *gpadc)
{ int ret; int chn;
u8 trim_regs[TWL6030_GPADC_NUM_TRIM_REGS];
s8 d1, d2;
/* * for calibration two measurements have been performed at * factory, for some channels, during the production test and * have been stored in registers. This two stored values are * used to correct the measurements. The values represent * offsets for the given input from the output on ideal curve.
*/
ret = twl_i2c_read(TWL6030_MODULE_ID2, trim_regs,
TWL6030_GPADC_TRIM1, TWL6030_GPADC_NUM_TRIM_REGS); if (ret < 0) {
dev_err(gpadc->dev, "calibration failed\n"); return ret;
}
for (chn = 0; chn < TWL6030_GPADC_MAX_CHANNELS; chn++) {
switch (chn) { case 0:
d1 = trim_regs[0];
d2 = trim_regs[1]; break; case 1: case 3: case 4: case 5: case 6:
d1 = trim_regs[4];
d2 = trim_regs[5]; break; case 2:
d1 = trim_regs[12];
d2 = trim_regs[13]; break; case 7:
d1 = trim_regs[6];
d2 = trim_regs[7]; break; case 8:
d1 = trim_regs[2];
d2 = trim_regs[3]; break; case 9:
d1 = trim_regs[8];
d2 = trim_regs[9]; break; case 10:
d1 = trim_regs[10];
d2 = trim_regs[11]; break; case 14:
d1 = trim_regs[14];
d2 = trim_regs[15]; break; default: continue;
}
val = (trim_regs[reg0] & mask0) << shift0;
val |= (trim_regs[reg1] & mask1) >> 1; if (trim_regs[reg1] & 0x01)
val = -val;
return val;
}
staticint twl6032_calibration(struct twl6030_gpadc_data *gpadc)
{ int chn, d1 = 0, d2 = 0, temp;
u8 trim_regs[TWL6030_GPADC_NUM_TRIM_REGS]; int ret;
ret = twl_i2c_read(TWL6030_MODULE_ID2, trim_regs,
TWL6030_GPADC_TRIM1, TWL6030_GPADC_NUM_TRIM_REGS); if (ret < 0) {
dev_err(gpadc->dev, "calibration failed\n"); return ret;
}
/* * Loop to calculate the value needed for returning voltages from * GPADC not values. * * gain is calculated to 3 decimal places fixed point.
*/ for (chn = 0; chn < TWL6032_GPADC_MAX_CHANNELS; chn++) {
switch (chn) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 11: case 14:
d1 = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f,
0x06, 2);
d2 = twl6032_get_trim_value(trim_regs, 3, 1, 0x3f,
0x06, 2); break; case 8:
temp = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f,
0x06, 2);
d1 = temp + twl6032_get_trim_value(trim_regs, 7, 6,
0x18, 0x1E, 1);
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.