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