aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMagnus Damm <damm@opensource.se>2011-10-31 20:11:55 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2011-10-31 20:30:54 -0400
commitf59b6f9f323ff1b4567a69f9063cdd8bb57805e6 (patch)
tree5f881aa4e699dbed67455422611038a3d86477d3
parent1bff3a2093161d1b982a0958e4eb194bf3c8ce47 (diff)
leds: Renesas TPU LED driver
Add V2 of the LED driver for a single timer channel for the TPU hardware block commonly found in Renesas SoCs. The driver has been written with optimal Power Management in mind, so to save power the LED is driven as a regular GPIO pin in case of maximum brightness and power off which allows the TPU hardware to be idle and which in turn allows the clocks to be stopped and the power domain to be turned off transparently. Any other brightness level requires use of the TPU hardware in PWM mode. TPU hardware device clocks and power are managed through Runtime PM. System suspend and resume is known to be working - during suspend the LED is set to off by the generic LED code. The TPU hardware timer is equipeed with a 16-bit counter together with an up-to-divide-by-64 prescaler which makes the hardware suitable for brightness control. Hardware blink is unsupported. The LED PWM waveform has been verified with a Fluke 123 Scope meter on a sh7372 Mackerel board. Tested with experimental sh7372 A3SP power domain patches. Platform device bind/unbind tested ok. V2 has been tested on the DS2 LED of the sh73a0-based AG5EVM. [axel.lin@gmail.com: include linux/module.h] Signed-off-by: Magnus Damm <damm@opensource.se> Cc: Paul Mundt <lethal@linux-sh.org> Cc: Richard Purdie <rpurdie@rpsys.net> Cc: Grant Likely <grant.likely@secretlab.ca> Signed-off-by: Axel Lin <axel.lin@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--drivers/leds/Kconfig12
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-renesas-tpu.c344
-rw-r--r--include/linux/leds-renesas-tpu.h14
4 files changed, 371 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index dc7caaddecf4..ff203a421863 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -375,6 +375,18 @@ config LEDS_ASIC3
375 cannot be used. This driver supports hardware blinking with an on+off 375 cannot be used. This driver supports hardware blinking with an on+off
376 period from 62ms to 125s. Say Y to enable LEDs on the HP iPAQ hx4700. 376 period from 62ms to 125s. Say Y to enable LEDs on the HP iPAQ hx4700.
377 377
378config LEDS_RENESAS_TPU
379 bool "LED support for Renesas TPU"
380 depends on LEDS_CLASS && HAVE_CLK && GENERIC_GPIO
381 help
382 This option enables build of the LED TPU platform driver,
383 suitable to drive any TPU channel on newer Renesas SoCs.
384 The driver controls the GPIO pin connected to the LED via
385 the GPIO framework and expects the LED to be connected to
386 a pin that can be driven in both GPIO mode and using TPU
387 pin function. The latter to support brightness control.
388 Brightness control is supported but hardware blinking is not.
389
378config LEDS_TRIGGERS 390config LEDS_TRIGGERS
379 bool "LED Trigger support" 391 bool "LED Trigger support"
380 depends on LEDS_CLASS 392 depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index a0a1b89d78a8..e4f6bf568880 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
42obj-$(CONFIG_LEDS_NS2) += leds-ns2.o 42obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
43obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o 43obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
44obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o 44obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
45obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o
45 46
46# LED SPI Drivers 47# LED SPI Drivers
47obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o 48obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
diff --git a/drivers/leds/leds-renesas-tpu.c b/drivers/leds/leds-renesas-tpu.c
new file mode 100644
index 000000000000..d9a7d72a7569
--- /dev/null
+++ b/drivers/leds/leds-renesas-tpu.c
@@ -0,0 +1,344 @@
1/*
2 * LED control using Renesas TPU
3 *
4 * Copyright (C) 2011 Magnus Damm
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20#include <linux/module.h>
21#include <linux/init.h>
22#include <linux/platform_device.h>
23#include <linux/spinlock.h>
24#include <linux/printk.h>
25#include <linux/ioport.h>
26#include <linux/io.h>
27#include <linux/clk.h>
28#include <linux/leds.h>
29#include <linux/leds-renesas-tpu.h>
30#include <linux/gpio.h>
31#include <linux/err.h>
32#include <linux/slab.h>
33#include <linux/pm_runtime.h>
34
35enum r_tpu_pin { R_TPU_PIN_UNUSED, R_TPU_PIN_GPIO, R_TPU_PIN_GPIO_FN };
36enum r_tpu_timer { R_TPU_TIMER_UNUSED, R_TPU_TIMER_ON };
37
38struct r_tpu_priv {
39 struct led_classdev ldev;
40 void __iomem *mapbase;
41 struct clk *clk;
42 struct platform_device *pdev;
43 enum r_tpu_pin pin_state;
44 enum r_tpu_timer timer_state;
45 unsigned long min_rate;
46 unsigned int refresh_rate;
47};
48
49static DEFINE_SPINLOCK(r_tpu_lock);
50
51#define TSTR -1 /* Timer start register (shared register) */
52#define TCR 0 /* Timer control register (+0x00) */
53#define TMDR 1 /* Timer mode register (+0x04) */
54#define TIOR 2 /* Timer I/O control register (+0x08) */
55#define TIER 3 /* Timer interrupt enable register (+0x0c) */
56#define TSR 4 /* Timer status register (+0x10) */
57#define TCNT 5 /* Timer counter (+0x14) */
58#define TGRA 6 /* Timer general register A (+0x18) */
59#define TGRB 7 /* Timer general register B (+0x1c) */
60#define TGRC 8 /* Timer general register C (+0x20) */
61#define TGRD 9 /* Timer general register D (+0x24) */
62
63static inline unsigned short r_tpu_read(struct r_tpu_priv *p, int reg_nr)
64{
65 struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
66 void __iomem *base = p->mapbase;
67 unsigned long offs = reg_nr << 2;
68
69 if (reg_nr == TSTR)
70 return ioread16(base - cfg->channel_offset);
71
72 return ioread16(base + offs);
73}
74
75static inline void r_tpu_write(struct r_tpu_priv *p, int reg_nr,
76 unsigned short value)
77{
78 struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
79 void __iomem *base = p->mapbase;
80 unsigned long offs = reg_nr << 2;
81
82 if (reg_nr == TSTR) {
83 iowrite16(value, base - cfg->channel_offset);
84 return;
85 }
86
87 iowrite16(value, base + offs);
88}
89
90static void r_tpu_start_stop_ch(struct r_tpu_priv *p, int start)
91{
92 struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
93 unsigned long flags, value;
94
95 /* start stop register shared by multiple timer channels */
96 spin_lock_irqsave(&r_tpu_lock, flags);
97 value = r_tpu_read(p, TSTR);
98
99 if (start)
100 value |= 1 << cfg->timer_bit;
101 else
102 value &= ~(1 << cfg->timer_bit);
103
104 r_tpu_write(p, TSTR, value);
105 spin_unlock_irqrestore(&r_tpu_lock, flags);
106}
107
108static int r_tpu_enable(struct r_tpu_priv *p, enum led_brightness brightness)
109{
110 struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
111 int prescaler[] = { 1, 4, 16, 64 };
112 int k, ret;
113 unsigned long rate, tmp;
114
115 if (p->timer_state == R_TPU_TIMER_ON)
116 return 0;
117
118 /* wake up device and enable clock */
119 pm_runtime_get_sync(&p->pdev->dev);
120 ret = clk_enable(p->clk);
121 if (ret) {
122 dev_err(&p->pdev->dev, "cannot enable clock\n");
123 return ret;
124 }
125
126 /* make sure channel is disabled */
127 r_tpu_start_stop_ch(p, 0);
128
129 /* get clock rate after enabling it */
130 rate = clk_get_rate(p->clk);
131
132 /* pick the lowest acceptable rate */
133 for (k = 0; k < ARRAY_SIZE(prescaler); k++)
134 if ((rate / prescaler[k]) < p->min_rate)
135 break;
136
137 if (!k) {
138 dev_err(&p->pdev->dev, "clock rate mismatch\n");
139 goto err0;
140 }
141 dev_dbg(&p->pdev->dev, "rate = %lu, prescaler %u\n",
142 rate, prescaler[k - 1]);
143
144 /* clear TCNT on TGRB match, count on rising edge, set prescaler */
145 r_tpu_write(p, TCR, 0x0040 | (k - 1));
146
147 /* output 0 until TGRA, output 1 until TGRB */
148 r_tpu_write(p, TIOR, 0x0002);
149
150 rate /= prescaler[k - 1] * p->refresh_rate;
151 r_tpu_write(p, TGRB, rate);
152 dev_dbg(&p->pdev->dev, "TRGB = 0x%04lx\n", rate);
153
154 tmp = (cfg->max_brightness - brightness) * rate;
155 r_tpu_write(p, TGRA, tmp / cfg->max_brightness);
156 dev_dbg(&p->pdev->dev, "TRGA = 0x%04lx\n", tmp / cfg->max_brightness);
157
158 /* PWM mode */
159 r_tpu_write(p, TMDR, 0x0002);
160
161 /* enable channel */
162 r_tpu_start_stop_ch(p, 1);
163
164 p->timer_state = R_TPU_TIMER_ON;
165 return 0;
166 err0:
167 clk_disable(p->clk);
168 pm_runtime_put_sync(&p->pdev->dev);
169 return -ENOTSUPP;
170}
171
172static void r_tpu_disable(struct r_tpu_priv *p)
173{
174 if (p->timer_state == R_TPU_TIMER_UNUSED)
175 return;
176
177 /* disable channel */
178 r_tpu_start_stop_ch(p, 0);
179
180 /* stop clock and mark device as idle */
181 clk_disable(p->clk);
182 pm_runtime_put_sync(&p->pdev->dev);
183
184 p->timer_state = R_TPU_TIMER_UNUSED;
185}
186
187static void r_tpu_set_pin(struct r_tpu_priv *p, enum r_tpu_pin new_state,
188 enum led_brightness brightness)
189{
190 struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
191
192 if (p->pin_state == new_state) {
193 if (p->pin_state == R_TPU_PIN_GPIO)
194 gpio_set_value(cfg->pin_gpio, brightness);
195 return;
196 }
197
198 if (p->pin_state == R_TPU_PIN_GPIO)
199 gpio_free(cfg->pin_gpio);
200
201 if (p->pin_state == R_TPU_PIN_GPIO_FN)
202 gpio_free(cfg->pin_gpio_fn);
203
204 if (new_state == R_TPU_PIN_GPIO) {
205 gpio_request(cfg->pin_gpio, cfg->name);
206 gpio_direction_output(cfg->pin_gpio, !!brightness);
207 }
208 if (new_state == R_TPU_PIN_GPIO_FN)
209 gpio_request(cfg->pin_gpio_fn, cfg->name);
210
211 p->pin_state = new_state;
212}
213
214static void r_tpu_set_brightness(struct led_classdev *ldev,
215 enum led_brightness brightness)
216{
217 struct r_tpu_priv *p = container_of(ldev, struct r_tpu_priv, ldev);
218
219 r_tpu_disable(p);
220
221 /* off and maximum are handled as GPIO pins, in between PWM */
222 if ((brightness == 0) || (brightness == ldev->max_brightness))
223 r_tpu_set_pin(p, R_TPU_PIN_GPIO, brightness);
224 else {
225 r_tpu_set_pin(p, R_TPU_PIN_GPIO_FN, 0);
226 r_tpu_enable(p, brightness);
227 }
228}
229
230static int __devinit r_tpu_probe(struct platform_device *pdev)
231{
232 struct led_renesas_tpu_config *cfg = pdev->dev.platform_data;
233 struct r_tpu_priv *p;
234 struct resource *res;
235 int ret = -ENXIO;
236
237 if (!cfg) {
238 dev_err(&pdev->dev, "missing platform data\n");
239 goto err0;
240 }
241
242 p = kzalloc(sizeof(*p), GFP_KERNEL);
243 if (p == NULL) {
244 dev_err(&pdev->dev, "failed to allocate driver data\n");
245 ret = -ENOMEM;
246 goto err0;
247 }
248
249 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
250 if (!res) {
251 dev_err(&pdev->dev, "failed to get I/O memory\n");
252 goto err1;
253 }
254
255 /* map memory, let mapbase point to our channel */
256 p->mapbase = ioremap_nocache(res->start, resource_size(res));
257 if (p->mapbase == NULL) {
258 dev_err(&pdev->dev, "failed to remap I/O memory\n");
259 goto err1;
260 }
261
262 /* get hold of clock */
263 p->clk = clk_get(&pdev->dev, NULL);
264 if (IS_ERR(p->clk)) {
265 dev_err(&pdev->dev, "cannot get clock\n");
266 ret = PTR_ERR(p->clk);
267 goto err2;
268 }
269
270 p->pdev = pdev;
271 p->pin_state = R_TPU_PIN_UNUSED;
272 p->timer_state = R_TPU_TIMER_UNUSED;
273 p->refresh_rate = cfg->refresh_rate ? cfg->refresh_rate : 100;
274 r_tpu_set_pin(p, R_TPU_PIN_GPIO, LED_OFF);
275 platform_set_drvdata(pdev, p);
276
277
278 p->ldev.name = cfg->name;
279 p->ldev.brightness = LED_OFF;
280 p->ldev.max_brightness = cfg->max_brightness;
281 p->ldev.brightness_set = r_tpu_set_brightness;
282 p->ldev.flags |= LED_CORE_SUSPENDRESUME;
283 ret = led_classdev_register(&pdev->dev, &p->ldev);
284 if (ret < 0)
285 goto err3;
286
287 /* max_brightness may be updated by the LED core code */
288 p->min_rate = p->ldev.max_brightness * p->refresh_rate;
289
290 pm_runtime_enable(&pdev->dev);
291 return 0;
292
293 err3:
294 r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF);
295 clk_put(p->clk);
296 err2:
297 iounmap(p->mapbase);
298 err1:
299 kfree(p);
300 err0:
301 return ret;
302}
303
304static int __devexit r_tpu_remove(struct platform_device *pdev)
305{
306 struct r_tpu_priv *p = platform_get_drvdata(pdev);
307
308 r_tpu_set_brightness(&p->ldev, LED_OFF);
309 led_classdev_unregister(&p->ldev);
310 r_tpu_disable(p);
311 r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF);
312
313 pm_runtime_disable(&pdev->dev);
314 clk_put(p->clk);
315
316 iounmap(p->mapbase);
317 kfree(p);
318 return 0;
319}
320
321static struct platform_driver r_tpu_device_driver = {
322 .probe = r_tpu_probe,
323 .remove = __devexit_p(r_tpu_remove),
324 .driver = {
325 .name = "leds-renesas-tpu",
326 }
327};
328
329static int __init r_tpu_init(void)
330{
331 return platform_driver_register(&r_tpu_device_driver);
332}
333
334static void __exit r_tpu_exit(void)
335{
336 platform_driver_unregister(&r_tpu_device_driver);
337}
338
339module_init(r_tpu_init);
340module_exit(r_tpu_exit);
341
342MODULE_AUTHOR("Magnus Damm");
343MODULE_DESCRIPTION("Renesas TPU LED Driver");
344MODULE_LICENSE("GPL v2");
diff --git a/include/linux/leds-renesas-tpu.h b/include/linux/leds-renesas-tpu.h
new file mode 100644
index 000000000000..055387086fc1
--- /dev/null
+++ b/include/linux/leds-renesas-tpu.h
@@ -0,0 +1,14 @@
1#ifndef __LEDS_RENESAS_TPU_H__
2#define __LEDS_RENESAS_TPU_H__
3
4struct led_renesas_tpu_config {
5 char *name;
6 unsigned pin_gpio_fn;
7 unsigned pin_gpio;
8 unsigned int channel_offset;
9 unsigned int timer_bit;
10 unsigned int max_brightness;
11 unsigned int refresh_rate;
12};
13
14#endif /* __LEDS_RENESAS_TPU_H__ */