<html><head></head><body>Two very quick comments based on quick glance as it may be a while before I can do a full review.<br>
<br>
We still have channels that are only usable for temperature being output to user space as voltage channels?  Is the conversion so very hard? <br>
<br>
You have the same entries in info_mask_shared by type and separate. That makes no sense. One or the other please!<br>
<br>
<br>
<br><br><div class="gmail_quote">Oleksandr Kozaruk <oleksandr.kozaruk@ti.com> wrote:<blockquote class="gmail_quote" style="margin: 0pt 0pt 0pt 0.8ex; border-left: 1px solid rgb(204, 204, 204); padding-left: 1ex;">
<pre class="k9mail">The GPADC is general purpose ADC found on TWL6030, and TWL6032 PMIC,<br />known also as Phoenix and PhoenixLite.<br /><br />The TWL6030 and TWL6032 have GPADC with 17 and 19 channels<br />respectively. Some channels have current source and are used for<br />measuring voltage drop on resistive load for detecting battery ID<br />resistance, or measuring voltage drop on NTC resistors for external<br />temperature measurements. Some channels measure voltage, (i.e. battery<br />voltage), and have voltage dividers, thus, capable to scale voltage.<br />Some channels are dedicated for measuring die temperature.<br /><br />Some channels are calibrated in 2 points, having offsets from ideal<br />values kept in trim registers. This is used to correct measurements.<br /><br />The differences between GPADC in TWL6030 and TWL6032:<br />- 10 bit vs 12 bit ADC;<br />- 17 vs 19 channels;<br />- channels have different purpose(i.e. battery voltage<br />channel 8 vs channel 18);<br
/>- trim values are interpreted differently.<br /><br />Based on the driver patched from Balaji TK, Graeme Gregory, Ambresh K,<br />Girish S Ghongdemath.<br /><br />Signed-off-by: Balaji T K <balajitk@ti.com><br />Signed-off-by: Graeme Gregory <gg@slimlogic.co.uk><br />Signed-off-by: Oleksandr Kozaruk <oleksandr.kozaruk@ti.com><br />---<br />drivers/iio/adc/Kconfig         |   14 +<br />drivers/iio/adc/Makefile        |    1 +<br />drivers/iio/adc/twl6030-gpadc.c | 1008 +++++++++++++++++++++++++++++++++++++++<br />3 files changed, 1023 insertions(+)<br />create mode 100644 drivers/iio/adc/twl6030-gpadc.c<br /><br />diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig<br />index ab0767e6..3172461 100644<br />--- a/drivers/iio/adc/Kconfig<br />+++ b/drivers/iio/adc/Kconfig<br />@@ -150,6 +150,20 @@ config TI_AM335X_ADC<br />   Say yes here to build support for Texas Instruments ADC<br />   driver which is also a MFD client.<br /><br />+config
TWL6030_GPADC<br />+ tristate "TWL6030 GPADC (General Purpose A/D Converter) Support"<br />+ depends on TWL4030_CORE<br />+ default n<br />+ help<br />+   Say yes here if you want support for the TWL6030/TWL6032 General<br />+   Purpose A/D Converter. This will add support for battery type<br />+   detection, battery voltage and temperature measurement, die<br />+   temperature measurement, system supply voltage, audio accessory,<br />+   USB ID detection.<br />+<br />+   This driver can also be built as a module. If so, the module will be<br />+   called twl6030-gpadc.<br />+<br />config VIPERBOARD_ADC<br /> tristate "Viperboard ADC support"<br /> depends on MFD_VIPERBOARD && USB<br />diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile<br />index 0a825be..996ba09 100644<br />--- a/drivers/iio/adc/Makefile<br />+++ b/drivers/iio/adc/Makefile<br />@@ -16,4 +16,5 @@ obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o<br />obj-$(CONFIG_MAX1363) += max1363.o<br
/>obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o<br />obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o<br />+obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o<br />obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o<br />diff --git a/drivers/iio/adc/twl6030-gpadc.c b/drivers/iio/adc/twl6030-gpadc.c<br />new file mode 100644<br />index 0000000..658f35b<br />--- /dev/null<br />+++ b/drivers/iio/adc/twl6030-gpadc.c<br />@@ -0,0 +1,1008 @@<br />+/*<br />+ * TWL6030 GPADC module driver<br />+ *<br />+ * Copyright (C) 2009-2013 Texas Instruments Inc.<br />+ * Nishant Kamat <nskamat@ti.com><br />+ * Balaji T K <balajitk@ti.com><br />+ * Graeme Gregory <gg@slimlogic.co.uk><br />+ * Girish S Ghongdemath <girishsg@ti.com><br />+ * Ambresh K <ambresh@ti.com><br />+ * Oleksandr Kozaruk <oleksandr.kozaruk@ti.com<br />+ *<br />+ * Based on twl4030-madc.c<br />+ * Copyright (C) 2008 Nokia Corporation<br />+ * Mikko Ylinen <mikko.k.ylinen@nokia.com><br />+ *<br />+ * This
program is free software; you can redistribute it and/or<br />+ * modify it under the terms of the GNU General Public License<br />+ * version 2 as published by the Free Software Foundation.<br />+ *<br />+ * This program is distributed in the hope that it will be useful, but<br />+ * WITHOUT ANY WARRANTY; without even the implied warranty of<br />+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU<br />+ * General Public License for more details.<br />+ *<br />+ * You should have received a copy of the GNU General Public License<br />+ * along with this program; if not, write to the Free Software<br />+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA<br />+ * 02110-1301 USA<br />+ *<br />+ */<br />+#include <linux/init.h><br />+#include <linux/interrupt.h><br />+#include <linux/kernel.h><br />+#include <linux/module.h><br />+#include <linux/platform_device.h><br />+#include <linux/of_platform.h><br />+#include
<linux/i2c/twl.h><br />+#include <linux/iio/iio.h><br />+#include <linux/iio/sysfs.h><br />+<br />+#define DRIVER_NAME  "twl6030_gpadc"<br />+<br />+#define TWL6030_GPADC_MAX_CHANNELS 17<br />+#define TWL6032_GPADC_MAX_CHANNELS 19<br />+<br />+#define TWL6030_GPADC_CTRL_P1   0x05<br />+<br />+#define TWL6032_GPADC_GPSELECT_ISB  0x07<br />+#define TWL6032_GPADC_CTRL_P1   0x08<br />+<br />+#define TWL6032_GPADC_GPCH0_LSB   0x0d<br />+#define TWL6032_GPADC_GPCH0_MSB   0x0e<br />+<br />+#define TWL6030_GPADC_CTRL_P1_SP1  BIT(3)<br />+<br />+#define TWL6030_GPADC_GPCH0_LSB   (0x29)<br />+<br />+#define TWL6030_GPADC_RT_SW1_EOC_MASK  BIT(5)<br />+<br />+#define TWL6030_GPADC_TRIM1   0xCD<br />+<br />+#define TWL6030_REG_TOGGLE1   0x90<br />+#define TWL6030_GPADCS    BIT(1)<br />+#define TWL6030_GPADCR    BIT(0)<br />+<br />+/**<br />+ * struct twl6030_chnl_calib - channel calibration<br />+ * @gain:  slope coefficient for ideal curve<br />+ * @gain_error:  gain error<br
/>+ * @offset_error: offset of the real curve<br />+ */<br />+struct twl6030_chnl_calib {<br />+ s32 gain;<br />+ s32 gain_error;<br />+ s32 offset_error;<br />+};<br />+<br />+/**<br />+ * struct twl6030_ideal_code - GPADC calibration parameters<br />+ * GPADC is calibrated in two points: close to the beginning and<br />+ * to the and of the measurable input range<br />+ *<br />+ * @code1: ideal code for the input at the beginning<br />+ * @code2: ideal code for at the end of the range<br />+ * @volt1: voltage input at the beginning(low voltage)<br />+ * @volt2: voltage input at the end(high voltage)<br />+ */<br />+struct twl6030_ideal_code {<br />+ u16 code1;<br />+ u16 code2;<br />+ u16 volt1;<br />+ u16 volt2;<br />+};<br />+<br />+struct twl6030_gpadc_data;<br />+<br />+/**<br />+ * struct twl6030_gpadc_platform_data - platform specific data<br />+ * @nchannels:  number of GPADC channels<br />+ * @iio_channels: iio channels<br />+ * @twl6030_ideal: pointer to calibration
parameters<br />+ * @start_conversion: pointer to ADC start conversion function<br />+ * @channel_to_reg pointer to ADC function to convert channel to<br />+ *    register address for reading conversion result<br />+ * @calibrate:  pointer to calibration function<br />+ */<br />+struct twl6030_gpadc_platform_data {<br />+ const int nchannels;<br />+ const struct iio_chan_spec *iio_channels;<br />+ const struct twl6030_ideal_code *ideal;<br />+ int (*start_conversion)(int channel);<br />+ u8 (*channel_to_reg)(int channel);<br />+ int (*calibrate)(struct twl6030_gpadc_data *gpadc);<br />+};<br />+<br />+/**<br />+ * struct twl6030_gpadc_data - GPADC data<br />+ * @dev:  device pointer<br />+ * @lock:  mutual exclusion lock for the structure<br />+ * @irq_complete: completion to signal end of conversion<br />+ * @twl6030_cal_tbl: pointer to calibration data for each<br />+ *    channel with gain error and offset<br />+ * @pdata:  pointer to device specific data<br />+ */<br />+struct
twl6030_gpadc_data {<br />+ struct device *dev;<br />+ struct mutex lock;<br />+ struct completion irq_complete;<br />+ struct twl6030_chnl_calib *twl6030_cal_tbl;<br />+ const struct twl6030_gpadc_platform_data *pdata;<br />+};<br />+<br />+/*<br />+ * channels 11, 12, 13, 15 and 16 have no calibration data<br />+ * calibration offset is same for channels 1, 3, 4, 5<br />+ *<br />+ * The data is taken from GPADC_TRIM registers description.<br />+ * GPADC_TRIM registers keeps difference between the code measured<br />+ * at volt1 and volt2 input voltages and corresponding code1 and code2<br />+ */<br />+static const struct twl6030_ideal_code<br />+ twl6030_ideal[TWL6030_GPADC_MAX_CHANNELS] = {<br />+ [0] = { /* ch 0, external, battery type, resistor value */<br />+  .code1 = 116,<br />+  .code2 = 745,<br />+  .volt1 = 141,<br />+  .volt2 = 910,<br />+ },<br />+ [1] = { /* ch 1, external, battery temperature, NTC resistor value */<br />+  .code1 = 82,<br />+  .code2 = 900,<br />+ 
volt1 = 100,<br />+  .volt2 = 1100,<br />+ },<br />+ [2] = { /* ch 2, external, audio accessory/general purpose */<br />+  .code1 = 55,<br />+  .code2 = 818,<br />+  .volt1 = 101,<br />+  .volt2 = 1499,<br />+ },<br />+ [3] = { /* ch 3, external, general purpose */<br />+  .code1 = 82,<br />+  .code2 = 900,<br />+  .volt1 = 100,<br />+  .volt2 = 1100,<br />+ },<br />+ [4] = { /* ch 4, external, temperature measurement/general purpose */<br />+  .code1 = 82,<br />+  .code2 = 900,<br />+  .volt1 = 100,<br />+  .volt2 = 1100,<br />+ },<br />+ [5] = { /* ch 5, external, general purpose */<br />+  .code1 = 82,<br />+  .code2 = 900,<br />+  .volt1 = 100,<br />+  .volt2 = 1100,<br />+ },<br />+ [6] = { /* ch 6, external, general purpose */<br />+  .code1 = 82,<br />+  .code2 = 900,<br />+  .volt1 = 100,<br />+  .volt2 = 1100,<br />+ },<br />+ [7] = { /* ch 7, internal, main battery */<br />+  .code1 = 614,<br />+  .code2 = 941,<br />+  .volt1 = 3001,<br />+  .volt2 = 4599,<br />+ },<br
/>+ [8] = { /* ch 8, internal, backup battery */<br />+  .code1 = 82,<br />+  .code2 = 688,<br />+  .volt1 = 501,<br />+  .volt2 = 4203,<br />+ },<br />+ [9] = { /* ch 9, internal, external charger input */<br />+  .code1 = 182,<br />+  .code2 = 818,<br />+  .volt1 = 2001,<br />+  .volt2 = 8996,<br />+ },<br />+ [10] = { /* ch 10, internal, VBUS */<br />+  .code1 = 149,<br />+  .code2 = 818,<br />+  .volt1 = 1001,<br />+  .volt2 = 5497,<br />+ },<br />+ [11] = {}, /* ch 11, internal, VBUS charging current */<br />+ [12] = {}, /* ch 12, internal, Die temperature */<br />+ [13] = {}, /* ch 13, internal, Die temperature */<br />+ [14] = { /* ch 14, internal, USB ID line */<br />+  .code1 = 48,<br />+  .code2 = 714,<br />+  .volt1 = 323,<br />+  .volt2 = 4800,<br />+ },<br />+ [15] = {}, /* ch 15, internal, test network */<br />+ [16] = {}, /* ch 16, internal, test network */<br />+};<br />+<br />+static const struct twl6030_ideal_code<br />+   twl6032_ideal[TWL6032_GPADC_MAX_CHANNELS]
= {<br />+ [0] = { /* ch 0, external, battery type, resistor value */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 440,<br />+  .volt2 = 1000,<br />+ },<br />+ [1] = { /* ch 1, external, battery temperature, NTC resistor value */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 440,<br />+  .volt2 = 1000,<br />+ },<br />+ [2] = { /* ch 2, external, audio accessory/general purpose */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 660,<br />+  .volt2 = 1500,<br />+ },<br />+ [3] = { /* ch 3, external, temperature with external diode/general<br />+        purpose */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 440,<br />+  .volt2 = 1000,<br />+ },<br />+ [4] = { /* ch 4, external, temperature measurement/general purpose */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 440,<br />+  .volt2 = 1000,<br />+ },<br />+ [5] = { /* ch 5, external, general purpose */<br />+  .code1 = 1441,<br />+  .code2 =
3276,<br />+  .volt1 = 440,<br />+  .volt2 = 1000,<br />+ },<br />+ [6] = { /* ch 6, external, general purpose */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 440,<br />+  .volt2 = 1000,<br />+ },<br />+ [7] = { /* ch7, internal, system supply */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 2200,<br />+  .volt2 = 5000,<br />+ },<br />+ [8] = { /* ch8, internal, backup battery */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 2200,<br />+  .volt2 = 5000,<br />+ },<br />+ [9] = { /* ch 9, internal, external charger input */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 3960,<br />+  .volt2 = 9000,<br />+ },<br />+ [10] = { /* ch10, internal, VBUS */<br />+  .code1 = 150,<br />+  .code2 = 751,<br />+  .volt1 = 1000,<br />+  .volt2 = 5000,<br />+ },<br />+ [11] = { /* ch 11, internal, VBUS DC-DC output current */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 660,<br />+  .volt2 = 1500,<br />+ },<br />+
[12] = { /* ch 12, internal, Die temperature */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 440,<br />+  .volt2 = 1000,<br />+ },<br />+ [13] = { /* ch 13, internal, Die temperature */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 440,<br />+  .volt2 = 1000,<br />+ },<br />+ [14] = { /* ch 14, internal, USB ID line */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 2420,<br />+  .volt2 = 5500,<br />+ },<br />+ [15] = {}, /* ch 15, internal, test network */<br />+ [16] = {}, /* ch 16, internal, test network */<br />+ [17] = {}, /* ch 17, internal, battery charging current */<br />+ [18] = { /* ch 18, internal, battery voltage */<br />+  .code1 = 1441,<br />+  .code2 = 3276,<br />+  .volt1 = 2200,<br />+  .volt2 = 5000,<br />+ },<br />+};<br />+<br />+static inline int twl6030_gpadc_write(u8 reg, u8 val)<br />+{<br />+ return twl_i2c_write_u8(TWL6030_MODULE_GPADC, val, reg);<br />+}<br />+<br />+static inline int twl6030_gpadc_read(u8
reg, u8 *val)<br />+{<br />+<br />+ return twl_i2c_read(TWL6030_MODULE_GPADC, val, reg, 2);<br />+}<br />+<br />+static int twl6030_gpadc_enable_irq(u8 mask)<br />+{<br />+ int ret;<br />+<br />+ ret = twl6030_interrupt_unmask(mask, REG_INT_MSK_LINE_B);<br />+ if (ret < 0)<br />+  return ret;<br />+<br />+ ret = twl6030_interrupt_unmask(mask, REG_INT_MSK_STS_B);<br />+<br />+ return ret;<br />+}<br />+<br />+static void twl6030_gpadc_disable_irq(u8 mask)<br />+{<br />+ twl6030_interrupt_mask(mask, REG_INT_MSK_LINE_B);<br />+ twl6030_interrupt_mask(mask, REG_INT_MSK_STS_B);<br />+}<br />+<br />+static irqreturn_t twl6030_gpadc_irq_handler(int irq, void *_gpadc)<br />+{<br />+ struct twl6030_gpadc_data *gpadc = _gpadc;<br />+<br />+ complete(&gpadc->irq_complete);<br />+<br />+ return IRQ_HANDLED;<br />+}<br />+<br />+static int twl6030_start_conversion(int channel)<br />+{<br />+ return twl6030_gpadc_write(TWL6030_GPADC_CTRL_P1,<br />+     TWL6030_GPADC_CTRL_P1_SP1);<br
/>+}<br />+<br />+static int twl6032_start_conversion(int channel)<br />+{<br />+ int ret;<br />+<br />+ ret = twl6030_gpadc_write(TWL6032_GPADC_GPSELECT_ISB, channel);<br />+ if (ret)<br />+  return ret;<br />+<br />+ return twl6030_gpadc_write(TWL6032_GPADC_CTRL_P1,<br />+      TWL6030_GPADC_CTRL_P1_SP1);<br />+}<br />+<br />+static u8 twl6030_channel_to_reg(int channel)<br />+{<br />+ return TWL6030_GPADC_GPCH0_LSB + 2 * channel;<br />+}<br />+<br />+static u8 twl6032_channel_to_reg(int channel)<br />+{<br />+ /*<br />+  * for any prior chosen channel, when the conversion is ready<br />+  * the result is avalable in GPCH0_LSB, GPCH0_MSB.<br />+  */<br />+<br />+ return TWL6032_GPADC_GPCH0_LSB;<br />+}<br />+<br />+static int twl6030_gpadc_get_raw(struct twl6030_gpadc_data *gpadc,<br />+  int channel, int *res)<br />+{<br />+ u8 reg = gpadc->pdata->channel_to_reg(channel);<br />+ __le16 val;<br />+ int ret;<br />+<br />+ ret = twl6030_gpadc_read(reg, (u8 *)&val);<br />+
if (ret) {<br />+  dev_dbg(gpadc->dev, "unable to read register 0x%X\n", reg);<br />+  return ret;<br />+ }<br />+<br />+ *res = le16_to_cpup(&val);<br />+<br />+ return ret;<br />+}<br />+<br />+static int twl6030_gpadc_get_processed(struct twl6030_gpadc_data *gpadc,<br />+  int channel, int *val)<br />+{<br />+ int raw_code;<br />+ int corrected_code;<br />+ int channel_value;<br />+ int ret;<br />+<br />+ ret = twl6030_gpadc_get_raw(gpadc, channel, &raw_code);<br />+ if (ret)<br />+  return ret;<br />+<br />+ corrected_code = ((raw_code * 1000) -<br />+  gpadc->twl6030_cal_tbl[channel].offset_error) /<br />+  gpadc->twl6030_cal_tbl[channel].gain_error;<br />+<br />+ channel_value = corrected_code *<br />+   gpadc->twl6030_cal_tbl[channel].gain;<br />+<br />+ /* Shift back into mV range */<br />+ channel_value /= 1000;<br />+<br />+ dev_dbg(gpadc->dev, "GPADC raw code: %d", raw_code);<br />+ dev_dbg(gpadc->dev, "GPADC corrected code: %d",
corrected_code);<br />+ dev_dbg(gpadc->dev, "GPADC value: %d", channel_value);<br />+<br />+ *val = channel_value;<br />+<br />+ return ret;<br />+}<br />+<br />+static int twl6030_gpadc_read_raw(struct iio_dev *indio_dev,<br />+        const struct iio_chan_spec *chan,<br />+        int *val, int *val2, long mask)<br />+{<br />+ struct twl6030_gpadc_data *gpadc = iio_priv(indio_dev);<br />+ int ret = -EINVAL;<br />+ long timeout;<br />+<br />+ mutex_lock(&gpadc->lock);<br />+<br />+ ret = gpadc->pdata->start_conversion(chan->channel);<br />+ if (ret) {<br />+  dev_err(gpadc->dev, "failed to start conversion\n");<br />+  goto err;<br />+ }<br />+ /* wait for conversion to complete */<br />+ timeout = wait_for_completion_interruptible_timeout(<br />+    &gpadc->irq_complete, msecs_to_jiffies(5000));<br />+ if (!timeout)<br />+  return -ETIMEDOUT;<br />+ else if (timeout < 0)<br />+  return EINTR;<br />+<br />+ switch (mask) {<br />+ case
IIO_CHAN_INFO_RAW:<br />+  ret = twl6030_gpadc_get_raw(gpadc, chan->channel, val);<br />+  ret = ret ? -EIO : IIO_VAL_INT;<br />+  break;<br />+<br />+ case IIO_CHAN_INFO_PROCESSED:<br />+  ret = twl6030_gpadc_get_processed(gpadc, chan->channel, val);<br />+  ret = ret ? -EIO : IIO_VAL_INT;<br />+  break;<br />+<br />+ default:<br />+  break;<br />+ }<br />+err:<br />+ mutex_unlock(&gpadc->lock);<br />+<br />+ return ret;<br />+}<br />+<br />+/*<br />+ * The GPADC channels are calibrated using a two point calibration method.<br />+ * The channels measured with two known values: volt1 and volt2, and<br />+ * ideal corresponding output codes are known: code1, code2.<br />+ * The difference(d1, d2) between ideal and measured codes stored in trim<br />+ * registers.<br />+ * The goal is to find offset and gain of the real curve for each calibrated<br />+ * channel.<br />+ * gain: k = 1 + ((d2 - d1) / (x2 - x1))<br />+ * offset: b = d1 + (k - 1) * x1<br />+ */<br />+static
void twl6030_calibrate_channel(struct twl6030_gpadc_data *gpadc,<br />+     int channel, int d1, int d2)<br />+{<br />+ int b, k, gain, x1, x2;<br />+ const struct twl6030_ideal_code *ideal = gpadc->pdata->ideal;<br />+<br />+ /* Gain */<br />+ gain = ((ideal[channel].volt2 - ideal[channel].volt1) * 1000) /<br />+  (ideal[channel].code2 - ideal[channel].code1);<br />+<br />+ x1 = ideal[channel].code1;<br />+ x2 = ideal[channel].code2;<br />+<br />+ /* k - real curve gain */<br />+ k = 1000 + (((d2 - d1) * 1000) / (x2 - x1));<br />+<br />+ /* b - offset of the real curve gain */<br />+ b = (d1 * 1000) - (k - 1000) * x1;<br />+<br />+ gpadc->twl6030_cal_tbl[channel].gain = gain;<br />+ gpadc->twl6030_cal_tbl[channel].gain_error = k;<br />+ gpadc->twl6030_cal_tbl[channel].offset_error = b;<br />+<br />+ dev_dbg(gpadc->dev, "GPADC d1   for Chn: %d = %d\n", channel, d1);<br />+ dev_dbg(gpadc->dev, "GPADC d2   for Chn: %d = %d\n", channel, d2);<br />+
dev_dbg(gpadc->dev, "GPADC x1   for Chn: %d = %d\n", channel, x1);<br />+ dev_dbg(gpadc->dev, "GPADC x2   for Chn: %d = %d\n", channel, x2);<br />+ dev_dbg(gpadc->dev, "GPADC Gain for Chn: %d = %d\n", channel, gain);<br />+ dev_dbg(gpadc->dev, "GPADC k    for Chn: %d = %d\n", channel, k);<br />+ dev_dbg(gpadc->dev, "GPADC b    for Chn: %d = %d\n", channel, b);<br />+}<br />+<br />+static inline int twl6030_gpadc_get_trim_offset(s8 d)<br />+{<br />+ /*<br />+  * XXX NOTE!<br />+  * bit 0 - sign, bit 7 - reserved, 6..1 - trim value<br />+  * though, the documentation states that trim value<br />+  * is absolute value, the correct conversion results are<br />+  * obtained if the value is interpreted as 2's complement.<br />+  */<br />+ __u32 temp = ((d & 0x7f) >> 1) | ((d & 1) << 6);<br />+<br />+ return sign_extend32(temp, 6);<br />+}<br />+<br />+static int twl6030_calibration(struct twl6030_gpadc_data *gpadc)<br />+{<br />+ int ret;<br />+ int
chn;<br />+ u8 trim_regs[TWL6030_GPADC_MAX_CHANNELS];<br />+ s8 d1, d2;<br />+<br />+ /*<br />+  * for calibration two measurements have been performed at<br />+  * factory, for some channels, during the production test and<br />+  * have been stored in registers. This two stored values are<br />+  * used to correct the measurements. The values represent<br />+  * offsets for the given input from the output on ideal curve.<br />+  */<br />+ ret = twl_i2c_read(TWL6030_MODULE_ID2, trim_regs,<br />+   TWL6030_GPADC_TRIM1, 16);<br />+ if (ret < 0) {<br />+  dev_err(gpadc->dev, "calibration failed\n");<br />+  return ret;<br />+ }<br />+<br />+ for (chn = 0; chn < TWL6030_GPADC_MAX_CHANNELS; chn++) {<br />+<br />+  switch (chn) {<br />+  case 0:<br />+   d1 = trim_regs[0];<br />+   d2 = trim_regs[1];<br />+   break;<br />+  case 1:<br />+  case 3:<br />+  case 4:<br />+  case 5:<br />+  case 6:<br />+   d1 = trim_regs[4];<br />+   d2 = trim_regs[5];<br />+   break;<br />+  case
2:<br />+   d1 = trim_regs[12];<br />+   d2 = trim_regs[13];<br />+   break;<br />+  case 7:<br />+   d1 = trim_regs[6];<br />+   d2 = trim_regs[7];<br />+   break;<br />+  case 8:<br />+   d1 = trim_regs[2];<br />+   d2 = trim_regs[3];<br />+   break;<br />+  case 9:<br />+   d1 = trim_regs[8];<br />+   d2 = trim_regs[9];<br />+   break;<br />+  case 10:<br />+   d1 = trim_regs[10];<br />+   d2 = trim_regs[11];<br />+   break;<br />+  case 14:<br />+   d1 = trim_regs[14];<br />+   d2 = trim_regs[15];<br />+   break;<br />+  default:<br />+   continue;<br />+  }<br />+<br />+  d1 = twl6030_gpadc_get_trim_offset(d1);<br />+  d2 = twl6030_gpadc_get_trim_offset(d2);<br />+<br />+  twl6030_calibrate_channel(gpadc, chn, d1, d2);<br />+ }<br />+<br />+ return 0;<br />+}<br />+<br />+static int twl6032_calibration(struct twl6030_gpadc_data *gpadc)<br />+{<br />+ int chn, d1 = 0, d2 = 0, temp;<br />+ u8 trim_regs[17];<br />+ int ret;<br />+<br />+ ret = twl_i2c_read(TWL6030_MODULE_ID2,
trim_regs + 1,<br />+   TWL6030_GPADC_TRIM1, 16);<br />+ if (ret < 0) {<br />+  dev_err(gpadc->dev, "calibration failed\n");<br />+  return ret;<br />+ }<br />+<br />+ /*<br />+  * Loop to calculate the value needed for returning voltages from<br />+  * GPADC not values.<br />+  *<br />+  * gain is calculated to 3 decimal places fixed point.<br />+  */<br />+ for (chn = 0; chn < TWL6032_GPADC_MAX_CHANNELS; chn++) {<br />+<br />+  switch (chn) {<br />+  case 0:<br />+  case 1:<br />+  case 2:<br />+  case 3:<br />+  case 4:<br />+  case 5:<br />+  case 6:<br />+  case 11:<br />+  case 12:<br />+  case 13:<br />+  case 14:<br />+   /* D1 */<br />+   d1 = (trim_regs[3] & 0x1F) << 2;<br />+   d1 |= (trim_regs[1] & 0x06) >> 1;<br />+   if (trim_regs[1] & 0x01)<br />+    d1 = -d1;<br />+<br />+   /* D2 */<br />+   d2 = (trim_regs[4] & 0x3F) << 2;<br />+   d2 |= (trim_regs[2] & 0x06) >> 1;<br />+   if (trim_regs[2] & 0x01)<br />+   
d2 = -d2;<br />+   break;<br />+  case 8:<br />+   /* D1 */<br />+   temp = (trim_regs[3] & 0x1F) << 2;<br />+   temp |= (trim_regs[1] & 0x06) >> 1;<br />+   if (trim_regs[1] & 0x01)<br />+    temp = -temp;<br />+<br />+   d1 = (trim_regs[8] & 0x18) << 1;<br />+   d1 |= (trim_regs[7] & 0x1E) >> 1;<br />+   if (trim_regs[7] & 0x01)<br />+    d1 = -d1;<br />+<br />+   d1 += temp;<br />+<br />+   /* D2 */<br />+   temp = (trim_regs[4] & 0x3F) << 2;<br />+   temp |= (trim_regs[2] & 0x06) >> 1;<br />+   if (trim_regs[2] & 0x01)<br />+    temp = -temp;<br />+<br />+   d2 = (trim_regs[10] & 0x1F) << 2;<br />+   d2 |= (trim_regs[8] & 0x06) >> 1;<br />+   if (trim_regs[8] & 0x01)<br />+    d2 = -d2;<br />+<br />+   d2 += temp;<br />+   break;<br />+  case 9:<br />+   /* D1 */<br />+   temp = (trim_regs[3] & 0x1F) << 2;<br />+   temp |= (trim_regs[1] & 0x06) >> 1;<br />+   if
(trim_regs[1] & 0x01)<br />+    temp = -temp;<br />+<br />+   d1 = (trim_regs[14] & 0x18) << 1;<br />+   d1 |= (trim_regs[12] & 0x1E) >> 1;<br />+   if (trim_regs[12] & 0x01)<br />+    d1 = -d1;<br />+<br />+   d1 += temp;<br />+<br />+   /* D2 */<br />+   temp = (trim_regs[4] & 0x3F) << 2;<br />+   temp |= (trim_regs[2] & 0x06) >> 1;<br />+   if (trim_regs[2] & 0x01)<br />+    temp = -temp;<br />+<br />+   d2 = (trim_regs[16] & 0x1F) << 2;<br />+   d2 |= (trim_regs[14] & 0x06) >> 1;<br />+   if (trim_regs[14] & 0x01)<br />+    d2 = -d2;<br />+<br />+   d2 += temp;<br />+  case 10:<br />+   /* D1 */<br />+   d1 = (trim_regs[11] & 0x0F) << 3;<br />+   d1 |= (trim_regs[9] & 0x0E) >> 1;<br />+   if (trim_regs[9] & 0x01)<br />+    d1 = -d1;<br />+<br />+   /* D2 */<br />+   d2 = (trim_regs[15] & 0x0F) << 3;<br />+   d2 |= (trim_regs[13] & 0x0E) >> 1;<br />+   if
(trim_regs[13] & 0x01)<br />+    d2 = -d2;<br />+   break;<br />+  case 7:<br />+  case 18:<br />+   /* D1 */<br />+   temp = (trim_regs[3] & 0x1F) << 2;<br />+   temp |= (trim_regs[1] & 0x06) >> 1;<br />+   if (trim_regs[1] & 0x01)<br />+    temp = -temp;<br />+<br />+   d1 = (trim_regs[5] & 0x7E) >> 1;<br />+   if (trim_regs[5] & 0x01)<br />+    d1 = -d1;<br />+<br />+   d1 += temp;<br />+<br />+   /* D2 */<br />+   temp = (trim_regs[4] & 0x3F) << 2;<br />+   temp |= (trim_regs[2] & 0x06) >> 1;<br />+   if (trim_regs[2] & 0x01)<br />+    temp = -temp;<br />+<br />+   d2 = (trim_regs[6] & 0xFE) >> 1;<br />+   if (trim_regs[6] & 0x01)<br />+    d2 = -d2;<br />+<br />+   d2 += temp;<br />+   break;<br />+  default:<br />+   /* No data for other channels */<br />+   continue;<br />+  }<br />+<br />+  twl6030_calibrate_channel(gpadc, chn, d1, d2);<br />+ }<br />+<br />+ return 0;<br />+}<br />+<br
/>+#define TWL6030_GPADC_CHAN(chn, chan_info) {  \<br />+ .type = IIO_VOLTAGE,    \<br />+ .channel = chn,     \<br />+ .info_mask = BIT(chan_info),   \<br />+ .info_mask_separate = BIT(chan_info),   \<br />+ .indexed = 1,     \<br />+}<br />+<br />+/* internal test network channel */<br />+#define TWL6030_GPADC_TEST_CHAN(chn, chan_info) { \<br />+ .type = IIO_VOLTAGE,    \<br />+ .channel = chn,     \<br />+ .info_mask = BIT(chan_info),   \<br />+ .indexed = 1,     \<br />+}<br />+<br />+static const struct iio_chan_spec twl6030_gpadc_iio_channels[] = {<br />+ TWL6030_GPADC_CHAN(0, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(1, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(2, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(3, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(4, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(5, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(6, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(7,
IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(8, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(9, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(10, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(11, IIO_CHAN_INFO_RAW),<br />+ TWL6030_GPADC_CHAN(12, IIO_CHAN_INFO_RAW),<br />+ TWL6030_GPADC_CHAN(13, IIO_CHAN_INFO_RAW),<br />+ TWL6030_GPADC_CHAN(14, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_TEST_CHAN(15, IIO_CHAN_INFO_RAW),<br />+ TWL6030_GPADC_TEST_CHAN(16, IIO_CHAN_INFO_RAW),<br />+};<br />+<br />+static const struct iio_chan_spec twl6032_gpadc_iio_channels[] = {<br />+ TWL6030_GPADC_CHAN(0, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(1, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(2, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(3, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(4, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(5, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(6, IIO_CHAN_INFO_PROCESSED),<br />+
TWL6030_GPADC_CHAN(7, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(8, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(9, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(10, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(11, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(12, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(13, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_CHAN(14, IIO_CHAN_INFO_PROCESSED),<br />+ TWL6030_GPADC_TEST_CHAN(15, IIO_CHAN_INFO_RAW),<br />+ TWL6030_GPADC_TEST_CHAN(16, IIO_CHAN_INFO_RAW),<br />+ TWL6030_GPADC_CHAN(17, IIO_CHAN_INFO_RAW),<br />+ TWL6030_GPADC_CHAN(18, IIO_CHAN_INFO_PROCESSED),<br />+};<br />+<br />+static const struct iio_info twl6030_gpadc_iio_info = {<br />+ .read_raw = &twl6030_gpadc_read_raw,<br />+ .driver_module = THIS_MODULE,<br />+};<br />+<br />+static const struct twl6030_gpadc_platform_data twl6030_pdata = {<br />+ .iio_channels = twl6030_gpadc_iio_channels,<br />+ .nchannels =
TWL6030_GPADC_MAX_CHANNELS,<br />+ .ideal = twl6030_ideal,<br />+ .start_conversion = twl6030_start_conversion,<br />+ .channel_to_reg = twl6030_channel_to_reg,<br />+ .calibrate = twl6030_calibration,<br />+};<br />+<br />+static const struct twl6030_gpadc_platform_data twl6032_pdata = {<br />+ .iio_channels = twl6032_gpadc_iio_channels,<br />+ .nchannels = TWL6032_GPADC_MAX_CHANNELS,<br />+ .ideal = twl6032_ideal,<br />+ .start_conversion = twl6032_start_conversion,<br />+ .channel_to_reg = twl6032_channel_to_reg,<br />+ .calibrate = twl6032_calibration,<br />+};<br />+<br />+static const struct of_device_id of_twl6030_match_tbl[] = {<br />+ {<br />+  .compatible = "ti,twl6030_gpadc",<br />+  .data = &twl6030_pdata,<br />+ },<br />+ {<br />+  .compatible = "ti,twl6032_gpadc",<br />+  .data = &twl6032_pdata,<br />+ },<br />+ { /* end */ }<br />+};<br />+<br />+static int twl6030_gpadc_probe(struct platform_device *pdev)<br />+{<br />+ struct device *dev =
&pdev->dev;<br />+ struct twl6030_gpadc_data *gpadc;<br />+ const struct twl6030_gpadc_platform_data *pdata;<br />+ const struct of_device_id *match;<br />+ struct iio_dev *indio_dev;<br />+ int irq;<br />+ int ret;<br />+<br />+ match = of_match_device(of_match_ptr(of_twl6030_match_tbl), dev);<br />+ pdata = match ? match->data : dev->platform_data;<br />+<br />+ if (!pdata)<br />+  return -EINVAL;<br />+<br />+ indio_dev = iio_device_alloc(sizeof(*gpadc));<br />+ if (!indio_dev) {<br />+  dev_err(dev, "failed allocating iio device\n");<br />+  ret = -ENOMEM;<br />+ }<br />+<br />+ gpadc = iio_priv(indio_dev);<br />+<br />+ gpadc->twl6030_cal_tbl = devm_kzalloc(dev,<br />+     sizeof(struct twl6030_chnl_calib) *<br />+     pdata->nchannels, GFP_KERNEL);<br />+ if (!gpadc->twl6030_cal_tbl)<br />+  goto err_free_device;<br />+<br />+ gpadc->dev = dev;<br />+ gpadc->pdata = pdata;<br />+<br />+ platform_set_drvdata(pdev, indio_dev);<br />+
mutex_init(&gpadc->lock);<br />+ init_completion(&gpadc->irq_complete);<br />+<br />+ ret = pdata->calibrate(gpadc);<br />+ if (ret < 0) {<br />+  dev_err(&pdev->dev, "failed to read calibration registers\n");<br />+  goto err_free_device;<br />+ }<br />+<br />+ irq = platform_get_irq(pdev, 0);<br />+ if (irq < 0) {<br />+  dev_err(&pdev->dev, "failed to get irq\n");<br />+  goto err_free_device;<br />+ }<br />+<br />+ ret = request_threaded_irq(irq, NULL, twl6030_gpadc_irq_handler,<br />+    IRQF_ONESHOT, "twl6030_gpadc", gpadc);<br />+ if (ret) {<br />+  dev_dbg(&pdev->dev, "could not request irq\n");<br />+  goto err_free_device;<br />+ }<br />+<br />+ ret = twl6030_gpadc_enable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK);<br />+ if (ret < 0) {<br />+  dev_err(&pdev->dev, "failed to enable GPADC interrupt\n");<br />+  goto err_free_irq;<br />+ }<br />+<br />+ ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS,<br />+    
TWL6030_REG_TOGGLE1);<br />+ if (ret < 0) {<br />+  dev_err(&pdev->dev, "failed to enable GPADC module\n");<br />+  goto err_free_irq;<br />+ }<br />+<br />+ indio_dev->name = DRIVER_NAME;<br />+ indio_dev->dev.parent = dev;<br />+ indio_dev->info = &twl6030_gpadc_iio_info;<br />+ indio_dev->modes = INDIO_DIRECT_MODE;<br />+ indio_dev->channels = pdata->iio_channels;<br />+ indio_dev->num_channels = pdata->nchannels;<br />+<br />+ ret = iio_device_register(indio_dev);<br />+ if (ret)<br />+  goto err_free_irq;<br />+<br />+ return ret;<br />+<br />+err_free_irq:<br />+ free_irq(irq, indio_dev);<br />+err_free_device:<br />+ iio_device_free(indio_dev);<br />+<br />+ return ret;<br />+}<br />+<br />+static int twl6030_gpadc_remove(struct platform_device *pdev)<br />+{<br />+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);<br />+<br />+ twl6030_gpadc_disable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK);<br />+ free_irq(platform_get_irq(pdev, 0),
indio_dev);<br />+ iio_device_unregister(indio_dev);<br />+ iio_device_free(indio_dev);<br />+<br />+ return 0;<br />+}<br />+<br />+#ifdef CONFIG_PM_SLEEP<br />+static int twl6030_gpadc_suspend(struct device *pdev)<br />+{<br />+ int ret;<br />+<br />+ ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCR,<br />+    TWL6030_REG_TOGGLE1);<br />+ if (ret)<br />+  dev_err(pdev, "error reseting GPADC (%d)!\n", ret);<br />+<br />+ return 0;<br />+};<br />+<br />+static int twl6030_gpadc_resume(struct device *pdev)<br />+{<br />+ int ret;<br />+<br />+ ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS,<br />+    TWL6030_REG_TOGGLE1);<br />+ if (ret)<br />+  dev_err(pdev, "error setting GPADC (%d)!\n", ret);<br />+<br />+ return 0;<br />+};<br />+#endif<br />+<br />+SIMPLE_DEV_PM_OPS(twl6030_gpadc_pm_ops, twl6030_gpadc_suspend,<br />+     twl6030_gpadc_resume);<br />+<br />+static struct platform_driver twl6030_gpadc_driver = {<br />+ .probe  = twl6030_gpadc_probe,<br />+
remove  = twl6030_gpadc_remove,<br />+ .driver  = {<br />+  .name = DRIVER_NAME,<br />+  .owner = THIS_MODULE,<br />+  .pm = &twl6030_gpadc_pm_ops,<br />+  .of_match_table = of_twl6030_match_tbl,<br />+ },<br />+};<br />+<br />+module_platform_driver(twl6030_gpadc_driver);<br />+<br />+MODULE_ALIAS("platform: " DRIVER_NAME);<br />+MODULE_AUTHOR("Texas Instruments Inc.");<br />+MODULE_DESCRIPTION("twl6030 ADC driver");<br />+MODULE_LICENSE("GPL");</pre></blockquote></div><br>
-- <br>
Sent from my Android phone with K-9 Mail. Please excuse my brevity.</body></html>