Hi Lee, Thank you for the review. There is v3 of this patch. My bad I didn't put it in reply to v1. I'll address you comments in v4. Would you care to review v3, please. Regards, OK. >> The GPADC is general purpose ADC found on TWL6030, >> and TWL6032 PMIC, known also as Phoenix and PhoenixLite. >> >> The TWL6030 and TWL6032 have GPADC with 17 and 19 >> channels respectively. Some channels have current >> source and are used for measuring voltage drop >> on resistive load for detecting battery ID resistance, >> or measuring voltage drop on NTC resistors for external >> temperature measurements. Some channels measure voltage, >> (i.e. battery voltage), and have voltage dividers, >> thus, capable to scale voltage. Some channels are dedicated >> for measuring die temperature. > >Can you use the full available width of the buffer please, either 72 >or 80 chars is normally fine. > >> Some channels are calibrated in 2 points, having >> offsets from ideal values kept in trim registers. This >> is used to correct measurements. >> >> The differences between GPADC in TWL6030 and TWL6032: >> - 10 bit vs 12 bit ADC; >> - 17 vs 19 channels; >> - channels have different purpose(i. e. battery voltage > >Nit: No space here -^ > >> channel 8 vs channel 18); >> - trim values are interpreted differently. >> >> The driver exports function returning converted value for >> requested channels. >> >> Based on the driver patched from Balaji TK, Graeme Gregory, Ambresh K, >> Girish S Ghongdemath. >> >> Signed-off-by: Balaji T K <balajitk@xxxxxx> >> Graeme Gregory <gg@xxxxxxxxxxxxxxx> > >SOB? RB? AB? > >> Signed-off-by: Oleksandr Kozaruk <oleksandr.kozaruk@xxxxxx> >> --- >> .../testing/sysfs-devices-platform-twl6030_gpadc | 5 + >> drivers/mfd/Kconfig | 8 + >> drivers/mfd/Makefile | 1 + >> drivers/mfd/twl6030-gpadc.c | 1053 ++++++++++++++++++++ >> include/linux/i2c/twl6030-gpadc.h | 51 + > >Why here instead of include/linux/mfd? > >> 5 files changed, 1118 insertions(+) >> create mode 100644 Documentation/ABI/testing/sysfs-devices-platform-twl6030_gpadc >> create mode 100644 drivers/mfd/twl6030-gpadc.c >> create mode 100644 include/linux/i2c/twl6030-gpadc.h >> >> diff --git a/Documentation/ABI/testing/sysfs-devices-platform-twl6030_gpadc b/Documentation/ABI/testing/sysfs-devices-platform-twl6030_gpadc >> new file mode 100644 >> index 0000000..e9c5812 >> --- /dev/null >> +++ b/Documentation/ABI/testing/sysfs-devices-platform-twl6030_gpadc >> @@ -0,0 +1,5 @@ >> +What: /sys/bus/platform/devices/twl603X_gpadc.26/inX_channel > >Are you sure this is where they will be created? > >What's with the .26? > >> +Date: June 2013 >> +Contact: Oleksandr Kozaruk <oleksandr.kozaruk@xxxxxx> >> +Description: Start GPADC conversion for chosen channel X and report the result. >> + The result is returned in millivolts. > >Does this require Greg's Ack? > >> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig >> index d54e985..8eb7494 100644 >> --- a/drivers/mfd/Kconfig >> +++ b/drivers/mfd/Kconfig >> @@ -970,6 +970,14 @@ config MFD_TC3589X >> additional drivers must be enabled in order to use the >> functionality of the device. >> >> +config TWL6030_GPADC > >As this supports the TWL6030 and TWL6032, wouldn't it be better to use >TWL603X. Same goes for the driver/header filenames. > >> + tristate "TWL6030 GPADC (General Purpose A/D Convertor) Support" >> + depends on TWL4030_CORE >> + default n >> + help >> + Say yes here if you want support for the TWL6030 General Purpose >> + A/D Convertor. >> + > >Any chance you can bundle this with the other TWL* variants instead of >dumping it in an arbitrary location within the Kconfig file? > >> config MFD_TMIO >> bool >> default n >> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile >> index 718e94a..59f504f 100644 >> --- a/drivers/mfd/Makefile >> +++ b/drivers/mfd/Makefile >> @@ -70,6 +70,7 @@ obj-$(CONFIG_MENELAUS) += menelaus.o >> obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o >> obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o >> obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o >> +obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o >> obj-$(CONFIG_MFD_TWL4030_AUDIO) += twl4030-audio.o >> obj-$(CONFIG_TWL6040_CORE) += twl6040.o >> >> diff --git a/drivers/mfd/twl6030-gpadc.c b/drivers/mfd/twl6030-gpadc.c >> new file mode 100644 >> index 0000000..1868bc0 >> --- /dev/null >> +++ b/drivers/mfd/twl6030-gpadc.c >> @@ -0,0 +1,1053 @@ >> +/* >> + * TWL6030 GPADC module driver >> + * >> + * Copyright (C) 2009-2013 Texas Instruments Inc. >> + * Nishant Kamat <nskamat@xxxxxx> >> + * Balaji T K <balajitk@xxxxxx> >> + * Graeme Gregory <gg@xxxxxxxxxxxxxxx> >> + * Girish S Ghongdemath <girishsg@xxxxxx> >> + * Ambresh K <ambresh@xxxxxx> >> + * Oleksandr Kozaruk <oleksandr.kozaruk@xxxxxx >> + * >> + * Based on twl4030-madc.c >> + * Copyright (C) 2008 Nokia Corporation >> + * Mikko Ylinen <mikko.k.ylinen@xxxxxxxxx> >> + * >> + * This program is free software; you can redistribute it and/or >> + * modify it under the terms of the GNU General Public License >> + * version 2 as published by the Free Software Foundation. >> + * >> + * This program is distributed in the hope that it will be useful, but >> + * WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >> + * General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public License >> + * along with this program; if not, write to the Free Software >> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA >> + * 02110-1301 USA >> + * >> + */ >> +#include <linux/init.h> >> +#include <linux/interrupt.h> >> +#include <linux/kernel.h> >> +#include <linux/types.h> >> +#include <linux/module.h> >> +#include <linux/platform_device.h> >> +#include <linux/slab.h> >> +#include <linux/hwmon-sysfs.h> >> +#include <linux/of_platform.h> >> +#include <linux/i2c/twl.h> >> +#include <linux/i2c/twl6030-gpadc.h> >> + >> +#define DRIVER_NAME "twl6030_gpadc" >> + >> +#define TWL6030_GPADC_MAX_CHANNELS 17 >> +#define TWL6032_GPADC_MAX_CHANNELS 19 >> +/* Define this as the biggest of all chips using this driver */ >> +#define GPADC_MAX_CHANNELS TWL6032_GPADC_MAX_CHANNELS >> + >> +#define TWL6030_GPADC_CTRL 0x00 /* 0x2e */ >> +#define TWL6030_GPADC_CTRL2 0x01 /* 0x2f */ >> + >> +#define TWL6030_GPADC_RTSELECT_LSB 0x02 /* 0x30 */ >> +#define TWL6030_GPADC_RTSELECT_ISB 0x03 >> +#define TWL6030_GPADC_RTSELECT_MSB 0x04 >> + >> +#define TWL6032_GPADC_RTSELECT_LSB 0x04 /* 0x32 */ >> +#define TWL6032_GPADC_RTSELECT_ISB 0x05 >> +#define TWL6032_GPADC_RTSELECT_MSB 0x06 >> + >> +#define TWL6030_GPADC_CTRL_P1 0x05 >> +#define TWL6030_GPADC_CTRL_P2 0x06 >> + >> +#define TWL6032_GPADC_GPSELECT_ISB 0x07 >> +#define TWL6032_GPADC_CTRL_P1 0x08 >> + >> +#define TWL6032_GPADC_RTCH0_LSB 0x09 >> +#define TWL6032_GPADC_RTCH0_MSB 0x0a >> +#define TWL6032_GPADC_RTCH1_LSB 0x0b >> +#define TWL6032_GPADC_RTCH1_MSB 0x0c >> +#define TWL6032_GPADC_GPCH0_LSB 0x0d >> +#define TWL6032_GPADC_GPCH0_MSB 0x0e >> + >> +#define TWL6030_GPADC_CTRL_P1_SP1 BIT(3) >> +#define TWL6030_GPADC_CTRL_P1_EOCRT BIT(2) >> +#define TWL6030_GPADC_CTRL_P1_EOCP1 BIT(1) >> +#define TWL6030_GPADC_CTRL_P1_BUSY BIT(0) >> + >> +#define TWL6030_GPADC_CTRL_P2_SP2 BIT(2) >> +#define TWL6030_GPADC_CTRL_P2_EOCP2 BIT(1) >> +#define TWL6030_GPADC_CTRL_P1_BUSY BIT(0) >> + >> +#define TWL6030_GPADC_EOC_SW BIT(1) >> +#define TWL6030_GPADC_BUSY BIT(0) >> + >> +#define TWL6030_GPADC_RTCH0_LSB (0x07) >> +#define TWL6030_GPADC_GPCH0_LSB (0x29) >> + >> +/* Fixed channels */ >> +#define TWL6030_GPADC_CTRL_TEMP1_EN BIT(0) /* input ch 1 */ >> +#define TWL6030_GPADC_CTRL_TEMP2_EN BIT(1) /* input ch 4 */ >> +#define TWL6030_GPADC_CTRL_SCALER_EN BIT(2) /* input ch 2 */ >> +#define TWL6030_GPADC_CTRL_SCALER_DIV4 BIT(3) >> +#define TWL6030_GPADC_CTRL_SCALER_EN_CH11 BIT(4) /* input ch 11 */ >> +#define TWL6030_GPADC_CTRL_TEMP1_EN_MONITOR BIT(5) >> +#define TWL6030_GPADC_CTRL_TEMP2_EN_MONITOR BIT(6) >> +#define TWL6030_GPADC_CTRL_ISOURCE_EN BIT(7) >> + >> +#define TWL6030_GPADC_CTRL2_REMSENSE_0 BIT(0) >> +#define TWL6030_GPADC_CTRL2_REMSENSE_1 BIT(1) >> +#define TWL6030_GPADC_CTRL2_SCALER_EN_CH18 BIT(2) >> +#define TWL6030_GPADC_CTRL2_VBAT_SCALER_DIV4 BIT(3) >> + >> +#define TWL6030_GPADC_RT_SW1_EOC_MASK BIT(5) >> +#define TWL6030_GPADC_SW2_EOC_MASK BIT(6) >> + >> +#define TWL6032_GPADC_RT_EOC_MASK BIT(4) >> +#define TWL6032_GPADC_SW_EOC_MASK BIT(5) >> + >> +#define TWL6030_GPADC_TRIM1 0xCD >> + >> +#define TWL6030_REG_TOGGLE1 0x90 >> +#define TWL6030_GPADCS BIT(1) >> +#define TWL6030_GPADCR BIT(0) > >Any chance we can rid this lot into a header file somewhere? > >> +/** >> + * 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: at the beginning and >> + * at the and of the measurable input range >> + * >> + * @code1: ideal code for the input at the beginning >> + * @code2: ideal code for at the end of the range >> + * @v1: voltage input at the beginning(low voltage) >> + * @v2: voltage input at the end(high voltage) >> + */ >> +struct twl6030_ideal_code { >> + s16 code1; >> + s16 code2; >> + s16 v1; >> + s16 v2; >> +}; > >The variable name choices used throughout this driver make it >incredibly hard to read. Variable names like; d, x, v1, v2 etc are >undecipherable when read in as function arguments, then used in some >random equation. I'll provide examples later. > >Perhaps 'code' would be better read as '[raw|read|register]_value', if >that's what it actually is. 'v' would be better understood if it was >'voltage', or at least 'volt'. The '1' and '2' here should be replaced >by 'start' and 'end' or similar too. > >> +struct twl6030_gpadc_data; >> + >> +/** >> + * struct twl6030_gpadc_platform_data - platform specific data >> + * @nchannels: number of GPADC channels >> + * @twl6030_ideal: pointer to calibration parameters >> + * @convert: pointer to ADC conversion function >> + * @calibrate: pointer to calibration function >> + */ >> +struct twl6030_gpadc_platform_data { >> + const int nchannels; >> + const struct twl6030_ideal_code *ideal; >> + int (*convert)(u32 channels, struct twl6030_gpadc_result *res); >> + int (*calibrate)(struct twl6030_gpadc_data *gpadc); >> +}; > >Is this actually platform data, or is it really device data? > >> +/** >> + * 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 > >... ah, so it's device data then? Calling it pdata, is misleading. > >> + */ >> +struct twl6030_gpadc_data { >> + struct device *dev; >> + struct mutex lock; >> + struct completion irq_complete; >> + struct twl6030_chnl_calib *twl6030_cal_tbl; >> + const struct twl6030_gpadc_platform_data *pdata; >> +}; >> + >> +static struct twl6030_gpadc_data *the_gpadc; > >This doesn't need to be global - see below. > >> +/* >> + * channels 11, 12, 13, 15 and 16 have no calibration data >> + * calibration offset is same for channels 1, 3, 4, 5 >> + */ >> +static const struct twl6030_ideal_code >> + twl6030_ideal[TWL6030_GPADC_MAX_CHANNELS] = { >> + { /* ch 0, external, battery type, resistor value */ >> + .code1 = 116, >> + .code2 = 745, >> + .v1 = 141, >> + .v2 = 910, >> + }, > >Care to elaborate on where these figures came from? > >> + { /* ch 1, external, battery temperature, NTC resistor value */ >> + .code1 = 82, >> + .code2 = 900, >> + .v1 = 100, >> + .v2 = 1100, >> + }, >> + { /* ch 2, external, audio accessory/general purpose */ >> + .code1 = 55, >> + .code2 = 818, >> + .v1 = 101, >> + .v2 = 1499, >> + }, >> + { /* ch 3, external, general purpose */ >> + .code1 = 82, >> + .code2 = 900, >> + .v1 = 100, >> + .v2 = 1100, >> + }, >> + { /* ch 4, external, temperature measurement/general purpose */ >> + .code1 = 82, >> + .code2 = 900, >> + .v1 = 100, >> + .v2 = 1100, >> + }, >> + { /* ch 5, external, general purpose */ >> + .code1 = 82, >> + .code2 = 900, >> + .v1 = 100, >> + .v2 = 1100, >> + }, >> + { /* ch 6, external, general purpose */ >> + .code1 = 82, >> + .code2 = 900, >> + .v1 = 100, >> + .v2 = 1100, >> + }, >> + { /* ch 7, internal, main battery */ >> + .code1 = 614, >> + .code2 = 941, >> + .v1 = 3001, >> + .v2 = 4599, >> + }, >> + { /* ch 8, internal, backup battery */ >> + .code1 = 82, >> + .code2 = 688, >> + .v1 = 501, >> + .v2 = 4203, >> + }, >> + { /* ch 9, internal, external charger input */ >> + .code1 = 182, >> + .code2 = 818, >> + .v1 = 2001, >> + .v2 = 8996, >> + }, >> + { /* ch 10, internal, VBUS */ >> + .code1 = 149, >> + .code2 = 818, >> + .v1 = 1001, >> + .v2 = 5497, >> + }, >> + {}, /* ch 11, internal, VBUS charging current */ >> + {}, /* ch 12, internal, Die temperature */ >> + {}, /* ch 13, internal, Die temperature */ >> + { /* ch 14, internal, USB ID line */ >> + .code1 = 48, >> + .code2 = 714, >> + .v1 = 323, >> + .v2 = 4800, >> + }, >> + {}, /* ch 15, internal, test network */ >> + {}, /* ch 16, internal, test network */ >> +}; >> + >> +static const struct twl6030_ideal_code >> + twl6032_ideal[TWL6032_GPADC_MAX_CHANNELS] = { >> + { /* ch 0, external, battery type, resistor value */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 440, >> + .v2 = 1000, >> + }, >> + { /* ch 1, external, battery temperature, NTC resistor value */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 440, >> + .v2 = 1000, >> + }, >> + { /* ch 2, external, audio accessory/general purpose */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 660, >> + .v2 = 1500, >> + }, >> + { /* ch 3, external, temperature with external diode/general purpose */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 440, >> + .v2 = 1000, >> + }, >> + { /* ch 4, external, temperature measurement/general purpose */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 440, >> + .v2 = 1000, >> + }, >> + { /* ch 5, external, general purpose */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 440, >> + .v2 = 1000, >> + }, >> + { /* ch 6, external, general purpose */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 440, >> + .v2 = 1000, >> + }, >> + { /* ch7, internal, system supply */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 2200, >> + .v2 = 5000, >> + }, >> + { /* ch8, internal, backup battery */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 2200, >> + .v2 = 5000, >> + }, >> + { /* ch 9, internal, external charger input */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 3960, >> + .v2 = 9000, >> + }, >> + { /* ch10, internal, VBUS */ >> + .code1 = 150, >> + .code2 = 751, >> + .v1 = 1000, >> + .v2 = 5000, >> + }, >> + { /* ch 11, internal, VBUS DC-DC output current */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 660, >> + .v2 = 1500, >> + }, >> + { /* ch 12, internal, Die temperature */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 440, >> + .v2 = 1000, >> + }, >> + { /* ch 13, internal, Die temperature */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 440, >> + .v2 = 1000, >> + }, >> + { /* ch 14, internal, USB ID line */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 2420, >> + .v2 = 5500, >> + }, >> + {}, /* ch 15, internal, test network */ >> + {}, /* ch 16, internal, test network */ >> + {}, /* ch 17, internal, battery charging current */ >> + { /* ch 18, internal, battery voltage */ >> + .code1 = 1441, >> + .code2 = 3276, >> + .v1 = 2200, >> + .v2 = 5000, >> + }, >> +}; >> + >> +static int twl6030_gpadc_read(struct twl6030_gpadc_data *gpadc, u8 reg) >> +{ >> + int ret; >> + u8 val = 0; >> + >> + ret = twl_i2c_read_u8(TWL6030_MODULE_GPADC, &val, reg); >> + if (ret) { >> + dev_dbg(gpadc->dev, "unable to read register 0x%X\n", reg); >> + return ret; >> + } >> + >> + return val; >> +} >> + >> +static int twl6030_gpadc_write(u8 reg, u8 val) >> +{ >> + return twl_i2c_write_u8(TWL6030_MODULE_GPADC, val, reg); >> +} >> + >> +static int twl6030_gpadc_channel_raw_read(struct twl6030_gpadc_data *gpadc, >> + u8 reg) >> +{ >> + u8 msb, lsb; >> + >> + msb = twl6030_gpadc_read(gpadc, reg + 1); >> + lsb = twl6030_gpadc_read(gpadc, reg); >> + >> + return (msb << 8) | lsb; >> +} >> + >> +static int twl6030_gpadc_enable_irq(u8 mask) >> +{ >> + int ret; >> + >> + ret = twl6030_interrupt_unmask(mask, REG_INT_MSK_LINE_B); >> + ret |= twl6030_interrupt_unmask(mask, REG_INT_MSK_STS_B); >> + >> + return ret; >> +} >> + >> +static void twl6030_gpadc_disable_irq(u8 mask) >> +{ >> + twl6030_interrupt_mask(mask, REG_INT_MSK_LINE_B); >> + twl6030_interrupt_mask(mask, REG_INT_MSK_STS_B); >> +} >> + >> +static irqreturn_t twl6030_gpadc_irq_handler(int irq, void *_gpadc) >> +{ >> + struct twl6030_gpadc_data *gpadc = _gpadc; >> + >> + complete(&gpadc->irq_complete); >> + >> + return IRQ_HANDLED; >> +} >> + >> +static int twl6030_channel_calibrated(const struct twl6030_gpadc_platform_data >> + *pdata, int channel) >> +{ >> + /* not calibrated channels have 0 in all structure members */ >> + return pdata->ideal[channel].code2; >> +} >> + >> +static void twl6030_gpadc_read_channel(struct twl6030_gpadc_data *gpadc, >> + int channel, u8 reg, struct twl6030_gpadc_result *res) >> +{ >> + s32 raw_code; >> + s32 corrected_code; >> + int channel_value; >> + >> + raw_code = twl6030_gpadc_channel_raw_read(gpadc, reg); >> + >> + res->raw_code = raw_code; >> + >> + if (!twl6030_channel_calibrated(gpadc->pdata, channel)) { >> + corrected_code = raw_code; >> + channel_value = raw_code; >> + } else { >> + corrected_code = ((raw_code * 1000) - >> + gpadc->twl6030_cal_tbl[channel].offset_error) / >> + gpadc->twl6030_cal_tbl[channel].gain_error; > >Can you add a small comment to describe what you're doing here? > >> + >> + channel_value = corrected_code * >> + gpadc->twl6030_cal_tbl[channel].gain; > >... and here. > >> + /* Shift back into mV range */ >> + channel_value /= 1000; >> + } >> + >> + dev_dbg(gpadc->dev, "GPADC raw: %d", raw_code); >> + dev_dbg(gpadc->dev, "GPADC cor: %d", corrected_code); >> + dev_dbg(gpadc->dev, "GPADC val: %d", channel_value); > >Odd that the debug prints are _less_ verbose than the variable names. > >> + res->corrected_code = corrected_code; >> + res->result = channel_value; >> +} >> + >> +static void twl6030_gpadc_get_results(u8 reg, u32 channels, >> + struct twl6030_gpadc_result *res) >> +{ >> + int i; >> + >> + for (i = 0; i < TWL6030_GPADC_MAX_CHANNELS; i++) { >> + >> + if (!(channels & BIT(i))) >> + continue; >> + >> + reg += 2 * i; >> + twl6030_gpadc_read_channel(the_gpadc, i, reg, &res[i]); >> + } >> +} >> + >> +/* locks held by caller */ >> +static int _twl6030_gpadc_conversion(u32 channels, >> + struct twl6030_gpadc_result *res) >> +{ >> + int ret; >> + >> + /* start conversion */ >> + ret = twl6030_gpadc_write(TWL6030_GPADC_CTRL_P1, >> + TWL6030_GPADC_CTRL_P1_SP1); >> + if (ret) { >> + pr_err("%s: failed to write\n", __func__); >> + return ret; >> + } >> + >> + /* wait for conversion to complete */ >> + wait_for_completion_interruptible_timeout(&the_gpadc->irq_complete, >> + msecs_to_jiffies(5000)); >> + >> + /* get the results */ >> + twl6030_gpadc_get_results(TWL6030_GPADC_GPCH0_LSB, channels, res); >> + >> + return ret; >> +} > >Why do the two *_gpadc_conversion() functions vastly differ semantically? > >> +/* locks held by caller */ >> +static int _twl6032_gpadc_conversion(u32 channels, >> + struct twl6030_gpadc_result *res) >> +{ >> + int i; >> + int ret; >> + >> + for (i = 0; i < TWL6032_GPADC_MAX_CHANNELS; i++) { >> + if (!(channels & BIT(i))) >> + continue; >> + >> + /* select the ADC channel to read */ >> + ret = twl6030_gpadc_write(TWL6032_GPADC_GPSELECT_ISB, i); >> + if (ret) >> + goto out; >> + >> + /* start conversion */ >> + ret = twl6030_gpadc_write(TWL6032_GPADC_CTRL_P1, >> + TWL6030_GPADC_CTRL_P1_SP1); >> + if (ret) >> + goto out; >> + >> + /* wait for conversion to complete */ >> + wait_for_completion_interruptible_timeout( >> + &the_gpadc->irq_complete, msecs_to_jiffies(5000)); >> + >> + twl6030_gpadc_read_channel(the_gpadc, i, >> + TWL6032_GPADC_GPCH0_LSB, &res[i]); >> + } >> + >> + return ret; >> +out: >> + pr_err("%s: failed to write\n", __func__); >> + return ret; >> +} >> + >> +int twl6030_gpadc_conversion(u32 channels, struct twl6030_gpadc_result *buf) >> +{ >> + struct twl6030_gpadc_data *gpadc = the_gpadc; >> + int ret; >> + >> + if (!gpadc) >> + return -EAGAIN; > >Why EAGAIN? Will 'the_gpadc' appear _later_? > >> + mutex_lock(&gpadc->lock); >> + >> + ret = gpadc->pdata->convert(channels, buf); >> + >> + mutex_unlock(&the_gpadc->lock); >> + >> + return ret; >> +} >> +EXPORT_SYMBOL(twl6030_gpadc_conversion); >> + >> +static ssize_t show_channel(struct device *dev, >> + struct device_attribute *devattr, char *buf) >> +{ >> + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); >> + struct twl6030_gpadc_result res[GPADC_MAX_CHANNELS]; >> + u32 channel = (1 << attr->index); > >Can we use BIT() again for the sake of consistency? > >> + int ret; >> + >> + ret = twl6030_gpadc_conversion(channel, res); >> + if (ret) >> + return ret; >> + >> + return sprintf(buf, "%d\n", res[attr->index].result); >> +} >> + >> +#define in_channel(index) \ >> +static SENSOR_DEVICE_ATTR(in##index##_channel, S_IRUGO, show_channel, \ >> + NULL, index); >> + >> +in_channel(0); >> +in_channel(1); >> +in_channel(2); >> +in_channel(3); >> +in_channel(4); >> +in_channel(5); >> +in_channel(6); >> +in_channel(7); >> +in_channel(8); >> +in_channel(9); >> +in_channel(10); >> +in_channel(11); >> +in_channel(12); >> +in_channel(13); >> +in_channel(14); >> +in_channel(15); >> +in_channel(16); >> +in_channel(17); >> +in_channel(18); >> + >> +#define IN_ATTRS_CHANNEL(X) (&sensor_dev_attr_in##X##_channel.dev_attr.attr) >> + >> +/* >> + * the number of attributes is chosen as for a device with >> + * max number of channels. It will be truncated before sysfs entries >> + * are created on probe. >> + */ >> +static struct attribute *twl6030_gpadc_attributes[] = { >> + IN_ATTRS_CHANNEL(0), >> + IN_ATTRS_CHANNEL(1), >> + IN_ATTRS_CHANNEL(2), >> + IN_ATTRS_CHANNEL(3), >> + IN_ATTRS_CHANNEL(4), >> + IN_ATTRS_CHANNEL(5), >> + IN_ATTRS_CHANNEL(6), >> + IN_ATTRS_CHANNEL(7), >> + IN_ATTRS_CHANNEL(8), >> + IN_ATTRS_CHANNEL(9), >> + IN_ATTRS_CHANNEL(10), >> + IN_ATTRS_CHANNEL(11), >> + IN_ATTRS_CHANNEL(12), >> + IN_ATTRS_CHANNEL(13), >> + IN_ATTRS_CHANNEL(14), >> + IN_ATTRS_CHANNEL(15), >> + IN_ATTRS_CHANNEL(16), >> + IN_ATTRS_CHANNEL(17), >> + IN_ATTRS_CHANNEL(18), >> + NULL >> +}; >> + >> +static const struct attribute_group twl6030_gpadc_group = { >> + .attrs = twl6030_gpadc_attributes, >> +}; >> + >> +static void twl6030_calibrate_channel(struct twl6030_gpadc_data *gpadc, >> + int channel, int d1, int d2) >> +{ >> + int b, k, gain, x1, x2; >> + const struct twl6030_ideal_code *ideal = gpadc->pdata->ideal; >> + >> + /* Gain */ >> + gain = ((ideal[channel].v2 - ideal[channel].v1) * 1000) / >> + (ideal[channel].code2 - ideal[channel].code1); >> + >> + x1 = ideal[channel].code1; >> + x2 = ideal[channel].code2; >> + >> + /* k */ >> + k = 1000 + (((d2 - d1) * 1000) / (x2 - x1)); >> + >> + /* b */ >> + b = (d1 * 1000) - (k - 1000) * x1; > >This function is pure craziness. A viewer should be able to decipher >what a function is doing without tracing back the source of each >variable. Better variable naming conventions and/or more verbose >commenting is required here. The equations also need better (or at >least some) explanation. > >> + gpadc->twl6030_cal_tbl[channel].gain = gain; >> + gpadc->twl6030_cal_tbl[channel].gain_error = k; >> + gpadc->twl6030_cal_tbl[channel].offset_error = b; >> + >> + 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); >> +} >> + >> +static inline int twl6030_gpadc_get_trim_offset(s8 d) >> +{ >> + int sign; >> + >> + /* >> + * 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. >> + */ >> + sign = d & 1; >> + d = (d & 0x7f) >> 1; >> + >> + return sign ? (d | 0xc0) : d; >> +} >> + >> +static int twl6030_calibration(struct twl6030_gpadc_data *gpadc) >> +{ >> + int ret; >> + int chn; >> + u8 trim_regs[TWL6030_GPADC_MAX_CHANNELS]; >> + 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, 16); >> + if (ret < 0) >> + 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: >> + d1 = trim_regs[4]; >> + d2 = trim_regs[5]; >> + break; >> + case 2: >> + d1 = trim_regs[12]; >> + d2 = trim_regs[13]; >> + break; >> + case 3: >> + case 4: >> + case 5: >> + case 6: >> + d1 = trim_regs[4]; >> + d2 = trim_regs[5]; >> + 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; >> + } >> + >> + d1 = twl6030_gpadc_get_trim_offset(d1); >> + d2 = twl6030_gpadc_get_trim_offset(d2); >> + >> + twl6030_calibrate_channel(gpadc, chn, d1, d2); >> + } >> + >> + return 0; >> +} >> + >> +static int twl6032_calibration(struct twl6030_gpadc_data *gpadc) >> +{ >> + int chn, d1 = 0, d2 = 0, temp; >> + u8 trim_regs[17]; >> + int ret; >> + >> + ret = twl_i2c_read(TWL6030_MODULE_ID2, trim_regs + 1, >> + TWL6030_GPADC_TRIM1, 16); >> + if (ret < 0) >> + return ret; >> + >> + /* Loop to calculate the value needed for returning voltages from >> + * GPADC not values. >> + * >> + * gain is calculated to 3 decimal places fixed point. >> + */ > >Please read: > >"The preferred style for long (multi-line) comments is:" > >in Documentation/CodingStyle. > >> + 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 12: >> + case 13: >> + case 14: >> + /* D1 */ >> + d1 = (trim_regs[3] & 0x1F) << 2; >> + d1 |= (trim_regs[1] & 0x06) >> 1; >> + if (trim_regs[1] & 0x01) >> + d1 = -d1; >> + >> + /* D2 */ >> + d2 = (trim_regs[4] & 0x3F) << 2; >> + d2 |= (trim_regs[2] & 0x06) >> 1; >> + if (trim_regs[2] & 0x01) >> + d2 = -d2; >> + break; >> + case 8: >> + /* D1 */ >> + temp = (trim_regs[3] & 0x1F) << 2; >> + temp |= (trim_regs[1] & 0x06) >> 1; >> + if (trim_regs[1] & 0x01) >> + temp = -temp; >> + >> + d1 = (trim_regs[8] & 0x18) << 1; >> + d1 |= (trim_regs[7] & 0x1E) >> 1; >> + if (trim_regs[7] & 0x01) >> + d1 = -d1; >> + >> + d1 += temp; >> + >> + /* D2 */ >> + temp = (trim_regs[4] & 0x3F) << 2; >> + temp |= (trim_regs[2] & 0x06) >> 1; >> + if (trim_regs[2] & 0x01) >> + temp = -temp; >> + >> + d2 = (trim_regs[10] & 0x1F) << 2; >> + d2 |= (trim_regs[8] & 0x06) >> 1; >> + if (trim_regs[8] & 0x01) >> + d2 = -d2; >> + >> + d2 += temp; >> + break; >> + case 9: >> + /* D1 */ >> + temp = (trim_regs[3] & 0x1F) << 2; >> + temp |= (trim_regs[1] & 0x06) >> 1; >> + if (trim_regs[1] & 0x01) >> + temp = -temp; >> + >> + d1 = (trim_regs[14] & 0x18) << 1; >> + d1 |= (trim_regs[12] & 0x1E) >> 1; >> + if (trim_regs[12] & 0x01) >> + d1 = -d1; >> + >> + d1 += temp; >> + >> + /* D2 */ >> + temp = (trim_regs[4] & 0x3F) << 2; >> + temp |= (trim_regs[2] & 0x06) >> 1; >> + if (trim_regs[2] & 0x01) >> + temp = -temp; >> + >> + d2 = (trim_regs[16] & 0x1F) << 2; >> + d2 |= (trim_regs[14] & 0x06) >> 1; >> + if (trim_regs[14] & 0x01) >> + d2 = -d2; >> + >> + d2 += temp; >> + case 10: >> + /* D1 */ >> + d1 = (trim_regs[11] & 0x0F) << 3; >> + d1 |= (trim_regs[9] & 0x0E) >> 1; >> + if (trim_regs[9] & 0x01) >> + d1 = -d1; >> + >> + /* D2 */ >> + d2 = (trim_regs[15] & 0x0F) << 3; >> + d2 |= (trim_regs[13] & 0x0E) >> 1; >> + if (trim_regs[13] & 0x01) >> + d2 = -d2; >> + break; >> + case 7: >> + case 18: >> + /* D1 */ >> + temp = (trim_regs[3] & 0x1F) << 2; >> + temp |= (trim_regs[1] & 0x06) >> 1; >> + if (trim_regs[1] & 0x01) >> + temp = -temp; >> + >> + d1 = (trim_regs[5] & 0x7E) >> 1; >> + if (trim_regs[5] & 0x01) >> + d1 = -d1; >> + >> + d1 += temp; >> + >> + /* D2 */ >> + temp = (trim_regs[4] & 0x3F) << 2; >> + temp |= (trim_regs[2] & 0x06) >> 1; >> + if (trim_regs[2] & 0x01) >> + temp = -temp; >> + >> + d2 = (trim_regs[6] & 0xFE) >> 1; >> + if (trim_regs[6] & 0x01) >> + d2 = -d2; >> + >> + d2 += temp; >> + break; > >I think more explanation surrounding the equation(s) is required. > >> + default: >> + /* No data for other channels */ >> + continue; >> + } >> + >> + twl6030_calibrate_channel(gpadc, chn, d1, d2); >> + } >> + >> + return 0; >> +} >> + >> +static const struct twl6030_gpadc_platform_data twl6030_pdata = { >> + .nchannels = TWL6030_GPADC_MAX_CHANNELS, >> + .ideal = twl6030_ideal, >> + .convert = _twl6030_gpadc_conversion, >> + .calibrate = twl6030_calibration, >> +}; >> + >> +static const struct twl6030_gpadc_platform_data twl6032_pdata = { >> + .nchannels = TWL6032_GPADC_MAX_CHANNELS, >> + .ideal = twl6032_ideal, >> + .convert = _twl6032_gpadc_conversion, >> + .calibrate = twl6032_calibration, >> +}; >> + >> +static struct of_device_id of_twl6030_match_tbl[] = { >> + { >> + .compatible = "ti,twl6030_gpadc", >> + .data = &twl6030_pdata, >> + }, >> + { >> + .compatible = "ti,twl6032_gpadc", >> + .data = &twl6032_pdata, >> + }, >> + { /* end */ } >> +}; >> + >> +static int twl6030_gpadc_probe(struct platform_device *pdev) >> +{ >> + struct device *dev = &pdev->dev; >> + struct twl6030_gpadc_data *gpadc; >> + const struct twl6030_gpadc_platform_data *pdata; >> + const struct of_device_id *match; >> + int irq; >> + int ret = 0; > >There's no requirement to initialise ret here. > >> + match = of_match_device(of_match_ptr(of_twl6030_match_tbl), dev); >> + pdata = match ? match->data : dev->platform_data; > >The twl6040 just went DT only. Is this the case for the twl6030x too? > >If so, can you remove pdata support. > > >> + if (!pdata) >> + return -EINVAL; >> + >> + gpadc = devm_kzalloc(dev, sizeof(struct twl6030_gpadc_data), >> + GFP_KERNEL); >> + if (!gpadc) >> + return -ENOMEM; >> + >> + gpadc->twl6030_cal_tbl = devm_kzalloc(dev, >> + sizeof(struct twl6030_chnl_calib) * >> + pdata->nchannels, GFP_KERNEL); >> + if (!gpadc->twl6030_cal_tbl) >> + return -ENOMEM; >> + >> + the_gpadc = gpadc; > >What's the point in this? Why not use 'the_gpadc' immediately? > >> + gpadc->dev = dev; >> + gpadc->pdata = pdata; >> + >> + platform_set_drvdata(pdev, gpadc); > >So you've made 'the_gpadc' global to maintain access to your core >information, then set it as drvdata in any case? Why not remove the >needlessly global and weirdly named 'the_gpadc' and use >platform_get_drvdata to fetch the information when you require it? > >> + mutex_init(&gpadc->lock); >> + init_completion(&gpadc->irq_complete); >> + >> + ret = pdata->calibrate(gpadc); >> + if (ret < 0) { >> + dev_err(&pdev->dev, "Failed to read calibration registers\n"); >> + return ret; >> + } >> + >> + irq = platform_get_irq(pdev, 0); >> + if (irq < 0) { >> + dev_err(&pdev->dev, "failed to get irq\n"); >> + return ret; >> + } >> + >> + ret = devm_request_threaded_irq(dev, irq, NULL, >> + twl6030_gpadc_irq_handler, >> + IRQF_ONESHOT, "twl6030_gpadc", gpadc); >> + if (ret) { >> + dev_dbg(&pdev->dev, "could not request irq\n"); >> + return ret; >> + } >> + >> + ret = twl6030_gpadc_enable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK); >> + if (ret < 0) { >> + dev_err(&pdev->dev, "Failed to enable GPADC interrupt\n"); >> + return ret; >> + } > >New line here please. > >> + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS, >> + TWL6030_REG_TOGGLE1); >> + if (ret < 0) { >> + dev_err(&pdev->dev, "Failed to enable GPADC module\n"); >> + return ret; >> + } >> + >> + /* create as many sysfs entries as it really exists on the device */ >> + twl6030_gpadc_group.attrs[pdata->nchannels] = NULL; >> + ret = sysfs_create_group(&pdev->dev.kobj, &twl6030_gpadc_group); >> + if (ret) >> + dev_err(&pdev->dev, "could not create sysfs files\n"); >> + >> + return ret; >> +} >> + >> +static int twl6030_gpadc_remove(struct platform_device *pdev) >> +{ >> + twl6030_gpadc_disable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK); >> + sysfs_remove_group(&pdev->dev.kobj, &twl6030_gpadc_group); >> + >> + return 0; >> +} >> + >> +static int twl6030_gpadc_suspend(struct device *pdev) >> +{ >> + int ret; >> + >> + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCR, >> + TWL6030_REG_TOGGLE1); >> + if (ret) >> + pr_err("%s: Error reseting GPADC (%d)!\n", __func__, ret); >> + >> + return 0; >> +}; >> + >> +static int twl6030_gpadc_resume(struct device *pdev) >> +{ >> + int ret; >> + >> + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS, >> + TWL6030_REG_TOGGLE1); >> + if (ret) >> + pr_err("%s: Error setting GPADC (%d)!\n", __func__, ret); >> + >> + return 0; >> +}; >> + >> +static const struct dev_pm_ops twl6030_gpadc_pm_ops = { >> + .suspend = twl6030_gpadc_suspend, >> + .resume = twl6030_gpadc_resume, >> +}; >> + >> +static struct platform_driver twl6030_gpadc_driver = { >> + .probe = twl6030_gpadc_probe, >> + .remove = twl6030_gpadc_remove, >> + .driver = { >> + .name = DRIVER_NAME, >> + .owner = THIS_MODULE, >> + .pm = &twl6030_gpadc_pm_ops, >> + .of_match_table = of_twl6030_match_tbl, >> + }, >> +}; >> + >> +module_platform_driver(twl6030_gpadc_driver); >> + >> +MODULE_ALIAS("platform: " DRIVER_NAME); >> +MODULE_AUTHOR("Texas Instruments Inc."); >> +MODULE_DESCRIPTION("twl6030 ADC driver"); >> +MODULE_LICENSE("GPL"); >> diff --git a/include/linux/i2c/twl6030-gpadc.h b/include/linux/i2c/twl6030-gpadc.h >> new file mode 100644 >> index 0000000..5cd11e7 >> --- /dev/null >> +++ b/include/linux/i2c/twl6030-gpadc.h >> @@ -0,0 +1,51 @@ >> +/* >> + * include/linux/i2c/twl6030-gpadc.h >> + * >> + * TWL6030 GPADC module driver header >> + * >> + * Copyright (C) 2009 Texas Instruments Inc. >> + * Nishant Kamat <nskamat@xxxxxx> >> + * >> + * Based on twl4030-madc.h >> + * Copyright (C) 2008 Nokia Corporation >> + * Mikko Ylinen <mikko.k.ylinen@xxxxxxxxx> >> + * >> + * This program is free software; you can redistribute it and/or >> + * modify it under the terms of the GNU General Public License >> + * version 2 as published by the Free Software Foundation. >> + * >> + * This program is distributed in the hope that it will be useful, but >> + * WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >> + * General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public License >> + * along with this program; if not, write to the Free Software >> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA >> + * 02110-1301 USA >> + * >> + */ >> + >> +#ifndef _TWL6030_GPADC_H >> +#define _TWL6030_GPADC_H >> + >> + >> +/** struct twl6030_gpadc_result >> + * @raw_code raw adc value from GPADC >> + * @corrected_code corrected code calibrated adc value >> + * @result calculated value >> + */ >> +struct twl6030_gpadc_result { >> + int raw_code; >> + int corrected_code; >> + int result; >> +}; >> + >> +/* >> + * the user passes the bit mask of channels he wants to read converted values >> + * end pointer to buffer allocated for the conversion results >> + * on success returns 0. >> + */ >> +int twl6030_gpadc_conversion(u32 channels, struct twl6030_gpadc_result *res); >> + >> +#endif > >-- >Lee Jones >Linaro ST-Ericsson Landing Team Lead >Linaro.org ? Open source software for ARM SoCs >Follow Linaro: Facebook | Twitter | Blog -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html