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"); |