]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/power/imx6_usb_charger.c
d94a30226664f9649edaf4a567b4d973de0dedcb
[karo-tx-linux.git] / drivers / power / imx6_usb_charger.c
1 /*
2  * Copyright (C) 2013 Freescale Semiconductor, Inc. All Rights Reserved.
3  *
4  * The code contained herein is licensed under the GNU General Public
5  * License. You may obtain a copy of the GNU General Public License
6  * Version 2 or later at the following locations:
7  *
8  * http://www.opensource.org/licenses/gpl-license.html
9  * http://www.gnu.org/copyleft/gpl.html
10  */
11
12 #include <linux/delay.h>
13 #include <linux/device.h>
14 #include <linux/power/imx6_usb_charger.h>
15 #include <linux/regmap.h>
16
17 #define HW_ANADIG_REG_3P0_SET   (0x00000124)
18 #define HW_ANADIG_REG_3P0_CLR   (0x00000128)
19 #define BM_ANADIG_REG_3P0_ENABLE_ILIMIT 0x00000004
20 #define BM_ANADIG_REG_3P0_ENABLE_LINREG 0x00000001
21
22 #define HW_ANADIG_USB1_CHRG_DETECT_SET  (0x000001b4)
23 #define HW_ANADIG_USB1_CHRG_DETECT_CLR  (0x000001b8)
24
25 #define BM_ANADIG_USB1_CHRG_DETECT_EN_B 0x00100000
26 #define BM_ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B 0x00080000
27 #define BM_ANADIG_USB1_CHRG_DETECT_CHK_CONTACT 0x00040000
28
29 #define HW_ANADIG_USB1_VBUS_DET_STAT    (0x000001c0)
30
31 #define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID 0x00000008
32
33 #define HW_ANADIG_USB1_CHRG_DET_STAT    (0x000001d0)
34
35 #define BM_ANADIG_USB1_CHRG_DET_STAT_DM_STATE 0x00000004
36 #define BM_ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED 0x00000002
37 #define BM_ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT 0x00000001
38
39 static char *imx6_usb_charger_supplied_to[] = {
40         "imx6_usb_charger",
41 };
42
43 static enum power_supply_property imx6_usb_charger_power_props[] = {
44         POWER_SUPPLY_PROP_PRESENT,      /* Charger detected */
45         POWER_SUPPLY_PROP_ONLINE,       /* VBUS online */
46         POWER_SUPPLY_PROP_CURRENT_MAX,  /* Maximum current in mA */
47 };
48
49 static int imx6_usb_charger_get_property(struct power_supply *psy,
50                                 enum power_supply_property psp,
51                                 union power_supply_propval *val)
52 {
53         struct usb_charger *charger =
54                 container_of(psy, struct usb_charger, psy);
55
56         switch (psp) {
57         case POWER_SUPPLY_PROP_PRESENT:
58                 val->intval = charger->present;
59                 break;
60         case POWER_SUPPLY_PROP_ONLINE:
61                 val->intval = charger->online;
62                 break;
63         case POWER_SUPPLY_PROP_CURRENT_MAX:
64                 val->intval = charger->max_current;
65                 break;
66         default:
67                 return -EINVAL;
68         }
69         return 0;
70 }
71
72 static void disable_charger_detector(struct regmap *regmap)
73 {
74         regmap_write(regmap, HW_ANADIG_USB1_CHRG_DETECT_SET,
75                 BM_ANADIG_USB1_CHRG_DETECT_EN_B |
76                 BM_ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
77 }
78
79 static void disable_current_limiter(struct regmap *regmap)
80 {
81         /* Disable the vdd3p0 current limiter */
82         regmap_write(regmap, HW_ANADIG_REG_3P0_CLR,
83                         BM_ANADIG_REG_3P0_ENABLE_ILIMIT);
84 }
85
86 /* Return value if the charger is present */
87 static int imx6_usb_charger_detect(struct usb_charger *charger)
88 {
89         struct regmap *regmap = charger->anatop;
90         u32 val;
91         int i, data_pin_contact_count = 0;
92
93         /* Enable the vdd3p0 curret limiter */
94         regmap_write(regmap, HW_ANADIG_REG_3P0_SET,
95                         BM_ANADIG_REG_3P0_ENABLE_LINREG |
96                         BM_ANADIG_REG_3P0_ENABLE_ILIMIT);
97
98         /* check if vbus is valid */
99         regmap_read(regmap, HW_ANADIG_USB1_VBUS_DET_STAT, &val);
100         if (!(val & BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)) {
101                 dev_err(charger->dev, "vbus is error\n");
102                 disable_current_limiter(regmap);
103                 return -EINVAL;
104         }
105
106         /* Enable charger detector */
107         regmap_write(regmap, HW_ANADIG_USB1_CHRG_DETECT_CLR,
108                         BM_ANADIG_USB1_CHRG_DETECT_EN_B);
109         /*
110          * - Do not check whether a charger is connected to the USB port
111          * - Check whether the USB plug has been in contact with each other
112          */
113         regmap_write(regmap, HW_ANADIG_USB1_CHRG_DETECT_SET,
114                         BM_ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
115                         BM_ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
116
117         /* Check if plug is connected */
118         for (i = 0; i < 100; i = i + 1) {
119                 regmap_read(regmap, HW_ANADIG_USB1_CHRG_DET_STAT, &val);
120                 if (val & BM_ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) {
121                         if (data_pin_contact_count++ > 5)
122                         /* Data pin makes contact */
123                                 break;
124                 } else {
125                         msleep(20);
126                 }
127         }
128
129         if (i == 100) {
130                 dev_err(charger->dev,
131                         "VBUS is coming from a dedicated power supply.\n");
132                 disable_current_limiter(regmap);
133                 disable_charger_detector(regmap);
134                 return -ENXIO;
135         }
136
137         /*
138          * - Do check whether a charger is connected to the USB port
139          * - Do not Check whether the USB plug has been in contact with
140          * each other
141          */
142         regmap_write(regmap, HW_ANADIG_USB1_CHRG_DETECT_CLR,
143                         BM_ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
144                         BM_ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
145         msleep(45);
146
147         /* Check if it is a charger */
148         regmap_read(regmap, HW_ANADIG_USB1_CHRG_DET_STAT, &val);
149         if (!(val & BM_ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) {
150                 dev_dbg(charger->dev, "It is a stardard downstream port\n");
151                 charger->psy.type = POWER_SUPPLY_TYPE_USB;
152                 charger->max_current = 500;
153                 disable_charger_detector(regmap);
154         } else {
155                 /* It is a charger */
156                 disable_charger_detector(regmap);
157                 msleep(45);
158         }
159
160         disable_current_limiter(regmap);
161
162         return 0;
163 }
164
165 /*
166  * imx6_usb_vbus_connect - inform about VBUS connection
167  * @charger: the usb charger
168  *
169  * Inform the charger VBUS is connected, vbus detect supplier should call it.
170  * Besides, the USB device controller is expected to keep the dataline
171  * pullups disabled.
172  */
173 int imx6_usb_vbus_connect(struct usb_charger *charger)
174 {
175         int ret;
176
177         charger->online = 1;
178
179         mutex_lock(&charger->lock);
180
181         /* Start the 1st period charger detection. */
182         ret = imx6_usb_charger_detect(charger);
183         if (ret)
184                 dev_err(charger->dev,
185                                 "Error occurs during detection: %d\n",
186                                 ret);
187         else
188                 charger->present = 1;
189
190         mutex_unlock(&charger->lock);
191
192         return ret;
193 }
194 EXPORT_SYMBOL(imx6_usb_vbus_connect);
195
196 /*
197  * It must be called after dp is pulled up (from USB controller driver),
198  * That is used to differentiate DCP and CDP
199  */
200 int imx6_usb_charger_detect_post(struct usb_charger *charger)
201 {
202         struct regmap *regmap = charger->anatop;
203         int val;
204
205         mutex_lock(&charger->lock);
206
207         msleep(40);
208
209         regmap_read(regmap, HW_ANADIG_USB1_CHRG_DET_STAT, &val);
210         if (val & BM_ANADIG_USB1_CHRG_DET_STAT_DM_STATE) {
211                 dev_dbg(charger->dev, "It is a dedicate charging port\n");
212                 charger->psy.type = POWER_SUPPLY_TYPE_USB_DCP;
213                 charger->max_current = 1500;
214         } else {
215                 dev_dbg(charger->dev, "It is a charging downstream port\n");
216                 charger->psy.type = POWER_SUPPLY_TYPE_USB_CDP;
217                 charger->max_current = 900;
218         }
219
220         power_supply_changed(&charger->psy);
221
222         mutex_unlock(&charger->lock);
223
224         return 0;
225 }
226 EXPORT_SYMBOL(imx6_usb_charger_detect_post);
227
228 /*
229  * imx6_usb_vbus_disconnect - inform about VBUS disconnection
230  * @charger: the usb charger
231  *
232  * Inform the charger that VBUS is disconnected. The charging will be
233  * stopped and the charger properties cleared.
234  */
235 int imx6_usb_vbus_disconnect(struct usb_charger *charger)
236 {
237         charger->online = 0;
238         charger->present = 0;
239         charger->max_current = 0;
240         charger->psy.type = POWER_SUPPLY_TYPE_MAINS;
241
242         power_supply_changed(&charger->psy);
243
244         return 0;
245 }
246 EXPORT_SYMBOL(imx6_usb_vbus_disconnect);
247
248 /*
249  * imx6_usb_create_charger - create a USB charger
250  * @charger: the charger to be initialized
251  * @name: name for the power supply
252
253  * Registers a power supply for the charger. The USB Controller
254  * driver will call this after filling struct usb_charger.
255  */
256 int imx6_usb_create_charger(struct usb_charger *charger,
257                 const char *name)
258 {
259         struct power_supply     *psy = &charger->psy;
260
261         if (!charger->dev)
262                 return -EINVAL;
263
264         if (name)
265                 psy->name = name;
266         else
267                 psy->name = "imx6_usb_charger";
268
269         charger->bc = BATTERY_CHARGING_SPEC_1_2;
270         mutex_init(&charger->lock);
271
272         psy->type               = POWER_SUPPLY_TYPE_MAINS;
273         psy->properties         = imx6_usb_charger_power_props;
274         psy->num_properties     = ARRAY_SIZE(imx6_usb_charger_power_props);
275         psy->get_property       = imx6_usb_charger_get_property;
276         psy->supplied_to        = imx6_usb_charger_supplied_to;
277         psy->num_supplicants    = sizeof(imx6_usb_charger_supplied_to)
278                 / sizeof(char *);
279
280         return power_supply_register(charger->dev, psy);
281 }
282 EXPORT_SYMBOL(imx6_usb_create_charger);
283
284 /*
285  * imx6_usb_remove_charger - remove a USB charger
286  * @charger: the charger to be removed
287  *
288  * Unregister the chargers power supply.
289  */
290 void imx6_usb_remove_charger(struct usb_charger *charger)
291 {
292         power_supply_unregister(&charger->psy);
293 }
294 EXPORT_SYMBOL(imx6_usb_remove_charger);