diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-11-24 20:18:58 -0500 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-11-24 20:18:58 -0500 |
commit | a26033a1f56b7b1f8a56050c0a9095694aecae11 (patch) | |
tree | 8bb03cc2c30770f1e795ea9f7ebeceb6a6ba3fff /drivers/iio | |
parent | 5d01410fe4d92081f349b013a2e7a95429e4f2c9 (diff) | |
parent | 8019f6962708985782b65bd97be88046a55e1e4d (diff) |
Merge branch 'ib-mfd-iio-3.19' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd into acpi-pmic
Pull MFD changes that the ACPI PMIC changes depend on from Lee Jones.
Diffstat (limited to 'drivers/iio')
-rw-r--r-- | drivers/iio/adc/Kconfig | 8 | ||||
-rw-r--r-- | drivers/iio/adc/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/adc/axp288_adc.c | 261 |
3 files changed, 270 insertions, 0 deletions
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 88bdc8f612e2..bc4e787096e8 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig | |||
@@ -127,6 +127,14 @@ config AT91_ADC | |||
127 | help | 127 | help |
128 | Say yes here to build support for Atmel AT91 ADC. | 128 | Say yes here to build support for Atmel AT91 ADC. |
129 | 129 | ||
130 | config AXP288_ADC | ||
131 | tristate "X-Powers AXP288 ADC driver" | ||
132 | depends on MFD_AXP20X | ||
133 | help | ||
134 | Say yes here to have support for X-Powers power management IC (PMIC) ADC | ||
135 | device. Depending on platform configuration, this general purpose ADC can | ||
136 | be used for sampling sensors such as thermal resistors. | ||
137 | |||
130 | config EXYNOS_ADC | 138 | config EXYNOS_ADC |
131 | tristate "Exynos ADC driver support" | 139 | tristate "Exynos ADC driver support" |
132 | depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST) | 140 | depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST) |
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index cb88a6a23b8f..f30093f5b67a 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile | |||
@@ -14,6 +14,7 @@ obj-$(CONFIG_AD7793) += ad7793.o | |||
14 | obj-$(CONFIG_AD7887) += ad7887.o | 14 | obj-$(CONFIG_AD7887) += ad7887.o |
15 | obj-$(CONFIG_AD799X) += ad799x.o | 15 | obj-$(CONFIG_AD799X) += ad799x.o |
16 | obj-$(CONFIG_AT91_ADC) += at91_adc.o | 16 | obj-$(CONFIG_AT91_ADC) += at91_adc.o |
17 | obj-$(CONFIG_AXP288_ADC) += axp288_adc.o | ||
17 | obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o | 18 | obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o |
18 | obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o | 19 | obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o |
19 | obj-$(CONFIG_MAX1027) += max1027.o | 20 | obj-$(CONFIG_MAX1027) += max1027.o |
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"); | ||