1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Maxim Integrated MAX3355 USB OTG chip extcon driver 4 * 5 * Copyright (C) 2014-2015 Cogent Embedded, Inc. 6 * Author: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> 7 */ 8 9 #include <linux/extcon-provider.h> 10 #include <linux/gpio/consumer.h> 11 #include <linux/interrupt.h> 12 #include <linux/module.h> 13 #include <linux/mod_devicetable.h> 14 #include <linux/platform_device.h> 15 16 struct max3355_data { 17 struct extcon_dev *edev; 18 struct gpio_desc *id_gpiod; 19 struct gpio_desc *shdn_gpiod; 20 }; 21 22 static const unsigned int max3355_cable[] = { 23 EXTCON_USB, 24 EXTCON_USB_HOST, 25 EXTCON_NONE, 26 }; 27 28 static irqreturn_t max3355_id_irq(int irq, void *dev_id) 29 { 30 struct max3355_data *data = dev_id; 31 int id = gpiod_get_value_cansleep(data->id_gpiod); 32 33 if (id) { 34 /* 35 * ID = 1 means USB HOST cable detached. 36 * As we don't have event for USB peripheral cable attached, 37 * we simulate USB peripheral attach here. 38 */ 39 extcon_set_state_sync(data->edev, EXTCON_USB_HOST, false); 40 extcon_set_state_sync(data->edev, EXTCON_USB, true); 41 } else { 42 /* 43 * ID = 0 means USB HOST cable attached. 44 * As we don't have event for USB peripheral cable detached, 45 * we simulate USB peripheral detach here. 46 */ 47 extcon_set_state_sync(data->edev, EXTCON_USB, false); 48 extcon_set_state_sync(data->edev, EXTCON_USB_HOST, true); 49 } 50 51 return IRQ_HANDLED; 52 } 53 54 static int max3355_probe(struct platform_device *pdev) 55 { 56 struct max3355_data *data; 57 struct gpio_desc *gpiod; 58 int irq, err; 59 60 data = devm_kzalloc(&pdev->dev, sizeof(struct max3355_data), 61 GFP_KERNEL); 62 if (!data) 63 return -ENOMEM; 64 65 gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN); 66 if (IS_ERR(gpiod)) { 67 dev_err(&pdev->dev, "failed to get ID_OUT GPIO\n"); 68 return PTR_ERR(gpiod); 69 } 70 data->id_gpiod = gpiod; 71 72 gpiod = devm_gpiod_get(&pdev->dev, "maxim,shdn", GPIOD_OUT_HIGH); 73 if (IS_ERR(gpiod)) { 74 dev_err(&pdev->dev, "failed to get SHDN# GPIO\n"); 75 return PTR_ERR(gpiod); 76 } 77 data->shdn_gpiod = gpiod; 78 79 data->edev = devm_extcon_dev_allocate(&pdev->dev, max3355_cable); 80 if (IS_ERR(data->edev)) { 81 dev_err(&pdev->dev, "failed to allocate extcon device\n"); 82 return PTR_ERR(data->edev); 83 } 84 85 err = devm_extcon_dev_register(&pdev->dev, data->edev); 86 if (err < 0) { 87 dev_err(&pdev->dev, "failed to register extcon device\n"); 88 return err; 89 } 90 91 irq = gpiod_to_irq(data->id_gpiod); 92 if (irq < 0) { 93 dev_err(&pdev->dev, "failed to translate ID_OUT GPIO to IRQ\n"); 94 return irq; 95 } 96 97 err = devm_request_threaded_irq(&pdev->dev, irq, NULL, max3355_id_irq, 98 IRQF_ONESHOT | IRQF_NO_SUSPEND | 99 IRQF_TRIGGER_RISING | 100 IRQF_TRIGGER_FALLING, 101 pdev->name, data); 102 if (err < 0) { 103 dev_err(&pdev->dev, "failed to request ID_OUT IRQ\n"); 104 return err; 105 } 106 107 platform_set_drvdata(pdev, data); 108 109 /* Perform initial detection */ 110 max3355_id_irq(irq, data); 111 112 return 0; 113 } 114 115 static void max3355_remove(struct platform_device *pdev) 116 { 117 struct max3355_data *data = platform_get_drvdata(pdev); 118 119 gpiod_set_value_cansleep(data->shdn_gpiod, 0); 120 } 121 122 static const struct of_device_id max3355_match_table[] = { 123 { .compatible = "maxim,max3355", }, 124 { } 125 }; 126 MODULE_DEVICE_TABLE(of, max3355_match_table); 127 128 static struct platform_driver max3355_driver = { 129 .probe = max3355_probe, 130 .remove_new = max3355_remove, 131 .driver = { 132 .name = "extcon-max3355", 133 .of_match_table = max3355_match_table, 134 }, 135 }; 136 137 module_platform_driver(max3355_driver); 138 139 MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>"); 140 MODULE_DESCRIPTION("Maxim MAX3355 extcon driver"); 141 MODULE_LICENSE("GPL v2"); 142