1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * LED driver for WM831x status LEDs |
4 | * |
5 | * Copyright(C) 2009 Wolfson Microelectronics PLC. |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/leds.h> |
12 | #include <linux/err.h> |
13 | #include <linux/mfd/wm831x/core.h> |
14 | #include <linux/mfd/wm831x/pdata.h> |
15 | #include <linux/mfd/wm831x/status.h> |
16 | #include <linux/module.h> |
17 | |
18 | |
19 | struct wm831x_status { |
20 | struct led_classdev cdev; |
21 | struct wm831x *wm831x; |
22 | struct mutex mutex; |
23 | |
24 | spinlock_t value_lock; |
25 | int reg; /* Control register */ |
26 | int reg_val; /* Control register value */ |
27 | |
28 | int blink; |
29 | int blink_time; |
30 | int blink_cyc; |
31 | int src; |
32 | enum led_brightness brightness; |
33 | }; |
34 | |
35 | #define to_wm831x_status(led_cdev) \ |
36 | container_of(led_cdev, struct wm831x_status, cdev) |
37 | |
38 | static void wm831x_status_set(struct wm831x_status *led) |
39 | { |
40 | unsigned long flags; |
41 | |
42 | mutex_lock(&led->mutex); |
43 | |
44 | led->reg_val &= ~(WM831X_LED_SRC_MASK | WM831X_LED_MODE_MASK | |
45 | WM831X_LED_DUTY_CYC_MASK | WM831X_LED_DUR_MASK); |
46 | |
47 | spin_lock_irqsave(&led->value_lock, flags); |
48 | |
49 | led->reg_val |= led->src << WM831X_LED_SRC_SHIFT; |
50 | if (led->blink) { |
51 | led->reg_val |= 2 << WM831X_LED_MODE_SHIFT; |
52 | led->reg_val |= led->blink_time << WM831X_LED_DUR_SHIFT; |
53 | led->reg_val |= led->blink_cyc; |
54 | } else { |
55 | if (led->brightness != LED_OFF) |
56 | led->reg_val |= 1 << WM831X_LED_MODE_SHIFT; |
57 | } |
58 | |
59 | spin_unlock_irqrestore(lock: &led->value_lock, flags); |
60 | |
61 | wm831x_reg_write(wm831x: led->wm831x, reg: led->reg, val: led->reg_val); |
62 | |
63 | mutex_unlock(lock: &led->mutex); |
64 | } |
65 | |
66 | static int wm831x_status_brightness_set(struct led_classdev *led_cdev, |
67 | enum led_brightness value) |
68 | { |
69 | struct wm831x_status *led = to_wm831x_status(led_cdev); |
70 | unsigned long flags; |
71 | |
72 | spin_lock_irqsave(&led->value_lock, flags); |
73 | led->brightness = value; |
74 | if (value == LED_OFF) |
75 | led->blink = 0; |
76 | spin_unlock_irqrestore(lock: &led->value_lock, flags); |
77 | wm831x_status_set(led); |
78 | |
79 | return 0; |
80 | } |
81 | |
82 | static int wm831x_status_blink_set(struct led_classdev *led_cdev, |
83 | unsigned long *delay_on, |
84 | unsigned long *delay_off) |
85 | { |
86 | struct wm831x_status *led = to_wm831x_status(led_cdev); |
87 | unsigned long flags; |
88 | int ret = 0; |
89 | |
90 | /* Pick some defaults if we've not been given times */ |
91 | if (*delay_on == 0 && *delay_off == 0) { |
92 | *delay_on = 250; |
93 | *delay_off = 250; |
94 | } |
95 | |
96 | spin_lock_irqsave(&led->value_lock, flags); |
97 | |
98 | /* We only have a limited selection of settings, see if we can |
99 | * support the configuration we're being given */ |
100 | switch (*delay_on) { |
101 | case 1000: |
102 | led->blink_time = 0; |
103 | break; |
104 | case 250: |
105 | led->blink_time = 1; |
106 | break; |
107 | case 125: |
108 | led->blink_time = 2; |
109 | break; |
110 | case 62: |
111 | case 63: |
112 | /* Actually 62.5ms */ |
113 | led->blink_time = 3; |
114 | break; |
115 | default: |
116 | ret = -EINVAL; |
117 | break; |
118 | } |
119 | |
120 | if (ret == 0) { |
121 | switch (*delay_off / *delay_on) { |
122 | case 1: |
123 | led->blink_cyc = 0; |
124 | break; |
125 | case 3: |
126 | led->blink_cyc = 1; |
127 | break; |
128 | case 4: |
129 | led->blink_cyc = 2; |
130 | break; |
131 | case 8: |
132 | led->blink_cyc = 3; |
133 | break; |
134 | default: |
135 | ret = -EINVAL; |
136 | break; |
137 | } |
138 | } |
139 | |
140 | if (ret == 0) |
141 | led->blink = 1; |
142 | else |
143 | led->blink = 0; |
144 | |
145 | spin_unlock_irqrestore(lock: &led->value_lock, flags); |
146 | wm831x_status_set(led); |
147 | |
148 | return ret; |
149 | } |
150 | |
151 | static const char * const led_src_texts[] = { |
152 | "otp" , |
153 | "power" , |
154 | "charger" , |
155 | "soft" , |
156 | }; |
157 | |
158 | static ssize_t src_show(struct device *dev, |
159 | struct device_attribute *attr, char *buf) |
160 | { |
161 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
162 | struct wm831x_status *led = to_wm831x_status(led_cdev); |
163 | int i; |
164 | ssize_t ret = 0; |
165 | |
166 | mutex_lock(&led->mutex); |
167 | |
168 | for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) |
169 | if (i == led->src) |
170 | ret += sprintf(buf: &buf[ret], fmt: "[%s] " , led_src_texts[i]); |
171 | else |
172 | ret += sprintf(buf: &buf[ret], fmt: "%s " , led_src_texts[i]); |
173 | |
174 | mutex_unlock(lock: &led->mutex); |
175 | |
176 | ret += sprintf(buf: &buf[ret], fmt: "\n" ); |
177 | |
178 | return ret; |
179 | } |
180 | |
181 | static ssize_t src_store(struct device *dev, |
182 | struct device_attribute *attr, |
183 | const char *buf, size_t size) |
184 | { |
185 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
186 | struct wm831x_status *led = to_wm831x_status(led_cdev); |
187 | int i; |
188 | |
189 | i = sysfs_match_string(led_src_texts, buf); |
190 | if (i >= 0) { |
191 | mutex_lock(&led->mutex); |
192 | led->src = i; |
193 | mutex_unlock(lock: &led->mutex); |
194 | wm831x_status_set(led); |
195 | } |
196 | |
197 | return size; |
198 | } |
199 | |
200 | static DEVICE_ATTR_RW(src); |
201 | |
202 | static struct attribute *wm831x_status_attrs[] = { |
203 | &dev_attr_src.attr, |
204 | NULL |
205 | }; |
206 | ATTRIBUTE_GROUPS(wm831x_status); |
207 | |
208 | static int wm831x_status_probe(struct platform_device *pdev) |
209 | { |
210 | struct wm831x *wm831x = dev_get_drvdata(dev: pdev->dev.parent); |
211 | struct wm831x_pdata *chip_pdata; |
212 | struct wm831x_status_pdata pdata; |
213 | struct wm831x_status *drvdata; |
214 | struct resource *res; |
215 | int id = pdev->id % ARRAY_SIZE(chip_pdata->status); |
216 | int ret; |
217 | |
218 | res = platform_get_resource(pdev, IORESOURCE_REG, 0); |
219 | if (res == NULL) { |
220 | dev_err(&pdev->dev, "No register resource\n" ); |
221 | return -EINVAL; |
222 | } |
223 | |
224 | drvdata = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct wm831x_status), |
225 | GFP_KERNEL); |
226 | if (!drvdata) |
227 | return -ENOMEM; |
228 | |
229 | drvdata->wm831x = wm831x; |
230 | drvdata->reg = res->start; |
231 | |
232 | if (dev_get_platdata(dev: wm831x->dev)) |
233 | chip_pdata = dev_get_platdata(dev: wm831x->dev); |
234 | else |
235 | chip_pdata = NULL; |
236 | |
237 | memset(&pdata, 0, sizeof(pdata)); |
238 | if (chip_pdata && chip_pdata->status[id]) |
239 | memcpy(&pdata, chip_pdata->status[id], sizeof(pdata)); |
240 | else |
241 | pdata.name = dev_name(dev: &pdev->dev); |
242 | |
243 | mutex_init(&drvdata->mutex); |
244 | spin_lock_init(&drvdata->value_lock); |
245 | |
246 | /* We cache the configuration register and read startup values |
247 | * from it. */ |
248 | drvdata->reg_val = wm831x_reg_read(wm831x, reg: drvdata->reg); |
249 | |
250 | if (drvdata->reg_val & WM831X_LED_MODE_MASK) |
251 | drvdata->brightness = LED_FULL; |
252 | else |
253 | drvdata->brightness = LED_OFF; |
254 | |
255 | /* Set a default source if configured, otherwise leave the |
256 | * current hardware setting. |
257 | */ |
258 | if (pdata.default_src == WM831X_STATUS_PRESERVE) { |
259 | drvdata->src = drvdata->reg_val; |
260 | drvdata->src &= WM831X_LED_SRC_MASK; |
261 | drvdata->src >>= WM831X_LED_SRC_SHIFT; |
262 | } else { |
263 | drvdata->src = pdata.default_src - 1; |
264 | } |
265 | |
266 | drvdata->cdev.name = pdata.name; |
267 | drvdata->cdev.default_trigger = pdata.default_trigger; |
268 | drvdata->cdev.brightness_set_blocking = wm831x_status_brightness_set; |
269 | drvdata->cdev.blink_set = wm831x_status_blink_set; |
270 | drvdata->cdev.groups = wm831x_status_groups; |
271 | |
272 | ret = led_classdev_register(parent: wm831x->dev, led_cdev: &drvdata->cdev); |
273 | if (ret < 0) { |
274 | dev_err(&pdev->dev, "Failed to register LED: %d\n" , ret); |
275 | return ret; |
276 | } |
277 | |
278 | platform_set_drvdata(pdev, data: drvdata); |
279 | |
280 | return 0; |
281 | } |
282 | |
283 | static void wm831x_status_remove(struct platform_device *pdev) |
284 | { |
285 | struct wm831x_status *drvdata = platform_get_drvdata(pdev); |
286 | |
287 | led_classdev_unregister(led_cdev: &drvdata->cdev); |
288 | } |
289 | |
290 | static struct platform_driver wm831x_status_driver = { |
291 | .driver = { |
292 | .name = "wm831x-status" , |
293 | }, |
294 | .probe = wm831x_status_probe, |
295 | .remove_new = wm831x_status_remove, |
296 | }; |
297 | |
298 | module_platform_driver(wm831x_status_driver); |
299 | |
300 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>" ); |
301 | MODULE_DESCRIPTION("WM831x status LED driver" ); |
302 | MODULE_LICENSE("GPL" ); |
303 | MODULE_ALIAS("platform:wm831x-status" ); |
304 | |