blob: 9509514898ac50cea84ece828e0719dc26bca377 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12#define pr_fmt(fmt) "%s: " fmt, __func__
13
14#include <linux/kernel.h>
15#include <linux/init.h>
16#include <linux/platform_device.h>
17#include <linux/leds.h>
18#include <linux/io.h>
19#include <linux/slab.h>
20#include <linux/err.h>
21#include <linux/delay.h>
22#include <linux/pm.h>
23#include <linux/pm_runtime.h>
24
25#ifdef CONFIG_HAS_EARLYSUSPEND
26#include <linux/earlysuspend.h>
27
28/* Early-suspend level */
29#define LED_SUSPEND_LEVEL 1
30#endif
31
32#define PDM_DUTY_MAXVAL BIT(16)
33#define PDM_DUTY_REFVAL BIT(15)
34
35struct pdm_led_data {
36 struct led_classdev cdev;
37 void __iomem *perph_base;
38 int pdm_offset;
39#ifdef CONFIG_HAS_EARLYSUSPEND
40 struct early_suspend early_suspend;
41#endif
42};
43
44static void msm_led_brightness_set_percent(struct pdm_led_data *led,
45 int duty_per)
46{
47 u16 duty_val;
48
49 duty_val = PDM_DUTY_REFVAL - ((PDM_DUTY_MAXVAL * duty_per) / 100);
50
51 if (!duty_per)
52 duty_val--;
53
54 writel_relaxed(duty_val, led->perph_base + led->pdm_offset);
55}
56
57static void msm_led_brightness_set(struct led_classdev *led_cdev,
58 enum led_brightness value)
59{
60 struct pdm_led_data *led =
61 container_of(led_cdev, struct pdm_led_data, cdev);
62
63 msm_led_brightness_set_percent(led, (value * 100) / LED_FULL);
64}
65
66#ifdef CONFIG_PM_SLEEP
67static int msm_led_pdm_suspend(struct device *dev)
68{
69 struct pdm_led_data *led = dev_get_drvdata(dev);
70
71 msm_led_brightness_set_percent(led, 0);
72
73 return 0;
74}
75
76#ifdef CONFIG_HAS_EARLYSUSPEND
77static void msm_led_pdm_early_suspend(struct early_suspend *h)
78{
79 struct pdm_led_data *led = container_of(h,
80 struct pdm_led_data, early_suspend);
81
82 msm_led_pdm_suspend(led->cdev.dev->parent);
83}
84
85#endif
86
87static const struct dev_pm_ops msm_led_pdm_pm_ops = {
88#ifndef CONFIG_HAS_EARLYSUSPEND
89 .suspend = msm_led_pdm_suspend,
90#endif
91};
92#endif
93
94static int __devinit msm_pdm_led_probe(struct platform_device *pdev)
95{
96 const struct led_info *pdata = pdev->dev.platform_data;
97 struct pdm_led_data *led;
98 struct resource *res, *ioregion;
99 u32 tcxo_pdm_ctl;
100 int rc;
101
102 if (!pdata) {
103 pr_err("platform data is invalid\n");
104 return -EINVAL;
105 }
106
107 if (pdev->id > 2) {
108 pr_err("pdm id is invalid\n");
109 return -EINVAL;
110 }
111
112 led = kzalloc(sizeof(struct pdm_led_data), GFP_KERNEL);
113 if (!led)
114 return -ENOMEM;
115
116 /* Enable runtime PM ops, start in ACTIVE mode */
117 rc = pm_runtime_set_active(&pdev->dev);
118 if (rc < 0)
119 dev_dbg(&pdev->dev, "unable to set runtime pm state\n");
120 pm_runtime_enable(&pdev->dev);
121
122 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
123 if (!res) {
124 pr_err("get resource failed\n");
125 rc = -EINVAL;
126 goto err_get_res;
127 }
128
129 ioregion = request_mem_region(res->start, resource_size(res),
130 pdev->name);
131 if (!ioregion) {
132 pr_err("request for mem region failed\n");
133 rc = -ENOMEM;
134 goto err_get_res;
135 }
136
137 led->perph_base = ioremap(res->start, resource_size(res));
138 if (!led->perph_base) {
139 pr_err("ioremap failed\n");
140 rc = -ENOMEM;
141 goto err_ioremap;
142 }
143
144 /* Pulse Density Modulation(PDM) ids start with 0 and
145 * every PDM register takes 4 bytes
146 */
147 led->pdm_offset = ((pdev->id) + 1) * 4;
148
149 /* program tcxo_pdm_ctl register to enable pdm*/
150 tcxo_pdm_ctl = readl_relaxed(led->perph_base);
151 tcxo_pdm_ctl |= (1 << pdev->id);
152 writel_relaxed(tcxo_pdm_ctl, led->perph_base);
153
154 /* Start with LED in off state */
155 msm_led_brightness_set_percent(led, 0);
156
157 led->cdev.brightness_set = msm_led_brightness_set;
158 led->cdev.name = pdata->name ? : "leds-msm-pdm";
159
160 rc = led_classdev_register(&pdev->dev, &led->cdev);
161 if (rc) {
162 pr_err("led class registration failed\n");
163 goto err_led_reg;
164 }
165
166#ifdef CONFIG_HAS_EARLYSUSPEND
167 led->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN +
168 LED_SUSPEND_LEVEL;
169 led->early_suspend.suspend = msm_led_pdm_early_suspend;
170 register_early_suspend(&led->early_suspend);
171#endif
172
173 platform_set_drvdata(pdev, led);
174 return 0;
175
176err_led_reg:
177 iounmap(led->perph_base);
178err_ioremap:
179 release_mem_region(res->start, resource_size(res));
180err_get_res:
181 pm_runtime_set_suspended(&pdev->dev);
182 pm_runtime_disable(&pdev->dev);
183 kfree(led);
184 return rc;
185}
186
187static int __devexit msm_pdm_led_remove(struct platform_device *pdev)
188{
189 struct pdm_led_data *led = platform_get_drvdata(pdev);
190 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
191
192#ifdef CONFIG_HAS_EARLYSUSPEND
193 unregister_early_suspend(&led->early_suspend);
194#endif
195 pm_runtime_set_suspended(&pdev->dev);
196 pm_runtime_disable(&pdev->dev);
197
198 led_classdev_unregister(&led->cdev);
199 msm_led_brightness_set_percent(led, 0);
200 iounmap(led->perph_base);
201 release_mem_region(res->start, resource_size(res));
202 kfree(led);
203
204 return 0;
205}
206
207static struct platform_driver msm_pdm_led_driver = {
208 .probe = msm_pdm_led_probe,
209 .remove = __devexit_p(msm_pdm_led_remove),
210 .driver = {
211 .name = "leds-msm-pdm",
212 .owner = THIS_MODULE,
213#ifdef CONFIG_PM_SLEEP
214 .pm = &msm_led_pdm_pm_ops,
215#endif
216 },
217};
218
219static int __init msm_pdm_led_init(void)
220{
221 return platform_driver_register(&msm_pdm_led_driver);
222}
223module_init(msm_pdm_led_init);
224
225static void __exit msm_pdm_led_exit(void)
226{
227 platform_driver_unregister(&msm_pdm_led_driver);
228}
229module_exit(msm_pdm_led_exit);
230
231MODULE_DESCRIPTION("MSM PDM LEDs driver");
232MODULE_LICENSE("GPL v2");
233MODULE_ALIAS("platform:leds-msm-pdm");