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