1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * LEDs triggers for power supply class
4 *
5 * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
6 * Copyright © 2004 Szabolcs Gyurko
7 * Copyright © 2003 Ian Molton <spyro@f2s.com>
8 *
9 * Modified: 2004, Oct Szabolcs Gyurko
10 */
11
12#include <linux/kernel.h>
13#include <linux/device.h>
14#include <linux/power_supply.h>
15#include <linux/slab.h>
16#include <linux/leds.h>
17
18#include "power_supply.h"
19
20/* Battery specific LEDs triggers. */
21
22struct power_supply_led_trigger {
23 struct led_trigger trig;
24 struct power_supply *psy;
25};
26
27#define trigger_to_psy_trigger(trigger) \
28 container_of(trigger, struct power_supply_led_trigger, trig)
29
30static int power_supply_led_trigger_activate(struct led_classdev *led_cdev)
31{
32 struct power_supply_led_trigger *psy_trig =
33 trigger_to_psy_trigger(led_cdev->trigger);
34
35 /* Sync current power-supply state to LED being activated */
36 power_supply_update_leds(psy: psy_trig->psy);
37 return 0;
38}
39
40static int power_supply_register_led_trigger(struct power_supply *psy,
41 const char *name_template,
42 struct led_trigger **tp, int *err)
43{
44 struct power_supply_led_trigger *psy_trig;
45 int ret = -ENOMEM;
46
47 /* Bail on previous errors */
48 if (err && *err)
49 return *err;
50
51 psy_trig = kzalloc(sizeof(*psy_trig), GFP_KERNEL);
52 if (!psy_trig)
53 goto err_free_trigger;
54
55 psy_trig->trig.name = kasprintf(GFP_KERNEL, fmt: name_template, psy->desc->name);
56 if (!psy_trig->trig.name)
57 goto err_free_trigger;
58
59 psy_trig->trig.activate = power_supply_led_trigger_activate;
60 psy_trig->psy = psy;
61
62 ret = led_trigger_register(trigger: &psy_trig->trig);
63 if (ret)
64 goto err_free_name;
65
66 *tp = &psy_trig->trig;
67 return 0;
68
69err_free_name:
70 kfree(objp: psy_trig->trig.name);
71err_free_trigger:
72 kfree(objp: psy_trig);
73 if (err)
74 *err = ret;
75
76 return ret;
77}
78
79static void power_supply_unregister_led_trigger(struct led_trigger *trig)
80{
81 struct power_supply_led_trigger *psy_trig;
82
83 if (!trig)
84 return;
85
86 psy_trig = trigger_to_psy_trigger(trig);
87 led_trigger_unregister(trigger: &psy_trig->trig);
88 kfree(objp: psy_trig->trig.name);
89 kfree(objp: psy_trig);
90}
91
92static void power_supply_update_bat_leds(struct power_supply *psy)
93{
94 union power_supply_propval status;
95 unsigned int intensity_green[3] = { 0, 255, 0 };
96 unsigned int intensity_orange[3] = { 255, 128, 0 };
97
98 if (power_supply_get_property(psy, psp: POWER_SUPPLY_PROP_STATUS, val: &status))
99 return;
100
101 dev_dbg(&psy->dev, "%s %d\n", __func__, status.intval);
102
103 switch (status.intval) {
104 case POWER_SUPPLY_STATUS_FULL:
105 led_trigger_event(trigger: psy->trig, event: LED_FULL);
106 led_trigger_event(trigger: psy->charging_trig, event: LED_OFF);
107 led_trigger_event(trigger: psy->full_trig, event: LED_FULL);
108 /* Going from blink to LED on requires a LED_OFF event to stop blink */
109 led_trigger_event(trigger: psy->charging_blink_full_solid_trig, event: LED_OFF);
110 led_trigger_event(trigger: psy->charging_blink_full_solid_trig, event: LED_FULL);
111 led_mc_trigger_event(trig: psy->charging_orange_full_green_trig,
112 intensity_value: intensity_green,
113 ARRAY_SIZE(intensity_green),
114 brightness: LED_FULL);
115 break;
116 case POWER_SUPPLY_STATUS_CHARGING:
117 led_trigger_event(trigger: psy->trig, event: LED_FULL);
118 led_trigger_event(trigger: psy->charging_trig, event: LED_FULL);
119 led_trigger_event(trigger: psy->full_trig, event: LED_OFF);
120 led_trigger_blink(trigger: psy->charging_blink_full_solid_trig, delay_on: 0, delay_off: 0);
121 led_mc_trigger_event(trig: psy->charging_orange_full_green_trig,
122 intensity_value: intensity_orange,
123 ARRAY_SIZE(intensity_orange),
124 brightness: LED_FULL);
125 break;
126 default:
127 led_trigger_event(trigger: psy->trig, event: LED_OFF);
128 led_trigger_event(trigger: psy->charging_trig, event: LED_OFF);
129 led_trigger_event(trigger: psy->full_trig, event: LED_OFF);
130 led_trigger_event(trigger: psy->charging_blink_full_solid_trig,
131 event: LED_OFF);
132 led_trigger_event(trigger: psy->charging_orange_full_green_trig,
133 event: LED_OFF);
134 break;
135 }
136}
137
138static void power_supply_remove_bat_triggers(struct power_supply *psy)
139{
140 power_supply_unregister_led_trigger(trig: psy->trig);
141 power_supply_unregister_led_trigger(trig: psy->charging_trig);
142 power_supply_unregister_led_trigger(trig: psy->full_trig);
143 power_supply_unregister_led_trigger(trig: psy->charging_blink_full_solid_trig);
144 power_supply_unregister_led_trigger(trig: psy->charging_orange_full_green_trig);
145}
146
147static int power_supply_create_bat_triggers(struct power_supply *psy)
148{
149 int err = 0;
150
151 power_supply_register_led_trigger(psy, name_template: "%s-charging-or-full",
152 tp: &psy->trig, err: &err);
153 power_supply_register_led_trigger(psy, name_template: "%s-charging",
154 tp: &psy->charging_trig, err: &err);
155 power_supply_register_led_trigger(psy, name_template: "%s-full",
156 tp: &psy->full_trig, err: &err);
157 power_supply_register_led_trigger(psy, name_template: "%s-charging-blink-full-solid",
158 tp: &psy->charging_blink_full_solid_trig, err: &err);
159 power_supply_register_led_trigger(psy, name_template: "%s-charging-orange-full-green",
160 tp: &psy->charging_orange_full_green_trig, err: &err);
161 if (err)
162 power_supply_remove_bat_triggers(psy);
163
164 return err;
165}
166
167/* Generated power specific LEDs triggers. */
168
169static void power_supply_update_gen_leds(struct power_supply *psy)
170{
171 union power_supply_propval online;
172
173 if (power_supply_get_property(psy, psp: POWER_SUPPLY_PROP_ONLINE, val: &online))
174 return;
175
176 dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval);
177
178 if (online.intval)
179 led_trigger_event(trigger: psy->trig, event: LED_FULL);
180 else
181 led_trigger_event(trigger: psy->trig, event: LED_OFF);
182}
183
184static int power_supply_create_gen_triggers(struct power_supply *psy)
185{
186 return power_supply_register_led_trigger(psy, name_template: "%s-online", tp: &psy->trig, NULL);
187}
188
189static void power_supply_remove_gen_triggers(struct power_supply *psy)
190{
191 power_supply_unregister_led_trigger(trig: psy->trig);
192}
193
194/* Choice what triggers to create&update. */
195
196void power_supply_update_leds(struct power_supply *psy)
197{
198 if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
199 power_supply_update_bat_leds(psy);
200 else
201 power_supply_update_gen_leds(psy);
202}
203
204int power_supply_create_triggers(struct power_supply *psy)
205{
206 if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
207 return power_supply_create_bat_triggers(psy);
208 return power_supply_create_gen_triggers(psy);
209}
210
211void power_supply_remove_triggers(struct power_supply *psy)
212{
213 if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
214 power_supply_remove_bat_triggers(psy);
215 else
216 power_supply_remove_gen_triggers(psy);
217}
218