1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (C) 2017 Álvaro Fernández Rojas <noltari@gmail.com>
4 */
5
6 #include <common.h>
7 #include <dm.h>
8 #include <errno.h>
9 #include <led.h>
10 #include <asm/io.h>
11 #include <dm/lists.h>
12
13 #define LEDS_MAX 32
14 #define LEDS_WAIT 100
15
16 /* LED Mode register */
17 #define LED_MODE_REG 0x0
18 #define LED_MODE_OFF 0
19 #define LED_MODE_ON 1
20 #define LED_MODE_MASK 1
21
22 /* LED Control register */
23 #define LED_CTRL_REG 0x4
24 #define LED_CTRL_CLK_MASK 0x3
25 #define LED_CTRL_CLK_1 0
26 #define LED_CTRL_CLK_2 1
27 #define LED_CTRL_CLK_4 2
28 #define LED_CTRL_CLK_8 3
29 #define LED_CTRL_POL_SHIFT 2
30 #define LED_CTRL_POL_MASK (1 << LED_CTRL_POL_SHIFT)
31 #define LED_CTRL_BUSY_SHIFT 3
32 #define LED_CTRL_BUSY_MASK (1 << LED_CTRL_BUSY_SHIFT)
33
34 struct bcm6358_led_priv {
35 void __iomem *regs;
36 uint8_t pin;
37 bool active_low;
38 };
39
bcm6358_led_busy(void __iomem * regs)40 static void bcm6358_led_busy(void __iomem *regs)
41 {
42 while (readl_be(regs + LED_CTRL_REG) & LED_CTRL_BUSY_MASK)
43 udelay(LEDS_WAIT);
44 }
45
bcm6358_led_get_mode(struct bcm6358_led_priv * priv)46 static unsigned long bcm6358_led_get_mode(struct bcm6358_led_priv *priv)
47 {
48 bcm6358_led_busy(priv->regs);
49
50 return (readl_be(priv->regs + LED_MODE_REG) >> priv->pin) &
51 LED_MODE_MASK;
52 }
53
bcm6358_led_set_mode(struct bcm6358_led_priv * priv,uint8_t mode)54 static int bcm6358_led_set_mode(struct bcm6358_led_priv *priv, uint8_t mode)
55 {
56 bcm6358_led_busy(priv->regs);
57
58 clrsetbits_be32(priv->regs + LED_MODE_REG,
59 (LED_MODE_MASK << priv->pin),
60 (mode << priv->pin));
61
62 return 0;
63 }
64
bcm6358_led_get_state(struct udevice * dev)65 static enum led_state_t bcm6358_led_get_state(struct udevice *dev)
66 {
67 struct bcm6358_led_priv *priv = dev_get_priv(dev);
68 enum led_state_t state = LEDST_OFF;
69
70 switch (bcm6358_led_get_mode(priv)) {
71 case LED_MODE_OFF:
72 state = (priv->active_low ? LEDST_ON : LEDST_OFF);
73 break;
74 case LED_MODE_ON:
75 state = (priv->active_low ? LEDST_OFF : LEDST_ON);
76 break;
77 }
78
79 return state;
80 }
81
bcm6358_led_set_state(struct udevice * dev,enum led_state_t state)82 static int bcm6358_led_set_state(struct udevice *dev, enum led_state_t state)
83 {
84 struct bcm6358_led_priv *priv = dev_get_priv(dev);
85 unsigned long mode;
86
87 switch (state) {
88 case LEDST_OFF:
89 mode = (priv->active_low ? LED_MODE_ON : LED_MODE_OFF);
90 break;
91 case LEDST_ON:
92 mode = (priv->active_low ? LED_MODE_OFF : LED_MODE_ON);
93 break;
94 case LEDST_TOGGLE:
95 if (bcm6358_led_get_state(dev) == LEDST_OFF)
96 return bcm6358_led_set_state(dev, LEDST_ON);
97 else
98 return bcm6358_led_set_state(dev, LEDST_OFF);
99 break;
100 default:
101 return -ENOSYS;
102 }
103
104 return bcm6358_led_set_mode(priv, mode);
105 }
106
107 static const struct led_ops bcm6358_led_ops = {
108 .get_state = bcm6358_led_get_state,
109 .set_state = bcm6358_led_set_state,
110 };
111
bcm6358_led_probe(struct udevice * dev)112 static int bcm6358_led_probe(struct udevice *dev)
113 {
114 struct led_uc_plat *uc_plat = dev_get_uclass_platdata(dev);
115
116 /* Top-level LED node */
117 if (!uc_plat->label) {
118 void __iomem *regs;
119 unsigned int clk_div;
120 u32 set_bits = 0;
121
122 regs = dev_remap_addr(dev);
123 if (!regs)
124 return -EINVAL;
125
126 if (dev_read_bool(dev, "brcm,clk-dat-low"))
127 set_bits |= LED_CTRL_POL_MASK;
128 clk_div = dev_read_u32_default(dev, "brcm,clk-div",
129 LED_CTRL_CLK_1);
130 switch (clk_div) {
131 case 8:
132 set_bits |= LED_CTRL_CLK_8;
133 break;
134 case 4:
135 set_bits |= LED_CTRL_CLK_4;
136 break;
137 case 2:
138 set_bits |= LED_CTRL_CLK_2;
139 break;
140 default:
141 set_bits |= LED_CTRL_CLK_1;
142 break;
143 }
144
145 bcm6358_led_busy(regs);
146 clrsetbits_be32(regs + LED_CTRL_REG,
147 LED_CTRL_POL_MASK | LED_CTRL_CLK_MASK,
148 set_bits);
149 } else {
150 struct bcm6358_led_priv *priv = dev_get_priv(dev);
151 unsigned int pin;
152
153 priv->regs = dev_remap_addr(dev);
154 if (!priv->regs)
155 return -EINVAL;
156
157 pin = dev_read_u32_default(dev, "reg", LEDS_MAX);
158 if (pin >= LEDS_MAX)
159 return -EINVAL;
160
161 priv->pin = pin;
162
163 if (dev_read_bool(dev, "active-low"))
164 priv->active_low = true;
165 }
166
167 return 0;
168 }
169
bcm6358_led_bind(struct udevice * parent)170 static int bcm6358_led_bind(struct udevice *parent)
171 {
172 ofnode node;
173
174 dev_for_each_subnode(node, parent) {
175 struct led_uc_plat *uc_plat;
176 struct udevice *dev;
177 const char *label;
178 int ret;
179
180 label = ofnode_read_string(node, "label");
181 if (!label) {
182 debug("%s: node %s has no label\n", __func__,
183 ofnode_get_name(node));
184 return -EINVAL;
185 }
186
187 ret = device_bind_driver_to_node(parent, "bcm6358-led",
188 ofnode_get_name(node),
189 node, &dev);
190 if (ret)
191 return ret;
192
193 uc_plat = dev_get_uclass_platdata(dev);
194 uc_plat->label = label;
195 }
196
197 return 0;
198 }
199
200 static const struct udevice_id bcm6358_led_ids[] = {
201 { .compatible = "brcm,bcm6358-leds" },
202 { /* sentinel */ }
203 };
204
205 U_BOOT_DRIVER(bcm6358_led) = {
206 .name = "bcm6358-led",
207 .id = UCLASS_LED,
208 .of_match = bcm6358_led_ids,
209 .bind = bcm6358_led_bind,
210 .probe = bcm6358_led_probe,
211 .priv_auto_alloc_size = sizeof(struct bcm6358_led_priv),
212 .ops = &bcm6358_led_ops,
213 };
214