diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-12-08 23:02:54 -0500 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-12-08 23:02:54 -0500 |
| commit | 3a7dbed7f23cdde8394e9adf92cc222856e0fc1e (patch) | |
| tree | a83fa52d24d2457acadf4c1bc72ea3a3e294b57d /drivers/iio/adc/axp288_adc.c | |
| parent | f2fb38049c724558c590c31e57627f6ba8d48a5b (diff) | |
| parent | a3b63979f8a32af9e975a793fd0f612d42072740 (diff) | |
Merge tag 'mfd-for-linus-3.19' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
Pull MFD updates from Lee Jones:
"Changes to the core:
- Honour PLATFORM_DEVID_NONE and PLATFORM_DEVID_AUTO dev IDs
Changes to existing drivers:
- IRQ additions/fixes; axp20x, da9063-core
- Code simplification; i2c-dln2
- Regmap additions/fixes; max77693
- Error checking/handling improvements; dln2, db8500-prcmu
- Bug fixes; dln2, wm8350-core
- DT support/documentation; max77693, max77686, tps65217, twl4030-power,
gpio-tc3589x
- Decouple syscon interface from platform devices
- Use MFD hotplug registration; rtsx_usb, viperboard, hid-sensor-hub
- Regulator fixups; sec-core
- Power Management additions/fixes; rts5227, tc6393xb
- Remove relic/redundant code; ab8500-sysctrl, lpc_sch, max77693-private
- Clean-up/coding style changes; tps65090
- Clk additions/fixes; tc6393xb, tc6387xb, t7l66xb
- Add USB-SPI support; dln2
- Trivial changes; max14577, arizona-spi, lpc_sch, wm8997-tables, wm5102-tables
wm5110-tables, axp20x, atmel-hlcdc, rtsx_pci
New drivers/supported devices:
- axp288 PMIC support added to axp20x
- s2mps13 support added to sec-core
- New support for Diolan DLN-2
- New support for atmel-hlcdc"
* tag 'mfd-for-linus-3.19' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (55 commits)
mfd: rtsx: Add func to split u32 into register
mfd: atmel-hlcdc: Add Kconfig option description and name
mfd: da9063: Get irq base dynamically before registering device
mfd: max14577: Fix obvious typo in company name in copyright
mfd: axp20x: Constify axp20x_acpi_match and rid unused warning
mfd: t7l66xb: prepare/unprepare clocks
mfd: tc6387xb: prepare/unprepare clocks
mfd: dln2: add support for USB-SPI module
mfd: wm5110: Add missing registers for AIF2 channels 3-6
mfd: tc3589x: get rid of static base
mfd: arizona: Document HP_CTRL_1L and HP_CTRL_1R registers
mfd: wm8997: Mark INTERRUPT_STATUS_2_MASK as readable
mfd: tc6393xb: Prepare/unprepare clocks
mfd: tps65090: Fix bonkers indenting strategy
mfd: tc6393xb: Fail ohci suspend if full state restore is required
mfd: lpc_sch: Don't call mfd_remove_devices()
mfd: wm8350-core: Fix probable mask then right shift defect
mfd: ab8500-sysctrl: Drop ab8500_restart
mfd: db8500-prcmu: Provide sane error path values
mfd: db8500-prcmu: Check return of devm_ioremap for error
...
Diffstat (limited to 'drivers/iio/adc/axp288_adc.c')
| -rw-r--r-- | drivers/iio/adc/axp288_adc.c | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c new file mode 100644 index 000000000000..08bcfb061ca5 --- /dev/null +++ b/drivers/iio/adc/axp288_adc.c | |||
| @@ -0,0 +1,261 @@ | |||
| 1 | /* | ||
| 2 | * axp288_adc.c - X-Powers AXP288 PMIC ADC Driver | ||
| 3 | * | ||
| 4 | * Copyright (C) 2014 Intel Corporation | ||
| 5 | * | ||
| 6 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| 7 | * | ||
| 8 | * This program is free software; you can redistribute it and/or modify | ||
| 9 | * it under the terms of the GNU General Public License as published by | ||
| 10 | * the Free Software Foundation; version 2 of the License. | ||
| 11 | * | ||
| 12 | * This program is distributed in the hope that it will be useful, but | ||
| 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 15 | * General Public License for more details. | ||
| 16 | * | ||
| 17 | */ | ||
| 18 | |||
| 19 | #include <linux/module.h> | ||
| 20 | #include <linux/kernel.h> | ||
| 21 | #include <linux/device.h> | ||
| 22 | #include <linux/regmap.h> | ||
| 23 | #include <linux/mfd/axp20x.h> | ||
| 24 | #include <linux/platform_device.h> | ||
| 25 | |||
| 26 | #include <linux/iio/iio.h> | ||
| 27 | #include <linux/iio/machine.h> | ||
| 28 | #include <linux/iio/driver.h> | ||
| 29 | |||
| 30 | #define AXP288_ADC_EN_MASK 0xF1 | ||
| 31 | #define AXP288_ADC_TS_PIN_GPADC 0xF2 | ||
| 32 | #define AXP288_ADC_TS_PIN_ON 0xF3 | ||
| 33 | |||
| 34 | enum axp288_adc_id { | ||
| 35 | AXP288_ADC_TS, | ||
| 36 | AXP288_ADC_PMIC, | ||
| 37 | AXP288_ADC_GP, | ||
| 38 | AXP288_ADC_BATT_CHRG_I, | ||
| 39 | AXP288_ADC_BATT_DISCHRG_I, | ||
| 40 | AXP288_ADC_BATT_V, | ||
| 41 | AXP288_ADC_NR_CHAN, | ||
| 42 | }; | ||
| 43 | |||
| 44 | struct axp288_adc_info { | ||
| 45 | int irq; | ||
| 46 | struct regmap *regmap; | ||
| 47 | }; | ||
| 48 | |||
| 49 | static const struct iio_chan_spec const axp288_adc_channels[] = { | ||
| 50 | { | ||
| 51 | .indexed = 1, | ||
| 52 | .type = IIO_TEMP, | ||
| 53 | .channel = 0, | ||
| 54 | .address = AXP288_TS_ADC_H, | ||
| 55 | .datasheet_name = "TS_PIN", | ||
| 56 | }, { | ||
| 57 | .indexed = 1, | ||
| 58 | .type = IIO_TEMP, | ||
| 59 | .channel = 1, | ||
| 60 | .address = AXP288_PMIC_ADC_H, | ||
| 61 | .datasheet_name = "PMIC_TEMP", | ||
| 62 | }, { | ||
| 63 | .indexed = 1, | ||
| 64 | .type = IIO_TEMP, | ||
| 65 | .channel = 2, | ||
| 66 | .address = AXP288_GP_ADC_H, | ||
| 67 | .datasheet_name = "GPADC", | ||
| 68 | }, { | ||
| 69 | .indexed = 1, | ||
| 70 | .type = IIO_CURRENT, | ||
| 71 | .channel = 3, | ||
| 72 | .address = AXP20X_BATT_CHRG_I_H, | ||
| 73 | .datasheet_name = "BATT_CHG_I", | ||
| 74 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), | ||
| 75 | }, { | ||
| 76 | .indexed = 1, | ||
| 77 | .type = IIO_CURRENT, | ||
| 78 | .channel = 4, | ||
| 79 | .address = AXP20X_BATT_DISCHRG_I_H, | ||
| 80 | .datasheet_name = "BATT_DISCHRG_I", | ||
| 81 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), | ||
| 82 | }, { | ||
| 83 | .indexed = 1, | ||
| 84 | .type = IIO_VOLTAGE, | ||
| 85 | .channel = 5, | ||
| 86 | .address = AXP20X_BATT_V_H, | ||
| 87 | .datasheet_name = "BATT_V", | ||
| 88 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), | ||
| 89 | }, | ||
| 90 | }; | ||
| 91 | |||
| 92 | #define AXP288_ADC_MAP(_adc_channel_label, _consumer_dev_name, \ | ||
| 93 | _consumer_channel) \ | ||
| 94 | { \ | ||
| 95 | .adc_channel_label = _adc_channel_label, \ | ||
| 96 | .consumer_dev_name = _consumer_dev_name, \ | ||
| 97 | .consumer_channel = _consumer_channel, \ | ||
| 98 | } | ||
| 99 | |||
| 100 | /* for consumer drivers */ | ||
| 101 | static struct iio_map axp288_adc_default_maps[] = { | ||
| 102 | AXP288_ADC_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"), | ||
| 103 | AXP288_ADC_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"), | ||
| 104 | AXP288_ADC_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"), | ||
| 105 | AXP288_ADC_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"), | ||
| 106 | AXP288_ADC_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"), | ||
| 107 | AXP288_ADC_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"), | ||
| 108 | {}, | ||
| 109 | }; | ||
| 110 | |||
| 111 | static int axp288_adc_read_channel(int *val, unsigned long address, | ||
| 112 | struct regmap *regmap) | ||
| 113 | { | ||
| 114 | u8 buf[2]; | ||
| 115 | |||
| 116 | if (regmap_bulk_read(regmap, address, buf, 2)) | ||
| 117 | return -EIO; | ||
| 118 | *val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F); | ||
| 119 | |||
| 120 | return IIO_VAL_INT; | ||
| 121 | } | ||
| 122 | |||
| 123 | static int axp288_adc_set_ts(struct regmap *regmap, unsigned int mode, | ||
| 124 | unsigned long address) | ||
| 125 | { | ||
| 126 | /* channels other than GPADC do not need to switch TS pin */ | ||
| 127 | if (address != AXP288_GP_ADC_H) | ||
| 128 | return 0; | ||
| 129 | |||
| 130 | return regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, mode); | ||
| 131 | } | ||
| 132 | |||
| 133 | static int axp288_adc_read_raw(struct iio_dev *indio_dev, | ||
| 134 | struct iio_chan_spec const *chan, | ||
| 135 | int *val, int *val2, long mask) | ||
| 136 | { | ||
| 137 | int ret; | ||
| 138 | struct axp288_adc_info *info = iio_priv(indio_dev); | ||
| 139 | |||
| 140 | mutex_lock(&indio_dev->mlock); | ||
| 141 | switch (mask) { | ||
| 142 | case IIO_CHAN_INFO_RAW: | ||
| 143 | if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_GPADC, | ||
| 144 | chan->address)) { | ||
| 145 | dev_err(&indio_dev->dev, "GPADC mode\n"); | ||
| 146 | ret = -EINVAL; | ||
| 147 | break; | ||
| 148 | } | ||
| 149 | ret = axp288_adc_read_channel(val, chan->address, info->regmap); | ||
| 150 | if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_ON, | ||
| 151 | chan->address)) | ||
| 152 | dev_err(&indio_dev->dev, "TS pin restore\n"); | ||
| 153 | break; | ||
| 154 | case IIO_CHAN_INFO_PROCESSED: | ||
| 155 | ret = axp288_adc_read_channel(val, chan->address, info->regmap); | ||
| 156 | break; | ||
| 157 | default: | ||
| 158 | ret = -EINVAL; | ||
| 159 | } | ||
| 160 | mutex_unlock(&indio_dev->mlock); | ||
| 161 | |||
| 162 | return ret; | ||
| 163 | } | ||
| 164 | |||
| 165 | static int axp288_adc_set_state(struct regmap *regmap) | ||
| 166 | { | ||
| 167 | /* ADC should be always enabled for internal FG to function */ | ||
| 168 | if (regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, AXP288_ADC_TS_PIN_ON)) | ||
| 169 | return -EIO; | ||
| 170 | |||
| 171 | return regmap_write(regmap, AXP20X_ADC_EN1, AXP288_ADC_EN_MASK); | ||
| 172 | } | ||
| 173 | |||
| 174 | static const struct iio_info axp288_adc_iio_info = { | ||
| 175 | .read_raw = &axp288_adc_read_raw, | ||
| 176 | .driver_module = THIS_MODULE, | ||
| 177 | }; | ||
| 178 | |||
| 179 | static int axp288_adc_probe(struct platform_device *pdev) | ||
| 180 | { | ||
| 181 | int ret; | ||
| 182 | struct axp288_adc_info *info; | ||
| 183 | struct iio_dev *indio_dev; | ||
| 184 | struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); | ||
| 185 | |||
| 186 | indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); | ||
| 187 | if (!indio_dev) | ||
| 188 | return -ENOMEM; | ||
| 189 | |||
| 190 | info = iio_priv(indio_dev); | ||
| 191 | info->irq = platform_get_irq(pdev, 0); | ||
| 192 | if (info->irq < 0) { | ||
| 193 | dev_err(&pdev->dev, "no irq resource?\n"); | ||
| 194 | return info->irq; | ||
| 195 | } | ||
| 196 | platform_set_drvdata(pdev, indio_dev); | ||
| 197 | info->regmap = axp20x->regmap; | ||
| 198 | /* | ||
| 199 | * Set ADC to enabled state at all time, including system suspend. | ||
| 200 | * otherwise internal fuel gauge functionality may be affected. | ||
| 201 | */ | ||
| 202 | ret = axp288_adc_set_state(axp20x->regmap); | ||
| 203 | if (ret) { | ||
| 204 | dev_err(&pdev->dev, "unable to enable ADC device\n"); | ||
| 205 | return ret; | ||
| 206 | } | ||
| 207 | |||
| 208 | indio_dev->dev.parent = &pdev->dev; | ||
| 209 | indio_dev->name = pdev->name; | ||
| 210 | indio_dev->channels = axp288_adc_channels; | ||
| 211 | indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels); | ||
| 212 | indio_dev->info = &axp288_adc_iio_info; | ||
| 213 | indio_dev->modes = INDIO_DIRECT_MODE; | ||
| 214 | ret = iio_map_array_register(indio_dev, axp288_adc_default_maps); | ||
| 215 | if (ret < 0) | ||
| 216 | return ret; | ||
| 217 | |||
| 218 | ret = iio_device_register(indio_dev); | ||
| 219 | if (ret < 0) { | ||
| 220 | dev_err(&pdev->dev, "unable to register iio device\n"); | ||
| 221 | goto err_array_unregister; | ||
| 222 | } | ||
| 223 | return 0; | ||
| 224 | |||
| 225 | err_array_unregister: | ||
| 226 | iio_map_array_unregister(indio_dev); | ||
| 227 | |||
| 228 | return ret; | ||
| 229 | } | ||
| 230 | |||
| 231 | static int axp288_adc_remove(struct platform_device *pdev) | ||
| 232 | { | ||
| 233 | struct iio_dev *indio_dev = platform_get_drvdata(pdev); | ||
| 234 | |||
| 235 | iio_device_unregister(indio_dev); | ||
| 236 | iio_map_array_unregister(indio_dev); | ||
| 237 | |||
| 238 | return 0; | ||
| 239 | } | ||
| 240 | |||
| 241 | static struct platform_device_id axp288_adc_id_table[] = { | ||
| 242 | { .name = "axp288_adc" }, | ||
| 243 | {}, | ||
| 244 | }; | ||
| 245 | |||
| 246 | static struct platform_driver axp288_adc_driver = { | ||
| 247 | .probe = axp288_adc_probe, | ||
| 248 | .remove = axp288_adc_remove, | ||
| 249 | .id_table = axp288_adc_id_table, | ||
| 250 | .driver = { | ||
| 251 | .name = "axp288_adc", | ||
| 252 | }, | ||
| 253 | }; | ||
| 254 | |||
| 255 | MODULE_DEVICE_TABLE(platform, axp288_adc_id_table); | ||
| 256 | |||
| 257 | module_platform_driver(axp288_adc_driver); | ||
| 258 | |||
| 259 | MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>"); | ||
| 260 | MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver"); | ||
| 261 | MODULE_LICENSE("GPL"); | ||
