Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
589fca16 ÁFR |
2 | /* |
3 | * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c | |
4 | * | |
5 | * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com> | |
589fca16 ÁFR |
6 | */ |
7 | #include <linux/delay.h> | |
8 | #include <linux/io.h> | |
9 | #include <linux/leds.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/of.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/spinlock.h> | |
14 | ||
15 | #define BCM6358_REG_MODE 0x0 | |
16 | #define BCM6358_REG_CTRL 0x4 | |
17 | ||
18 | #define BCM6358_SLED_CLKDIV_MASK 3 | |
19 | #define BCM6358_SLED_CLKDIV_1 0 | |
20 | #define BCM6358_SLED_CLKDIV_2 1 | |
21 | #define BCM6358_SLED_CLKDIV_4 2 | |
22 | #define BCM6358_SLED_CLKDIV_8 3 | |
23 | ||
24 | #define BCM6358_SLED_POLARITY BIT(2) | |
25 | #define BCM6358_SLED_BUSY BIT(3) | |
26 | ||
27 | #define BCM6358_SLED_MAX_COUNT 32 | |
28 | #define BCM6358_SLED_WAIT 100 | |
29 | ||
30 | /** | |
31 | * struct bcm6358_led - state container for bcm6358 based LEDs | |
32 | * @cdev: LED class device for this LED | |
33 | * @mem: memory resource | |
34 | * @lock: memory lock | |
35 | * @pin: LED pin number | |
36 | * @active_low: LED is active low | |
37 | */ | |
38 | struct bcm6358_led { | |
39 | struct led_classdev cdev; | |
40 | void __iomem *mem; | |
41 | spinlock_t *lock; | |
42 | unsigned long pin; | |
43 | bool active_low; | |
44 | }; | |
45 | ||
46 | static void bcm6358_led_write(void __iomem *reg, unsigned long data) | |
47 | { | |
4ba113b6 | 48 | #ifdef CONFIG_CPU_BIG_ENDIAN |
589fca16 | 49 | iowrite32be(data, reg); |
4ba113b6 ÁFR |
50 | #else |
51 | writel(data, reg); | |
52 | #endif | |
589fca16 ÁFR |
53 | } |
54 | ||
55 | static unsigned long bcm6358_led_read(void __iomem *reg) | |
56 | { | |
4ba113b6 | 57 | #ifdef CONFIG_CPU_BIG_ENDIAN |
589fca16 | 58 | return ioread32be(reg); |
4ba113b6 ÁFR |
59 | #else |
60 | return readl(reg); | |
61 | #endif | |
589fca16 ÁFR |
62 | } |
63 | ||
64 | static unsigned long bcm6358_led_busy(void __iomem *mem) | |
65 | { | |
66 | unsigned long val; | |
67 | ||
68 | while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) & | |
69 | BCM6358_SLED_BUSY) | |
70 | udelay(BCM6358_SLED_WAIT); | |
71 | ||
72 | return val; | |
73 | } | |
74 | ||
6e636a0a ÁFR |
75 | static void bcm6358_led_set(struct led_classdev *led_cdev, |
76 | enum led_brightness value) | |
589fca16 | 77 | { |
6e636a0a ÁFR |
78 | struct bcm6358_led *led = |
79 | container_of(led_cdev, struct bcm6358_led, cdev); | |
80 | unsigned long flags, val; | |
589fca16 | 81 | |
6e636a0a | 82 | spin_lock_irqsave(led->lock, flags); |
589fca16 | 83 | bcm6358_led_busy(led->mem); |
589fca16 ÁFR |
84 | val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); |
85 | if ((led->active_low && value == LED_OFF) || | |
86 | (!led->active_low && value != LED_OFF)) | |
87 | val |= BIT(led->pin); | |
88 | else | |
89 | val &= ~(BIT(led->pin)); | |
90 | bcm6358_led_write(led->mem + BCM6358_REG_MODE, val); | |
589fca16 ÁFR |
91 | spin_unlock_irqrestore(led->lock, flags); |
92 | } | |
93 | ||
94 | static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg, | |
95 | void __iomem *mem, spinlock_t *lock) | |
96 | { | |
97 | struct bcm6358_led *led; | |
589fca16 ÁFR |
98 | const char *state; |
99 | int rc; | |
100 | ||
101 | led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); | |
102 | if (!led) | |
103 | return -ENOMEM; | |
104 | ||
105 | led->pin = reg; | |
106 | led->mem = mem; | |
107 | led->lock = lock; | |
108 | ||
109 | if (of_property_read_bool(nc, "active-low")) | |
110 | led->active_low = true; | |
111 | ||
112 | led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name; | |
113 | led->cdev.default_trigger = of_get_property(nc, | |
114 | "linux,default-trigger", | |
115 | NULL); | |
116 | ||
589fca16 ÁFR |
117 | if (!of_property_read_string(nc, "default-state", &state)) { |
118 | if (!strcmp(state, "on")) { | |
119 | led->cdev.brightness = LED_FULL; | |
120 | } else if (!strcmp(state, "keep")) { | |
121 | unsigned long val; | |
589fca16 ÁFR |
122 | val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); |
123 | val &= BIT(led->pin); | |
124 | if ((led->active_low && !val) || | |
125 | (!led->active_low && val)) | |
126 | led->cdev.brightness = LED_FULL; | |
127 | else | |
128 | led->cdev.brightness = LED_OFF; | |
129 | } else { | |
130 | led->cdev.brightness = LED_OFF; | |
131 | } | |
132 | } else { | |
133 | led->cdev.brightness = LED_OFF; | |
134 | } | |
589fca16 | 135 | |
42273caa ÁFR |
136 | bcm6358_led_set(&led->cdev, led->cdev.brightness); |
137 | ||
589fca16 ÁFR |
138 | led->cdev.brightness_set = bcm6358_led_set; |
139 | ||
140 | rc = led_classdev_register(dev, &led->cdev); | |
141 | if (rc < 0) | |
142 | return rc; | |
143 | ||
144 | dev_dbg(dev, "registered LED %s\n", led->cdev.name); | |
145 | ||
146 | return 0; | |
147 | } | |
148 | ||
149 | static int bcm6358_leds_probe(struct platform_device *pdev) | |
150 | { | |
151 | struct device *dev = &pdev->dev; | |
152 | struct device_node *np = pdev->dev.of_node; | |
153 | struct device_node *child; | |
154 | struct resource *mem_r; | |
155 | void __iomem *mem; | |
156 | spinlock_t *lock; /* memory lock */ | |
157 | unsigned long val; | |
158 | u32 clk_div; | |
159 | ||
160 | mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
161 | if (!mem_r) | |
162 | return -EINVAL; | |
163 | ||
164 | mem = devm_ioremap_resource(dev, mem_r); | |
165 | if (IS_ERR(mem)) | |
166 | return PTR_ERR(mem); | |
167 | ||
168 | lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); | |
169 | if (!lock) | |
170 | return -ENOMEM; | |
171 | ||
172 | spin_lock_init(lock); | |
173 | ||
174 | val = bcm6358_led_busy(mem); | |
175 | val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK); | |
176 | if (of_property_read_bool(np, "brcm,clk-dat-low")) | |
177 | val |= BCM6358_SLED_POLARITY; | |
178 | of_property_read_u32(np, "brcm,clk-div", &clk_div); | |
179 | switch (clk_div) { | |
180 | case 8: | |
181 | val |= BCM6358_SLED_CLKDIV_8; | |
182 | break; | |
183 | case 4: | |
184 | val |= BCM6358_SLED_CLKDIV_4; | |
185 | break; | |
186 | case 2: | |
187 | val |= BCM6358_SLED_CLKDIV_2; | |
188 | break; | |
189 | default: | |
190 | val |= BCM6358_SLED_CLKDIV_1; | |
191 | break; | |
192 | } | |
193 | bcm6358_led_write(mem + BCM6358_REG_CTRL, val); | |
194 | ||
195 | for_each_available_child_of_node(np, child) { | |
196 | int rc; | |
197 | u32 reg; | |
198 | ||
199 | if (of_property_read_u32(child, "reg", ®)) | |
200 | continue; | |
201 | ||
202 | if (reg >= BCM6358_SLED_MAX_COUNT) { | |
203 | dev_err(dev, "invalid LED (%u >= %d)\n", reg, | |
204 | BCM6358_SLED_MAX_COUNT); | |
205 | continue; | |
206 | } | |
207 | ||
208 | rc = bcm6358_led(dev, child, reg, mem, lock); | |
4b6ba5e2 JL |
209 | if (rc < 0) { |
210 | of_node_put(child); | |
589fca16 | 211 | return rc; |
4b6ba5e2 | 212 | } |
589fca16 ÁFR |
213 | } |
214 | ||
215 | return 0; | |
216 | } | |
217 | ||
218 | static const struct of_device_id bcm6358_leds_of_match[] = { | |
219 | { .compatible = "brcm,bcm6358-leds", }, | |
220 | { }, | |
221 | }; | |
01736f07 | 222 | MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match); |
589fca16 ÁFR |
223 | |
224 | static struct platform_driver bcm6358_leds_driver = { | |
225 | .probe = bcm6358_leds_probe, | |
226 | .driver = { | |
227 | .name = "leds-bcm6358", | |
228 | .of_match_table = bcm6358_leds_of_match, | |
229 | }, | |
230 | }; | |
231 | ||
232 | module_platform_driver(bcm6358_leds_driver); | |
233 | ||
234 | MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); | |
235 | MODULE_DESCRIPTION("LED driver for BCM6358 controllers"); | |
236 | MODULE_LICENSE("GPL v2"); | |
237 | MODULE_ALIAS("platform:leds-bcm6358"); |