1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Generic Syscon LEDs Driver |
4 | * |
5 | * Copyright (c) 2014, Linaro Limited |
6 | * Author: Linus Walleij <linus.walleij@linaro.org> |
7 | */ |
8 | #include <linux/io.h> |
9 | #include <linux/init.h> |
10 | #include <linux/of.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/stat.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/mfd/syscon.h> |
15 | #include <linux/regmap.h> |
16 | #include <linux/leds.h> |
17 | |
18 | /** |
19 | * struct syscon_led - state container for syscon based LEDs |
20 | * @cdev: LED class device for this LED |
21 | * @map: regmap to access the syscon device backing this LED |
22 | * @offset: the offset into the syscon regmap for the LED register |
23 | * @mask: the bit in the register corresponding to the LED |
24 | * @state: current state of the LED |
25 | */ |
26 | struct syscon_led { |
27 | struct led_classdev cdev; |
28 | struct regmap *map; |
29 | u32 offset; |
30 | u32 mask; |
31 | bool state; |
32 | }; |
33 | |
34 | static void syscon_led_set(struct led_classdev *led_cdev, |
35 | enum led_brightness value) |
36 | { |
37 | struct syscon_led *sled = |
38 | container_of(led_cdev, struct syscon_led, cdev); |
39 | u32 val; |
40 | int ret; |
41 | |
42 | if (value == LED_OFF) { |
43 | val = 0; |
44 | sled->state = false; |
45 | } else { |
46 | val = sled->mask; |
47 | sled->state = true; |
48 | } |
49 | |
50 | ret = regmap_update_bits(map: sled->map, reg: sled->offset, mask: sled->mask, val); |
51 | if (ret < 0) |
52 | dev_err(sled->cdev.dev, "error updating LED status\n" ); |
53 | } |
54 | |
55 | static int syscon_led_probe(struct platform_device *pdev) |
56 | { |
57 | struct led_init_data init_data = {}; |
58 | struct device *dev = &pdev->dev; |
59 | struct device_node *np = dev_of_node(dev); |
60 | struct device *parent; |
61 | struct regmap *map; |
62 | struct syscon_led *sled; |
63 | enum led_default_state state; |
64 | u32 value; |
65 | int ret; |
66 | |
67 | parent = dev->parent; |
68 | if (!parent) { |
69 | dev_err(dev, "no parent for syscon LED\n" ); |
70 | return -ENODEV; |
71 | } |
72 | map = syscon_node_to_regmap(np: dev_of_node(dev: parent)); |
73 | if (IS_ERR(ptr: map)) { |
74 | dev_err(dev, "no regmap for syscon LED parent\n" ); |
75 | return PTR_ERR(ptr: map); |
76 | } |
77 | |
78 | sled = devm_kzalloc(dev, size: sizeof(*sled), GFP_KERNEL); |
79 | if (!sled) |
80 | return -ENOMEM; |
81 | |
82 | sled->map = map; |
83 | |
84 | if (of_property_read_u32(np, propname: "offset" , out_value: &sled->offset)) |
85 | return -EINVAL; |
86 | if (of_property_read_u32(np, propname: "mask" , out_value: &sled->mask)) |
87 | return -EINVAL; |
88 | |
89 | init_data.fwnode = of_fwnode_handle(np); |
90 | |
91 | state = led_init_default_state_get(fwnode: init_data.fwnode); |
92 | switch (state) { |
93 | case LEDS_DEFSTATE_ON: |
94 | ret = regmap_update_bits(map, reg: sled->offset, mask: sled->mask, val: sled->mask); |
95 | if (ret < 0) |
96 | return ret; |
97 | sled->state = true; |
98 | break; |
99 | case LEDS_DEFSTATE_KEEP: |
100 | ret = regmap_read(map, reg: sled->offset, val: &value); |
101 | if (ret < 0) |
102 | return ret; |
103 | sled->state = !!(value & sled->mask); |
104 | break; |
105 | default: |
106 | ret = regmap_update_bits(map, reg: sled->offset, mask: sled->mask, val: 0); |
107 | if (ret < 0) |
108 | return ret; |
109 | sled->state = false; |
110 | } |
111 | sled->cdev.brightness_set = syscon_led_set; |
112 | |
113 | ret = devm_led_classdev_register_ext(parent: dev, led_cdev: &sled->cdev, init_data: &init_data); |
114 | if (ret < 0) |
115 | return ret; |
116 | |
117 | platform_set_drvdata(pdev, data: sled); |
118 | dev_info(dev, "registered LED %s\n" , sled->cdev.name); |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | static const struct of_device_id of_syscon_leds_match[] = { |
124 | { .compatible = "register-bit-led" , }, |
125 | {}, |
126 | }; |
127 | |
128 | static struct platform_driver syscon_led_driver = { |
129 | .probe = syscon_led_probe, |
130 | .driver = { |
131 | .name = "leds-syscon" , |
132 | .of_match_table = of_syscon_leds_match, |
133 | .suppress_bind_attrs = true, |
134 | }, |
135 | }; |
136 | builtin_platform_driver(syscon_led_driver); |
137 | |