diff options
Diffstat (limited to 'drivers/power/isp1704_charger.c')
-rw-r--r-- | drivers/power/isp1704_charger.c | 180 |
1 files changed, 142 insertions, 38 deletions
diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c index 87252e13e29c..10c7cc5fb5f9 100644 --- a/drivers/power/isp1704_charger.c +++ b/drivers/power/isp1704_charger.c | |||
@@ -59,11 +59,61 @@ struct isp1704_charger { | |||
59 | struct notifier_block nb; | 59 | struct notifier_block nb; |
60 | struct work_struct work; | 60 | struct work_struct work; |
61 | 61 | ||
62 | /* properties */ | ||
62 | char model[8]; | 63 | char model[8]; |
63 | unsigned present:1; | 64 | unsigned present:1; |
65 | unsigned online:1; | ||
66 | unsigned current_max; | ||
67 | |||
68 | /* temp storage variables */ | ||
69 | unsigned long event; | ||
70 | unsigned max_power; | ||
64 | }; | 71 | }; |
65 | 72 | ||
66 | /* | 73 | /* |
74 | * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB | ||
75 | * chargers). | ||
76 | * | ||
77 | * REVISIT: The method is defined in Battery Charging Specification and is | ||
78 | * applicable to any ULPI transceiver. Nothing isp170x specific here. | ||
79 | */ | ||
80 | static inline int isp1704_charger_type(struct isp1704_charger *isp) | ||
81 | { | ||
82 | u8 reg; | ||
83 | u8 func_ctrl; | ||
84 | u8 otg_ctrl; | ||
85 | int type = POWER_SUPPLY_TYPE_USB_DCP; | ||
86 | |||
87 | func_ctrl = otg_io_read(isp->otg, ULPI_FUNC_CTRL); | ||
88 | otg_ctrl = otg_io_read(isp->otg, ULPI_OTG_CTRL); | ||
89 | |||
90 | /* disable pulldowns */ | ||
91 | reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN; | ||
92 | otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), reg); | ||
93 | |||
94 | /* full speed */ | ||
95 | otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), | ||
96 | ULPI_FUNC_CTRL_XCVRSEL_MASK); | ||
97 | otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), | ||
98 | ULPI_FUNC_CTRL_FULL_SPEED); | ||
99 | |||
100 | /* Enable strong pull-up on DP (1.5K) and reset */ | ||
101 | reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; | ||
102 | otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), reg); | ||
103 | usleep_range(1000, 2000); | ||
104 | |||
105 | reg = otg_io_read(isp->otg, ULPI_DEBUG); | ||
106 | if ((reg & 3) != 3) | ||
107 | type = POWER_SUPPLY_TYPE_USB_CDP; | ||
108 | |||
109 | /* recover original state */ | ||
110 | otg_io_write(isp->otg, ULPI_FUNC_CTRL, func_ctrl); | ||
111 | otg_io_write(isp->otg, ULPI_OTG_CTRL, otg_ctrl); | ||
112 | |||
113 | return type; | ||
114 | } | ||
115 | |||
116 | /* | ||
67 | * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger | 117 | * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger |
68 | * is actually a dedicated charger, the following steps need to be taken. | 118 | * is actually a dedicated charger, the following steps need to be taken. |
69 | */ | 119 | */ |
@@ -127,16 +177,19 @@ static inline int isp1704_charger_verify(struct isp1704_charger *isp) | |||
127 | static inline int isp1704_charger_detect(struct isp1704_charger *isp) | 177 | static inline int isp1704_charger_detect(struct isp1704_charger *isp) |
128 | { | 178 | { |
129 | unsigned long timeout; | 179 | unsigned long timeout; |
130 | u8 r; | 180 | u8 pwr_ctrl; |
131 | int ret = 0; | 181 | int ret = 0; |
132 | 182 | ||
183 | pwr_ctrl = otg_io_read(isp->otg, ISP1704_PWR_CTRL); | ||
184 | |||
133 | /* set SW control bit in PWR_CTRL register */ | 185 | /* set SW control bit in PWR_CTRL register */ |
134 | otg_io_write(isp->otg, ISP1704_PWR_CTRL, | 186 | otg_io_write(isp->otg, ISP1704_PWR_CTRL, |
135 | ISP1704_PWR_CTRL_SWCTRL); | 187 | ISP1704_PWR_CTRL_SWCTRL); |
136 | 188 | ||
137 | /* enable manual charger detection */ | 189 | /* enable manual charger detection */ |
138 | r = (ISP1704_PWR_CTRL_SWCTRL | ISP1704_PWR_CTRL_DPVSRC_EN); | 190 | otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), |
139 | otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), r); | 191 | ISP1704_PWR_CTRL_SWCTRL |
192 | | ISP1704_PWR_CTRL_DPVSRC_EN); | ||
140 | usleep_range(1000, 2000); | 193 | usleep_range(1000, 2000); |
141 | 194 | ||
142 | timeout = jiffies + msecs_to_jiffies(300); | 195 | timeout = jiffies + msecs_to_jiffies(300); |
@@ -147,7 +200,10 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp) | |||
147 | ret = isp1704_charger_verify(isp); | 200 | ret = isp1704_charger_verify(isp); |
148 | break; | 201 | break; |
149 | } | 202 | } |
150 | } while (!time_after(jiffies, timeout)); | 203 | } while (!time_after(jiffies, timeout) && isp->online); |
204 | |||
205 | /* recover original state */ | ||
206 | otg_io_write(isp->otg, ISP1704_PWR_CTRL, pwr_ctrl); | ||
151 | 207 | ||
152 | return ret; | 208 | return ret; |
153 | } | 209 | } |
@@ -155,53 +211,93 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp) | |||
155 | static void isp1704_charger_work(struct work_struct *data) | 211 | static void isp1704_charger_work(struct work_struct *data) |
156 | { | 212 | { |
157 | int detect; | 213 | int detect; |
214 | unsigned long event; | ||
215 | unsigned power; | ||
158 | struct isp1704_charger *isp = | 216 | struct isp1704_charger *isp = |
159 | container_of(data, struct isp1704_charger, work); | 217 | container_of(data, struct isp1704_charger, work); |
218 | static DEFINE_MUTEX(lock); | ||
160 | 219 | ||
161 | /* | 220 | event = isp->event; |
162 | * FIXME Only supporting dedicated chargers even though isp1704 can | 221 | power = isp->max_power; |
163 | * detect HUB and HOST chargers. If the device has already been | ||
164 | * enumerated, the detection will break the connection. | ||
165 | */ | ||
166 | if (isp->otg->state != OTG_STATE_B_IDLE) | ||
167 | return; | ||
168 | |||
169 | /* disable data pullups */ | ||
170 | if (isp->otg->gadget) | ||
171 | usb_gadget_disconnect(isp->otg->gadget); | ||
172 | |||
173 | /* detect charger */ | ||
174 | detect = isp1704_charger_detect(isp); | ||
175 | if (detect) { | ||
176 | isp->present = detect; | ||
177 | power_supply_changed(&isp->psy); | ||
178 | } | ||
179 | 222 | ||
180 | /* enable data pullups */ | 223 | mutex_lock(&lock); |
181 | if (isp->otg->gadget) | ||
182 | usb_gadget_connect(isp->otg->gadget); | ||
183 | } | ||
184 | |||
185 | static int isp1704_notifier_call(struct notifier_block *nb, | ||
186 | unsigned long event, void *unused) | ||
187 | { | ||
188 | struct isp1704_charger *isp = | ||
189 | container_of(nb, struct isp1704_charger, nb); | ||
190 | 224 | ||
191 | switch (event) { | 225 | switch (event) { |
192 | case USB_EVENT_VBUS: | 226 | case USB_EVENT_VBUS: |
193 | schedule_work(&isp->work); | 227 | isp->online = true; |
228 | |||
229 | /* detect charger */ | ||
230 | detect = isp1704_charger_detect(isp); | ||
231 | |||
232 | if (detect) { | ||
233 | isp->present = detect; | ||
234 | isp->psy.type = isp1704_charger_type(isp); | ||
235 | } | ||
236 | |||
237 | switch (isp->psy.type) { | ||
238 | case POWER_SUPPLY_TYPE_USB_DCP: | ||
239 | isp->current_max = 1800; | ||
240 | break; | ||
241 | case POWER_SUPPLY_TYPE_USB_CDP: | ||
242 | /* | ||
243 | * Only 500mA here or high speed chirp | ||
244 | * handshaking may break | ||
245 | */ | ||
246 | isp->current_max = 500; | ||
247 | /* FALLTHROUGH */ | ||
248 | case POWER_SUPPLY_TYPE_USB: | ||
249 | default: | ||
250 | /* enable data pullups */ | ||
251 | if (isp->otg->gadget) | ||
252 | usb_gadget_connect(isp->otg->gadget); | ||
253 | } | ||
194 | break; | 254 | break; |
195 | case USB_EVENT_NONE: | 255 | case USB_EVENT_NONE: |
196 | if (isp->present) { | 256 | isp->online = false; |
197 | isp->present = 0; | 257 | isp->current_max = 0; |
198 | power_supply_changed(&isp->psy); | 258 | isp->present = 0; |
199 | } | 259 | isp->current_max = 0; |
260 | isp->psy.type = POWER_SUPPLY_TYPE_USB; | ||
261 | |||
262 | /* | ||
263 | * Disable data pullups. We need to prevent the controller from | ||
264 | * enumerating. | ||
265 | * | ||
266 | * FIXME: This is here to allow charger detection with Host/HUB | ||
267 | * chargers. The pullups may be enabled elsewhere, so this can | ||
268 | * not be the final solution. | ||
269 | */ | ||
270 | if (isp->otg->gadget) | ||
271 | usb_gadget_disconnect(isp->otg->gadget); | ||
272 | break; | ||
273 | case USB_EVENT_ENUMERATED: | ||
274 | if (isp->present) | ||
275 | isp->current_max = 1800; | ||
276 | else | ||
277 | isp->current_max = power; | ||
200 | break; | 278 | break; |
201 | default: | 279 | default: |
202 | return NOTIFY_DONE; | 280 | goto out; |
203 | } | 281 | } |
204 | 282 | ||
283 | power_supply_changed(&isp->psy); | ||
284 | out: | ||
285 | mutex_unlock(&lock); | ||
286 | } | ||
287 | |||
288 | static int isp1704_notifier_call(struct notifier_block *nb, | ||
289 | unsigned long event, void *power) | ||
290 | { | ||
291 | struct isp1704_charger *isp = | ||
292 | container_of(nb, struct isp1704_charger, nb); | ||
293 | |||
294 | isp->event = event; | ||
295 | |||
296 | if (power) | ||
297 | isp->max_power = *((unsigned *)power); | ||
298 | |||
299 | schedule_work(&isp->work); | ||
300 | |||
205 | return NOTIFY_OK; | 301 | return NOTIFY_OK; |
206 | } | 302 | } |
207 | 303 | ||
@@ -216,6 +312,12 @@ static int isp1704_charger_get_property(struct power_supply *psy, | |||
216 | case POWER_SUPPLY_PROP_PRESENT: | 312 | case POWER_SUPPLY_PROP_PRESENT: |
217 | val->intval = isp->present; | 313 | val->intval = isp->present; |
218 | break; | 314 | break; |
315 | case POWER_SUPPLY_PROP_ONLINE: | ||
316 | val->intval = isp->online; | ||
317 | break; | ||
318 | case POWER_SUPPLY_PROP_CURRENT_MAX: | ||
319 | val->intval = isp->current_max; | ||
320 | break; | ||
219 | case POWER_SUPPLY_PROP_MODEL_NAME: | 321 | case POWER_SUPPLY_PROP_MODEL_NAME: |
220 | val->strval = isp->model; | 322 | val->strval = isp->model; |
221 | break; | 323 | break; |
@@ -230,6 +332,8 @@ static int isp1704_charger_get_property(struct power_supply *psy, | |||
230 | 332 | ||
231 | static enum power_supply_property power_props[] = { | 333 | static enum power_supply_property power_props[] = { |
232 | POWER_SUPPLY_PROP_PRESENT, | 334 | POWER_SUPPLY_PROP_PRESENT, |
335 | POWER_SUPPLY_PROP_ONLINE, | ||
336 | POWER_SUPPLY_PROP_CURRENT_MAX, | ||
233 | POWER_SUPPLY_PROP_MODEL_NAME, | 337 | POWER_SUPPLY_PROP_MODEL_NAME, |
234 | POWER_SUPPLY_PROP_MANUFACTURER, | 338 | POWER_SUPPLY_PROP_MANUFACTURER, |
235 | }; | 339 | }; |