1/* 2 * Tahvo USB transceiver driver 3 * 4 * Copyright (C) 2005-2006 Nokia Corporation 5 * 6 * Parts copied from isp1301_omap.c. 7 * Copyright (C) 2004 Texas Instruments 8 * Copyright (C) 2004 David Brownell 9 * 10 * Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs. 11 * Modified for Retu/Tahvo MFD by Aaro Koskinen. 12 * 13 * This file is subject to the terms and conditions of the GNU General 14 * Public License. See the file "COPYING" in the main directory of this 15 * archive for more details. 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/io.h> 24#include <linux/clk.h> 25#include <linux/usb.h> 26#include <linux/extcon.h> 27#include <linux/kernel.h> 28#include <linux/module.h> 29#include <linux/usb/otg.h> 30#include <linux/mfd/retu.h> 31#include <linux/usb/gadget.h> 32#include <linux/platform_device.h> 33 34#define DRIVER_NAME "tahvo-usb" 35 36#define TAHVO_REG_IDSR 0x02 37#define TAHVO_REG_USBR 0x06 38 39#define USBR_SLAVE_CONTROL (1 << 8) 40#define USBR_VPPVIO_SW (1 << 7) 41#define USBR_SPEED (1 << 6) 42#define USBR_REGOUT (1 << 5) 43#define USBR_MASTER_SW2 (1 << 4) 44#define USBR_MASTER_SW1 (1 << 3) 45#define USBR_SLAVE_SW (1 << 2) 46#define USBR_NSUSPEND (1 << 1) 47#define USBR_SEMODE (1 << 0) 48 49#define TAHVO_MODE_HOST 0 50#define TAHVO_MODE_PERIPHERAL 1 51 52struct tahvo_usb { 53 struct platform_device *pt_dev; 54 struct usb_phy phy; 55 int vbus_state; 56 struct mutex serialize; 57 struct clk *ick; 58 int irq; 59 int tahvo_mode; 60 struct extcon_dev extcon; 61}; 62 63static const char *tahvo_cable[] = { 64 "USB-HOST", 65 "USB", 66 NULL, 67}; 68 69static ssize_t vbus_state_show(struct device *device, 70 struct device_attribute *attr, char *buf) 71{ 72 struct tahvo_usb *tu = dev_get_drvdata(device); 73 return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off"); 74} 75static DEVICE_ATTR(vbus, 0444, vbus_state_show, NULL); 76 77static void check_vbus_state(struct tahvo_usb *tu) 78{ 79 struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); 80 int reg, prev_state; 81 82 reg = retu_read(rdev, TAHVO_REG_IDSR); 83 if (reg & TAHVO_STAT_VBUS) { 84 switch (tu->phy.state) { 85 case OTG_STATE_B_IDLE: 86 /* Enable the gadget driver */ 87 if (tu->phy.otg->gadget) 88 usb_gadget_vbus_connect(tu->phy.otg->gadget); 89 tu->phy.state = OTG_STATE_B_PERIPHERAL; 90 break; 91 case OTG_STATE_A_IDLE: 92 /* 93 * Session is now valid assuming the USB hub is driving 94 * Vbus. 95 */ 96 tu->phy.state = OTG_STATE_A_HOST; 97 break; 98 default: 99 break; 100 } 101 dev_info(&tu->pt_dev->dev, "USB cable connected\n"); 102 } else { 103 switch (tu->phy.state) { 104 case OTG_STATE_B_PERIPHERAL: 105 if (tu->phy.otg->gadget) 106 usb_gadget_vbus_disconnect(tu->phy.otg->gadget); 107 tu->phy.state = OTG_STATE_B_IDLE; 108 break; 109 case OTG_STATE_A_HOST: 110 tu->phy.state = OTG_STATE_A_IDLE; 111 break; 112 default: 113 break; 114 } 115 dev_info(&tu->pt_dev->dev, "USB cable disconnected\n"); 116 } 117 118 prev_state = tu->vbus_state; 119 tu->vbus_state = reg & TAHVO_STAT_VBUS; 120 if (prev_state != tu->vbus_state) { 121 extcon_set_cable_state(&tu->extcon, "USB", tu->vbus_state); 122 sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state"); 123 } 124} 125 126static void tahvo_usb_become_host(struct tahvo_usb *tu) 127{ 128 struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); 129 130 extcon_set_cable_state(&tu->extcon, "USB-HOST", true); 131 132 /* Power up the transceiver in USB host mode */ 133 retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND | 134 USBR_MASTER_SW2 | USBR_MASTER_SW1); 135 tu->phy.state = OTG_STATE_A_IDLE; 136 137 check_vbus_state(tu); 138} 139 140static void tahvo_usb_stop_host(struct tahvo_usb *tu) 141{ 142 tu->phy.state = OTG_STATE_A_IDLE; 143} 144 145static void tahvo_usb_become_peripheral(struct tahvo_usb *tu) 146{ 147 struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); 148 149 extcon_set_cable_state(&tu->extcon, "USB-HOST", false); 150 151 /* Power up transceiver and set it in USB peripheral mode */ 152 retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT | 153 USBR_NSUSPEND | USBR_SLAVE_SW); 154 tu->phy.state = OTG_STATE_B_IDLE; 155 156 check_vbus_state(tu); 157} 158 159static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu) 160{ 161 if (tu->phy.otg->gadget) 162 usb_gadget_vbus_disconnect(tu->phy.otg->gadget); 163 tu->phy.state = OTG_STATE_B_IDLE; 164} 165 166static void tahvo_usb_power_off(struct tahvo_usb *tu) 167{ 168 struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); 169 170 /* Disable gadget controller if any */ 171 if (tu->phy.otg->gadget) 172 usb_gadget_vbus_disconnect(tu->phy.otg->gadget); 173 174 /* Power off transceiver */ 175 retu_write(rdev, TAHVO_REG_USBR, 0); 176 tu->phy.state = OTG_STATE_UNDEFINED; 177} 178 179static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend) 180{ 181 struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy); 182 struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); 183 u16 w; 184 185 dev_dbg(&tu->pt_dev->dev, "%s\n", __func__); 186 187 w = retu_read(rdev, TAHVO_REG_USBR); 188 if (suspend) 189 w &= ~USBR_NSUSPEND; 190 else 191 w |= USBR_NSUSPEND; 192 retu_write(rdev, TAHVO_REG_USBR, w); 193 194 return 0; 195} 196 197static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host) 198{ 199 struct tahvo_usb *tu = container_of(otg->phy, struct tahvo_usb, phy); 200 201 dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, host); 202 203 mutex_lock(&tu->serialize); 204 205 if (host == NULL) { 206 if (tu->tahvo_mode == TAHVO_MODE_HOST) 207 tahvo_usb_power_off(tu); 208 otg->host = NULL; 209 mutex_unlock(&tu->serialize); 210 return 0; 211 } 212 213 if (tu->tahvo_mode == TAHVO_MODE_HOST) { 214 otg->host = NULL; 215 tahvo_usb_become_host(tu); 216 } 217 218 otg->host = host; 219 220 mutex_unlock(&tu->serialize); 221 222 return 0; 223} 224 225static int tahvo_usb_set_peripheral(struct usb_otg *otg, 226 struct usb_gadget *gadget) 227{ 228 struct tahvo_usb *tu = container_of(otg->phy, struct tahvo_usb, phy); 229 230 dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, gadget); 231 232 mutex_lock(&tu->serialize); 233 234 if (!gadget) { 235 if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) 236 tahvo_usb_power_off(tu); 237 tu->phy.otg->gadget = NULL; 238 mutex_unlock(&tu->serialize); 239 return 0; 240 } 241 242 tu->phy.otg->gadget = gadget; 243 if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) 244 tahvo_usb_become_peripheral(tu); 245 246 mutex_unlock(&tu->serialize); 247 248 return 0; 249} 250 251static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu) 252{ 253 struct tahvo_usb *tu = _tu; 254 255 mutex_lock(&tu->serialize); 256 check_vbus_state(tu); 257 mutex_unlock(&tu->serialize); 258 259 return IRQ_HANDLED; 260} 261 262static ssize_t otg_mode_show(struct device *device, 263 struct device_attribute *attr, char *buf) 264{ 265 struct tahvo_usb *tu = dev_get_drvdata(device); 266 267 switch (tu->tahvo_mode) { 268 case TAHVO_MODE_HOST: 269 return sprintf(buf, "host\n"); 270 case TAHVO_MODE_PERIPHERAL: 271 return sprintf(buf, "peripheral\n"); 272 } 273 274 return -EINVAL; 275} 276 277static ssize_t otg_mode_store(struct device *device, 278 struct device_attribute *attr, 279 const char *buf, size_t count) 280{ 281 struct tahvo_usb *tu = dev_get_drvdata(device); 282 int r; 283 284 mutex_lock(&tu->serialize); 285 if (count >= 4 && strncmp(buf, "host", 4) == 0) { 286 if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) 287 tahvo_usb_stop_peripheral(tu); 288 tu->tahvo_mode = TAHVO_MODE_HOST; 289 if (tu->phy.otg->host) { 290 dev_info(device, "HOST mode: host controller present\n"); 291 tahvo_usb_become_host(tu); 292 } else { 293 dev_info(device, "HOST mode: no host controller, powering off\n"); 294 tahvo_usb_power_off(tu); 295 } 296 r = strlen(buf); 297 } else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) { 298 if (tu->tahvo_mode == TAHVO_MODE_HOST) 299 tahvo_usb_stop_host(tu); 300 tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; 301 if (tu->phy.otg->gadget) { 302 dev_info(device, "PERIPHERAL mode: gadget driver present\n"); 303 tahvo_usb_become_peripheral(tu); 304 } else { 305 dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n"); 306 tahvo_usb_power_off(tu); 307 } 308 r = strlen(buf); 309 } else { 310 r = -EINVAL; 311 } 312 mutex_unlock(&tu->serialize); 313 314 return r; 315} 316static DEVICE_ATTR(otg_mode, 0644, otg_mode_show, otg_mode_store); 317 318static struct attribute *tahvo_attributes[] = { 319 &dev_attr_vbus.attr, 320 &dev_attr_otg_mode.attr, 321 NULL 322}; 323 324static struct attribute_group tahvo_attr_group = { 325 .attrs = tahvo_attributes, 326}; 327 328static int tahvo_usb_probe(struct platform_device *pdev) 329{ 330 struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent); 331 struct tahvo_usb *tu; 332 int ret; 333 334 tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL); 335 if (!tu) 336 return -ENOMEM; 337 338 tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg), 339 GFP_KERNEL); 340 if (!tu->phy.otg) 341 return -ENOMEM; 342 343 tu->pt_dev = pdev; 344 345 /* Default mode */ 346#ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT 347 tu->tahvo_mode = TAHVO_MODE_HOST; 348#else 349 tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; 350#endif 351 352 mutex_init(&tu->serialize); 353 354 tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick"); 355 if (!IS_ERR(tu->ick)) 356 clk_enable(tu->ick); 357 358 /* 359 * Set initial state, so that we generate kevents only on state changes. 360 */ 361 tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS; 362 363 tu->extcon.name = DRIVER_NAME; 364 tu->extcon.supported_cable = tahvo_cable; 365 tu->extcon.dev.parent = &pdev->dev; 366 367 ret = extcon_dev_register(&tu->extcon); 368 if (ret) { 369 dev_err(&pdev->dev, "could not register extcon device: %d\n", 370 ret); 371 goto err_disable_clk; 372 } 373 374 /* Set the initial cable state. */ 375 extcon_set_cable_state(&tu->extcon, "USB-HOST", 376 tu->tahvo_mode == TAHVO_MODE_HOST); 377 extcon_set_cable_state(&tu->extcon, "USB", tu->vbus_state); 378 379 /* Create OTG interface */ 380 tahvo_usb_power_off(tu); 381 tu->phy.dev = &pdev->dev; 382 tu->phy.state = OTG_STATE_UNDEFINED; 383 tu->phy.label = DRIVER_NAME; 384 tu->phy.set_suspend = tahvo_usb_set_suspend; 385 386 tu->phy.otg->phy = &tu->phy; 387 tu->phy.otg->set_host = tahvo_usb_set_host; 388 tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral; 389 390 ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2); 391 if (ret < 0) { 392 dev_err(&pdev->dev, "cannot register USB transceiver: %d\n", 393 ret); 394 goto err_extcon_unreg; 395 } 396 397 dev_set_drvdata(&pdev->dev, tu); 398 399 tu->irq = platform_get_irq(pdev, 0); 400 ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt, 0, 401 "tahvo-vbus", tu); 402 if (ret) { 403 dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n", 404 ret); 405 goto err_remove_phy; 406 } 407 408 /* Attributes */ 409 ret = sysfs_create_group(&pdev->dev.kobj, &tahvo_attr_group); 410 if (ret) { 411 dev_err(&pdev->dev, "cannot create sysfs group: %d\n", ret); 412 goto err_free_irq; 413 } 414 415 return 0; 416 417err_free_irq: 418 free_irq(tu->irq, tu); 419err_remove_phy: 420 usb_remove_phy(&tu->phy); 421err_extcon_unreg: 422 extcon_dev_unregister(&tu->extcon); 423err_disable_clk: 424 if (!IS_ERR(tu->ick)) 425 clk_disable(tu->ick); 426 427 return ret; 428} 429 430static int tahvo_usb_remove(struct platform_device *pdev) 431{ 432 struct tahvo_usb *tu = platform_get_drvdata(pdev); 433 434 sysfs_remove_group(&pdev->dev.kobj, &tahvo_attr_group); 435 free_irq(tu->irq, tu); 436 usb_remove_phy(&tu->phy); 437 extcon_dev_unregister(&tu->extcon); 438 if (!IS_ERR(tu->ick)) 439 clk_disable(tu->ick); 440 441 return 0; 442} 443 444static struct platform_driver tahvo_usb_driver = { 445 .probe = tahvo_usb_probe, 446 .remove = tahvo_usb_remove, 447 .driver = { 448 .name = "tahvo-usb", 449 .owner = THIS_MODULE, 450 }, 451}; 452module_platform_driver(tahvo_usb_driver); 453 454MODULE_DESCRIPTION("Tahvo USB transceiver driver"); 455MODULE_LICENSE("GPL"); 456MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs"); 457MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); 458