1 /* 2 * Palmas USB transceiver driver 3 * 4 * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * Author: Graeme Gregory <gg@slimlogic.co.uk> 11 * Author: Kishon Vijay Abraham I <kishon@ti.com> 12 * 13 * Based on twl6030_usb.c 14 * 15 * Author: Hema HK <hemahk@ti.com> 16 * 17 * This program is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * GNU General Public License for more details. 21 */ 22 23 #include <linux/module.h> 24 #include <linux/interrupt.h> 25 #include <linux/platform_device.h> 26 #include <linux/err.h> 27 #include <linux/mfd/palmas.h> 28 #include <linux/of.h> 29 #include <linux/of_platform.h> 30 31 static const char *palmas_extcon_cable[] = { 32 [0] = "USB", 33 [1] = "USB-HOST", 34 NULL, 35 }; 36 37 static const int mutually_exclusive[] = {0x3, 0x0}; 38 39 static void palmas_usb_wakeup(struct palmas *palmas, int enable) 40 { 41 if (enable) 42 palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 43 PALMAS_USB_WAKEUP_ID_WK_UP_COMP); 44 else 45 palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 0); 46 } 47 48 static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) 49 { 50 struct palmas_usb *palmas_usb = _palmas_usb; 51 unsigned int vbus_line_state; 52 53 palmas_read(palmas_usb->palmas, PALMAS_INTERRUPT_BASE, 54 PALMAS_INT3_LINE_STATE, &vbus_line_state); 55 56 if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) { 57 if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) { 58 palmas_usb->linkstat = PALMAS_USB_STATE_VBUS; 59 extcon_set_cable_state(&palmas_usb->edev, "USB", true); 60 } else { 61 dev_dbg(palmas_usb->dev, 62 "Spurious connect event detected\n"); 63 } 64 } else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) { 65 if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) { 66 palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; 67 extcon_set_cable_state(&palmas_usb->edev, "USB", false); 68 } else { 69 dev_dbg(palmas_usb->dev, 70 "Spurious disconnect event detected\n"); 71 } 72 } 73 74 return IRQ_HANDLED; 75 } 76 77 static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) 78 { 79 unsigned int set; 80 struct palmas_usb *palmas_usb = _palmas_usb; 81 82 palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 83 PALMAS_USB_ID_INT_LATCH_SET, &set); 84 85 if (set & PALMAS_USB_ID_INT_SRC_ID_GND) { 86 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 87 PALMAS_USB_ID_INT_EN_HI_SET, 88 PALMAS_USB_ID_INT_EN_HI_SET_ID_FLOAT); 89 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 90 PALMAS_USB_ID_INT_EN_HI_CLR, 91 PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND); 92 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 93 PALMAS_USB_ID_INT_LATCH_CLR, 94 PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND); 95 palmas_usb->linkstat = PALMAS_USB_STATE_ID; 96 extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); 97 } else if (set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) { 98 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 99 PALMAS_USB_ID_INT_EN_HI_SET, 100 PALMAS_USB_ID_INT_EN_HI_SET_ID_GND); 101 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 102 PALMAS_USB_ID_INT_EN_HI_CLR, 103 PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT); 104 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 105 PALMAS_USB_ID_INT_LATCH_CLR, 106 PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT); 107 palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; 108 extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); 109 } 110 111 return IRQ_HANDLED; 112 } 113 114 static void palmas_enable_irq(struct palmas_usb *palmas_usb) 115 { 116 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 117 PALMAS_USB_VBUS_CTRL_SET, 118 PALMAS_USB_VBUS_CTRL_SET_VBUS_ACT_COMP); 119 120 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 121 PALMAS_USB_ID_CTRL_SET, PALMAS_USB_ID_CTRL_SET_ID_ACT_COMP); 122 123 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, 124 PALMAS_USB_ID_INT_EN_HI_SET, 125 PALMAS_USB_ID_INT_EN_HI_SET_ID_GND); 126 127 palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb); 128 129 /* cold plug for host mode needs this delay */ 130 msleep(30); 131 palmas_id_irq_handler(palmas_usb->id_irq, palmas_usb); 132 } 133 134 static int palmas_usb_probe(struct platform_device *pdev) 135 { 136 struct palmas *palmas = dev_get_drvdata(pdev->dev.parent); 137 struct palmas_usb_platform_data *pdata = pdev->dev.platform_data; 138 struct device_node *node = pdev->dev.of_node; 139 struct palmas_usb *palmas_usb; 140 int status; 141 142 if (node && !pdata) { 143 pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); 144 145 if (!pdata) 146 return -ENOMEM; 147 148 pdata->wakeup = of_property_read_bool(node, "ti,wakeup"); 149 } else if (!pdata) { 150 return -EINVAL; 151 } 152 153 palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL); 154 if (!palmas_usb) 155 return -ENOMEM; 156 157 palmas->usb = palmas_usb; 158 palmas_usb->palmas = palmas; 159 160 palmas_usb->dev = &pdev->dev; 161 162 palmas_usb->id_otg_irq = regmap_irq_get_virq(palmas->irq_data, 163 PALMAS_ID_OTG_IRQ); 164 palmas_usb->id_irq = regmap_irq_get_virq(palmas->irq_data, 165 PALMAS_ID_IRQ); 166 palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data, 167 PALMAS_VBUS_OTG_IRQ); 168 palmas_usb->vbus_irq = regmap_irq_get_virq(palmas->irq_data, 169 PALMAS_VBUS_IRQ); 170 171 palmas_usb_wakeup(palmas, pdata->wakeup); 172 173 platform_set_drvdata(pdev, palmas_usb); 174 175 palmas_usb->edev.name = "palmas-usb"; 176 palmas_usb->edev.supported_cable = palmas_extcon_cable; 177 palmas_usb->edev.mutually_exclusive = mutually_exclusive; 178 179 status = extcon_dev_register(&palmas_usb->edev, palmas_usb->dev); 180 if (status) { 181 dev_err(&pdev->dev, "failed to register extcon device\n"); 182 return status; 183 } 184 185 status = devm_request_threaded_irq(palmas_usb->dev, palmas_usb->id_irq, 186 NULL, palmas_id_irq_handler, 187 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 188 "palmas_usb_id", palmas_usb); 189 if (status < 0) { 190 dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", 191 palmas_usb->id_irq, status); 192 goto fail_extcon; 193 } 194 195 status = devm_request_threaded_irq(palmas_usb->dev, 196 palmas_usb->vbus_irq, NULL, palmas_vbus_irq_handler, 197 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 198 "palmas_usb_vbus", palmas_usb); 199 if (status < 0) { 200 dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", 201 palmas_usb->vbus_irq, status); 202 goto fail_extcon; 203 } 204 205 palmas_enable_irq(palmas_usb); 206 207 return 0; 208 209 fail_extcon: 210 extcon_dev_unregister(&palmas_usb->edev); 211 212 return status; 213 } 214 215 static int palmas_usb_remove(struct platform_device *pdev) 216 { 217 struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); 218 219 extcon_dev_unregister(&palmas_usb->edev); 220 221 return 0; 222 } 223 224 static struct of_device_id of_palmas_match_tbl[] = { 225 { .compatible = "ti,palmas-usb", }, 226 { .compatible = "ti,twl6035-usb", }, 227 { /* end */ } 228 }; 229 230 static struct platform_driver palmas_usb_driver = { 231 .probe = palmas_usb_probe, 232 .remove = palmas_usb_remove, 233 .driver = { 234 .name = "palmas-usb", 235 .of_match_table = of_palmas_match_tbl, 236 .owner = THIS_MODULE, 237 }, 238 }; 239 240 module_platform_driver(palmas_usb_driver); 241 242 MODULE_ALIAS("platform:palmas-usb"); 243 MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); 244 MODULE_DESCRIPTION("Palmas USB transceiver driver"); 245 MODULE_LICENSE("GPL"); 246 MODULE_DEVICE_TABLE(of, of_palmas_match_tbl); 247