]>
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 | { | |
e4e912a3 | 97 | struct led_init_data init_data = {}; |
589fca16 | 98 | struct bcm6358_led *led; |
589fca16 ÁFR |
99 | const char *state; |
100 | int rc; | |
101 | ||
102 | led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); | |
103 | if (!led) | |
104 | return -ENOMEM; | |
105 | ||
106 | led->pin = reg; | |
107 | led->mem = mem; | |
108 | led->lock = lock; | |
109 | ||
110 | if (of_property_read_bool(nc, "active-low")) | |
111 | led->active_low = true; | |
112 | ||
589fca16 ÁFR |
113 | if (!of_property_read_string(nc, "default-state", &state)) { |
114 | if (!strcmp(state, "on")) { | |
115 | led->cdev.brightness = LED_FULL; | |
116 | } else if (!strcmp(state, "keep")) { | |
117 | unsigned long val; | |
589fca16 ÁFR |
118 | val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); |
119 | val &= BIT(led->pin); | |
120 | if ((led->active_low && !val) || | |
121 | (!led->active_low && val)) | |
122 | led->cdev.brightness = LED_FULL; | |
123 | else | |
124 | led->cdev.brightness = LED_OFF; | |
125 | } else { | |
126 | led->cdev.brightness = LED_OFF; | |
127 | } | |
128 | } else { | |
129 | led->cdev.brightness = LED_OFF; | |
130 | } | |
589fca16 | 131 | |
42273caa ÁFR |
132 | bcm6358_led_set(&led->cdev, led->cdev.brightness); |
133 | ||
589fca16 | 134 | led->cdev.brightness_set = bcm6358_led_set; |
e4e912a3 | 135 | init_data.fwnode = of_fwnode_handle(nc); |
589fca16 | 136 | |
e4e912a3 | 137 | rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); |
589fca16 ÁFR |
138 | if (rc < 0) |
139 | return rc; | |
140 | ||
141 | dev_dbg(dev, "registered LED %s\n", led->cdev.name); | |
142 | ||
143 | return 0; | |
144 | } | |
145 | ||
146 | static int bcm6358_leds_probe(struct platform_device *pdev) | |
147 | { | |
148 | struct device *dev = &pdev->dev; | |
8853c95e | 149 | struct device_node *np = dev_of_node(&pdev->dev); |
589fca16 | 150 | struct device_node *child; |
589fca16 ÁFR |
151 | void __iomem *mem; |
152 | spinlock_t *lock; /* memory lock */ | |
153 | unsigned long val; | |
154 | u32 clk_div; | |
155 | ||
8b4423d6 | 156 | mem = devm_platform_ioremap_resource(pdev, 0); |
589fca16 ÁFR |
157 | if (IS_ERR(mem)) |
158 | return PTR_ERR(mem); | |
159 | ||
160 | lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); | |
161 | if (!lock) | |
162 | return -ENOMEM; | |
163 | ||
164 | spin_lock_init(lock); | |
165 | ||
166 | val = bcm6358_led_busy(mem); | |
167 | val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK); | |
168 | if (of_property_read_bool(np, "brcm,clk-dat-low")) | |
169 | val |= BCM6358_SLED_POLARITY; | |
170 | of_property_read_u32(np, "brcm,clk-div", &clk_div); | |
171 | switch (clk_div) { | |
172 | case 8: | |
173 | val |= BCM6358_SLED_CLKDIV_8; | |
174 | break; | |
175 | case 4: | |
176 | val |= BCM6358_SLED_CLKDIV_4; | |
177 | break; | |
178 | case 2: | |
179 | val |= BCM6358_SLED_CLKDIV_2; | |
180 | break; | |
181 | default: | |
182 | val |= BCM6358_SLED_CLKDIV_1; | |
183 | break; | |
184 | } | |
185 | bcm6358_led_write(mem + BCM6358_REG_CTRL, val); | |
186 | ||
187 | for_each_available_child_of_node(np, child) { | |
188 | int rc; | |
189 | u32 reg; | |
190 | ||
191 | if (of_property_read_u32(child, "reg", ®)) | |
192 | continue; | |
193 | ||
194 | if (reg >= BCM6358_SLED_MAX_COUNT) { | |
195 | dev_err(dev, "invalid LED (%u >= %d)\n", reg, | |
196 | BCM6358_SLED_MAX_COUNT); | |
197 | continue; | |
198 | } | |
199 | ||
200 | rc = bcm6358_led(dev, child, reg, mem, lock); | |
4b6ba5e2 JL |
201 | if (rc < 0) { |
202 | of_node_put(child); | |
589fca16 | 203 | return rc; |
4b6ba5e2 | 204 | } |
589fca16 ÁFR |
205 | } |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | static const struct of_device_id bcm6358_leds_of_match[] = { | |
211 | { .compatible = "brcm,bcm6358-leds", }, | |
212 | { }, | |
213 | }; | |
01736f07 | 214 | MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match); |
589fca16 ÁFR |
215 | |
216 | static struct platform_driver bcm6358_leds_driver = { | |
217 | .probe = bcm6358_leds_probe, | |
218 | .driver = { | |
219 | .name = "leds-bcm6358", | |
220 | .of_match_table = bcm6358_leds_of_match, | |
221 | }, | |
222 | }; | |
223 | ||
224 | module_platform_driver(bcm6358_leds_driver); | |
225 | ||
226 | MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); | |
227 | MODULE_DESCRIPTION("LED driver for BCM6358 controllers"); | |
228 | MODULE_LICENSE("GPL v2"); | |
229 | MODULE_ALIAS("platform:leds-bcm6358"); |