Skip to content

Commit fbc1d05

Browse files
clamor-ssre
authored andcommitted
power: supply: Add driver for Pegatron Chagall battery
The Pegatron Chagall is an Android tablet utilizing a customized Cypress CG7153AM microcontroller (MCU) as its battery fuel gauge. It supports a single-cell battery and features a dual-color charging LED. Signed-off-by: Svyatoslav Ryhel <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Sebastian Reichel <[email protected]>
1 parent 615a8d9 commit fbc1d05

File tree

3 files changed

+304
-0
lines changed

3 files changed

+304
-0
lines changed

drivers/power/supply/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ config BATTERY_ACT8945A
107107
Say Y here to enable support for power supply provided by
108108
Active-semi ActivePath ACT8945A charger.
109109

110+
config BATTERY_CHAGALL
111+
tristate "Pegatron Chagall battery driver"
112+
depends on I2C
113+
depends on LEDS_CLASS
114+
help
115+
Say Y to include support for Cypress CG7153AM IC based battery
116+
fuel gauge with custom firmware found in Pegatron Chagall based
117+
tablet line.
118+
119+
This driver can also be built as a module. If so, the module will be
120+
called chagall-battery.
121+
110122
config BATTERY_CPCAP
111123
tristate "Motorola CPCAP PMIC battery driver"
112124
depends on MFD_CPCAP && IIO

drivers/power/supply/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o
2323
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
2424
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
2525
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
26+
obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o
2627
obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
2728
obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o
2829
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
#include <linux/array_size.h>
4+
#include <linux/delay.h>
5+
#include <linux/devm-helpers.h>
6+
#include <linux/err.h>
7+
#include <linux/i2c.h>
8+
#include <linux/leds.h>
9+
#include <linux/mod_devicetable.h>
10+
#include <linux/module.h>
11+
#include <linux/power_supply.h>
12+
#include <linux/regmap.h>
13+
14+
#define CHAGALL_REG_LED_AMBER 0x60
15+
#define CHAGALL_REG_LED_WHITE 0x70
16+
#define CHAGALL_REG_BATTERY_TEMPERATURE 0xa2
17+
#define CHAGALL_REG_BATTERY_VOLTAGE 0xa4
18+
#define CHAGALL_REG_BATTERY_CURRENT 0xa6
19+
#define CHAGALL_REG_BATTERY_CAPACITY 0xa8
20+
#define CHAGALL_REG_BATTERY_CHARGING_CURRENT 0xaa
21+
#define CHAGALL_REG_BATTERY_CHARGING_VOLTAGE 0xac
22+
#define CHAGALL_REG_BATTERY_STATUS 0xae
23+
#define BATTERY_DISCHARGING BIT(6)
24+
#define BATTERY_FULL_CHARGED BIT(5)
25+
#define BATTERY_FULL_DISCHARGED BIT(4)
26+
#define CHAGALL_REG_BATTERY_REMAIN_CAPACITY 0xb0
27+
#define CHAGALL_REG_BATTERY_FULL_CAPACITY 0xb2
28+
#define CHAGALL_REG_MAX_COUNT 0xb4
29+
30+
#define CHAGALL_BATTERY_DATA_REFRESH 5000
31+
#define TEMP_CELSIUS_OFFSET 2731
32+
33+
static const struct regmap_config chagall_battery_regmap_config = {
34+
.reg_bits = 8,
35+
.val_bits = 8,
36+
.max_register = CHAGALL_REG_MAX_COUNT,
37+
.reg_format_endian = REGMAP_ENDIAN_LITTLE,
38+
.val_format_endian = REGMAP_ENDIAN_LITTLE,
39+
};
40+
41+
struct chagall_battery_data {
42+
struct regmap *regmap;
43+
struct led_classdev amber_led;
44+
struct led_classdev white_led;
45+
struct power_supply *battery;
46+
struct delayed_work poll_work;
47+
u16 last_state;
48+
};
49+
50+
static void chagall_led_set_brightness_amber(struct led_classdev *led,
51+
enum led_brightness brightness)
52+
{
53+
struct chagall_battery_data *cg =
54+
container_of(led, struct chagall_battery_data, amber_led);
55+
56+
regmap_write(cg->regmap, CHAGALL_REG_LED_AMBER, brightness);
57+
}
58+
59+
static void chagall_led_set_brightness_white(struct led_classdev *led,
60+
enum led_brightness brightness)
61+
{
62+
struct chagall_battery_data *cg =
63+
container_of(led, struct chagall_battery_data, white_led);
64+
65+
regmap_write(cg->regmap, CHAGALL_REG_LED_WHITE, brightness);
66+
}
67+
68+
static const enum power_supply_property chagall_battery_properties[] = {
69+
POWER_SUPPLY_PROP_STATUS,
70+
POWER_SUPPLY_PROP_PRESENT,
71+
POWER_SUPPLY_PROP_VOLTAGE_NOW,
72+
POWER_SUPPLY_PROP_VOLTAGE_MAX,
73+
POWER_SUPPLY_PROP_CURRENT_NOW,
74+
POWER_SUPPLY_PROP_CURRENT_MAX,
75+
POWER_SUPPLY_PROP_CAPACITY,
76+
POWER_SUPPLY_PROP_TEMP,
77+
POWER_SUPPLY_PROP_CHARGE_FULL,
78+
POWER_SUPPLY_PROP_CHARGE_NOW,
79+
};
80+
81+
static const unsigned int chagall_battery_prop_offs[] = {
82+
[POWER_SUPPLY_PROP_STATUS] = CHAGALL_REG_BATTERY_STATUS,
83+
[POWER_SUPPLY_PROP_VOLTAGE_NOW] = CHAGALL_REG_BATTERY_VOLTAGE,
84+
[POWER_SUPPLY_PROP_VOLTAGE_MAX] = CHAGALL_REG_BATTERY_CHARGING_VOLTAGE,
85+
[POWER_SUPPLY_PROP_CURRENT_NOW] = CHAGALL_REG_BATTERY_CURRENT,
86+
[POWER_SUPPLY_PROP_CURRENT_MAX] = CHAGALL_REG_BATTERY_CHARGING_CURRENT,
87+
[POWER_SUPPLY_PROP_CAPACITY] = CHAGALL_REG_BATTERY_CAPACITY,
88+
[POWER_SUPPLY_PROP_TEMP] = CHAGALL_REG_BATTERY_TEMPERATURE,
89+
[POWER_SUPPLY_PROP_CHARGE_FULL] = CHAGALL_REG_BATTERY_FULL_CAPACITY,
90+
[POWER_SUPPLY_PROP_CHARGE_NOW] = CHAGALL_REG_BATTERY_REMAIN_CAPACITY,
91+
};
92+
93+
static int chagall_battery_get_value(struct chagall_battery_data *cg,
94+
enum power_supply_property psp, u32 *val)
95+
{
96+
if (psp >= ARRAY_SIZE(chagall_battery_prop_offs))
97+
return -EINVAL;
98+
if (!chagall_battery_prop_offs[psp])
99+
return -EINVAL;
100+
101+
/* Battery data is stored in 2 consecutive registers with little-endian */
102+
return regmap_bulk_read(cg->regmap, chagall_battery_prop_offs[psp], val, 2);
103+
}
104+
105+
static int chagall_battery_get_status(u32 status_reg)
106+
{
107+
if (status_reg & BATTERY_FULL_CHARGED)
108+
return POWER_SUPPLY_STATUS_FULL;
109+
else if (status_reg & BATTERY_DISCHARGING)
110+
return POWER_SUPPLY_STATUS_DISCHARGING;
111+
else
112+
return POWER_SUPPLY_STATUS_CHARGING;
113+
}
114+
115+
static int chagall_battery_get_property(struct power_supply *psy,
116+
enum power_supply_property psp,
117+
union power_supply_propval *val)
118+
{
119+
struct chagall_battery_data *cg = power_supply_get_drvdata(psy);
120+
int ret;
121+
122+
switch (psp) {
123+
case POWER_SUPPLY_PROP_PRESENT:
124+
val->intval = 1;
125+
break;
126+
127+
default:
128+
ret = chagall_battery_get_value(cg, psp, &val->intval);
129+
if (ret)
130+
return ret;
131+
132+
switch (psp) {
133+
case POWER_SUPPLY_PROP_TEMP:
134+
val->intval -= TEMP_CELSIUS_OFFSET;
135+
break;
136+
137+
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
138+
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
139+
case POWER_SUPPLY_PROP_CURRENT_MAX:
140+
case POWER_SUPPLY_PROP_CURRENT_NOW:
141+
case POWER_SUPPLY_PROP_CHARGE_FULL:
142+
case POWER_SUPPLY_PROP_CHARGE_NOW:
143+
val->intval *= 1000;
144+
break;
145+
146+
case POWER_SUPPLY_PROP_STATUS:
147+
val->intval = chagall_battery_get_status(val->intval);
148+
break;
149+
150+
default:
151+
break;
152+
}
153+
154+
break;
155+
}
156+
157+
return 0;
158+
}
159+
160+
static void chagall_battery_poll_work(struct work_struct *work)
161+
{
162+
struct chagall_battery_data *cg =
163+
container_of(work, struct chagall_battery_data, poll_work.work);
164+
u32 state;
165+
int ret;
166+
167+
ret = chagall_battery_get_value(cg, POWER_SUPPLY_PROP_STATUS, &state);
168+
if (ret)
169+
return;
170+
171+
state = chagall_battery_get_status(state);
172+
173+
if (cg->last_state != state) {
174+
cg->last_state = state;
175+
power_supply_changed(cg->battery);
176+
}
177+
178+
/* continuously send uevent notification */
179+
schedule_delayed_work(&cg->poll_work,
180+
msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
181+
}
182+
183+
static const struct power_supply_desc chagall_battery_desc = {
184+
.name = "chagall-battery",
185+
.type = POWER_SUPPLY_TYPE_BATTERY,
186+
.properties = chagall_battery_properties,
187+
.num_properties = ARRAY_SIZE(chagall_battery_properties),
188+
.get_property = chagall_battery_get_property,
189+
.external_power_changed = power_supply_changed,
190+
};
191+
192+
static int chagall_battery_probe(struct i2c_client *client)
193+
{
194+
struct chagall_battery_data *cg;
195+
struct device *dev = &client->dev;
196+
struct power_supply_config cfg = { };
197+
int ret;
198+
199+
cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL);
200+
if (!cg)
201+
return -ENOMEM;
202+
203+
cfg.drv_data = cg;
204+
cfg.fwnode = dev_fwnode(dev);
205+
206+
i2c_set_clientdata(client, cg);
207+
208+
cg->regmap = devm_regmap_init_i2c(client, &chagall_battery_regmap_config);
209+
if (IS_ERR(cg->regmap))
210+
return dev_err_probe(dev, PTR_ERR(cg->regmap), "cannot allocate regmap\n");
211+
212+
cg->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
213+
cg->battery = devm_power_supply_register(dev, &chagall_battery_desc, &cfg);
214+
if (IS_ERR(cg->battery))
215+
return dev_err_probe(dev, PTR_ERR(cg->battery),
216+
"failed to register power supply\n");
217+
218+
cg->amber_led.name = "power::amber";
219+
cg->amber_led.max_brightness = 1;
220+
cg->amber_led.flags = LED_CORE_SUSPENDRESUME;
221+
cg->amber_led.brightness_set = chagall_led_set_brightness_amber;
222+
cg->amber_led.default_trigger = "chagall-battery-charging";
223+
224+
ret = devm_led_classdev_register(dev, &cg->amber_led);
225+
if (ret)
226+
return dev_err_probe(dev, ret, "failed to register amber LED\n");
227+
228+
cg->white_led.name = "power::white";
229+
cg->white_led.max_brightness = 1;
230+
cg->white_led.flags = LED_CORE_SUSPENDRESUME;
231+
cg->white_led.brightness_set = chagall_led_set_brightness_white;
232+
cg->white_led.default_trigger = "chagall-battery-full";
233+
234+
ret = devm_led_classdev_register(dev, &cg->white_led);
235+
if (ret)
236+
return dev_err_probe(dev, ret, "failed to register white LED\n");
237+
238+
led_set_brightness(&cg->amber_led, LED_OFF);
239+
led_set_brightness(&cg->white_led, LED_OFF);
240+
241+
ret = devm_delayed_work_autocancel(dev, &cg->poll_work, chagall_battery_poll_work);
242+
if (ret)
243+
return ret;
244+
245+
schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
246+
247+
return 0;
248+
}
249+
250+
static int __maybe_unused chagall_battery_suspend(struct device *dev)
251+
{
252+
struct i2c_client *client = to_i2c_client(dev);
253+
struct chagall_battery_data *cg = i2c_get_clientdata(client);
254+
255+
cancel_delayed_work_sync(&cg->poll_work);
256+
257+
return 0;
258+
}
259+
260+
static int __maybe_unused chagall_battery_resume(struct device *dev)
261+
{
262+
struct i2c_client *client = to_i2c_client(dev);
263+
struct chagall_battery_data *cg = i2c_get_clientdata(client);
264+
265+
schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
266+
267+
return 0;
268+
}
269+
270+
static SIMPLE_DEV_PM_OPS(chagall_battery_pm_ops,
271+
chagall_battery_suspend, chagall_battery_resume);
272+
273+
static const struct of_device_id chagall_of_match[] = {
274+
{ .compatible = "pegatron,chagall-ec" },
275+
{ }
276+
};
277+
MODULE_DEVICE_TABLE(of, chagall_of_match);
278+
279+
static struct i2c_driver chagall_battery_driver = {
280+
.driver = {
281+
.name = "chagall-battery",
282+
.pm = &chagall_battery_pm_ops,
283+
.of_match_table = chagall_of_match,
284+
},
285+
.probe = chagall_battery_probe,
286+
};
287+
module_i2c_driver(chagall_battery_driver);
288+
289+
MODULE_AUTHOR("Svyatoslav Ryhel <[email protected]>");
290+
MODULE_DESCRIPTION("Pegatron Chagall fuel gauge driver");
291+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)