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