1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Motorola Mapphone MDM6600 modem GPIO controlled USB PHY driver 4 * Copyright (C) 2018 Tony Lindgren <tony@atomide.com> 5 */ 6 7 #include <linux/delay.h> 8 #include <linux/err.h> 9 #include <linux/io.h> 10 #include <linux/interrupt.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 #include <linux/platform_device.h> 14 #include <linux/slab.h> 15 16 #include <linux/gpio/consumer.h> 17 #include <linux/of_platform.h> 18 #include <linux/phy/phy.h> 19 20 #define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */ 21 #define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */ 22 #define MDM6600_MODEM_IDLE_DELAY_MS 1000 /* modem after USB suspend */ 23 #define MDM6600_MODEM_WAKE_DELAY_MS 200 /* modem response after idle */ 24 25 enum phy_mdm6600_ctrl_lines { 26 PHY_MDM6600_ENABLE, /* USB PHY enable */ 27 PHY_MDM6600_POWER, /* Device power */ 28 PHY_MDM6600_RESET, /* Device reset */ 29 PHY_MDM6600_NR_CTRL_LINES, 30 }; 31 32 enum phy_mdm6600_bootmode_lines { 33 PHY_MDM6600_MODE0, /* out USB mode0 and OOB wake */ 34 PHY_MDM6600_MODE1, /* out USB mode1, in OOB wake */ 35 PHY_MDM6600_NR_MODE_LINES, 36 }; 37 38 enum phy_mdm6600_cmd_lines { 39 PHY_MDM6600_CMD0, 40 PHY_MDM6600_CMD1, 41 PHY_MDM6600_CMD2, 42 PHY_MDM6600_NR_CMD_LINES, 43 }; 44 45 enum phy_mdm6600_status_lines { 46 PHY_MDM6600_STATUS0, 47 PHY_MDM6600_STATUS1, 48 PHY_MDM6600_STATUS2, 49 PHY_MDM6600_NR_STATUS_LINES, 50 }; 51 52 /* 53 * MDM6600 command codes. These are based on Motorola Mapphone Linux 54 * kernel tree. 55 */ 56 enum phy_mdm6600_cmd { 57 PHY_MDM6600_CMD_BP_PANIC_ACK, 58 PHY_MDM6600_CMD_DATA_ONLY_BYPASS, /* Reroute USB to CPCAP PHY */ 59 PHY_MDM6600_CMD_FULL_BYPASS, /* Reroute USB to CPCAP PHY */ 60 PHY_MDM6600_CMD_NO_BYPASS, /* Request normal USB mode */ 61 PHY_MDM6600_CMD_BP_SHUTDOWN_REQ, /* Request device power off */ 62 PHY_MDM6600_CMD_BP_UNKNOWN_5, 63 PHY_MDM6600_CMD_BP_UNKNOWN_6, 64 PHY_MDM6600_CMD_UNDEFINED, 65 }; 66 67 /* 68 * MDM6600 status codes. These are based on Motorola Mapphone Linux 69 * kernel tree. 70 */ 71 enum phy_mdm6600_status { 72 PHY_MDM6600_STATUS_PANIC, /* Seems to be really off */ 73 PHY_MDM6600_STATUS_PANIC_BUSY_WAIT, 74 PHY_MDM6600_STATUS_QC_DLOAD, 75 PHY_MDM6600_STATUS_RAM_DOWNLOADER, /* MDM6600 USB flashing mode */ 76 PHY_MDM6600_STATUS_PHONE_CODE_AWAKE, /* MDM6600 normal USB mode */ 77 PHY_MDM6600_STATUS_PHONE_CODE_ASLEEP, 78 PHY_MDM6600_STATUS_SHUTDOWN_ACK, 79 PHY_MDM6600_STATUS_UNDEFINED, 80 }; 81 82 static const char * const 83 phy_mdm6600_status_name[] = { 84 "off", "busy", "qc_dl", "ram_dl", "awake", 85 "asleep", "shutdown", "undefined", 86 }; 87 88 struct phy_mdm6600 { 89 struct device *dev; 90 struct phy *generic_phy; 91 struct phy_provider *phy_provider; 92 struct gpio_desc *ctrl_gpios[PHY_MDM6600_NR_CTRL_LINES]; 93 struct gpio_descs *mode_gpios; 94 struct gpio_descs *status_gpios; 95 struct gpio_descs *cmd_gpios; 96 struct delayed_work bootup_work; 97 struct delayed_work status_work; 98 struct delayed_work modem_wake_work; 99 struct completion ack; 100 bool enabled; /* mdm6600 phy enabled */ 101 bool running; /* mdm6600 boot done */ 102 bool awake; /* mdm6600 respnds on n_gsm */ 103 int status; 104 }; 105 106 static int phy_mdm6600_init(struct phy *x) 107 { 108 struct phy_mdm6600 *ddata = phy_get_drvdata(x); 109 struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; 110 111 if (!ddata->enabled) 112 return -EPROBE_DEFER; 113 114 gpiod_set_value_cansleep(enable_gpio, 0); 115 116 return 0; 117 } 118 119 static int phy_mdm6600_power_on(struct phy *x) 120 { 121 struct phy_mdm6600 *ddata = phy_get_drvdata(x); 122 struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; 123 124 if (!ddata->enabled) 125 return -ENODEV; 126 127 gpiod_set_value_cansleep(enable_gpio, 1); 128 129 return 0; 130 } 131 132 static int phy_mdm6600_power_off(struct phy *x) 133 { 134 struct phy_mdm6600 *ddata = phy_get_drvdata(x); 135 struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; 136 137 if (!ddata->enabled) 138 return -ENODEV; 139 140 gpiod_set_value_cansleep(enable_gpio, 0); 141 142 return 0; 143 } 144 145 static const struct phy_ops gpio_usb_ops = { 146 .init = phy_mdm6600_init, 147 .power_on = phy_mdm6600_power_on, 148 .power_off = phy_mdm6600_power_off, 149 .owner = THIS_MODULE, 150 }; 151 152 /** 153 * phy_mdm6600_cmd() - send a command request to mdm6600 154 * @ddata: device driver data 155 * 156 * Configures the three command request GPIOs to the specified value. 157 */ 158 static void phy_mdm6600_cmd(struct phy_mdm6600 *ddata, int val) 159 { 160 DECLARE_BITMAP(values, PHY_MDM6600_NR_CMD_LINES); 161 162 values[0] = val; 163 164 gpiod_set_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, 165 ddata->cmd_gpios->desc, 166 ddata->cmd_gpios->info, values); 167 } 168 169 /** 170 * phy_mdm6600_status() - read mdm6600 status lines 171 * @ddata: device driver data 172 */ 173 static void phy_mdm6600_status(struct work_struct *work) 174 { 175 struct phy_mdm6600 *ddata; 176 struct device *dev; 177 DECLARE_BITMAP(values, PHY_MDM6600_NR_STATUS_LINES); 178 int error, i, val = 0; 179 180 ddata = container_of(work, struct phy_mdm6600, status_work.work); 181 dev = ddata->dev; 182 183 error = gpiod_get_array_value_cansleep(PHY_MDM6600_NR_STATUS_LINES, 184 ddata->status_gpios->desc, 185 ddata->status_gpios->info, 186 values); 187 if (error) 188 return; 189 190 for (i = 0; i < PHY_MDM6600_NR_STATUS_LINES; i++) { 191 val |= test_bit(i, values) << i; 192 dev_dbg(ddata->dev, "XXX %s: i: %i values[i]: %i val: %i\n", 193 __func__, i, test_bit(i, values), val); 194 } 195 ddata->status = values[0]; 196 197 dev_info(dev, "modem status: %i %s\n", 198 ddata->status, 199 phy_mdm6600_status_name[ddata->status & 7]); 200 complete(&ddata->ack); 201 } 202 203 static irqreturn_t phy_mdm6600_irq_thread(int irq, void *data) 204 { 205 struct phy_mdm6600 *ddata = data; 206 207 schedule_delayed_work(&ddata->status_work, msecs_to_jiffies(10)); 208 209 return IRQ_HANDLED; 210 } 211 212 /** 213 * phy_mdm6600_wakeirq_thread - handle mode1 line OOB wake after booting 214 * @irq: interrupt 215 * @data: interrupt handler data 216 * 217 * GPIO mode1 is used initially as output to configure the USB boot 218 * mode for mdm6600. After booting it is used as input for OOB wake 219 * signal from mdm6600 to the SoC. Just use it for debug info only 220 * for now. 221 */ 222 static irqreturn_t phy_mdm6600_wakeirq_thread(int irq, void *data) 223 { 224 struct phy_mdm6600 *ddata = data; 225 struct gpio_desc *mode_gpio1; 226 227 mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; 228 dev_dbg(ddata->dev, "OOB wake on mode_gpio1: %i\n", 229 gpiod_get_value(mode_gpio1)); 230 231 return IRQ_HANDLED; 232 } 233 234 /** 235 * phy_mdm6600_init_irq() - initialize mdm6600 status IRQ lines 236 * @ddata: device driver data 237 */ 238 static void phy_mdm6600_init_irq(struct phy_mdm6600 *ddata) 239 { 240 struct device *dev = ddata->dev; 241 int i, error, irq; 242 243 for (i = PHY_MDM6600_STATUS0; 244 i <= PHY_MDM6600_STATUS2; i++) { 245 struct gpio_desc *gpio = ddata->status_gpios->desc[i]; 246 247 irq = gpiod_to_irq(gpio); 248 if (irq <= 0) 249 continue; 250 251 error = devm_request_threaded_irq(dev, irq, NULL, 252 phy_mdm6600_irq_thread, 253 IRQF_TRIGGER_RISING | 254 IRQF_TRIGGER_FALLING | 255 IRQF_ONESHOT, 256 "mdm6600", 257 ddata); 258 if (error) 259 dev_warn(dev, "no modem status irq%i: %i\n", 260 irq, error); 261 } 262 } 263 264 struct phy_mdm6600_map { 265 const char *name; 266 int direction; 267 }; 268 269 static const struct phy_mdm6600_map 270 phy_mdm6600_ctrl_gpio_map[PHY_MDM6600_NR_CTRL_LINES] = { 271 { "enable", GPIOD_OUT_LOW, }, /* low = phy disabled */ 272 { "power", GPIOD_OUT_LOW, }, /* low = off */ 273 { "reset", GPIOD_OUT_HIGH, }, /* high = reset */ 274 }; 275 276 /** 277 * phy_mdm6600_init_lines() - initialize mdm6600 GPIO lines 278 * @ddata: device driver data 279 */ 280 static int phy_mdm6600_init_lines(struct phy_mdm6600 *ddata) 281 { 282 struct device *dev = ddata->dev; 283 int i; 284 285 /* MDM6600 control lines */ 286 for (i = 0; i < ARRAY_SIZE(phy_mdm6600_ctrl_gpio_map); i++) { 287 const struct phy_mdm6600_map *map = 288 &phy_mdm6600_ctrl_gpio_map[i]; 289 struct gpio_desc **gpio = &ddata->ctrl_gpios[i]; 290 291 *gpio = devm_gpiod_get(dev, map->name, map->direction); 292 if (IS_ERR(*gpio)) { 293 dev_info(dev, "gpio %s error %li\n", 294 map->name, PTR_ERR(*gpio)); 295 return PTR_ERR(*gpio); 296 } 297 } 298 299 /* MDM6600 USB start-up mode output lines */ 300 ddata->mode_gpios = devm_gpiod_get_array(dev, "motorola,mode", 301 GPIOD_OUT_LOW); 302 if (IS_ERR(ddata->mode_gpios)) 303 return PTR_ERR(ddata->mode_gpios); 304 305 if (ddata->mode_gpios->ndescs != PHY_MDM6600_NR_MODE_LINES) 306 return -EINVAL; 307 308 /* MDM6600 status input lines */ 309 ddata->status_gpios = devm_gpiod_get_array(dev, "motorola,status", 310 GPIOD_IN); 311 if (IS_ERR(ddata->status_gpios)) 312 return PTR_ERR(ddata->status_gpios); 313 314 if (ddata->status_gpios->ndescs != PHY_MDM6600_NR_STATUS_LINES) 315 return -EINVAL; 316 317 /* MDM6600 cmd output lines */ 318 ddata->cmd_gpios = devm_gpiod_get_array(dev, "motorola,cmd", 319 GPIOD_OUT_LOW); 320 if (IS_ERR(ddata->cmd_gpios)) 321 return PTR_ERR(ddata->cmd_gpios); 322 323 if (ddata->cmd_gpios->ndescs != PHY_MDM6600_NR_CMD_LINES) 324 return -EINVAL; 325 326 return 0; 327 } 328 329 /** 330 * phy_mdm6600_device_power_on() - power on mdm6600 device 331 * @ddata: device driver data 332 * 333 * To get the integrated USB phy in MDM6600 takes some hoops. We must ensure 334 * the shared USB bootmode GPIOs are configured, then request modem start-up, 335 * reset and power-up.. And then we need to recycle the shared USB bootmode 336 * GPIOs as they are also used for Out of Band (OOB) wake for the USB and 337 * TS 27.010 serial mux. 338 */ 339 static int phy_mdm6600_device_power_on(struct phy_mdm6600 *ddata) 340 { 341 struct gpio_desc *mode_gpio0, *mode_gpio1, *reset_gpio, *power_gpio; 342 int error = 0, wakeirq; 343 344 mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0]; 345 mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; 346 reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; 347 power_gpio = ddata->ctrl_gpios[PHY_MDM6600_POWER]; 348 349 /* 350 * Shared GPIOs must be low for normal USB mode. After booting 351 * they are used for OOB wake signaling. These can be also used 352 * to configure USB flashing mode later on based on a module 353 * parameter. 354 */ 355 gpiod_set_value_cansleep(mode_gpio0, 0); 356 gpiod_set_value_cansleep(mode_gpio1, 0); 357 358 /* Request start-up mode */ 359 phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_NO_BYPASS); 360 361 /* Request a reset first */ 362 gpiod_set_value_cansleep(reset_gpio, 0); 363 msleep(100); 364 365 /* Toggle power GPIO to request mdm6600 to start */ 366 gpiod_set_value_cansleep(power_gpio, 1); 367 msleep(100); 368 gpiod_set_value_cansleep(power_gpio, 0); 369 370 /* 371 * Looks like the USB PHY needs between 2.2 to 4 seconds. 372 * If we try to use it before that, we will get L3 errors 373 * from omap-usb-host trying to access the PHY. See also 374 * phy_mdm6600_init() for -EPROBE_DEFER. 375 */ 376 msleep(PHY_MDM6600_PHY_DELAY_MS); 377 ddata->enabled = true; 378 379 /* Booting up the rest of MDM6600 will take total about 8 seconds */ 380 dev_info(ddata->dev, "Waiting for power up request to complete..\n"); 381 if (wait_for_completion_timeout(&ddata->ack, 382 msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS))) { 383 if (ddata->status > PHY_MDM6600_STATUS_PANIC && 384 ddata->status < PHY_MDM6600_STATUS_SHUTDOWN_ACK) 385 dev_info(ddata->dev, "Powered up OK\n"); 386 } else { 387 ddata->enabled = false; 388 error = -ETIMEDOUT; 389 dev_err(ddata->dev, "Timed out powering up\n"); 390 } 391 392 /* Reconfigure mode1 GPIO as input for OOB wake */ 393 gpiod_direction_input(mode_gpio1); 394 395 wakeirq = gpiod_to_irq(mode_gpio1); 396 if (wakeirq <= 0) 397 return wakeirq; 398 399 error = devm_request_threaded_irq(ddata->dev, wakeirq, NULL, 400 phy_mdm6600_wakeirq_thread, 401 IRQF_TRIGGER_RISING | 402 IRQF_TRIGGER_FALLING | 403 IRQF_ONESHOT, 404 "mdm6600-wake", 405 ddata); 406 if (error) 407 dev_warn(ddata->dev, "no modem wakeirq irq%i: %i\n", 408 wakeirq, error); 409 410 ddata->running = true; 411 412 return error; 413 } 414 415 /** 416 * phy_mdm6600_device_power_off() - power off mdm6600 device 417 * @ddata: device driver data 418 */ 419 static void phy_mdm6600_device_power_off(struct phy_mdm6600 *ddata) 420 { 421 struct gpio_desc *reset_gpio = 422 ddata->ctrl_gpios[PHY_MDM6600_RESET]; 423 424 ddata->enabled = false; 425 phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_BP_SHUTDOWN_REQ); 426 msleep(100); 427 428 gpiod_set_value_cansleep(reset_gpio, 1); 429 430 dev_info(ddata->dev, "Waiting for power down request to complete.. "); 431 if (wait_for_completion_timeout(&ddata->ack, 432 msecs_to_jiffies(5000))) { 433 if (ddata->status == PHY_MDM6600_STATUS_PANIC) 434 dev_info(ddata->dev, "Powered down OK\n"); 435 } else { 436 dev_err(ddata->dev, "Timed out powering down\n"); 437 } 438 } 439 440 static void phy_mdm6600_deferred_power_on(struct work_struct *work) 441 { 442 struct phy_mdm6600 *ddata; 443 int error; 444 445 ddata = container_of(work, struct phy_mdm6600, bootup_work.work); 446 447 error = phy_mdm6600_device_power_on(ddata); 448 if (error) 449 dev_err(ddata->dev, "Device not functional\n"); 450 } 451 452 /* 453 * USB suspend puts mdm6600 into low power mode. For any n_gsm using apps, 454 * we need to keep the modem awake by kicking it's mode0 GPIO. This will 455 * keep the modem awake for about 1.2 seconds. When no n_gsm apps are using 456 * the modem, runtime PM auto mode can be enabled so modem can enter low 457 * power mode. 458 */ 459 static void phy_mdm6600_wake_modem(struct phy_mdm6600 *ddata) 460 { 461 struct gpio_desc *mode_gpio0; 462 463 mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0]; 464 gpiod_set_value_cansleep(mode_gpio0, 1); 465 usleep_range(5, 15); 466 gpiod_set_value_cansleep(mode_gpio0, 0); 467 if (ddata->awake) 468 usleep_range(5, 15); 469 else 470 msleep(MDM6600_MODEM_WAKE_DELAY_MS); 471 } 472 473 static void phy_mdm6600_modem_wake(struct work_struct *work) 474 { 475 struct phy_mdm6600 *ddata; 476 477 ddata = container_of(work, struct phy_mdm6600, modem_wake_work.work); 478 phy_mdm6600_wake_modem(ddata); 479 schedule_delayed_work(&ddata->modem_wake_work, 480 msecs_to_jiffies(MDM6600_MODEM_IDLE_DELAY_MS)); 481 } 482 483 static int __maybe_unused phy_mdm6600_runtime_suspend(struct device *dev) 484 { 485 struct phy_mdm6600 *ddata = dev_get_drvdata(dev); 486 487 cancel_delayed_work_sync(&ddata->modem_wake_work); 488 ddata->awake = false; 489 490 return 0; 491 } 492 493 static int __maybe_unused phy_mdm6600_runtime_resume(struct device *dev) 494 { 495 struct phy_mdm6600 *ddata = dev_get_drvdata(dev); 496 497 phy_mdm6600_modem_wake(&ddata->modem_wake_work.work); 498 ddata->awake = true; 499 500 return 0; 501 } 502 503 static const struct dev_pm_ops phy_mdm6600_pm_ops = { 504 SET_RUNTIME_PM_OPS(phy_mdm6600_runtime_suspend, 505 phy_mdm6600_runtime_resume, NULL) 506 }; 507 508 static const struct of_device_id phy_mdm6600_id_table[] = { 509 { .compatible = "motorola,mapphone-mdm6600", }, 510 {}, 511 }; 512 MODULE_DEVICE_TABLE(of, phy_mdm6600_id_table); 513 514 static int phy_mdm6600_probe(struct platform_device *pdev) 515 { 516 struct phy_mdm6600 *ddata; 517 int error; 518 519 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 520 if (!ddata) 521 return -ENOMEM; 522 523 INIT_DELAYED_WORK(&ddata->bootup_work, 524 phy_mdm6600_deferred_power_on); 525 INIT_DELAYED_WORK(&ddata->status_work, phy_mdm6600_status); 526 INIT_DELAYED_WORK(&ddata->modem_wake_work, phy_mdm6600_modem_wake); 527 init_completion(&ddata->ack); 528 529 ddata->dev = &pdev->dev; 530 platform_set_drvdata(pdev, ddata); 531 532 error = phy_mdm6600_init_lines(ddata); 533 if (error) 534 return error; 535 536 phy_mdm6600_init_irq(ddata); 537 538 ddata->generic_phy = devm_phy_create(ddata->dev, NULL, &gpio_usb_ops); 539 if (IS_ERR(ddata->generic_phy)) { 540 error = PTR_ERR(ddata->generic_phy); 541 goto cleanup; 542 } 543 544 phy_set_drvdata(ddata->generic_phy, ddata); 545 546 ddata->phy_provider = 547 devm_of_phy_provider_register(ddata->dev, 548 of_phy_simple_xlate); 549 if (IS_ERR(ddata->phy_provider)) { 550 error = PTR_ERR(ddata->phy_provider); 551 goto cleanup; 552 } 553 554 schedule_delayed_work(&ddata->bootup_work, 0); 555 556 /* 557 * See phy_mdm6600_device_power_on(). We should be able 558 * to remove this eventually when ohci-platform can deal 559 * with -EPROBE_DEFER. 560 */ 561 msleep(PHY_MDM6600_PHY_DELAY_MS + 500); 562 563 /* 564 * Enable PM runtime only after PHY has been powered up properly. 565 * It is currently only needed after USB suspends mdm6600 and n_gsm 566 * needs to access the device. We don't want to do this earlier as 567 * gpio mode0 pin doubles as mdm6600 wake-up gpio. 568 */ 569 pm_runtime_use_autosuspend(ddata->dev); 570 pm_runtime_set_autosuspend_delay(ddata->dev, 571 MDM6600_MODEM_IDLE_DELAY_MS); 572 pm_runtime_enable(ddata->dev); 573 error = pm_runtime_get_sync(ddata->dev); 574 if (error < 0) { 575 dev_warn(ddata->dev, "failed to wake modem: %i\n", error); 576 pm_runtime_put_noidle(ddata->dev); 577 } 578 pm_runtime_mark_last_busy(ddata->dev); 579 pm_runtime_put_autosuspend(ddata->dev); 580 581 return 0; 582 583 cleanup: 584 phy_mdm6600_device_power_off(ddata); 585 return error; 586 } 587 588 static int phy_mdm6600_remove(struct platform_device *pdev) 589 { 590 struct phy_mdm6600 *ddata = platform_get_drvdata(pdev); 591 struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; 592 593 pm_runtime_dont_use_autosuspend(ddata->dev); 594 pm_runtime_put_sync(ddata->dev); 595 pm_runtime_disable(ddata->dev); 596 597 if (!ddata->running) 598 wait_for_completion_timeout(&ddata->ack, 599 msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS)); 600 601 gpiod_set_value_cansleep(reset_gpio, 1); 602 phy_mdm6600_device_power_off(ddata); 603 604 cancel_delayed_work_sync(&ddata->modem_wake_work); 605 cancel_delayed_work_sync(&ddata->bootup_work); 606 cancel_delayed_work_sync(&ddata->status_work); 607 608 return 0; 609 } 610 611 static struct platform_driver phy_mdm6600_driver = { 612 .probe = phy_mdm6600_probe, 613 .remove = phy_mdm6600_remove, 614 .driver = { 615 .name = "phy-mapphone-mdm6600", 616 .pm = &phy_mdm6600_pm_ops, 617 .of_match_table = of_match_ptr(phy_mdm6600_id_table), 618 }, 619 }; 620 621 module_platform_driver(phy_mdm6600_driver); 622 623 MODULE_ALIAS("platform:gpio_usb"); 624 MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); 625 MODULE_DESCRIPTION("mdm6600 gpio usb phy driver"); 626 MODULE_LICENSE("GPL v2"); 627