TI LP8788 has ADC function. The result of LP878 ADC is used in the LP8788 power supply driver. (such like getting the battery voltage, temperature and etc) Signed-off-by: Milo(Woogyom) Kim <milo.kim@xxxxxx> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/lp8788_adc.c | 240 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 0 deletions(-) create mode 100644 drivers/iio/adc/lp8788_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 8a78b4f..30c06ed 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -22,4 +22,10 @@ config AT91_ADC help Say yes here to build support for Atmel AT91 ADC. +config LP8788_ADC + bool "LP8788 ADC driver" + depends on MFD_LP8788 + help + Say yes here to build support for TI LP8788 ADC. + endmenu diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 52eec25..72f9a76 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_AD7266) += ad7266.o obj-$(CONFIG_AT91_ADC) += at91_adc.o +obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o diff --git a/drivers/iio/adc/lp8788_adc.c b/drivers/iio/adc/lp8788_adc.c new file mode 100644 index 0000000..30767d5 --- /dev/null +++ b/drivers/iio/adc/lp8788_adc.c @@ -0,0 +1,240 @@ +/* + * TI LP8788 MFD - ADC driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@xxxxxx> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/driver.h> +#include <linux/mfd/lp8788.h> + +/* register address */ +#define LP8788_ADC_CONF 0x60 +#define LP8788_ADC_RAW 0x61 +#define LP8788_ADC_DONE 0x63 + +#define START_ADC_CHANNEL LPADC_VBATT_5P5 +#define END_ADC_CHANNEL LPADC_MAX +#define ADC_CONV_START 1 +#define ADC_CONV_DELAY_US 100 + +struct lp8788_adc { + struct lp8788 *lp; + struct iio_dev *indio_dev; +}; + +static const int adc_const[LPADC_MAX] = { + [LPADC_VBATT_5P5] = 1343, + [LPADC_VIN_CHG] = 3052, + [LPADC_IBATT] = 610, + [LPADC_IC_TEMP] = 610, + [LPADC_VBATT_6P0] = 1465, + [LPADC_VBATT_5P0] = 1221, + [LPADC_ADC1] = 610, + [LPADC_ADC2] = 610, + [LPADC_VDD] = 1025, + [LPADC_VCOIN] = 757, + [LPADC_VDD_LDO] = 610, + [LPADC_ADC3] = 610, + [LPADC_ADC4] = 610, +}; + +static inline unsigned int _get_adc_micro_unit(enum lp8788_adc_id id, + unsigned int adc_result) +{ + return adc_const[id] * ((adc_result * 1000 + 500) / 1000); +} + +static int lp8788_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lp8788_adc *adc = iio_priv(indio_dev); + int retry = 5; + unsigned int msb, lsb; + u8 data, rawdata[2], shift; + int size = ARRAY_SIZE(rawdata); + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + data = (chan->channel << 1) | ADC_CONV_START; + if (lp8788_write_byte(adc->lp, LP8788_ADC_CONF, data)) + goto err; + + /* retry until adc conversion is done */ + data = 0; + while (retry--) { + udelay(ADC_CONV_DELAY_US); + + if (lp8788_read_byte(adc->lp, LP8788_ADC_DONE, &data)) + goto err; + + /* conversion done */ + if (data) + break; + } + + if (lp8788_read_multi_bytes(adc->lp, LP8788_ADC_RAW, rawdata, size)) + goto err; + + shift = chan->scan_type.shift; + msb = (rawdata[0] << shift) & 0x00000ff0; + lsb = (rawdata[1] >> shift) & 0x0000000f; + *val = _get_adc_micro_unit(chan->channel, msb | lsb); + + return IIO_VAL_INT; +err: + return -EINVAL; +} + +static const struct iio_info lp8788_adc_info = { + .read_raw = &lp8788_adc_read_raw, + .driver_module = THIS_MODULE, +}; + +#define LP8788_COMMON_CH(id) \ + .indexed = 1, \ + .output = 1, \ + .channel = LPADC_##id, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT, \ + .address = LP8788_ADC_RAW, \ + .scan_type = IIO_ST('u', 8, 12, 4), \ + .scan_index = 1, \ + .datasheet_name = #id, \ + +#define LP8788_V_CHAN(id) { \ + .type = IIO_VOLTAGE, \ + LP8788_COMMON_CH(id) \ +} + +#define LP8788_I_CHAN(id) { \ + .type = IIO_CURRENT, \ + LP8788_COMMON_CH(id) \ +} + +#define LP8788_T_CHAN(id) { \ + .type = IIO_TEMP, \ + LP8788_COMMON_CH(id) \ +} + +static struct iio_chan_spec lp8788_adc_channels[] = { + [LPADC_VBATT_5P5] = LP8788_V_CHAN(VBATT_5P5), + [LPADC_VIN_CHG] = LP8788_V_CHAN(VIN_CHG), + [LPADC_IBATT] = LP8788_I_CHAN(IBATT), + [LPADC_IC_TEMP] = LP8788_T_CHAN(IC_TEMP), + [LPADC_VBATT_6P0] = LP8788_V_CHAN(VBATT_6P0), + [LPADC_VBATT_5P0] = LP8788_V_CHAN(VBATT_5P0), + [LPADC_ADC1] = LP8788_V_CHAN(ADC1), + [LPADC_ADC2] = LP8788_V_CHAN(ADC2), + [LPADC_VDD] = LP8788_V_CHAN(VDD), + [LPADC_VCOIN] = LP8788_V_CHAN(VCOIN), + [LPADC_VDD_LDO] = LP8788_V_CHAN(VDD_LDO), + [LPADC_ADC3] = LP8788_V_CHAN(ADC3), + [LPADC_ADC4] = LP8788_V_CHAN(ADC4), +}; + +static int __devinit lp8788_adc_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + struct iio_map *map; + struct lp8788_adc *adc; + struct iio_dev *indio_dev; + int i, ret; + + adc = devm_kzalloc(lp->dev, sizeof(struct lp8788_adc), GFP_KERNEL); + if (!adc) + return -ENOMEM; + + indio_dev = iio_device_alloc(sizeof(struct iio_dev)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->lp = lp; + adc->indio_dev = indio_dev; + platform_set_drvdata(pdev, adc); + + if (lp->pdata) { + for (i = START_ADC_CHANNEL; i < END_ADC_CHANNEL ; i++) { + map = lp->pdata->adc_pdata[i]; + ret = iio_map_array_register(indio_dev, map); + if (ret) { + dev_err(lp->dev, "iio map err: %d\n", ret); + goto err_iio_map_array; + } + } + } + + indio_dev->dev.parent = lp->dev; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &lp8788_adc_info; + indio_dev->channels = lp8788_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(lp8788_adc_channels); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(lp->dev, "iio dev register err: %d\n", ret); + goto err_iio_register; + } + + return 0; + +err_iio_map_array: + while (--i >= START_ADC_CHANNEL) { + map = lp->pdata->adc_pdata[i]; + iio_map_array_unregister(indio_dev, map); + } +err_iio_register: + iio_device_free(indio_dev); + return ret; +} + +static int __devexit lp8788_adc_remove(struct platform_device *pdev) +{ + struct lp8788_adc *adc = platform_get_drvdata(pdev); + struct lp8788 *lp = adc->lp; + struct iio_dev *indio_dev = adc->indio_dev; + struct iio_map *map; + int i; + + if (lp->pdata) { + for (i = START_ADC_CHANNEL; i < END_ADC_CHANNEL ; i++) { + map = lp->pdata->adc_pdata[i]; + iio_map_array_unregister(indio_dev, map); + } + } + + iio_device_unregister(indio_dev); + iio_device_free(indio_dev); + + return 0; +} + +static struct platform_driver lp8788_adc_driver = { + .probe = lp8788_adc_probe, + .remove = __devexit_p(lp8788_adc_remove), + .driver = { + .name = LP8788_DEV_ADC, + .owner = THIS_MODULE, + }, +}; +module_platform_driver(lp8788_adc_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8788 ADC Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lp8788-adc"); -- 1.7.2.5 Best Regards, Milo -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html