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