extcon-axp288.c (edc39c9b4589a4ce9a69273b5a27a1459c3423d4) extcon-axp288.c (d54f063cdbe414590c97d990111ddff25c6f9593)
1/*
2 * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
3 *
1/*
2 * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
3 *
4 * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
4 * Copyright (C) 2015 Intel Corporation
5 * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
5 * Copyright (C) 2015 Intel Corporation
6 * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 */
17
18#include <linux/acpi.h>
17#include <linux/module.h>
18#include <linux/kernel.h>
19#include <linux/io.h>
20#include <linux/slab.h>
21#include <linux/interrupt.h>
22#include <linux/platform_device.h>
23#include <linux/property.h>
24#include <linux/notifier.h>
25#include <linux/extcon-provider.h>
26#include <linux/regmap.h>
27#include <linux/mfd/axp20x.h>
19#include <linux/module.h>
20#include <linux/kernel.h>
21#include <linux/io.h>
22#include <linux/slab.h>
23#include <linux/interrupt.h>
24#include <linux/platform_device.h>
25#include <linux/property.h>
26#include <linux/notifier.h>
27#include <linux/extcon-provider.h>
28#include <linux/regmap.h>
29#include <linux/mfd/axp20x.h>
30#include <linux/usb/role.h>
31#include <linux/workqueue.h>
28
32
33#include <asm/cpu_device_id.h>
34#include <asm/intel-family.h>
35
29/* Power source status register */
30#define PS_STAT_VBUS_TRIGGER BIT(0)
31#define PS_STAT_BAT_CHRG_DIR BIT(2)
32#define PS_STAT_VBUS_ABOVE_VHOLD BIT(3)
33#define PS_STAT_VBUS_VALID BIT(4)
34#define PS_STAT_VBUS_PRESENT BIT(5)
35
36/* BC module global register */

--- 55 unchanged lines hidden (view full) ---

92 EXTCON_USB,
93 EXTCON_NONE,
94};
95
96struct axp288_extcon_info {
97 struct device *dev;
98 struct regmap *regmap;
99 struct regmap_irq_chip_data *regmap_irqc;
36/* Power source status register */
37#define PS_STAT_VBUS_TRIGGER BIT(0)
38#define PS_STAT_BAT_CHRG_DIR BIT(2)
39#define PS_STAT_VBUS_ABOVE_VHOLD BIT(3)
40#define PS_STAT_VBUS_VALID BIT(4)
41#define PS_STAT_VBUS_PRESENT BIT(5)
42
43/* BC module global register */

--- 55 unchanged lines hidden (view full) ---

99 EXTCON_USB,
100 EXTCON_NONE,
101};
102
103struct axp288_extcon_info {
104 struct device *dev;
105 struct regmap *regmap;
106 struct regmap_irq_chip_data *regmap_irqc;
107 struct usb_role_switch *role_sw;
108 struct work_struct role_work;
100 int irq[EXTCON_IRQ_END];
101 struct extcon_dev *edev;
109 int irq[EXTCON_IRQ_END];
110 struct extcon_dev *edev;
111 struct extcon_dev *id_extcon;
112 struct notifier_block id_nb;
102 unsigned int previous_cable;
113 unsigned int previous_cable;
114 bool vbus_attach;
103};
104
115};
116
117static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
118 { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY },
119 {}
120};
121
105/* Power up/down reason string array */
106static const char * const axp288_pwr_up_down_info[] = {
107 "Last wake caused by user pressing the power button",
108 "Last wake caused by a charger insertion",
109 "Last wake caused by a battery insertion",
110 "Last wake caused by SOC initiated global reset",
111 "Last wake caused by cold reset",
112 "Last shutdown caused by PMIC UVLO threshold",

--- 19 unchanged lines hidden (view full) ---

132 clear_mask |= BIT(i);
133 }
134 }
135
136 /* Clear the register value for next reboot (write 1 to clear bit) */
137 regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
138}
139
122/* Power up/down reason string array */
123static const char * const axp288_pwr_up_down_info[] = {
124 "Last wake caused by user pressing the power button",
125 "Last wake caused by a charger insertion",
126 "Last wake caused by a battery insertion",
127 "Last wake caused by SOC initiated global reset",
128 "Last wake caused by cold reset",
129 "Last shutdown caused by PMIC UVLO threshold",

--- 19 unchanged lines hidden (view full) ---

149 clear_mask |= BIT(i);
150 }
151 }
152
153 /* Clear the register value for next reboot (write 1 to clear bit) */
154 regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
155}
156
140static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
157/*
158 * The below code to control the USB role-switch on devices with an AXP288
159 * may seem out of place, but there are 2 reasons why this is the best place
160 * to control the USB role-switch on such devices:
161 * 1) On many devices the USB role is controlled by AML code, but the AML code
162 * only switches between the host and none roles, because of Windows not
163 * really using device mode. To make device mode work we need to toggle
164 * between the none/device roles based on Vbus presence, and this driver
165 * gets interrupts on Vbus insertion / removal.
166 * 2) In order for our BC1.2 charger detection to work properly the role
167 * mux must be properly set to device mode before we do the detection.
168 */
169
170/* Returns the id-pin value, note pulled low / false == host-mode */
171static bool axp288_get_id_pin(struct axp288_extcon_info *info)
141{
172{
142 int ret, stat, cfg, pwr_stat;
143 u8 chrg_type;
144 unsigned int cable = info->previous_cable;
145 bool vbus_attach = false;
173 enum usb_role role;
146
174
175 if (info->id_extcon)
176 return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
177
178 /* We cannot access the id-pin, see what mode the AML code has set */
179 role = usb_role_switch_get_role(info->role_sw);
180 return role != USB_ROLE_HOST;
181}
182
183static void axp288_usb_role_work(struct work_struct *work)
184{
185 struct axp288_extcon_info *info =
186 container_of(work, struct axp288_extcon_info, role_work);
187 enum usb_role role;
188 bool id_pin;
189 int ret;
190
191 id_pin = axp288_get_id_pin(info);
192 if (!id_pin)
193 role = USB_ROLE_HOST;
194 else if (info->vbus_attach)
195 role = USB_ROLE_DEVICE;
196 else
197 role = USB_ROLE_NONE;
198
199 ret = usb_role_switch_set_role(info->role_sw, role);
200 if (ret)
201 dev_err(info->dev, "failed to set role: %d\n", ret);
202}
203
204static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
205{
206 int ret, pwr_stat;
207
147 ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
148 if (ret < 0) {
149 dev_err(info->dev, "failed to read vbus status\n");
208 ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
209 if (ret < 0) {
210 dev_err(info->dev, "failed to read vbus status\n");
150 return ret;
211 return false;
151 }
152
212 }
213
153 vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
214 return !!(pwr_stat & PS_STAT_VBUS_VALID);
215}
216
217static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
218{
219 int ret, stat, cfg;
220 u8 chrg_type;
221 unsigned int cable = info->previous_cable;
222 bool vbus_attach = false;
223
224 vbus_attach = axp288_get_vbus_attach(info);
154 if (!vbus_attach)
155 goto no_vbus;
156
157 /* Check charger detection completion status */
158 ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
159 if (ret < 0)
160 goto dev_det_ret;
161 if (cfg & BC_GLOBAL_DET_STAT) {

--- 34 unchanged lines hidden (view full) ---

196 extcon_set_state_sync(info->edev, cable, vbus_attach);
197 if (cable == EXTCON_CHG_USB_SDP)
198 extcon_set_state_sync(info->edev, EXTCON_USB,
199 vbus_attach);
200
201 info->previous_cable = cable;
202 }
203
225 if (!vbus_attach)
226 goto no_vbus;
227
228 /* Check charger detection completion status */
229 ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
230 if (ret < 0)
231 goto dev_det_ret;
232 if (cfg & BC_GLOBAL_DET_STAT) {

--- 34 unchanged lines hidden (view full) ---

267 extcon_set_state_sync(info->edev, cable, vbus_attach);
268 if (cable == EXTCON_CHG_USB_SDP)
269 extcon_set_state_sync(info->edev, EXTCON_USB,
270 vbus_attach);
271
272 info->previous_cable = cable;
273 }
274
275 if (info->role_sw && info->vbus_attach != vbus_attach) {
276 info->vbus_attach = vbus_attach;
277 /* Setting the role can take a while */
278 queue_work(system_long_wq, &info->role_work);
279 }
280
204 return 0;
205
206dev_det_ret:
207 if (ret < 0)
208 dev_err(info->dev, "failed to detect BC Mod\n");
209
210 return ret;
211}
212
281 return 0;
282
283dev_det_ret:
284 if (ret < 0)
285 dev_err(info->dev, "failed to detect BC Mod\n");
286
287 return ret;
288}
289
290static int axp288_extcon_id_evt(struct notifier_block *nb,
291 unsigned long event, void *param)
292{
293 struct axp288_extcon_info *info =
294 container_of(nb, struct axp288_extcon_info, id_nb);
295
296 /* We may not sleep and setting the role can take a while */
297 queue_work(system_long_wq, &info->role_work);
298
299 return NOTIFY_OK;
300}
301
213static irqreturn_t axp288_extcon_isr(int irq, void *data)
214{
215 struct axp288_extcon_info *info = data;
216 int ret;
217
218 ret = axp288_handle_chrg_det_event(info);
219 if (ret < 0)
220 dev_err(info->dev, "failed to handle the interrupt\n");

--- 5 unchanged lines hidden (view full) ---

226{
227 regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
228 BC_GLOBAL_RUN, 0);
229 /* Enable the charger detection logic */
230 regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
231 BC_GLOBAL_RUN, BC_GLOBAL_RUN);
232}
233
302static irqreturn_t axp288_extcon_isr(int irq, void *data)
303{
304 struct axp288_extcon_info *info = data;
305 int ret;
306
307 ret = axp288_handle_chrg_det_event(info);
308 if (ret < 0)
309 dev_err(info->dev, "failed to handle the interrupt\n");

--- 5 unchanged lines hidden (view full) ---

315{
316 regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
317 BC_GLOBAL_RUN, 0);
318 /* Enable the charger detection logic */
319 regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
320 BC_GLOBAL_RUN, BC_GLOBAL_RUN);
321}
322
323static void axp288_put_role_sw(void *data)
324{
325 struct axp288_extcon_info *info = data;
326
327 cancel_work_sync(&info->role_work);
328 usb_role_switch_put(info->role_sw);
329}
330
234static int axp288_extcon_probe(struct platform_device *pdev)
235{
236 struct axp288_extcon_info *info;
237 struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
331static int axp288_extcon_probe(struct platform_device *pdev)
332{
333 struct axp288_extcon_info *info;
334 struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
335 struct device *dev = &pdev->dev;
336 const char *name;
238 int ret, i, pirq;
239
240 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
241 if (!info)
242 return -ENOMEM;
243
244 info->dev = &pdev->dev;
245 info->regmap = axp20x->regmap;
246 info->regmap_irqc = axp20x->regmap_irqc;
247 info->previous_cable = EXTCON_NONE;
337 int ret, i, pirq;
338
339 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
340 if (!info)
341 return -ENOMEM;
342
343 info->dev = &pdev->dev;
344 info->regmap = axp20x->regmap;
345 info->regmap_irqc = axp20x->regmap_irqc;
346 info->previous_cable = EXTCON_NONE;
347 INIT_WORK(&info->role_work, axp288_usb_role_work);
348 info->id_nb.notifier_call = axp288_extcon_id_evt;
248
249 platform_set_drvdata(pdev, info);
250
349
350 platform_set_drvdata(pdev, info);
351
352 info->role_sw = usb_role_switch_get(dev);
353 if (IS_ERR(info->role_sw))
354 return PTR_ERR(info->role_sw);
355 if (info->role_sw) {
356 ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
357 if (ret)
358 return ret;
359
360 name = acpi_dev_get_first_match_name("INT3496", NULL, -1);
361 if (name) {
362 info->id_extcon = extcon_get_extcon_dev(name);
363 if (!info->id_extcon)
364 return -EPROBE_DEFER;
365
366 dev_info(dev, "controlling USB role\n");
367 } else {
368 dev_info(dev, "controlling USB role based on Vbus presence\n");
369 }
370 }
371
372 info->vbus_attach = axp288_get_vbus_attach(info);
373
251 axp288_extcon_log_rsi(info);
252
253 /* Initialize extcon device */
254 info->edev = devm_extcon_dev_allocate(&pdev->dev,
255 axp288_extcon_cables);
256 if (IS_ERR(info->edev)) {
257 dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
258 return PTR_ERR(info->edev);

--- 25 unchanged lines hidden (view full) ---

284 pdev->name, info);
285 if (ret) {
286 dev_err(&pdev->dev, "failed to request interrupt=%d\n",
287 info->irq[i]);
288 return ret;
289 }
290 }
291
374 axp288_extcon_log_rsi(info);
375
376 /* Initialize extcon device */
377 info->edev = devm_extcon_dev_allocate(&pdev->dev,
378 axp288_extcon_cables);
379 if (IS_ERR(info->edev)) {
380 dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
381 return PTR_ERR(info->edev);

--- 25 unchanged lines hidden (view full) ---

407 pdev->name, info);
408 if (ret) {
409 dev_err(&pdev->dev, "failed to request interrupt=%d\n",
410 info->irq[i]);
411 return ret;
412 }
413 }
414
415 if (info->id_extcon) {
416 ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
417 &info->id_nb);
418 if (ret)
419 return ret;
420 }
421
422 /* Make sure the role-sw is set correctly before doing BC detection */
423 if (info->role_sw) {
424 queue_work(system_long_wq, &info->role_work);
425 flush_work(&info->role_work);
426 }
427
292 /* Start charger cable type detection */
293 axp288_extcon_enable(info);
294
295 return 0;
296}
297
298static const struct platform_device_id axp288_extcon_table[] = {
299 { .name = "axp288_extcon" },
300 {},
301};
302MODULE_DEVICE_TABLE(platform, axp288_extcon_table);
303
304static struct platform_driver axp288_extcon_driver = {
305 .probe = axp288_extcon_probe,
306 .id_table = axp288_extcon_table,
307 .driver = {
308 .name = "axp288_extcon",
309 },
310};
428 /* Start charger cable type detection */
429 axp288_extcon_enable(info);
430
431 return 0;
432}
433
434static const struct platform_device_id axp288_extcon_table[] = {
435 { .name = "axp288_extcon" },
436 {},
437};
438MODULE_DEVICE_TABLE(platform, axp288_extcon_table);
439
440static struct platform_driver axp288_extcon_driver = {
441 .probe = axp288_extcon_probe,
442 .id_table = axp288_extcon_table,
443 .driver = {
444 .name = "axp288_extcon",
445 },
446};
311module_platform_driver(axp288_extcon_driver);
312
447
448static struct device_connection axp288_extcon_role_sw_conn = {
449 .endpoint[0] = "axp288_extcon",
450 .endpoint[1] = "intel_xhci_usb_sw-role-switch",
451 .id = "usb-role-switch",
452};
453
454static int __init axp288_extcon_init(void)
455{
456 if (x86_match_cpu(cherry_trail_cpu_ids))
457 device_connection_add(&axp288_extcon_role_sw_conn);
458
459 return platform_driver_register(&axp288_extcon_driver);
460}
461module_init(axp288_extcon_init);
462
463static void __exit axp288_extcon_exit(void)
464{
465 if (x86_match_cpu(cherry_trail_cpu_ids))
466 device_connection_remove(&axp288_extcon_role_sw_conn);
467
468 platform_driver_unregister(&axp288_extcon_driver);
469}
470module_exit(axp288_extcon_exit);
471
313MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
472MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
473MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
314MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
315MODULE_LICENSE("GPL v2");
474MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
475MODULE_LICENSE("GPL v2");