Merge branch 'android-tegra' into android-tegra-moto
[firefly-linux-kernel-4.4.55.git] / drivers / leds / leds-ld-cpcap.c
1 /*
2  * Copyright (C) 2010 Motorola, Inc.
3  *
4  * This program is free dispware; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free dispware Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free dispware
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
16  * 02111-1307, USA
17  */
18
19 #include <linux/err.h>
20 #include <linux/leds.h>
21 #include <linux/leds-ld-cpcap.h>
22 #include <linux/platform_device.h>
23 #include <linux/slab.h>
24 #include <linux/workqueue.h>
25 #include <linux/regulator/consumer.h>
26 #include <linux/spi/cpcap.h>
27 #include <linux/spi/cpcap-regbits.h>
28
29 struct cpcap_led_data {
30         struct led_classdev cpcap_class_dev;
31         struct cpcap_device *cpcap;
32         struct cpcap_led *pdata;
33         struct regulator *regulator;
34         struct work_struct brightness_work;
35         enum led_brightness brightness;
36         int regulator_state;
37         short blink_val;
38 };
39
40 static void cpcap_set(struct led_classdev *led_cdev,
41                             enum led_brightness brightness)
42 {
43         struct cpcap_led_data *cpcap_led_data =
44                 container_of(led_cdev, struct cpcap_led_data,
45                 cpcap_class_dev);
46
47         if (brightness > 255)
48                 brightness = 255;
49
50         cpcap_led_data->brightness = brightness;
51         schedule_work(&cpcap_led_data->brightness_work);
52 }
53 EXPORT_SYMBOL(cpcap_set);
54
55 static int cpcap_led_blink(struct led_classdev *led_cdev,
56                                unsigned long *delay_on,
57                                unsigned long *delay_off)
58 {
59         struct cpcap_led_data *info =
60                 container_of(led_cdev, struct cpcap_led_data,
61                          cpcap_class_dev);
62
63         info->blink_val = *delay_on;
64
65         if (info->pdata->blink_able) {
66                 if(info->blink_val) {
67                         cpcap_uc_start(info->cpcap, CPCAP_MACRO_6);
68                 } else {
69                         cpcap_uc_stop(info->cpcap, CPCAP_MACRO_6);
70                         schedule_work(&info->brightness_work);
71                 }
72         }
73
74         return 0;
75 }
76
77 static void cpcap_brightness_work(struct work_struct *work)
78 {
79         int cpcap_status = 0;
80         unsigned short brightness = 0;
81
82         struct cpcap_led_data *cpcap_led_data =
83             container_of(work, struct cpcap_led_data, brightness_work);
84
85         brightness = cpcap_led_data->brightness;
86
87         if (brightness > 0) {
88                 brightness = (cpcap_led_data->pdata->cpcap_reg_period |
89                                  cpcap_led_data->pdata->cpcap_reg_duty_cycle |
90                                  cpcap_led_data->pdata->cpcap_reg_current |
91                                  0x01);
92
93                 if ((cpcap_led_data->regulator) &&
94                     (cpcap_led_data->regulator_state == 0)) {
95                         regulator_enable(cpcap_led_data->regulator);
96                         cpcap_led_data->regulator_state = 1;
97                 }
98
99                 cpcap_status = cpcap_regacc_write(cpcap_led_data->cpcap,
100                                 cpcap_led_data->pdata->cpcap_register,
101                                 brightness,
102                                 cpcap_led_data->pdata->cpcap_reg_mask);
103
104                 if (cpcap_status < 0)
105                         pr_err("%s: Writing to the register failed for %i\n",
106                                __func__, cpcap_status);
107
108         } else {
109                 if ((cpcap_led_data->regulator) &&
110                     (cpcap_led_data->regulator_state == 1)) {
111                         regulator_disable(cpcap_led_data->regulator);
112                         cpcap_led_data->regulator_state = 0;
113                 }
114                 /* Due to a HW issue turn off the current then
115                 turn off the duty cycle */
116                 brightness = 0x01;
117                 cpcap_status = cpcap_regacc_write(cpcap_led_data->cpcap,
118                                 cpcap_led_data->pdata->cpcap_register,
119                                 brightness,
120                                 cpcap_led_data->pdata->cpcap_reg_mask);
121
122
123                 brightness = 0x00;
124                 cpcap_status = cpcap_regacc_write(cpcap_led_data->cpcap,
125                                 cpcap_led_data->pdata->cpcap_register,
126                                 brightness,
127                                 cpcap_led_data->pdata->cpcap_reg_mask);
128
129
130                 if (cpcap_status < 0)
131                         pr_err("%s: Writing to the register failed for %i\n",
132                                __func__, cpcap_status);
133
134         }
135 }
136
137 static int cpcap_probe(struct platform_device *pdev)
138 {
139         int ret = 0;
140         struct cpcap_led_data *info;
141
142         if (pdev == NULL) {
143                 pr_err("%s: platform data required\n", __func__);
144                 return -ENODEV;
145
146         }
147         info = kzalloc(sizeof(struct cpcap_led_data), GFP_KERNEL);
148         if (info == NULL) {
149                 ret = -ENOMEM;
150                 return ret;
151         }
152
153         info->pdata = pdev->dev.platform_data;
154         info->cpcap = platform_get_drvdata(pdev);
155         platform_set_drvdata(pdev, info);
156
157         if (info->pdata->led_regulator != NULL) {
158                 info->regulator = regulator_get(&pdev->dev,
159                                 info->pdata->led_regulator);
160                 if (IS_ERR(info->regulator)) {
161                         pr_err("%s: Cannot get %s regulator\n",
162                                 __func__, info->pdata->led_regulator);
163                         ret = PTR_ERR(info->regulator);
164                         goto exit_request_reg_failed;
165
166                 }
167         }
168         info->regulator_state = 0;
169
170         info->cpcap_class_dev.name = info->pdata->class_name;
171         info->cpcap_class_dev.brightness_set = cpcap_set;
172         info->cpcap_class_dev.blink_set = cpcap_led_blink;
173         info->cpcap_class_dev.brightness = LED_OFF;
174         info->cpcap_class_dev.max_brightness = 255;
175         if (info->pdata->blink_able)
176                 info->cpcap_class_dev.default_trigger = "timer";
177
178         ret = led_classdev_register(&pdev->dev, &info->cpcap_class_dev);
179         if (ret < 0) {
180                 pr_err("%s:Register %s class failed\n",
181                         __func__, info->cpcap_class_dev.name);
182                 goto err_reg_button_class_failed;
183         }
184
185         INIT_WORK(&info->brightness_work, cpcap_brightness_work);
186
187         return ret;
188
189 err_reg_button_class_failed:
190         if (info->regulator)
191                 regulator_put(info->regulator);
192 exit_request_reg_failed:
193         kfree(info);
194         return ret;
195 }
196
197 static int cpcap_remove(struct platform_device *pdev)
198 {
199         struct cpcap_led_data *info = platform_get_drvdata(pdev);
200
201         if (info->regulator)
202                 regulator_put(info->regulator);
203         led_classdev_unregister(&info->cpcap_class_dev);
204         return 0;
205 }
206
207 static struct platform_driver ld_cpcap_driver = {
208         .probe = cpcap_probe,
209         .remove = cpcap_remove,
210         .driver = {
211                    .name = LD_CPCAP_LED_DRV,
212         },
213 };
214
215 static int __init led_cpcap_init(void)
216 {
217         return platform_driver_register(&ld_cpcap_driver);
218 }
219
220 static void __exit led_cpcap_exit(void)
221 {
222         platform_driver_unregister(&ld_cpcap_driver);
223 }
224
225 module_init(led_cpcap_init);
226 module_exit(led_cpcap_exit);
227
228 MODULE_DESCRIPTION("CPCAP Lighting driver");
229 MODULE_AUTHOR("Dan Murphy <D.Murphy@Motorola.com>");
230 MODULE_LICENSE("GPL");