aboutsummaryrefslogtreecommitdiff
path: root/drivers/power/supply/generic-adc-battery.c
blob: 0124d8d51af7e7a10dc59807bf0b46441f57bf4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/*
 * Generic battery driver code using IIO
 * Copyright (C) 2012, Anish Kumar <anish198519851985@gmail.com>
 * based on jz4740-battery.c
 * based on s3c_adc_battery.c
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive for
 * more details.
 *
 */
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/gpio/consumer.h>
#include <linux/err.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/iio/consumer.h>
#include <linux/iio/types.h>
#include <linux/devm-helpers.h>

#define JITTER_DEFAULT 10 /* hope 10ms is enough */

enum gab_chan_type {
	GAB_VOLTAGE = 0,
	GAB_CURRENT,
	GAB_POWER,
	GAB_TEMP,
	GAB_MAX_CHAN_TYPE
};

/*
 * gab_chan_name suggests the standard channel names for commonly used
 * channel types.
 */
static const char *const gab_chan_name[] = {
	[GAB_VOLTAGE]	= "voltage",
	[GAB_CURRENT]	= "current",
	[GAB_POWER]	= "power",
	[GAB_TEMP]	= "temperature",
};

struct gab {
	struct power_supply		*psy;
	struct power_supply_desc	psy_desc;
	struct iio_channel	*channel[GAB_MAX_CHAN_TYPE];
	struct delayed_work bat_work;
	int	status;
	bool cable_plugged;
	struct gpio_desc *charge_finished;
};

static struct gab *to_generic_bat(struct power_supply *psy)
{
	return power_supply_get_drvdata(psy);
}

static void gab_ext_power_changed(struct power_supply *psy)
{
	struct gab *adc_bat = to_generic_bat(psy);

	schedule_delayed_work(&adc_bat->bat_work, msecs_to_jiffies(0));
}

static const enum power_supply_property gab_props[] = {
	POWER_SUPPLY_PROP_STATUS,
};

/*
 * This properties are set based on the received platform data and this
 * should correspond one-to-one with enum chan_type.
 */
static const enum power_supply_property gab_dyn_props[] = {
	POWER_SUPPLY_PROP_VOLTAGE_NOW,
	POWER_SUPPLY_PROP_CURRENT_NOW,
	POWER_SUPPLY_PROP_POWER_NOW,
	POWER_SUPPLY_PROP_TEMP,
};

static bool gab_charge_finished(struct gab *adc_bat)
{
	if (!adc_bat->charge_finished)
		return false;
	return gpiod_get_value(adc_bat->charge_finished);
}

static int read_channel(struct gab *adc_bat, enum gab_chan_type channel,
		int *result)
{
	int ret;

	ret = iio_read_channel_processed(adc_bat->channel[channel], result);
	if (ret < 0)
		pr_err("read channel error\n");
	else
		*result *= 1000;

	return ret;
}

static int gab_get_property(struct power_supply *psy,
		enum power_supply_property psp, union power_supply_propval *val)
{
	struct gab *adc_bat = to_generic_bat(psy);

	switch (psp) {
	case POWER_SUPPLY_PROP_STATUS:
		val->intval = adc_bat->status;
		return 0;
	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
		return read_channel(adc_bat, GAB_VOLTAGE, &val->intval);
	case POWER_SUPPLY_PROP_CURRENT_NOW:
		return read_channel(adc_bat, GAB_CURRENT, &val->intval);
	case POWER_SUPPLY_PROP_POWER_NOW:
		return read_channel(adc_bat, GAB_POWER, &val->intval);
	case POWER_SUPPLY_PROP_TEMP:
		return read_channel(adc_bat, GAB_TEMP, &val->intval);
	default:
		return -EINVAL;
	}
}

static void gab_work(struct work_struct *work)
{
	struct gab *adc_bat;
	struct delayed_work *delayed_work;
	bool is_plugged;
	int status;

	delayed_work = to_delayed_work(work);
	adc_bat = container_of(delayed_work, struct gab, bat_work);
	status = adc_bat->status;

	is_plugged = power_supply_am_i_supplied(adc_bat->psy);
	adc_bat->cable_plugged = is_plugged;

	if (!is_plugged)
		adc_bat->status =  POWER_SUPPLY_STATUS_DISCHARGING;
	else if (gab_charge_finished(adc_bat))
		adc_bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
	else
		adc_bat->status = POWER_SUPPLY_STATUS_CHARGING;

	if (status != adc_bat->status)
		power_supply_changed(adc_bat->psy);
}

static irqreturn_t gab_charged(int irq, void *dev_id)
{
	struct gab *adc_bat = dev_id;

	schedule_delayed_work(&adc_bat->bat_work,
			      msecs_to_jiffies(JITTER_DEFAULT));

	return IRQ_HANDLED;
}

static int gab_probe(struct platform_device *pdev)
{
	struct gab *adc_bat;
	struct power_supply_desc *psy_desc;
	struct power_supply_config psy_cfg = {};
	enum power_supply_property *properties;
	int ret = 0;
	int chan;
	int index = ARRAY_SIZE(gab_props);
	bool any = false;

	adc_bat = devm_kzalloc(&pdev->dev, sizeof(*adc_bat), GFP_KERNEL);
	if (!adc_bat)
		return -ENOMEM;

	psy_cfg.drv_data = adc_bat;
	psy_desc = &adc_bat->psy_desc;
	psy_desc->name = dev_name(&pdev->dev);

	/* bootup default values for the battery */
	adc_bat->cable_plugged = false;
	adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
	psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
	psy_desc->get_property = gab_get_property;
	psy_desc->external_power_changed = gab_ext_power_changed;

	/*
	 * copying the static properties and allocating extra memory for holding
	 * the extra configurable properties received from platform data.
	 */
	properties = devm_kcalloc(&pdev->dev,
				  ARRAY_SIZE(gab_props) +
				  ARRAY_SIZE(gab_chan_name),
				  sizeof(*properties),
				  GFP_KERNEL);
	if (!properties)
		return -ENOMEM;

	memcpy(properties, gab_props, sizeof(gab_props));

	/*
	 * getting channel from iio and copying the battery properties
	 * based on the channel supported by consumer device.
	 */
	for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) {
		adc_bat->channel[chan] = devm_iio_channel_get(&pdev->dev, gab_chan_name[chan]);
		if (IS_ERR(adc_bat->channel[chan])) {
			ret = PTR_ERR(adc_bat->channel[chan]);
			if (ret != -ENODEV)
				return dev_err_probe(&pdev->dev, ret, "Failed to get ADC channel %s\n", gab_chan_name[chan]);
			adc_bat->channel[chan] = NULL;
		} else if (adc_bat->channel[chan]) {
			/* copying properties for supported channels only */
			int index2;

			for (index2 = 0; index2 < index; index2++) {
				if (properties[index2] == gab_dyn_props[chan])
					break;	/* already known */
			}
			if (index2 == index)	/* really new */
				properties[index++] = gab_dyn_props[chan];
			any = true;
		}
	}

	/* none of the channels are supported so let's bail out */
	if (!any)
		return dev_err_probe(&pdev->dev, -ENODEV, "Failed to get any ADC channel\n");

	/*
	 * Total number of properties is equal to static properties
	 * plus the dynamic properties.Some properties may not be set
	 * as come channels may be not be supported by the device.So
	 * we need to take care of that.
	 */
	psy_desc->properties = properties;
	psy_desc->num_properties = index;

	adc_bat->psy = devm_power_supply_register(&pdev->dev, psy_desc, &psy_cfg);
	if (IS_ERR(adc_bat->psy))
		return dev_err_probe(&pdev->dev, PTR_ERR(adc_bat->psy), "Failed to register power-supply device\n");

	ret = devm_delayed_work_autocancel(&pdev->dev, &adc_bat->bat_work, gab_work);
	if (ret)
		return dev_err_probe(&pdev->dev, ret, "Failed to register delayed work\n");

	adc_bat->charge_finished = devm_gpiod_get_optional(&pdev->dev, "charged", GPIOD_IN);
	if (adc_bat->charge_finished) {
		int irq;

		irq = gpiod_to_irq(adc_bat->charge_finished);
		ret = devm_request_any_context_irq(&pdev->dev, irq, gab_charged,
				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
				"battery charged", adc_bat);
		if (ret < 0)
			return dev_err_probe(&pdev->dev, ret, "Failed to register irq\n");
	}

	platform_set_drvdata(pdev, adc_bat);

	/* Schedule timer to check current status */
	schedule_delayed_work(&adc_bat->bat_work,
			msecs_to_jiffies(0));
	return 0;
}

static int __maybe_unused gab_suspend(struct device *dev)
{
	struct gab *adc_bat = dev_get_drvdata(dev);

	cancel_delayed_work_sync(&adc_bat->bat_work);
	adc_bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
	return 0;
}

static int __maybe_unused gab_resume(struct device *dev)
{
	struct gab *adc_bat = dev_get_drvdata(dev);

	/* Schedule timer to check current status */
	schedule_delayed_work(&adc_bat->bat_work,
			      msecs_to_jiffies(JITTER_DEFAULT));

	return 0;
}

static SIMPLE_DEV_PM_OPS(gab_pm_ops, gab_suspend, gab_resume);

static struct platform_driver gab_driver = {
	.driver		= {
		.name	= "generic-adc-battery",
		.pm	= &gab_pm_ops,
	},
	.probe		= gab_probe,
};
module_platform_driver(gab_driver);

MODULE_AUTHOR("anish kumar <anish198519851985@gmail.com>");
MODULE_DESCRIPTION("generic battery driver using IIO");
MODULE_LICENSE("GPL");