summaryrefslogtreecommitdiff
path: root/drivers/power/supply/wilco-charger.c
blob: 98ade073ef05fec46a804126f7635adfc30cd1e0 (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
// SPDX-License-Identifier: GPL-2.0
/*
 * Charging control driver for the Wilco EC
 *
 * Copyright 2019 Google LLC
 *
 * See Documentation/ABI/testing/sysfs-class-power and
 * Documentation/ABI/testing/sysfs-class-power-wilco for userspace interface
 * and other info.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/power_supply.h>

#define DRV_NAME "wilco-charger"

/* Property IDs and related EC constants */
#define PID_CHARGE_MODE		0x0710
#define PID_CHARGE_LOWER_LIMIT	0x0711
#define PID_CHARGE_UPPER_LIMIT	0x0712

enum charge_mode {
	CHARGE_MODE_STD = 1,	/* Used for Standard */
	CHARGE_MODE_EXP = 2,	/* Express Charge, used for Fast */
	CHARGE_MODE_AC = 3,	/* Mostly AC use, used for Trickle */
	CHARGE_MODE_AUTO = 4,	/* Used for Adaptive */
	CHARGE_MODE_CUSTOM = 5,	/* Used for Custom */
	CHARGE_MODE_LONGLIFE = 6, /* Used for Long Life */
};

#define CHARGE_LOWER_LIMIT_MIN	50
#define CHARGE_LOWER_LIMIT_MAX	95
#define CHARGE_UPPER_LIMIT_MIN	55
#define CHARGE_UPPER_LIMIT_MAX	100

/* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */
static int psp_val_to_charge_mode(int psp_val)
{
	switch (psp_val) {
	case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
		return CHARGE_MODE_AC;
	case POWER_SUPPLY_CHARGE_TYPE_FAST:
		return CHARGE_MODE_EXP;
	case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
		return CHARGE_MODE_STD;
	case POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE:
		return CHARGE_MODE_AUTO;
	case POWER_SUPPLY_CHARGE_TYPE_CUSTOM:
		return CHARGE_MODE_CUSTOM;
	case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
		return CHARGE_MODE_LONGLIFE;
	default:
		return -EINVAL;
	}
}

/* Convert from EC's charge mode to POWER_SUPPLY_PROP_CHARGE_TYPE value */
static int charge_mode_to_psp_val(enum charge_mode mode)
{
	switch (mode) {
	case CHARGE_MODE_AC:
		return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
	case CHARGE_MODE_EXP:
		return POWER_SUPPLY_CHARGE_TYPE_FAST;
	case CHARGE_MODE_STD:
		return POWER_SUPPLY_CHARGE_TYPE_STANDARD;
	case CHARGE_MODE_AUTO:
		return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE;
	case CHARGE_MODE_CUSTOM:
		return POWER_SUPPLY_CHARGE_TYPE_CUSTOM;
	case CHARGE_MODE_LONGLIFE:
		return POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
	default:
		return -EINVAL;
	}
}

static enum power_supply_property wilco_charge_props[] = {
	POWER_SUPPLY_PROP_CHARGE_TYPE,
	POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
};

static int wilco_charge_get_property(struct power_supply *psy,
				     enum power_supply_property psp,
				     union power_supply_propval *val)
{
	struct wilco_ec_device *ec = power_supply_get_drvdata(psy);
	u32 property_id;
	int ret;
	u8 raw;

	switch (psp) {
	case POWER_SUPPLY_PROP_CHARGE_TYPE:
		property_id = PID_CHARGE_MODE;
		break;
	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
		property_id = PID_CHARGE_LOWER_LIMIT;
		break;
	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
		property_id = PID_CHARGE_UPPER_LIMIT;
		break;
	default:
		return -EINVAL;
	}

	ret = wilco_ec_get_byte_property(ec, property_id, &raw);
	if (ret < 0)
		return ret;
	if (property_id == PID_CHARGE_MODE) {
		ret = charge_mode_to_psp_val(raw);
		if (ret < 0)
			return -EBADMSG;
		raw = ret;
	}
	val->intval = raw;

	return 0;
}

static int wilco_charge_set_property(struct power_supply *psy,
				     enum power_supply_property psp,
				     const union power_supply_propval *val)
{
	struct wilco_ec_device *ec = power_supply_get_drvdata(psy);
	int mode;

	switch (psp) {
	case POWER_SUPPLY_PROP_CHARGE_TYPE:
		mode = psp_val_to_charge_mode(val->intval);
		if (mode < 0)
			return -EINVAL;
		return wilco_ec_set_byte_property(ec, PID_CHARGE_MODE, mode);
	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
		if (val->intval < CHARGE_LOWER_LIMIT_MIN ||
		    val->intval > CHARGE_LOWER_LIMIT_MAX)
			return -EINVAL;
		return wilco_ec_set_byte_property(ec, PID_CHARGE_LOWER_LIMIT,
						  val->intval);
	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
		if (val->intval < CHARGE_UPPER_LIMIT_MIN ||
		    val->intval > CHARGE_UPPER_LIMIT_MAX)
			return -EINVAL;
		return wilco_ec_set_byte_property(ec, PID_CHARGE_UPPER_LIMIT,
						  val->intval);
	default:
		return -EINVAL;
	}
}

static int wilco_charge_property_is_writeable(struct power_supply *psy,
					      enum power_supply_property psp)
{
	return 1;
}

static const struct power_supply_desc wilco_ps_desc = {
	.properties		= wilco_charge_props,
	.num_properties		= ARRAY_SIZE(wilco_charge_props),
	.get_property		= wilco_charge_get_property,
	.set_property		= wilco_charge_set_property,
	.property_is_writeable	= wilco_charge_property_is_writeable,
	.name			= DRV_NAME,
	.type			= POWER_SUPPLY_TYPE_MAINS,
};

static int wilco_charge_probe(struct platform_device *pdev)
{
	struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
	struct power_supply_config psy_cfg = {};
	struct power_supply *psy;

	psy_cfg.drv_data = ec;
	psy = devm_power_supply_register(&pdev->dev, &wilco_ps_desc, &psy_cfg);

	return PTR_ERR_OR_ZERO(psy);
}

static struct platform_driver wilco_charge_driver = {
	.probe	= wilco_charge_probe,
	.driver = {
		.name = DRV_NAME,
	}
};
module_platform_driver(wilco_charge_driver);

MODULE_ALIAS("platform:" DRV_NAME);
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Wilco EC charge control driver");