15d1ebbdaSTony Lindgren // SPDX-License-Identifier: GPL-2.0 25d1ebbdaSTony Lindgren /* 35d1ebbdaSTony Lindgren * Motorola Mapphone MDM6600 modem GPIO controlled USB PHY driver 45d1ebbdaSTony Lindgren * Copyright (C) 2018 Tony Lindgren <tony@atomide.com> 55d1ebbdaSTony Lindgren */ 65d1ebbdaSTony Lindgren 75d1ebbdaSTony Lindgren #include <linux/delay.h> 85d1ebbdaSTony Lindgren #include <linux/err.h> 95d1ebbdaSTony Lindgren #include <linux/io.h> 105d1ebbdaSTony Lindgren #include <linux/interrupt.h> 115d1ebbdaSTony Lindgren #include <linux/module.h> 125d1ebbdaSTony Lindgren #include <linux/of.h> 135d1ebbdaSTony Lindgren #include <linux/platform_device.h> 145d1ebbdaSTony Lindgren #include <linux/slab.h> 155d1ebbdaSTony Lindgren 165d1ebbdaSTony Lindgren #include <linux/gpio/consumer.h> 175d1ebbdaSTony Lindgren #include <linux/of_platform.h> 185d1ebbdaSTony Lindgren #include <linux/phy/phy.h> 195d1ebbdaSTony Lindgren 205d1ebbdaSTony Lindgren #define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */ 215d1ebbdaSTony Lindgren #define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */ 225d1ebbdaSTony Lindgren 235d1ebbdaSTony Lindgren enum phy_mdm6600_ctrl_lines { 245d1ebbdaSTony Lindgren PHY_MDM6600_ENABLE, /* USB PHY enable */ 255d1ebbdaSTony Lindgren PHY_MDM6600_POWER, /* Device power */ 265d1ebbdaSTony Lindgren PHY_MDM6600_RESET, /* Device reset */ 275d1ebbdaSTony Lindgren PHY_MDM6600_NR_CTRL_LINES, 285d1ebbdaSTony Lindgren }; 295d1ebbdaSTony Lindgren 305d1ebbdaSTony Lindgren enum phy_mdm6600_bootmode_lines { 315d1ebbdaSTony Lindgren PHY_MDM6600_MODE0, /* out USB mode0 and OOB wake */ 325d1ebbdaSTony Lindgren PHY_MDM6600_MODE1, /* out USB mode1, in OOB wake */ 335d1ebbdaSTony Lindgren PHY_MDM6600_NR_MODE_LINES, 345d1ebbdaSTony Lindgren }; 355d1ebbdaSTony Lindgren 365d1ebbdaSTony Lindgren enum phy_mdm6600_cmd_lines { 375d1ebbdaSTony Lindgren PHY_MDM6600_CMD0, 385d1ebbdaSTony Lindgren PHY_MDM6600_CMD1, 395d1ebbdaSTony Lindgren PHY_MDM6600_CMD2, 405d1ebbdaSTony Lindgren PHY_MDM6600_NR_CMD_LINES, 415d1ebbdaSTony Lindgren }; 425d1ebbdaSTony Lindgren 435d1ebbdaSTony Lindgren enum phy_mdm6600_status_lines { 445d1ebbdaSTony Lindgren PHY_MDM6600_STATUS0, 455d1ebbdaSTony Lindgren PHY_MDM6600_STATUS1, 465d1ebbdaSTony Lindgren PHY_MDM6600_STATUS2, 475d1ebbdaSTony Lindgren PHY_MDM6600_NR_STATUS_LINES, 485d1ebbdaSTony Lindgren }; 495d1ebbdaSTony Lindgren 505d1ebbdaSTony Lindgren /* 515d1ebbdaSTony Lindgren * MDM6600 command codes. These are based on Motorola Mapphone Linux 525d1ebbdaSTony Lindgren * kernel tree. 535d1ebbdaSTony Lindgren */ 545d1ebbdaSTony Lindgren enum phy_mdm6600_cmd { 555d1ebbdaSTony Lindgren PHY_MDM6600_CMD_BP_PANIC_ACK, 565d1ebbdaSTony Lindgren PHY_MDM6600_CMD_DATA_ONLY_BYPASS, /* Reroute USB to CPCAP PHY */ 575d1ebbdaSTony Lindgren PHY_MDM6600_CMD_FULL_BYPASS, /* Reroute USB to CPCAP PHY */ 585d1ebbdaSTony Lindgren PHY_MDM6600_CMD_NO_BYPASS, /* Request normal USB mode */ 595d1ebbdaSTony Lindgren PHY_MDM6600_CMD_BP_SHUTDOWN_REQ, /* Request device power off */ 605d1ebbdaSTony Lindgren PHY_MDM6600_CMD_BP_UNKNOWN_5, 615d1ebbdaSTony Lindgren PHY_MDM6600_CMD_BP_UNKNOWN_6, 625d1ebbdaSTony Lindgren PHY_MDM6600_CMD_UNDEFINED, 635d1ebbdaSTony Lindgren }; 645d1ebbdaSTony Lindgren 655d1ebbdaSTony Lindgren /* 665d1ebbdaSTony Lindgren * MDM6600 status codes. These are based on Motorola Mapphone Linux 675d1ebbdaSTony Lindgren * kernel tree. 685d1ebbdaSTony Lindgren */ 695d1ebbdaSTony Lindgren enum phy_mdm6600_status { 705d1ebbdaSTony Lindgren PHY_MDM6600_STATUS_PANIC, /* Seems to be really off */ 715d1ebbdaSTony Lindgren PHY_MDM6600_STATUS_PANIC_BUSY_WAIT, 725d1ebbdaSTony Lindgren PHY_MDM6600_STATUS_QC_DLOAD, 735d1ebbdaSTony Lindgren PHY_MDM6600_STATUS_RAM_DOWNLOADER, /* MDM6600 USB flashing mode */ 745d1ebbdaSTony Lindgren PHY_MDM6600_STATUS_PHONE_CODE_AWAKE, /* MDM6600 normal USB mode */ 755d1ebbdaSTony Lindgren PHY_MDM6600_STATUS_PHONE_CODE_ASLEEP, 765d1ebbdaSTony Lindgren PHY_MDM6600_STATUS_SHUTDOWN_ACK, 775d1ebbdaSTony Lindgren PHY_MDM6600_STATUS_UNDEFINED, 785d1ebbdaSTony Lindgren }; 795d1ebbdaSTony Lindgren 805d1ebbdaSTony Lindgren static const char * const 815d1ebbdaSTony Lindgren phy_mdm6600_status_name[] = { 825d1ebbdaSTony Lindgren "off", "busy", "qc_dl", "ram_dl", "awake", 835d1ebbdaSTony Lindgren "asleep", "shutdown", "undefined", 845d1ebbdaSTony Lindgren }; 855d1ebbdaSTony Lindgren 865d1ebbdaSTony Lindgren struct phy_mdm6600 { 875d1ebbdaSTony Lindgren struct device *dev; 885d1ebbdaSTony Lindgren struct phy *generic_phy; 895d1ebbdaSTony Lindgren struct phy_provider *phy_provider; 905d1ebbdaSTony Lindgren struct gpio_desc *ctrl_gpios[PHY_MDM6600_NR_CTRL_LINES]; 915d1ebbdaSTony Lindgren struct gpio_descs *mode_gpios; 925d1ebbdaSTony Lindgren struct gpio_descs *status_gpios; 935d1ebbdaSTony Lindgren struct gpio_descs *cmd_gpios; 945d1ebbdaSTony Lindgren struct delayed_work bootup_work; 955d1ebbdaSTony Lindgren struct delayed_work status_work; 965d1ebbdaSTony Lindgren struct completion ack; 975d1ebbdaSTony Lindgren bool enabled; /* mdm6600 phy enabled */ 985d1ebbdaSTony Lindgren bool running; /* mdm6600 boot done */ 995d1ebbdaSTony Lindgren int status; 1005d1ebbdaSTony Lindgren }; 1015d1ebbdaSTony Lindgren 1025d1ebbdaSTony Lindgren static int phy_mdm6600_init(struct phy *x) 1035d1ebbdaSTony Lindgren { 1045d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata = phy_get_drvdata(x); 1055d1ebbdaSTony Lindgren struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; 1065d1ebbdaSTony Lindgren 1075d1ebbdaSTony Lindgren if (!ddata->enabled) 1085d1ebbdaSTony Lindgren return -EPROBE_DEFER; 1095d1ebbdaSTony Lindgren 1105d1ebbdaSTony Lindgren gpiod_set_value_cansleep(enable_gpio, 0); 1115d1ebbdaSTony Lindgren 1125d1ebbdaSTony Lindgren return 0; 1135d1ebbdaSTony Lindgren } 1145d1ebbdaSTony Lindgren 1155d1ebbdaSTony Lindgren static int phy_mdm6600_power_on(struct phy *x) 1165d1ebbdaSTony Lindgren { 1175d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata = phy_get_drvdata(x); 1185d1ebbdaSTony Lindgren struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; 1195d1ebbdaSTony Lindgren 1205d1ebbdaSTony Lindgren if (!ddata->enabled) 1215d1ebbdaSTony Lindgren return -ENODEV; 1225d1ebbdaSTony Lindgren 1235d1ebbdaSTony Lindgren gpiod_set_value_cansleep(enable_gpio, 1); 1245d1ebbdaSTony Lindgren 1255d1ebbdaSTony Lindgren return 0; 1265d1ebbdaSTony Lindgren } 1275d1ebbdaSTony Lindgren 1285d1ebbdaSTony Lindgren static int phy_mdm6600_power_off(struct phy *x) 1295d1ebbdaSTony Lindgren { 1305d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata = phy_get_drvdata(x); 1315d1ebbdaSTony Lindgren struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; 1325d1ebbdaSTony Lindgren 1335d1ebbdaSTony Lindgren if (!ddata->enabled) 1345d1ebbdaSTony Lindgren return -ENODEV; 1355d1ebbdaSTony Lindgren 1365d1ebbdaSTony Lindgren gpiod_set_value_cansleep(enable_gpio, 0); 1375d1ebbdaSTony Lindgren 1385d1ebbdaSTony Lindgren return 0; 1395d1ebbdaSTony Lindgren } 1405d1ebbdaSTony Lindgren 1415d1ebbdaSTony Lindgren static const struct phy_ops gpio_usb_ops = { 1425d1ebbdaSTony Lindgren .init = phy_mdm6600_init, 1435d1ebbdaSTony Lindgren .power_on = phy_mdm6600_power_on, 1445d1ebbdaSTony Lindgren .power_off = phy_mdm6600_power_off, 1455d1ebbdaSTony Lindgren .owner = THIS_MODULE, 1465d1ebbdaSTony Lindgren }; 1475d1ebbdaSTony Lindgren 1485d1ebbdaSTony Lindgren /** 1495d1ebbdaSTony Lindgren * phy_mdm6600_cmd() - send a command request to mdm6600 1505d1ebbdaSTony Lindgren * @ddata: device driver data 1515d1ebbdaSTony Lindgren * 1525d1ebbdaSTony Lindgren * Configures the three command request GPIOs to the specified value. 1535d1ebbdaSTony Lindgren */ 1545d1ebbdaSTony Lindgren static void phy_mdm6600_cmd(struct phy_mdm6600 *ddata, int val) 1555d1ebbdaSTony Lindgren { 1565d1ebbdaSTony Lindgren int values[PHY_MDM6600_NR_CMD_LINES]; 1575d1ebbdaSTony Lindgren int i; 1585d1ebbdaSTony Lindgren 1595d1ebbdaSTony Lindgren val &= (1 << PHY_MDM6600_NR_CMD_LINES) - 1; 1605d1ebbdaSTony Lindgren for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++) 1615d1ebbdaSTony Lindgren values[i] = (val & BIT(i)) >> i; 1625d1ebbdaSTony Lindgren 1635d1ebbdaSTony Lindgren gpiod_set_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, 1645d1ebbdaSTony Lindgren ddata->cmd_gpios->desc, values); 1655d1ebbdaSTony Lindgren } 1665d1ebbdaSTony Lindgren 1675d1ebbdaSTony Lindgren /** 1685d1ebbdaSTony Lindgren * phy_mdm6600_status() - read mdm6600 status lines 1695d1ebbdaSTony Lindgren * @ddata: device driver data 1705d1ebbdaSTony Lindgren */ 1715d1ebbdaSTony Lindgren static void phy_mdm6600_status(struct work_struct *work) 1725d1ebbdaSTony Lindgren { 1735d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata; 1745d1ebbdaSTony Lindgren struct device *dev; 1755d1ebbdaSTony Lindgren int values[PHY_MDM6600_NR_STATUS_LINES]; 1765d1ebbdaSTony Lindgren int error, i, val = 0; 1775d1ebbdaSTony Lindgren 1785d1ebbdaSTony Lindgren ddata = container_of(work, struct phy_mdm6600, status_work.work); 1795d1ebbdaSTony Lindgren dev = ddata->dev; 1805d1ebbdaSTony Lindgren 1815d1ebbdaSTony Lindgren error = gpiod_get_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, 1825d1ebbdaSTony Lindgren ddata->status_gpios->desc, 1835d1ebbdaSTony Lindgren values); 1845d1ebbdaSTony Lindgren if (error) 1855d1ebbdaSTony Lindgren return; 1865d1ebbdaSTony Lindgren 1875d1ebbdaSTony Lindgren for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++) { 1885d1ebbdaSTony Lindgren val |= values[i] << i; 1895d1ebbdaSTony Lindgren dev_dbg(ddata->dev, "XXX %s: i: %i values[i]: %i val: %i\n", 1905d1ebbdaSTony Lindgren __func__, i, values[i], val); 1915d1ebbdaSTony Lindgren } 1925d1ebbdaSTony Lindgren ddata->status = val; 1935d1ebbdaSTony Lindgren 1945d1ebbdaSTony Lindgren dev_info(dev, "modem status: %i %s\n", 1955d1ebbdaSTony Lindgren ddata->status, 1965d1ebbdaSTony Lindgren phy_mdm6600_status_name[ddata->status & 7]); 1975d1ebbdaSTony Lindgren complete(&ddata->ack); 1985d1ebbdaSTony Lindgren } 1995d1ebbdaSTony Lindgren 2005d1ebbdaSTony Lindgren static irqreturn_t phy_mdm6600_irq_thread(int irq, void *data) 2015d1ebbdaSTony Lindgren { 2025d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata = data; 2035d1ebbdaSTony Lindgren 2045d1ebbdaSTony Lindgren schedule_delayed_work(&ddata->status_work, msecs_to_jiffies(10)); 2055d1ebbdaSTony Lindgren 2065d1ebbdaSTony Lindgren return IRQ_HANDLED; 2075d1ebbdaSTony Lindgren } 2085d1ebbdaSTony Lindgren 2095d1ebbdaSTony Lindgren /** 2105d1ebbdaSTony Lindgren * phy_mdm6600_wakeirq_thread - handle mode1 line OOB wake after booting 2115d1ebbdaSTony Lindgren * @irq: interrupt 2125d1ebbdaSTony Lindgren * @data: interrupt handler data 2135d1ebbdaSTony Lindgren * 2145d1ebbdaSTony Lindgren * GPIO mode1 is used initially as output to configure the USB boot 2155d1ebbdaSTony Lindgren * mode for mdm6600. After booting it is used as input for OOB wake 2165d1ebbdaSTony Lindgren * signal from mdm6600 to the SoC. Just use it for debug info only 2175d1ebbdaSTony Lindgren * for now. 2185d1ebbdaSTony Lindgren */ 2195d1ebbdaSTony Lindgren static irqreturn_t phy_mdm6600_wakeirq_thread(int irq, void *data) 2205d1ebbdaSTony Lindgren { 2215d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata = data; 2225d1ebbdaSTony Lindgren struct gpio_desc *mode_gpio1; 2235d1ebbdaSTony Lindgren 2245d1ebbdaSTony Lindgren mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; 2255d1ebbdaSTony Lindgren dev_dbg(ddata->dev, "OOB wake on mode_gpio1: %i\n", 2265d1ebbdaSTony Lindgren gpiod_get_value(mode_gpio1)); 2275d1ebbdaSTony Lindgren 2285d1ebbdaSTony Lindgren return IRQ_HANDLED; 2295d1ebbdaSTony Lindgren } 2305d1ebbdaSTony Lindgren 2315d1ebbdaSTony Lindgren /** 2325d1ebbdaSTony Lindgren * phy_mdm6600_init_irq() - initialize mdm6600 status IRQ lines 2335d1ebbdaSTony Lindgren * @ddata: device driver data 2345d1ebbdaSTony Lindgren */ 2355d1ebbdaSTony Lindgren static void phy_mdm6600_init_irq(struct phy_mdm6600 *ddata) 2365d1ebbdaSTony Lindgren { 2375d1ebbdaSTony Lindgren struct device *dev = ddata->dev; 2385d1ebbdaSTony Lindgren int i, error, irq; 2395d1ebbdaSTony Lindgren 2405d1ebbdaSTony Lindgren for (i = PHY_MDM6600_STATUS0; 2415d1ebbdaSTony Lindgren i <= PHY_MDM6600_STATUS2; i++) { 2425d1ebbdaSTony Lindgren struct gpio_desc *gpio = ddata->status_gpios->desc[i]; 2435d1ebbdaSTony Lindgren 2445d1ebbdaSTony Lindgren irq = gpiod_to_irq(gpio); 2455d1ebbdaSTony Lindgren if (irq <= 0) 2465d1ebbdaSTony Lindgren continue; 2475d1ebbdaSTony Lindgren 2485d1ebbdaSTony Lindgren error = devm_request_threaded_irq(dev, irq, NULL, 2495d1ebbdaSTony Lindgren phy_mdm6600_irq_thread, 2505d1ebbdaSTony Lindgren IRQF_TRIGGER_RISING | 2515d1ebbdaSTony Lindgren IRQF_TRIGGER_FALLING | 2525d1ebbdaSTony Lindgren IRQF_ONESHOT, 2535d1ebbdaSTony Lindgren "mdm6600", 2545d1ebbdaSTony Lindgren ddata); 2555d1ebbdaSTony Lindgren if (error) 2565d1ebbdaSTony Lindgren dev_warn(dev, "no modem status irq%i: %i\n", 2575d1ebbdaSTony Lindgren irq, error); 2585d1ebbdaSTony Lindgren } 2595d1ebbdaSTony Lindgren } 2605d1ebbdaSTony Lindgren 2615d1ebbdaSTony Lindgren struct phy_mdm6600_map { 2625d1ebbdaSTony Lindgren const char *name; 2635d1ebbdaSTony Lindgren int direction; 2645d1ebbdaSTony Lindgren }; 2655d1ebbdaSTony Lindgren 2665d1ebbdaSTony Lindgren static const struct phy_mdm6600_map 2675d1ebbdaSTony Lindgren phy_mdm6600_ctrl_gpio_map[PHY_MDM6600_NR_CTRL_LINES] = { 2685d1ebbdaSTony Lindgren { "enable", GPIOD_OUT_LOW, }, /* low = phy disabled */ 2695d1ebbdaSTony Lindgren { "power", GPIOD_OUT_LOW, }, /* low = off */ 2705d1ebbdaSTony Lindgren { "reset", GPIOD_OUT_HIGH, }, /* high = reset */ 2715d1ebbdaSTony Lindgren }; 2725d1ebbdaSTony Lindgren 2735d1ebbdaSTony Lindgren /** 2745d1ebbdaSTony Lindgren * phy_mdm6600_init_lines() - initialize mdm6600 GPIO lines 2755d1ebbdaSTony Lindgren * @ddata: device driver data 2765d1ebbdaSTony Lindgren */ 2775d1ebbdaSTony Lindgren static int phy_mdm6600_init_lines(struct phy_mdm6600 *ddata) 2785d1ebbdaSTony Lindgren { 2795d1ebbdaSTony Lindgren struct device *dev = ddata->dev; 2805d1ebbdaSTony Lindgren int i; 2815d1ebbdaSTony Lindgren 2825d1ebbdaSTony Lindgren /* MDM6600 control lines */ 2835d1ebbdaSTony Lindgren for (i = 0; i < ARRAY_SIZE(phy_mdm6600_ctrl_gpio_map); i++) { 2845d1ebbdaSTony Lindgren const struct phy_mdm6600_map *map = 2855d1ebbdaSTony Lindgren &phy_mdm6600_ctrl_gpio_map[i]; 2865d1ebbdaSTony Lindgren struct gpio_desc **gpio = &ddata->ctrl_gpios[i]; 2875d1ebbdaSTony Lindgren 2885d1ebbdaSTony Lindgren *gpio = devm_gpiod_get(dev, map->name, map->direction); 2895d1ebbdaSTony Lindgren if (IS_ERR(*gpio)) { 2905d1ebbdaSTony Lindgren dev_info(dev, "gpio %s error %li\n", 2915d1ebbdaSTony Lindgren map->name, PTR_ERR(*gpio)); 2925d1ebbdaSTony Lindgren return PTR_ERR(*gpio); 2935d1ebbdaSTony Lindgren } 2945d1ebbdaSTony Lindgren } 2955d1ebbdaSTony Lindgren 2965d1ebbdaSTony Lindgren /* MDM6600 USB start-up mode output lines */ 2975d1ebbdaSTony Lindgren ddata->mode_gpios = devm_gpiod_get_array(dev, "motorola,mode", 2985d1ebbdaSTony Lindgren GPIOD_OUT_LOW); 2995d1ebbdaSTony Lindgren if (IS_ERR(ddata->mode_gpios)) 3005d1ebbdaSTony Lindgren return PTR_ERR(ddata->mode_gpios); 3015d1ebbdaSTony Lindgren 3025d1ebbdaSTony Lindgren if (ddata->mode_gpios->ndescs != PHY_MDM6600_NR_MODE_LINES) 3035d1ebbdaSTony Lindgren return -EINVAL; 3045d1ebbdaSTony Lindgren 3055d1ebbdaSTony Lindgren /* MDM6600 status input lines */ 3065d1ebbdaSTony Lindgren ddata->status_gpios = devm_gpiod_get_array(dev, "motorola,status", 3075d1ebbdaSTony Lindgren GPIOD_IN); 3085d1ebbdaSTony Lindgren if (IS_ERR(ddata->status_gpios)) 3095d1ebbdaSTony Lindgren return PTR_ERR(ddata->status_gpios); 3105d1ebbdaSTony Lindgren 3115d1ebbdaSTony Lindgren if (ddata->status_gpios->ndescs != PHY_MDM6600_NR_STATUS_LINES) 3125d1ebbdaSTony Lindgren return -EINVAL; 3135d1ebbdaSTony Lindgren 3145d1ebbdaSTony Lindgren /* MDM6600 cmd output lines */ 3155d1ebbdaSTony Lindgren ddata->cmd_gpios = devm_gpiod_get_array(dev, "motorola,cmd", 3165d1ebbdaSTony Lindgren GPIOD_OUT_LOW); 3175d1ebbdaSTony Lindgren if (IS_ERR(ddata->cmd_gpios)) 3185d1ebbdaSTony Lindgren return PTR_ERR(ddata->cmd_gpios); 3195d1ebbdaSTony Lindgren 3205d1ebbdaSTony Lindgren if (ddata->cmd_gpios->ndescs != PHY_MDM6600_NR_CMD_LINES) 3215d1ebbdaSTony Lindgren return -EINVAL; 3225d1ebbdaSTony Lindgren 3235d1ebbdaSTony Lindgren return 0; 3245d1ebbdaSTony Lindgren } 3255d1ebbdaSTony Lindgren 3265d1ebbdaSTony Lindgren /** 3275d1ebbdaSTony Lindgren * phy_mdm6600_device_power_on() - power on mdm6600 device 3285d1ebbdaSTony Lindgren * @ddata: device driver data 3295d1ebbdaSTony Lindgren * 3305d1ebbdaSTony Lindgren * To get the integrated USB phy in MDM6600 takes some hoops. We must ensure 3315d1ebbdaSTony Lindgren * the shared USB bootmode GPIOs are configured, then request modem start-up, 3325d1ebbdaSTony Lindgren * reset and power-up.. And then we need to recycle the shared USB bootmode 3335d1ebbdaSTony Lindgren * GPIOs as they are also used for Out of Band (OOB) wake for the USB and 3345d1ebbdaSTony Lindgren * TS 27.010 serial mux. 3355d1ebbdaSTony Lindgren */ 3365d1ebbdaSTony Lindgren static int phy_mdm6600_device_power_on(struct phy_mdm6600 *ddata) 3375d1ebbdaSTony Lindgren { 3385d1ebbdaSTony Lindgren struct gpio_desc *mode_gpio0, *mode_gpio1, *reset_gpio, *power_gpio; 3395d1ebbdaSTony Lindgren int error = 0, wakeirq; 3405d1ebbdaSTony Lindgren 3415d1ebbdaSTony Lindgren mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0]; 3425d1ebbdaSTony Lindgren mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; 3435d1ebbdaSTony Lindgren reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; 3445d1ebbdaSTony Lindgren power_gpio = ddata->ctrl_gpios[PHY_MDM6600_POWER]; 3455d1ebbdaSTony Lindgren 3465d1ebbdaSTony Lindgren /* 3475d1ebbdaSTony Lindgren * Shared GPIOs must be low for normal USB mode. After booting 3485d1ebbdaSTony Lindgren * they are used for OOB wake signaling. These can be also used 3495d1ebbdaSTony Lindgren * to configure USB flashing mode later on based on a module 3505d1ebbdaSTony Lindgren * parameter. 3515d1ebbdaSTony Lindgren */ 3525d1ebbdaSTony Lindgren gpiod_set_value_cansleep(mode_gpio0, 0); 3535d1ebbdaSTony Lindgren gpiod_set_value_cansleep(mode_gpio1, 0); 3545d1ebbdaSTony Lindgren 3555d1ebbdaSTony Lindgren /* Request start-up mode */ 3565d1ebbdaSTony Lindgren phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_NO_BYPASS); 3575d1ebbdaSTony Lindgren 3585d1ebbdaSTony Lindgren /* Request a reset first */ 3595d1ebbdaSTony Lindgren gpiod_set_value_cansleep(reset_gpio, 0); 3605d1ebbdaSTony Lindgren msleep(100); 3615d1ebbdaSTony Lindgren 3625d1ebbdaSTony Lindgren /* Toggle power GPIO to request mdm6600 to start */ 3635d1ebbdaSTony Lindgren gpiod_set_value_cansleep(power_gpio, 1); 3645d1ebbdaSTony Lindgren msleep(100); 3655d1ebbdaSTony Lindgren gpiod_set_value_cansleep(power_gpio, 0); 3665d1ebbdaSTony Lindgren 3675d1ebbdaSTony Lindgren /* 3685d1ebbdaSTony Lindgren * Looks like the USB PHY needs between 2.2 to 4 seconds. 3695d1ebbdaSTony Lindgren * If we try to use it before that, we will get L3 errors 3705d1ebbdaSTony Lindgren * from omap-usb-host trying to access the PHY. See also 3715d1ebbdaSTony Lindgren * phy_mdm6600_init() for -EPROBE_DEFER. 3725d1ebbdaSTony Lindgren */ 3735d1ebbdaSTony Lindgren msleep(PHY_MDM6600_PHY_DELAY_MS); 3745d1ebbdaSTony Lindgren ddata->enabled = true; 3755d1ebbdaSTony Lindgren 3765d1ebbdaSTony Lindgren /* Booting up the rest of MDM6600 will take total about 8 seconds */ 3775d1ebbdaSTony Lindgren dev_info(ddata->dev, "Waiting for power up request to complete..\n"); 3785d1ebbdaSTony Lindgren if (wait_for_completion_timeout(&ddata->ack, 3795d1ebbdaSTony Lindgren msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS))) { 3805d1ebbdaSTony Lindgren if (ddata->status > PHY_MDM6600_STATUS_PANIC && 3815d1ebbdaSTony Lindgren ddata->status < PHY_MDM6600_STATUS_SHUTDOWN_ACK) 3825d1ebbdaSTony Lindgren dev_info(ddata->dev, "Powered up OK\n"); 3835d1ebbdaSTony Lindgren } else { 3845d1ebbdaSTony Lindgren ddata->enabled = false; 3855d1ebbdaSTony Lindgren error = -ETIMEDOUT; 3865d1ebbdaSTony Lindgren dev_err(ddata->dev, "Timed out powering up\n"); 3875d1ebbdaSTony Lindgren } 3885d1ebbdaSTony Lindgren 3895d1ebbdaSTony Lindgren /* Reconfigure mode1 GPIO as input for OOB wake */ 3905d1ebbdaSTony Lindgren gpiod_direction_input(mode_gpio1); 3915d1ebbdaSTony Lindgren 3925d1ebbdaSTony Lindgren wakeirq = gpiod_to_irq(mode_gpio1); 3935d1ebbdaSTony Lindgren if (wakeirq <= 0) 3945d1ebbdaSTony Lindgren return wakeirq; 3955d1ebbdaSTony Lindgren 3965d1ebbdaSTony Lindgren error = devm_request_threaded_irq(ddata->dev, wakeirq, NULL, 3975d1ebbdaSTony Lindgren phy_mdm6600_wakeirq_thread, 3985d1ebbdaSTony Lindgren IRQF_TRIGGER_RISING | 3995d1ebbdaSTony Lindgren IRQF_TRIGGER_FALLING | 4005d1ebbdaSTony Lindgren IRQF_ONESHOT, 4015d1ebbdaSTony Lindgren "mdm6600-wake", 4025d1ebbdaSTony Lindgren ddata); 4035d1ebbdaSTony Lindgren if (error) 4045d1ebbdaSTony Lindgren dev_warn(ddata->dev, "no modem wakeirq irq%i: %i\n", 4055d1ebbdaSTony Lindgren wakeirq, error); 4065d1ebbdaSTony Lindgren 4075d1ebbdaSTony Lindgren ddata->running = true; 4085d1ebbdaSTony Lindgren 4095d1ebbdaSTony Lindgren return error; 4105d1ebbdaSTony Lindgren } 4115d1ebbdaSTony Lindgren 4125d1ebbdaSTony Lindgren /** 4135d1ebbdaSTony Lindgren * phy_mdm6600_device_power_off() - power off mdm6600 device 4145d1ebbdaSTony Lindgren * @ddata: device driver data 4155d1ebbdaSTony Lindgren */ 4165d1ebbdaSTony Lindgren static void phy_mdm6600_device_power_off(struct phy_mdm6600 *ddata) 4175d1ebbdaSTony Lindgren { 4185d1ebbdaSTony Lindgren struct gpio_desc *reset_gpio = 4195d1ebbdaSTony Lindgren ddata->ctrl_gpios[PHY_MDM6600_RESET]; 4205d1ebbdaSTony Lindgren 4215d1ebbdaSTony Lindgren ddata->enabled = false; 4225d1ebbdaSTony Lindgren phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_BP_SHUTDOWN_REQ); 4235d1ebbdaSTony Lindgren msleep(100); 4245d1ebbdaSTony Lindgren 4255d1ebbdaSTony Lindgren gpiod_set_value_cansleep(reset_gpio, 1); 4265d1ebbdaSTony Lindgren 4275d1ebbdaSTony Lindgren dev_info(ddata->dev, "Waiting for power down request to complete.. "); 4285d1ebbdaSTony Lindgren if (wait_for_completion_timeout(&ddata->ack, 4295d1ebbdaSTony Lindgren msecs_to_jiffies(5000))) { 4305d1ebbdaSTony Lindgren if (ddata->status == PHY_MDM6600_STATUS_PANIC) 4315d1ebbdaSTony Lindgren dev_info(ddata->dev, "Powered down OK\n"); 4325d1ebbdaSTony Lindgren } else { 4335d1ebbdaSTony Lindgren dev_err(ddata->dev, "Timed out powering down\n"); 4345d1ebbdaSTony Lindgren } 4355d1ebbdaSTony Lindgren } 4365d1ebbdaSTony Lindgren 4375d1ebbdaSTony Lindgren static void phy_mdm6600_deferred_power_on(struct work_struct *work) 4385d1ebbdaSTony Lindgren { 4395d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata; 4405d1ebbdaSTony Lindgren int error; 4415d1ebbdaSTony Lindgren 4425d1ebbdaSTony Lindgren ddata = container_of(work, struct phy_mdm6600, bootup_work.work); 4435d1ebbdaSTony Lindgren 4445d1ebbdaSTony Lindgren error = phy_mdm6600_device_power_on(ddata); 4455d1ebbdaSTony Lindgren if (error) 4465d1ebbdaSTony Lindgren dev_err(ddata->dev, "Device not functional\n"); 4475d1ebbdaSTony Lindgren } 4485d1ebbdaSTony Lindgren 4495d1ebbdaSTony Lindgren static const struct of_device_id phy_mdm6600_id_table[] = { 4505d1ebbdaSTony Lindgren { .compatible = "motorola,mapphone-mdm6600", }, 4515d1ebbdaSTony Lindgren {}, 4525d1ebbdaSTony Lindgren }; 4535d1ebbdaSTony Lindgren MODULE_DEVICE_TABLE(of, phy_mdm6600_id_table); 4545d1ebbdaSTony Lindgren 4555d1ebbdaSTony Lindgren static int phy_mdm6600_probe(struct platform_device *pdev) 4565d1ebbdaSTony Lindgren { 4575d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata; 4585d1ebbdaSTony Lindgren int error; 4595d1ebbdaSTony Lindgren 4605d1ebbdaSTony Lindgren ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 4615d1ebbdaSTony Lindgren if (!ddata) 4625d1ebbdaSTony Lindgren return -ENOMEM; 4635d1ebbdaSTony Lindgren 4645d1ebbdaSTony Lindgren INIT_DELAYED_WORK(&ddata->bootup_work, 4655d1ebbdaSTony Lindgren phy_mdm6600_deferred_power_on); 4665d1ebbdaSTony Lindgren INIT_DELAYED_WORK(&ddata->status_work, phy_mdm6600_status); 4675d1ebbdaSTony Lindgren init_completion(&ddata->ack); 4685d1ebbdaSTony Lindgren 4695d1ebbdaSTony Lindgren ddata->dev = &pdev->dev; 4705d1ebbdaSTony Lindgren platform_set_drvdata(pdev, ddata); 4715d1ebbdaSTony Lindgren 4725d1ebbdaSTony Lindgren error = phy_mdm6600_init_lines(ddata); 4735d1ebbdaSTony Lindgren if (error) 4745d1ebbdaSTony Lindgren return error; 4755d1ebbdaSTony Lindgren 4765d1ebbdaSTony Lindgren phy_mdm6600_init_irq(ddata); 4775d1ebbdaSTony Lindgren 4785d1ebbdaSTony Lindgren ddata->generic_phy = devm_phy_create(ddata->dev, NULL, &gpio_usb_ops); 4795d1ebbdaSTony Lindgren if (IS_ERR(ddata->generic_phy)) { 4805d1ebbdaSTony Lindgren error = PTR_ERR(ddata->generic_phy); 4815d1ebbdaSTony Lindgren goto cleanup; 4825d1ebbdaSTony Lindgren } 4835d1ebbdaSTony Lindgren 4845d1ebbdaSTony Lindgren phy_set_drvdata(ddata->generic_phy, ddata); 4855d1ebbdaSTony Lindgren 4865d1ebbdaSTony Lindgren ddata->phy_provider = 4875d1ebbdaSTony Lindgren devm_of_phy_provider_register(ddata->dev, 4885d1ebbdaSTony Lindgren of_phy_simple_xlate); 4895d1ebbdaSTony Lindgren if (IS_ERR(ddata->phy_provider)) { 4905d1ebbdaSTony Lindgren error = PTR_ERR(ddata->phy_provider); 4915d1ebbdaSTony Lindgren goto cleanup; 4925d1ebbdaSTony Lindgren } 4935d1ebbdaSTony Lindgren 4945d1ebbdaSTony Lindgren schedule_delayed_work(&ddata->bootup_work, 0); 4955d1ebbdaSTony Lindgren 4965d1ebbdaSTony Lindgren /* 4975d1ebbdaSTony Lindgren * See phy_mdm6600_device_power_on(). We should be able 4985d1ebbdaSTony Lindgren * to remove this eventually when ohci-platform can deal 4995d1ebbdaSTony Lindgren * with -EPROBE_DEFER. 5005d1ebbdaSTony Lindgren */ 5015d1ebbdaSTony Lindgren msleep(PHY_MDM6600_PHY_DELAY_MS + 500); 5025d1ebbdaSTony Lindgren 5035d1ebbdaSTony Lindgren return 0; 5045d1ebbdaSTony Lindgren 5055d1ebbdaSTony Lindgren cleanup: 5065d1ebbdaSTony Lindgren phy_mdm6600_device_power_off(ddata); 5075d1ebbdaSTony Lindgren return error; 5085d1ebbdaSTony Lindgren } 5095d1ebbdaSTony Lindgren 5105d1ebbdaSTony Lindgren static int phy_mdm6600_remove(struct platform_device *pdev) 5115d1ebbdaSTony Lindgren { 5125d1ebbdaSTony Lindgren struct phy_mdm6600 *ddata = platform_get_drvdata(pdev); 5135d1ebbdaSTony Lindgren struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; 5145d1ebbdaSTony Lindgren 5155d1ebbdaSTony Lindgren if (!ddata->running) 5165d1ebbdaSTony Lindgren wait_for_completion_timeout(&ddata->ack, 5175d1ebbdaSTony Lindgren msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS)); 5185d1ebbdaSTony Lindgren 5195d1ebbdaSTony Lindgren gpiod_set_value_cansleep(reset_gpio, 1); 5205d1ebbdaSTony Lindgren phy_mdm6600_device_power_off(ddata); 5215d1ebbdaSTony Lindgren 5225d1ebbdaSTony Lindgren cancel_delayed_work_sync(&ddata->bootup_work); 5235d1ebbdaSTony Lindgren cancel_delayed_work_sync(&ddata->status_work); 5245d1ebbdaSTony Lindgren 5255d1ebbdaSTony Lindgren return 0; 5265d1ebbdaSTony Lindgren } 5275d1ebbdaSTony Lindgren 5285d1ebbdaSTony Lindgren static struct platform_driver phy_mdm6600_driver = { 5295d1ebbdaSTony Lindgren .probe = phy_mdm6600_probe, 5305d1ebbdaSTony Lindgren .remove = phy_mdm6600_remove, 5315d1ebbdaSTony Lindgren .driver = { 5325d1ebbdaSTony Lindgren .name = "phy-mapphone-mdm6600", 5335d1ebbdaSTony Lindgren .of_match_table = of_match_ptr(phy_mdm6600_id_table), 5345d1ebbdaSTony Lindgren }, 5355d1ebbdaSTony Lindgren }; 5365d1ebbdaSTony Lindgren 5375d1ebbdaSTony Lindgren module_platform_driver(phy_mdm6600_driver); 5385d1ebbdaSTony Lindgren 5395d1ebbdaSTony Lindgren MODULE_ALIAS("platform:gpio_usb"); 5405d1ebbdaSTony Lindgren MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); 5415d1ebbdaSTony Lindgren MODULE_DESCRIPTION("mdm6600 gpio usb phy driver"); 5425d1ebbdaSTony Lindgren MODULE_LICENSE("GPL v2"); 543