955 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			955 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *
 | |
|  * TWL4030 MADC module driver-This driver monitors the real time
 | |
|  * conversion of analog signals like battery temperature,
 | |
|  * battery type, battery level etc.
 | |
|  *
 | |
|  * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
 | |
|  * J Keerthy <j-keerthy@ti.com>
 | |
|  *
 | |
|  * Based on twl4030-madc.c
 | |
|  * Copyright (C) 2008 Nokia Corporation
 | |
|  * Mikko Ylinen <mikko.k.ylinen@nokia.com>
 | |
|  *
 | |
|  * Amit Kucheria <amit.kucheria@canonical.com>
 | |
|  *
 | |
|  * 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/device.h>
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/mfd/twl.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/stddef.h>
 | |
| #include <linux/mutex.h>
 | |
| #include <linux/bitops.h>
 | |
| #include <linux/jiffies.h>
 | |
| #include <linux/types.h>
 | |
| #include <linux/gfp.h>
 | |
| #include <linux/err.h>
 | |
| #include <linux/regulator/consumer.h>
 | |
| 
 | |
| #include <linux/iio/iio.h>
 | |
| 
 | |
| #define TWL4030_MADC_MAX_CHANNELS 16
 | |
| 
 | |
| #define TWL4030_MADC_CTRL1		0x00
 | |
| #define TWL4030_MADC_CTRL2		0x01
 | |
| 
 | |
| #define TWL4030_MADC_RTSELECT_LSB	0x02
 | |
| #define TWL4030_MADC_SW1SELECT_LSB	0x06
 | |
| #define TWL4030_MADC_SW2SELECT_LSB	0x0A
 | |
| 
 | |
| #define TWL4030_MADC_RTAVERAGE_LSB	0x04
 | |
| #define TWL4030_MADC_SW1AVERAGE_LSB	0x08
 | |
| #define TWL4030_MADC_SW2AVERAGE_LSB	0x0C
 | |
| 
 | |
| #define TWL4030_MADC_CTRL_SW1		0x12
 | |
| #define TWL4030_MADC_CTRL_SW2		0x13
 | |
| 
 | |
| #define TWL4030_MADC_RTCH0_LSB		0x17
 | |
| #define TWL4030_MADC_GPCH0_LSB		0x37
 | |
| 
 | |
| #define TWL4030_MADC_MADCON	(1 << 0)	/* MADC power on */
 | |
| #define TWL4030_MADC_BUSY	(1 << 0)	/* MADC busy */
 | |
| /* MADC conversion completion */
 | |
| #define TWL4030_MADC_EOC_SW	(1 << 1)
 | |
| /* MADC SWx start conversion */
 | |
| #define TWL4030_MADC_SW_START	(1 << 5)
 | |
| #define TWL4030_MADC_ADCIN0	(1 << 0)
 | |
| #define TWL4030_MADC_ADCIN1	(1 << 1)
 | |
| #define TWL4030_MADC_ADCIN2	(1 << 2)
 | |
| #define TWL4030_MADC_ADCIN3	(1 << 3)
 | |
| #define TWL4030_MADC_ADCIN4	(1 << 4)
 | |
| #define TWL4030_MADC_ADCIN5	(1 << 5)
 | |
| #define TWL4030_MADC_ADCIN6	(1 << 6)
 | |
| #define TWL4030_MADC_ADCIN7	(1 << 7)
 | |
| #define TWL4030_MADC_ADCIN8	(1 << 8)
 | |
| #define TWL4030_MADC_ADCIN9	(1 << 9)
 | |
| #define TWL4030_MADC_ADCIN10	(1 << 10)
 | |
| #define TWL4030_MADC_ADCIN11	(1 << 11)
 | |
| #define TWL4030_MADC_ADCIN12	(1 << 12)
 | |
| #define TWL4030_MADC_ADCIN13	(1 << 13)
 | |
| #define TWL4030_MADC_ADCIN14	(1 << 14)
 | |
| #define TWL4030_MADC_ADCIN15	(1 << 15)
 | |
| 
 | |
| /* Fixed channels */
 | |
| #define TWL4030_MADC_BTEMP	TWL4030_MADC_ADCIN1
 | |
| #define TWL4030_MADC_VBUS	TWL4030_MADC_ADCIN8
 | |
| #define TWL4030_MADC_VBKB	TWL4030_MADC_ADCIN9
 | |
| #define TWL4030_MADC_ICHG	TWL4030_MADC_ADCIN10
 | |
| #define TWL4030_MADC_VCHG	TWL4030_MADC_ADCIN11
 | |
| #define TWL4030_MADC_VBAT	TWL4030_MADC_ADCIN12
 | |
| 
 | |
| /* Step size and prescaler ratio */
 | |
| #define TEMP_STEP_SIZE          147
 | |
| #define TEMP_PSR_R              100
 | |
| #define CURR_STEP_SIZE		147
 | |
| #define CURR_PSR_R1		44
 | |
| #define CURR_PSR_R2		88
 | |
| 
 | |
| #define TWL4030_BCI_BCICTL1	0x23
 | |
| #define TWL4030_BCI_CGAIN	0x020
 | |
| #define TWL4030_BCI_MESBAT	(1 << 1)
 | |
| #define TWL4030_BCI_TYPEN	(1 << 4)
 | |
| #define TWL4030_BCI_ITHEN	(1 << 3)
 | |
| 
 | |
| #define REG_BCICTL2             0x024
 | |
| #define TWL4030_BCI_ITHSENS	0x007
 | |
| 
 | |
| /* Register and bits for GPBR1 register */
 | |
| #define TWL4030_REG_GPBR1		0x0c
 | |
| #define TWL4030_GPBR1_MADC_HFCLK_EN	(1 << 7)
 | |
| 
 | |
| #define TWL4030_USB_SEL_MADC_MCPC	(1<<3)
 | |
| #define TWL4030_USB_CARKIT_ANA_CTRL	0xBB
 | |
| 
 | |
| struct twl4030_madc_conversion_method {
 | |
| 	u8 sel;
 | |
| 	u8 avg;
 | |
| 	u8 rbase;
 | |
| 	u8 ctrl;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * struct twl4030_madc_request - madc request packet for channel conversion
 | |
|  * @channels:	16 bit bitmap for individual channels
 | |
|  * @do_avg:	sample the input channel for 4 consecutive cycles
 | |
|  * @method:	RT, SW1, SW2
 | |
|  * @type:	Polling or interrupt based method
 | |
|  * @active:	Flag if request is active
 | |
|  * @result_pending: Flag from irq handler, that result is ready
 | |
|  * @raw:	Return raw value, do not convert it
 | |
|  * @rbuf:	Result buffer
 | |
|  */
 | |
| struct twl4030_madc_request {
 | |
| 	unsigned long channels;
 | |
| 	bool do_avg;
 | |
| 	u16 method;
 | |
| 	u16 type;
 | |
| 	bool active;
 | |
| 	bool result_pending;
 | |
| 	bool raw;
 | |
| 	int rbuf[TWL4030_MADC_MAX_CHANNELS];
 | |
| };
 | |
| 
 | |
| enum conversion_methods {
 | |
| 	TWL4030_MADC_RT,
 | |
| 	TWL4030_MADC_SW1,
 | |
| 	TWL4030_MADC_SW2,
 | |
| 	TWL4030_MADC_NUM_METHODS
 | |
| };
 | |
| 
 | |
| enum sample_type {
 | |
| 	TWL4030_MADC_WAIT,
 | |
| 	TWL4030_MADC_IRQ_ONESHOT,
 | |
| 	TWL4030_MADC_IRQ_REARM
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * struct twl4030_madc_data - a container for madc info
 | |
|  * @dev:		Pointer to device structure for madc
 | |
|  * @lock:		Mutex protecting this data structure
 | |
|  * @regulator:		Pointer to bias regulator for madc
 | |
|  * @requests:		Array of request struct corresponding to SW1, SW2 and RT
 | |
|  * @use_second_irq:	IRQ selection (main or co-processor)
 | |
|  * @imr:		Interrupt mask register of MADC
 | |
|  * @isr:		Interrupt status register of MADC
 | |
|  */
 | |
| struct twl4030_madc_data {
 | |
| 	struct device *dev;
 | |
| 	struct mutex lock;	/* mutex protecting this data structure */
 | |
| 	struct regulator *usb3v1;
 | |
| 	struct twl4030_madc_request requests[TWL4030_MADC_NUM_METHODS];
 | |
| 	bool use_second_irq;
 | |
| 	u8 imr;
 | |
| 	u8 isr;
 | |
| };
 | |
| 
 | |
| static int twl4030_madc_conversion(struct twl4030_madc_request *req);
 | |
| 
 | |
| static int twl4030_madc_read(struct iio_dev *iio_dev,
 | |
| 			     const struct iio_chan_spec *chan,
 | |
| 			     int *val, int *val2, long mask)
 | |
| {
 | |
| 	struct twl4030_madc_data *madc = iio_priv(iio_dev);
 | |
| 	struct twl4030_madc_request req;
 | |
| 	int ret;
 | |
| 
 | |
| 	req.method = madc->use_second_irq ? TWL4030_MADC_SW2 : TWL4030_MADC_SW1;
 | |
| 
 | |
| 	req.channels = BIT(chan->channel);
 | |
| 	req.active = false;
 | |
| 	req.type = TWL4030_MADC_WAIT;
 | |
| 	req.raw = !(mask == IIO_CHAN_INFO_PROCESSED);
 | |
| 	req.do_avg = (mask == IIO_CHAN_INFO_AVERAGE_RAW);
 | |
| 
 | |
| 	ret = twl4030_madc_conversion(&req);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	*val = req.rbuf[chan->channel];
 | |
| 
 | |
| 	return IIO_VAL_INT;
 | |
| }
 | |
| 
 | |
| static const struct iio_info twl4030_madc_iio_info = {
 | |
| 	.read_raw = &twl4030_madc_read,
 | |
| };
 | |
| 
 | |
| #define TWL4030_ADC_CHANNEL(_channel, _type, _name) {	\
 | |
| 	.type = _type,					\
 | |
| 	.channel = _channel,				\
 | |
| 	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |  \
 | |
| 			      BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \
 | |
| 			      BIT(IIO_CHAN_INFO_PROCESSED), \
 | |
| 	.datasheet_name = _name,			\
 | |
| 	.indexed = 1,					\
 | |
| }
 | |
| 
 | |
| static const struct iio_chan_spec twl4030_madc_iio_channels[] = {
 | |
| 	TWL4030_ADC_CHANNEL(0, IIO_VOLTAGE, "ADCIN0"),
 | |
| 	TWL4030_ADC_CHANNEL(1, IIO_TEMP, "ADCIN1"),
 | |
| 	TWL4030_ADC_CHANNEL(2, IIO_VOLTAGE, "ADCIN2"),
 | |
| 	TWL4030_ADC_CHANNEL(3, IIO_VOLTAGE, "ADCIN3"),
 | |
| 	TWL4030_ADC_CHANNEL(4, IIO_VOLTAGE, "ADCIN4"),
 | |
| 	TWL4030_ADC_CHANNEL(5, IIO_VOLTAGE, "ADCIN5"),
 | |
| 	TWL4030_ADC_CHANNEL(6, IIO_VOLTAGE, "ADCIN6"),
 | |
| 	TWL4030_ADC_CHANNEL(7, IIO_VOLTAGE, "ADCIN7"),
 | |
| 	TWL4030_ADC_CHANNEL(8, IIO_VOLTAGE, "ADCIN8"),
 | |
| 	TWL4030_ADC_CHANNEL(9, IIO_VOLTAGE, "ADCIN9"),
 | |
| 	TWL4030_ADC_CHANNEL(10, IIO_CURRENT, "ADCIN10"),
 | |
| 	TWL4030_ADC_CHANNEL(11, IIO_VOLTAGE, "ADCIN11"),
 | |
| 	TWL4030_ADC_CHANNEL(12, IIO_VOLTAGE, "ADCIN12"),
 | |
| 	TWL4030_ADC_CHANNEL(13, IIO_VOLTAGE, "ADCIN13"),
 | |
| 	TWL4030_ADC_CHANNEL(14, IIO_VOLTAGE, "ADCIN14"),
 | |
| 	TWL4030_ADC_CHANNEL(15, IIO_VOLTAGE, "ADCIN15"),
 | |
| };
 | |
| 
 | |
| static struct twl4030_madc_data *twl4030_madc;
 | |
| 
 | |
| struct twl4030_prescale_divider_ratios {
 | |
| 	s16 numerator;
 | |
| 	s16 denominator;
 | |
| };
 | |
| 
 | |
| static const struct twl4030_prescale_divider_ratios
 | |
| twl4030_divider_ratios[16] = {
 | |
| 	{1, 1},		/* CHANNEL 0 No Prescaler */
 | |
| 	{1, 1},		/* CHANNEL 1 No Prescaler */
 | |
| 	{6, 10},	/* CHANNEL 2 */
 | |
| 	{6, 10},	/* CHANNEL 3 */
 | |
| 	{6, 10},	/* CHANNEL 4 */
 | |
| 	{6, 10},	/* CHANNEL 5 */
 | |
| 	{6, 10},	/* CHANNEL 6 */
 | |
| 	{6, 10},	/* CHANNEL 7 */
 | |
| 	{3, 14},	/* CHANNEL 8 */
 | |
| 	{1, 3},		/* CHANNEL 9 */
 | |
| 	{1, 1},		/* CHANNEL 10 No Prescaler */
 | |
| 	{15, 100},	/* CHANNEL 11 */
 | |
| 	{1, 4},		/* CHANNEL 12 */
 | |
| 	{1, 1},		/* CHANNEL 13 Reserved channels */
 | |
| 	{1, 1},		/* CHANNEL 14 Reseved channels */
 | |
| 	{5, 11},	/* CHANNEL 15 */
 | |
| };
 | |
| 
 | |
| 
 | |
| /* Conversion table from -3 to 55 degrees Celcius */
 | |
| static int twl4030_therm_tbl[] = {
 | |
| 	30800,	29500,	28300,	27100,
 | |
| 	26000,	24900,	23900,	22900,	22000,	21100,	20300,	19400,	18700,
 | |
| 	17900,	17200,	16500,	15900,	15300,	14700,	14100,	13600,	13100,
 | |
| 	12600,	12100,	11600,	11200,	10800,	10400,	10000,	9630,	9280,
 | |
| 	8950,	8620,	8310,	8020,	7730,	7460,	7200,	6950,	6710,
 | |
| 	6470,	6250,	6040,	5830,	5640,	5450,	5260,	5090,	4920,
 | |
| 	4760,	4600,	4450,	4310,	4170,	4040,	3910,	3790,	3670,
 | |
| 	3550
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Structure containing the registers
 | |
|  * of different conversion methods supported by MADC.
 | |
|  * Hardware or RT real time conversion request initiated by external host
 | |
|  * processor for RT Signal conversions.
 | |
|  * External host processors can also request for non RT conversions
 | |
|  * SW1 and SW2 software conversions also called asynchronous or GPC request.
 | |
|  */
 | |
| static
 | |
| const struct twl4030_madc_conversion_method twl4030_conversion_methods[] = {
 | |
| 	[TWL4030_MADC_RT] = {
 | |
| 			     .sel = TWL4030_MADC_RTSELECT_LSB,
 | |
| 			     .avg = TWL4030_MADC_RTAVERAGE_LSB,
 | |
| 			     .rbase = TWL4030_MADC_RTCH0_LSB,
 | |
| 			     },
 | |
| 	[TWL4030_MADC_SW1] = {
 | |
| 			      .sel = TWL4030_MADC_SW1SELECT_LSB,
 | |
| 			      .avg = TWL4030_MADC_SW1AVERAGE_LSB,
 | |
| 			      .rbase = TWL4030_MADC_GPCH0_LSB,
 | |
| 			      .ctrl = TWL4030_MADC_CTRL_SW1,
 | |
| 			      },
 | |
| 	[TWL4030_MADC_SW2] = {
 | |
| 			      .sel = TWL4030_MADC_SW2SELECT_LSB,
 | |
| 			      .avg = TWL4030_MADC_SW2AVERAGE_LSB,
 | |
| 			      .rbase = TWL4030_MADC_GPCH0_LSB,
 | |
| 			      .ctrl = TWL4030_MADC_CTRL_SW2,
 | |
| 			      },
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * twl4030_madc_channel_raw_read() - Function to read a particular channel value
 | |
|  * @madc:	pointer to struct twl4030_madc_data
 | |
|  * @reg:	lsb of ADC Channel
 | |
|  *
 | |
|  * Return: 0 on success, an error code otherwise.
 | |
|  */
 | |
| static int twl4030_madc_channel_raw_read(struct twl4030_madc_data *madc, u8 reg)
 | |
| {
 | |
| 	u16 val;
 | |
| 	int ret;
 | |
| 	/*
 | |
| 	 * For each ADC channel, we have MSB and LSB register pair. MSB address
 | |
| 	 * is always LSB address+1. reg parameter is the address of LSB register
 | |
| 	 */
 | |
| 	ret = twl_i2c_read_u16(TWL4030_MODULE_MADC, &val, reg);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to read register 0x%X\n", reg);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return (int)(val >> 6);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Return battery temperature in degrees Celsius
 | |
|  * Or < 0 on failure.
 | |
|  */
 | |
| static int twl4030battery_temperature(int raw_volt)
 | |
| {
 | |
| 	u8 val;
 | |
| 	int temp, curr, volt, res, ret;
 | |
| 
 | |
| 	volt = (raw_volt * TEMP_STEP_SIZE) / TEMP_PSR_R;
 | |
| 	/* Getting and calculating the supply current in micro amperes */
 | |
| 	ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val,
 | |
| 		REG_BCICTL2);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	curr = ((val & TWL4030_BCI_ITHSENS) + 1) * 10;
 | |
| 	/* Getting and calculating the thermistor resistance in ohms */
 | |
| 	res = volt * 1000 / curr;
 | |
| 	/* calculating temperature */
 | |
| 	for (temp = 58; temp >= 0; temp--) {
 | |
| 		int actual = twl4030_therm_tbl[temp];
 | |
| 		if ((actual - res) >= 0)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	return temp + 1;
 | |
| }
 | |
| 
 | |
| static int twl4030battery_current(int raw_volt)
 | |
| {
 | |
| 	int ret;
 | |
| 	u8 val;
 | |
| 
 | |
| 	ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val,
 | |
| 		TWL4030_BCI_BCICTL1);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 	if (val & TWL4030_BCI_CGAIN) /* slope of 0.44 mV/mA */
 | |
| 		return (raw_volt * CURR_STEP_SIZE) / CURR_PSR_R1;
 | |
| 	else /* slope of 0.88 mV/mA */
 | |
| 		return (raw_volt * CURR_STEP_SIZE) / CURR_PSR_R2;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Function to read channel values
 | |
|  * @madc - pointer to twl4030_madc_data struct
 | |
|  * @reg_base - Base address of the first channel
 | |
|  * @Channels - 16 bit bitmap. If the bit is set, channel's value is read
 | |
|  * @buf - The channel values are stored here. if read fails error
 | |
|  * @raw - Return raw values without conversion
 | |
|  * value is stored
 | |
|  * Returns the number of successfully read channels.
 | |
|  */
 | |
| static int twl4030_madc_read_channels(struct twl4030_madc_data *madc,
 | |
| 				      u8 reg_base, unsigned
 | |
| 				      long channels, int *buf,
 | |
| 				      bool raw)
 | |
| {
 | |
| 	int count = 0;
 | |
| 	int i;
 | |
| 	u8 reg;
 | |
| 
 | |
| 	for_each_set_bit(i, &channels, TWL4030_MADC_MAX_CHANNELS) {
 | |
| 		reg = reg_base + (2 * i);
 | |
| 		buf[i] = twl4030_madc_channel_raw_read(madc, reg);
 | |
| 		if (buf[i] < 0) {
 | |
| 			dev_err(madc->dev, "Unable to read register 0x%X\n",
 | |
| 				reg);
 | |
| 			return buf[i];
 | |
| 		}
 | |
| 		if (raw) {
 | |
| 			count++;
 | |
| 			continue;
 | |
| 		}
 | |
| 		switch (i) {
 | |
| 		case 10:
 | |
| 			buf[i] = twl4030battery_current(buf[i]);
 | |
| 			if (buf[i] < 0) {
 | |
| 				dev_err(madc->dev, "err reading current\n");
 | |
| 				return buf[i];
 | |
| 			} else {
 | |
| 				count++;
 | |
| 				buf[i] = buf[i] - 750;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 1:
 | |
| 			buf[i] = twl4030battery_temperature(buf[i]);
 | |
| 			if (buf[i] < 0) {
 | |
| 				dev_err(madc->dev, "err reading temperature\n");
 | |
| 				return buf[i];
 | |
| 			} else {
 | |
| 				buf[i] -= 3;
 | |
| 				count++;
 | |
| 			}
 | |
| 			break;
 | |
| 		default:
 | |
| 			count++;
 | |
| 			/* Analog Input (V) = conv_result * step_size / R
 | |
| 			 * conv_result = decimal value of 10-bit conversion
 | |
| 			 *		 result
 | |
| 			 * step size = 1.5 / (2 ^ 10 -1)
 | |
| 			 * R = Prescaler ratio for input channels.
 | |
| 			 * Result given in mV hence multiplied by 1000.
 | |
| 			 */
 | |
| 			buf[i] = (buf[i] * 3 * 1000 *
 | |
| 				 twl4030_divider_ratios[i].denominator)
 | |
| 				/ (2 * 1023 *
 | |
| 				twl4030_divider_ratios[i].numerator);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Disables irq.
 | |
|  * @madc - pointer to twl4030_madc_data struct
 | |
|  * @id - irq number to be disabled
 | |
|  * can take one of TWL4030_MADC_RT, TWL4030_MADC_SW1, TWL4030_MADC_SW2
 | |
|  * corresponding to RT, SW1, SW2 conversion requests.
 | |
|  * Returns error if i2c read/write fails.
 | |
|  */
 | |
| static int twl4030_madc_disable_irq(struct twl4030_madc_data *madc, u8 id)
 | |
| {
 | |
| 	u8 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &val, madc->imr);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to read imr register 0x%X\n",
 | |
| 			madc->imr);
 | |
| 		return ret;
 | |
| 	}
 | |
| 	val |= (1 << id);
 | |
| 	ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, val, madc->imr);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev,
 | |
| 			"unable to write imr register 0x%X\n", madc->imr);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static irqreturn_t twl4030_madc_threaded_irq_handler(int irq, void *_madc)
 | |
| {
 | |
| 	struct twl4030_madc_data *madc = _madc;
 | |
| 	const struct twl4030_madc_conversion_method *method;
 | |
| 	u8 isr_val, imr_val;
 | |
| 	int i, len, ret;
 | |
| 	struct twl4030_madc_request *r;
 | |
| 
 | |
| 	mutex_lock(&madc->lock);
 | |
| 	ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &isr_val, madc->isr);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to read isr register 0x%X\n",
 | |
| 			madc->isr);
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 	ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &imr_val, madc->imr);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to read imr register 0x%X\n",
 | |
| 			madc->imr);
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 	isr_val &= ~imr_val;
 | |
| 	for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) {
 | |
| 		if (!(isr_val & (1 << i)))
 | |
| 			continue;
 | |
| 		ret = twl4030_madc_disable_irq(madc, i);
 | |
| 		if (ret < 0)
 | |
| 			dev_dbg(madc->dev, "Disable interrupt failed %d\n", i);
 | |
| 		madc->requests[i].result_pending = 1;
 | |
| 	}
 | |
| 	for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) {
 | |
| 		r = &madc->requests[i];
 | |
| 		/* No pending results for this method, move to next one */
 | |
| 		if (!r->result_pending)
 | |
| 			continue;
 | |
| 		method = &twl4030_conversion_methods[r->method];
 | |
| 		/* Read results */
 | |
| 		len = twl4030_madc_read_channels(madc, method->rbase,
 | |
| 						 r->channels, r->rbuf, r->raw);
 | |
| 		/* Free request */
 | |
| 		r->result_pending = 0;
 | |
| 		r->active = 0;
 | |
| 	}
 | |
| 	mutex_unlock(&madc->lock);
 | |
| 
 | |
| 	return IRQ_HANDLED;
 | |
| 
 | |
| err_i2c:
 | |
| 	/*
 | |
| 	 * In case of error check whichever request is active
 | |
| 	 * and service the same.
 | |
| 	 */
 | |
| 	for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) {
 | |
| 		r = &madc->requests[i];
 | |
| 		if (r->active == 0)
 | |
| 			continue;
 | |
| 		method = &twl4030_conversion_methods[r->method];
 | |
| 		/* Read results */
 | |
| 		len = twl4030_madc_read_channels(madc, method->rbase,
 | |
| 						 r->channels, r->rbuf, r->raw);
 | |
| 		/* Free request */
 | |
| 		r->result_pending = 0;
 | |
| 		r->active = 0;
 | |
| 	}
 | |
| 	mutex_unlock(&madc->lock);
 | |
| 
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Function which enables the madc conversion
 | |
|  * by writing to the control register.
 | |
|  * @madc - pointer to twl4030_madc_data struct
 | |
|  * @conv_method - can be TWL4030_MADC_RT, TWL4030_MADC_SW2, TWL4030_MADC_SW1
 | |
|  * corresponding to RT SW1 or SW2 conversion methods.
 | |
|  * Returns 0 if succeeds else a negative error value
 | |
|  */
 | |
| static int twl4030_madc_start_conversion(struct twl4030_madc_data *madc,
 | |
| 					 int conv_method)
 | |
| {
 | |
| 	const struct twl4030_madc_conversion_method *method;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	if (conv_method != TWL4030_MADC_SW1 && conv_method != TWL4030_MADC_SW2)
 | |
| 		return -ENOTSUPP;
 | |
| 
 | |
| 	method = &twl4030_conversion_methods[conv_method];
 | |
| 	ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, TWL4030_MADC_SW_START,
 | |
| 			       method->ctrl);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to write ctrl register 0x%X\n",
 | |
| 			method->ctrl);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Function that waits for conversion to be ready
 | |
|  * @madc - pointer to twl4030_madc_data struct
 | |
|  * @timeout_ms - timeout value in milliseconds
 | |
|  * @status_reg - ctrl register
 | |
|  * returns 0 if succeeds else a negative error value
 | |
|  */
 | |
| static int twl4030_madc_wait_conversion_ready(struct twl4030_madc_data *madc,
 | |
| 					      unsigned int timeout_ms,
 | |
| 					      u8 status_reg)
 | |
| {
 | |
| 	unsigned long timeout;
 | |
| 	int ret;
 | |
| 
 | |
| 	timeout = jiffies + msecs_to_jiffies(timeout_ms);
 | |
| 	do {
 | |
| 		u8 reg;
 | |
| 
 | |
| 		ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, ®, status_reg);
 | |
| 		if (ret) {
 | |
| 			dev_err(madc->dev,
 | |
| 				"unable to read status register 0x%X\n",
 | |
| 				status_reg);
 | |
| 			return ret;
 | |
| 		}
 | |
| 		if (!(reg & TWL4030_MADC_BUSY) && (reg & TWL4030_MADC_EOC_SW))
 | |
| 			return 0;
 | |
| 		usleep_range(500, 2000);
 | |
| 	} while (!time_after(jiffies, timeout));
 | |
| 	dev_err(madc->dev, "conversion timeout!\n");
 | |
| 
 | |
| 	return -EAGAIN;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * An exported function which can be called from other kernel drivers.
 | |
|  * @req twl4030_madc_request structure
 | |
|  * req->rbuf will be filled with read values of channels based on the
 | |
|  * channel index. If a particular channel reading fails there will
 | |
|  * be a negative error value in the corresponding array element.
 | |
|  * returns 0 if succeeds else error value
 | |
|  */
 | |
| static int twl4030_madc_conversion(struct twl4030_madc_request *req)
 | |
| {
 | |
| 	const struct twl4030_madc_conversion_method *method;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (!req || !twl4030_madc)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	mutex_lock(&twl4030_madc->lock);
 | |
| 	if (req->method < TWL4030_MADC_RT || req->method > TWL4030_MADC_SW2) {
 | |
| 		ret = -EINVAL;
 | |
| 		goto out;
 | |
| 	}
 | |
| 	/* Do we have a conversion request ongoing */
 | |
| 	if (twl4030_madc->requests[req->method].active) {
 | |
| 		ret = -EBUSY;
 | |
| 		goto out;
 | |
| 	}
 | |
| 	method = &twl4030_conversion_methods[req->method];
 | |
| 	/* Select channels to be converted */
 | |
| 	ret = twl_i2c_write_u16(TWL4030_MODULE_MADC, req->channels, method->sel);
 | |
| 	if (ret) {
 | |
| 		dev_err(twl4030_madc->dev,
 | |
| 			"unable to write sel register 0x%X\n", method->sel);
 | |
| 		goto out;
 | |
| 	}
 | |
| 	/* Select averaging for all channels if do_avg is set */
 | |
| 	if (req->do_avg) {
 | |
| 		ret = twl_i2c_write_u16(TWL4030_MODULE_MADC, req->channels,
 | |
| 				       method->avg);
 | |
| 		if (ret) {
 | |
| 			dev_err(twl4030_madc->dev,
 | |
| 				"unable to write avg register 0x%X\n",
 | |
| 				method->avg);
 | |
| 			goto out;
 | |
| 		}
 | |
| 	}
 | |
| 	/* With RT method we should not be here anymore */
 | |
| 	if (req->method == TWL4030_MADC_RT) {
 | |
| 		ret = -EINVAL;
 | |
| 		goto out;
 | |
| 	}
 | |
| 	ret = twl4030_madc_start_conversion(twl4030_madc, req->method);
 | |
| 	if (ret < 0)
 | |
| 		goto out;
 | |
| 	twl4030_madc->requests[req->method].active = 1;
 | |
| 	/* Wait until conversion is ready (ctrl register returns EOC) */
 | |
| 	ret = twl4030_madc_wait_conversion_ready(twl4030_madc, 5, method->ctrl);
 | |
| 	if (ret) {
 | |
| 		twl4030_madc->requests[req->method].active = 0;
 | |
| 		goto out;
 | |
| 	}
 | |
| 	ret = twl4030_madc_read_channels(twl4030_madc, method->rbase,
 | |
| 					 req->channels, req->rbuf, req->raw);
 | |
| 	twl4030_madc->requests[req->method].active = 0;
 | |
| 
 | |
| out:
 | |
| 	mutex_unlock(&twl4030_madc->lock);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * twl4030_madc_set_current_generator() - setup bias current
 | |
|  *
 | |
|  * @madc:	pointer to twl4030_madc_data struct
 | |
|  * @chan:	can be one of the two values:
 | |
|  *		0 - Enables bias current for main battery type reading
 | |
|  *		1 - Enables bias current for main battery temperature sensing
 | |
|  * @on:		enable or disable chan.
 | |
|  *
 | |
|  * Function to enable or disable bias current for
 | |
|  * main battery type reading or temperature sensing
 | |
|  */
 | |
| static int twl4030_madc_set_current_generator(struct twl4030_madc_data *madc,
 | |
| 					      int chan, int on)
 | |
| {
 | |
| 	int ret;
 | |
| 	int regmask;
 | |
| 	u8 regval;
 | |
| 
 | |
| 	ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE,
 | |
| 			      ®val, TWL4030_BCI_BCICTL1);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to read BCICTL1 reg 0x%X",
 | |
| 			TWL4030_BCI_BCICTL1);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	regmask = chan ? TWL4030_BCI_ITHEN : TWL4030_BCI_TYPEN;
 | |
| 	if (on)
 | |
| 		regval |= regmask;
 | |
| 	else
 | |
| 		regval &= ~regmask;
 | |
| 
 | |
| 	ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
 | |
| 			       regval, TWL4030_BCI_BCICTL1);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to write BCICTL1 reg 0x%X\n",
 | |
| 			TWL4030_BCI_BCICTL1);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Function that sets MADC software power on bit to enable MADC
 | |
|  * @madc - pointer to twl4030_madc_data struct
 | |
|  * @on - Enable or disable MADC software power on bit.
 | |
|  * returns error if i2c read/write fails else 0
 | |
|  */
 | |
| static int twl4030_madc_set_power(struct twl4030_madc_data *madc, int on)
 | |
| {
 | |
| 	u8 regval;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE,
 | |
| 			      ®val, TWL4030_MADC_CTRL1);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to read madc ctrl1 reg 0x%X\n",
 | |
| 			TWL4030_MADC_CTRL1);
 | |
| 		return ret;
 | |
| 	}
 | |
| 	if (on)
 | |
| 		regval |= TWL4030_MADC_MADCON;
 | |
| 	else
 | |
| 		regval &= ~TWL4030_MADC_MADCON;
 | |
| 	ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, regval, TWL4030_MADC_CTRL1);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "unable to write madc ctrl1 reg 0x%X\n",
 | |
| 			TWL4030_MADC_CTRL1);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Initialize MADC and request for threaded irq
 | |
|  */
 | |
| static int twl4030_madc_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct twl4030_madc_data *madc;
 | |
| 	struct twl4030_madc_platform_data *pdata = dev_get_platdata(&pdev->dev);
 | |
| 	struct device_node *np = pdev->dev.of_node;
 | |
| 	int irq, ret;
 | |
| 	u8 regval;
 | |
| 	struct iio_dev *iio_dev = NULL;
 | |
| 
 | |
| 	if (!pdata && !np) {
 | |
| 		dev_err(&pdev->dev, "neither platform data nor Device Tree node available\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*madc));
 | |
| 	if (!iio_dev) {
 | |
| 		dev_err(&pdev->dev, "failed allocating iio device\n");
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	madc = iio_priv(iio_dev);
 | |
| 	madc->dev = &pdev->dev;
 | |
| 
 | |
| 	iio_dev->name = dev_name(&pdev->dev);
 | |
| 	iio_dev->dev.parent = &pdev->dev;
 | |
| 	iio_dev->dev.of_node = pdev->dev.of_node;
 | |
| 	iio_dev->info = &twl4030_madc_iio_info;
 | |
| 	iio_dev->modes = INDIO_DIRECT_MODE;
 | |
| 	iio_dev->channels = twl4030_madc_iio_channels;
 | |
| 	iio_dev->num_channels = ARRAY_SIZE(twl4030_madc_iio_channels);
 | |
| 
 | |
| 	/*
 | |
| 	 * Phoenix provides 2 interrupt lines. The first one is connected to
 | |
| 	 * the OMAP. The other one can be connected to the other processor such
 | |
| 	 * as modem. Hence two separate ISR and IMR registers.
 | |
| 	 */
 | |
| 	if (pdata)
 | |
| 		madc->use_second_irq = (pdata->irq_line != 1);
 | |
| 	else
 | |
| 		madc->use_second_irq = of_property_read_bool(np,
 | |
| 				       "ti,system-uses-second-madc-irq");
 | |
| 
 | |
| 	madc->imr = madc->use_second_irq ? TWL4030_MADC_IMR2 :
 | |
| 					   TWL4030_MADC_IMR1;
 | |
| 	madc->isr = madc->use_second_irq ? TWL4030_MADC_ISR2 :
 | |
| 					   TWL4030_MADC_ISR1;
 | |
| 
 | |
| 	ret = twl4030_madc_set_power(madc, 1);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 	ret = twl4030_madc_set_current_generator(madc, 0, 1);
 | |
| 	if (ret < 0)
 | |
| 		goto err_current_generator;
 | |
| 
 | |
| 	ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE,
 | |
| 			      ®val, TWL4030_BCI_BCICTL1);
 | |
| 	if (ret) {
 | |
| 		dev_err(&pdev->dev, "unable to read reg BCI CTL1 0x%X\n",
 | |
| 			TWL4030_BCI_BCICTL1);
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 	regval |= TWL4030_BCI_MESBAT;
 | |
| 	ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
 | |
| 			       regval, TWL4030_BCI_BCICTL1);
 | |
| 	if (ret) {
 | |
| 		dev_err(&pdev->dev, "unable to write reg BCI Ctl1 0x%X\n",
 | |
| 			TWL4030_BCI_BCICTL1);
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 
 | |
| 	/* Check that MADC clock is on */
 | |
| 	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, ®val, TWL4030_REG_GPBR1);
 | |
| 	if (ret) {
 | |
| 		dev_err(&pdev->dev, "unable to read reg GPBR1 0x%X\n",
 | |
| 				TWL4030_REG_GPBR1);
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 
 | |
| 	/* If MADC clk is not on, turn it on */
 | |
| 	if (!(regval & TWL4030_GPBR1_MADC_HFCLK_EN)) {
 | |
| 		dev_info(&pdev->dev, "clk disabled, enabling\n");
 | |
| 		regval |= TWL4030_GPBR1_MADC_HFCLK_EN;
 | |
| 		ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, regval,
 | |
| 				       TWL4030_REG_GPBR1);
 | |
| 		if (ret) {
 | |
| 			dev_err(&pdev->dev, "unable to write reg GPBR1 0x%X\n",
 | |
| 					TWL4030_REG_GPBR1);
 | |
| 			goto err_i2c;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	platform_set_drvdata(pdev, iio_dev);
 | |
| 	mutex_init(&madc->lock);
 | |
| 
 | |
| 	irq = platform_get_irq(pdev, 0);
 | |
| 	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
 | |
| 				   twl4030_madc_threaded_irq_handler,
 | |
| 				   IRQF_TRIGGER_RISING | IRQF_ONESHOT,
 | |
| 				   "twl4030_madc", madc);
 | |
| 	if (ret) {
 | |
| 		dev_err(&pdev->dev, "could not request irq\n");
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 	twl4030_madc = madc;
 | |
| 
 | |
| 	/* Configure MADC[3:6] */
 | |
| 	ret = twl_i2c_read_u8(TWL_MODULE_USB, ®val,
 | |
| 			TWL4030_USB_CARKIT_ANA_CTRL);
 | |
| 	if (ret) {
 | |
| 		dev_err(&pdev->dev, "unable to read reg CARKIT_ANA_CTRL  0x%X\n",
 | |
| 				TWL4030_USB_CARKIT_ANA_CTRL);
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 	regval |= TWL4030_USB_SEL_MADC_MCPC;
 | |
| 	ret = twl_i2c_write_u8(TWL_MODULE_USB, regval,
 | |
| 				 TWL4030_USB_CARKIT_ANA_CTRL);
 | |
| 	if (ret) {
 | |
| 		dev_err(&pdev->dev, "unable to write reg CARKIT_ANA_CTRL 0x%X\n",
 | |
| 				TWL4030_USB_CARKIT_ANA_CTRL);
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 
 | |
| 	/* Enable 3v1 bias regulator for MADC[3:6] */
 | |
| 	madc->usb3v1 = devm_regulator_get(madc->dev, "vusb3v1");
 | |
| 	if (IS_ERR(madc->usb3v1)) {
 | |
| 		ret = -ENODEV;
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 
 | |
| 	ret = regulator_enable(madc->usb3v1);
 | |
| 	if (ret) {
 | |
| 		dev_err(madc->dev, "could not enable 3v1 bias regulator\n");
 | |
| 		goto err_i2c;
 | |
| 	}
 | |
| 
 | |
| 	ret = iio_device_register(iio_dev);
 | |
| 	if (ret) {
 | |
| 		dev_err(&pdev->dev, "could not register iio device\n");
 | |
| 		goto err_usb3v1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_usb3v1:
 | |
| 	regulator_disable(madc->usb3v1);
 | |
| err_i2c:
 | |
| 	twl4030_madc_set_current_generator(madc, 0, 0);
 | |
| err_current_generator:
 | |
| 	twl4030_madc_set_power(madc, 0);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int twl4030_madc_remove(struct platform_device *pdev)
 | |
| {
 | |
| 	struct iio_dev *iio_dev = platform_get_drvdata(pdev);
 | |
| 	struct twl4030_madc_data *madc = iio_priv(iio_dev);
 | |
| 
 | |
| 	iio_device_unregister(iio_dev);
 | |
| 
 | |
| 	twl4030_madc_set_current_generator(madc, 0, 0);
 | |
| 	twl4030_madc_set_power(madc, 0);
 | |
| 
 | |
| 	regulator_disable(madc->usb3v1);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_OF
 | |
| static const struct of_device_id twl_madc_of_match[] = {
 | |
| 	{ .compatible = "ti,twl4030-madc", },
 | |
| 	{ },
 | |
| };
 | |
| MODULE_DEVICE_TABLE(of, twl_madc_of_match);
 | |
| #endif
 | |
| 
 | |
| static struct platform_driver twl4030_madc_driver = {
 | |
| 	.probe = twl4030_madc_probe,
 | |
| 	.remove = twl4030_madc_remove,
 | |
| 	.driver = {
 | |
| 		   .name = "twl4030_madc",
 | |
| 		   .of_match_table = of_match_ptr(twl_madc_of_match),
 | |
| 	},
 | |
| };
 | |
| 
 | |
| module_platform_driver(twl4030_madc_driver);
 | |
| 
 | |
| MODULE_DESCRIPTION("TWL4030 ADC driver");
 | |
| MODULE_LICENSE("GPL");
 | |
| MODULE_AUTHOR("J Keerthy");
 | |
| MODULE_ALIAS("platform:twl4030_madc");
 | 
