leds: Create generic CPCAP led driver
[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 };
38
39 static void cpcap_set(struct led_classdev *led_cdev,
40                             enum led_brightness brightness)
41 {
42         struct cpcap_led_data *cpcap_led_data =
43                 container_of(led_cdev, struct cpcap_led_data,
44                 cpcap_class_dev);
45
46         if (brightness > 255)
47                 brightness = 255;
48
49         cpcap_led_data->brightness = brightness;
50         schedule_work(&cpcap_led_data->brightness_work);
51 }
52 EXPORT_SYMBOL(cpcap_set);
53
54 static void cpcap_brightness_work(struct work_struct *work)
55 {
56         int cpcap_status = 0;
57         unsigned short brightness = 0;
58
59         struct cpcap_led_data *cpcap_led_data =
60             container_of(work, struct cpcap_led_data, brightness_work);
61
62         brightness = cpcap_led_data->brightness;
63
64         if (brightness > 0) {
65                 brightness = (cpcap_led_data->pdata->cpcap_duty_cycle |
66                         cpcap_led_data->pdata->cpcap_current | 0x01);
67
68                 if ((cpcap_led_data->regulator) &&
69                     (cpcap_led_data->regulator_state == 0)) {
70                         regulator_enable(cpcap_led_data->regulator);
71                         cpcap_led_data->regulator_state = 1;
72                 }
73
74                 cpcap_status = cpcap_regacc_write(cpcap_led_data->cpcap,
75                                 cpcap_led_data->pdata->cpcap_register,
76                                 brightness,
77                                 cpcap_led_data->pdata->cpcap_mask);
78
79                 if (cpcap_status < 0)
80                         pr_err("%s: Writing to the register failed for %i\n",
81                                __func__, cpcap_status);
82
83         } else {
84                 if ((cpcap_led_data->regulator) &&
85                     (cpcap_led_data->regulator_state == 1)) {
86                         regulator_disable(cpcap_led_data->regulator);
87                         cpcap_led_data->regulator_state = 0;
88                 }
89                 /* Due to a HW issue turn off the current then
90                 turn off the duty cycle */
91                 brightness = 0x01;
92                 cpcap_status = cpcap_regacc_write(cpcap_led_data->cpcap,
93                                 cpcap_led_data->pdata->cpcap_register,
94                                 brightness,
95                                 cpcap_led_data->pdata->cpcap_mask);
96
97
98                 brightness = 0x00;
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_mask);
103
104
105                 if (cpcap_status < 0)
106                         pr_err("%s: Writing to the register failed for %i\n",
107                                __func__, cpcap_status);
108         }
109 }
110
111 static int cpcap_probe(struct platform_device *pdev)
112 {
113         int ret = 0;
114         struct cpcap_led_data *info;
115
116         if (pdev == NULL) {
117                 pr_err("%s: platform data required\n", __func__);
118                 return -ENODEV;
119
120         }
121         info = kzalloc(sizeof(struct cpcap_led_data), GFP_KERNEL);
122         if (info == NULL) {
123                 ret = -ENOMEM;
124                 return ret;
125         }
126
127         info->pdata = pdev->dev.platform_data;
128         info->cpcap = platform_get_drvdata(pdev);
129         platform_set_drvdata(pdev, info);
130
131         if (info->pdata->led_regulator != NULL) {
132                 info->regulator = regulator_get(&pdev->dev,
133                                 info->pdata->led_regulator);
134                 if (IS_ERR(info->regulator)) {
135                         pr_err("%s: Cannot get %s regulator\n",
136                                 __func__, info->pdata->led_regulator);
137                         ret = PTR_ERR(info->regulator);
138                         goto exit_request_reg_failed;
139
140                 }
141         }
142         info->regulator_state = 0;
143
144         info->cpcap_class_dev.name = info->pdata->class_name;
145         info->cpcap_class_dev.brightness_set = cpcap_set;
146         ret = led_classdev_register(&pdev->dev, &info->cpcap_class_dev);
147         if (ret < 0) {
148                 pr_err("%s:Register %s class failed\n",
149                         __func__, info->cpcap_class_dev.name);
150                 goto err_reg_button_class_failed;
151         }
152         INIT_WORK(&info->brightness_work, cpcap_brightness_work);
153         return ret;
154
155 err_reg_button_class_failed:
156         if (info->regulator)
157                 regulator_put(info->regulator);
158 exit_request_reg_failed:
159         kfree(info);
160         return ret;
161 }
162
163 static int cpcap_remove(struct platform_device *pdev)
164 {
165         struct cpcap_led_data *info = platform_get_drvdata(pdev);
166
167         if (info->regulator)
168                 regulator_put(info->regulator);
169
170         led_classdev_unregister(&info->cpcap_class_dev);
171         return 0;
172 }
173
174 static struct platform_driver ld_cpcap_driver = {
175         .probe = cpcap_probe,
176         .remove = cpcap_remove,
177         .driver = {
178                    .name = LD_CPCAP_LED_DRV,
179         },
180 };
181
182 static int __init led_cpcap_init(void)
183 {
184         return platform_driver_register(&ld_cpcap_driver);
185 }
186
187 static void __exit led_cpcap_exit(void)
188 {
189         platform_driver_unregister(&ld_cpcap_driver);
190 }
191
192 module_init(led_cpcap_init);
193 module_exit(led_cpcap_exit);
194
195 MODULE_DESCRIPTION("CPCAP Lighting driver");
196 MODULE_AUTHOR("Dan Murphy <D.Murphy@Motorola.com>");
197 MODULE_LICENSE("GPL");