cregit-Linux how code gets into the kernel

Release 4.17 drivers/phy/motorola/phy-mapphone-mdm6600.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Motorola Mapphone MDM6600 modem GPIO controlled USB PHY driver
 * Copyright (C) 2018 Tony Lindgren <tony@atomide.com>
 */

#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

#include <linux/gpio/consumer.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>


#define PHY_MDM6600_PHY_DELAY_MS	4000	
/* PHY enable 2.2s to 3.5s */

#define PHY_MDM6600_ENABLED_DELAY_MS	8000	
/* 8s more total for MDM6600 */


enum phy_mdm6600_ctrl_lines {
	
PHY_MDM6600_ENABLE,			/* USB PHY enable */
	
PHY_MDM6600_POWER,			/* Device power */
	
PHY_MDM6600_RESET,			/* Device reset */
	
PHY_MDM6600_NR_CTRL_LINES,
};


enum phy_mdm6600_bootmode_lines {
	
PHY_MDM6600_MODE0,			/* out USB mode0 and OOB wake */
	
PHY_MDM6600_MODE1,			/* out USB mode1, in OOB wake */
	
PHY_MDM6600_NR_MODE_LINES,
};


enum phy_mdm6600_cmd_lines {
	
PHY_MDM6600_CMD0,
	
PHY_MDM6600_CMD1,
	
PHY_MDM6600_CMD2,
	
PHY_MDM6600_NR_CMD_LINES,
};


enum phy_mdm6600_status_lines {
	
PHY_MDM6600_STATUS0,
	
PHY_MDM6600_STATUS1,
	
PHY_MDM6600_STATUS2,
	
PHY_MDM6600_NR_STATUS_LINES,
};

/*
 * MDM6600 command codes. These are based on Motorola Mapphone Linux
 * kernel tree.
 */

enum phy_mdm6600_cmd {
	
PHY_MDM6600_CMD_BP_PANIC_ACK,
	
PHY_MDM6600_CMD_DATA_ONLY_BYPASS,	/* Reroute USB to CPCAP PHY */
	
PHY_MDM6600_CMD_FULL_BYPASS,		/* Reroute USB to CPCAP PHY */
	
PHY_MDM6600_CMD_NO_BYPASS,		/* Request normal USB mode */
	
PHY_MDM6600_CMD_BP_SHUTDOWN_REQ,	/* Request device power off */
	
PHY_MDM6600_CMD_BP_UNKNOWN_5,
	
PHY_MDM6600_CMD_BP_UNKNOWN_6,
	
PHY_MDM6600_CMD_UNDEFINED,
};

/*
 * MDM6600 status codes. These are based on Motorola Mapphone Linux
 * kernel tree.
 */

enum phy_mdm6600_status {
	
PHY_MDM6600_STATUS_PANIC,		/* Seems to be really off */
	
PHY_MDM6600_STATUS_PANIC_BUSY_WAIT,
	
PHY_MDM6600_STATUS_QC_DLOAD,
	
PHY_MDM6600_STATUS_RAM_DOWNLOADER,	/* MDM6600 USB flashing mode */
	
PHY_MDM6600_STATUS_PHONE_CODE_AWAKE,	/* MDM6600 normal USB mode */
	
PHY_MDM6600_STATUS_PHONE_CODE_ASLEEP,
	
PHY_MDM6600_STATUS_SHUTDOWN_ACK,
	
PHY_MDM6600_STATUS_UNDEFINED,
};

static const char * const

phy_mdm6600_status_name[] = {
	"off", "busy", "qc_dl", "ram_dl", "awake",
	"asleep", "shutdown", "undefined",
};


struct phy_mdm6600 {
	
struct device *dev;
	
struct phy *generic_phy;
	
struct phy_provider *phy_provider;
	
struct gpio_desc *ctrl_gpios[PHY_MDM6600_NR_CTRL_LINES];
	
struct gpio_descs *mode_gpios;
	
struct gpio_descs *status_gpios;
	
struct gpio_descs *cmd_gpios;
	
struct delayed_work bootup_work;
	
struct delayed_work status_work;
	
struct completion ack;
	
bool enabled;				/* mdm6600 phy enabled */
	
bool running;				/* mdm6600 boot done */
	
int status;
};


static int phy_mdm6600_init(struct phy *x) { struct phy_mdm6600 *ddata = phy_get_drvdata(x); struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; if (!ddata->enabled) return -EPROBE_DEFER; gpiod_set_value_cansleep(enable_gpio, 0); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren54100.00%1100.00%
Total54100.00%1100.00%


static int phy_mdm6600_power_on(struct phy *x) { struct phy_mdm6600 *ddata = phy_get_drvdata(x); struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; if (!ddata->enabled) return -ENODEV; gpiod_set_value_cansleep(enable_gpio, 1); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren54100.00%1100.00%
Total54100.00%1100.00%


static int phy_mdm6600_power_off(struct phy *x) { struct phy_mdm6600 *ddata = phy_get_drvdata(x); struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; if (!ddata->enabled) return -ENODEV; gpiod_set_value_cansleep(enable_gpio, 0); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren54100.00%1100.00%
Total54100.00%1100.00%

static const struct phy_ops gpio_usb_ops = { .init = phy_mdm6600_init, .power_on = phy_mdm6600_power_on, .power_off = phy_mdm6600_power_off, .owner = THIS_MODULE, }; /** * phy_mdm6600_cmd() - send a command request to mdm6600 * @ddata: device driver data * * Configures the three command request GPIOs to the specified value. */
static void phy_mdm6600_cmd(struct phy_mdm6600 *ddata, int val) { int values[PHY_MDM6600_NR_CMD_LINES]; int i; val &= (1 << PHY_MDM6600_NR_CMD_LINES) - 1; for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++) values[i] = (val & BIT(i)) >> i; gpiod_set_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, ddata->cmd_gpios->desc, values); }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren75100.00%1100.00%
Total75100.00%1100.00%

/** * phy_mdm6600_status() - read mdm6600 status lines * @ddata: device driver data */
static void phy_mdm6600_status(struct work_struct *work) { struct phy_mdm6600 *ddata; struct device *dev; int values[PHY_MDM6600_NR_STATUS_LINES]; int error, i, val = 0; ddata = container_of(work, struct phy_mdm6600, status_work.work); dev = ddata->dev; error = gpiod_get_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, ddata->status_gpios->desc, values); if (error) return; for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++) { val |= values[i] << i; dev_dbg(ddata->dev, "XXX %s: i: %i values[i]: %i val: %i\n", __func__, i, values[i], val); } ddata->status = val; dev_info(dev, "modem status: %i %s\n", ddata->status, phy_mdm6600_status_name[ddata->status & 7]); complete(&ddata->ack); }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren154100.00%1100.00%
Total154100.00%1100.00%


static irqreturn_t phy_mdm6600_irq_thread(int irq, void *data) { struct phy_mdm6600 *ddata = data; schedule_delayed_work(&ddata->status_work, msecs_to_jiffies(10)); return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren36100.00%1100.00%
Total36100.00%1100.00%

/** * phy_mdm6600_wakeirq_thread - handle mode1 line OOB wake after booting * @irq: interrupt * @data: interrupt handler data * * GPIO mode1 is used initially as output to configure the USB boot * mode for mdm6600. After booting it is used as input for OOB wake * signal from mdm6600 to the SoC. Just use it for debug info only * for now. */
static irqreturn_t phy_mdm6600_wakeirq_thread(int irq, void *data) { struct phy_mdm6600 *ddata = data; struct gpio_desc *mode_gpio1; mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; dev_dbg(ddata->dev, "OOB wake on mode_gpio1: %i\n", gpiod_get_value(mode_gpio1)); return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren53100.00%1100.00%
Total53100.00%1100.00%

/** * phy_mdm6600_init_irq() - initialize mdm6600 status IRQ lines * @ddata: device driver data */
static void phy_mdm6600_init_irq(struct phy_mdm6600 *ddata) { struct device *dev = ddata->dev; int i, error, irq; for (i = PHY_MDM6600_STATUS0; i <= PHY_MDM6600_STATUS2; i++) { struct gpio_desc *gpio = ddata->status_gpios->desc[i]; irq = gpiod_to_irq(gpio); if (irq <= 0) continue; error = devm_request_threaded_irq(dev, irq, NULL, phy_mdm6600_irq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "mdm6600", ddata); if (error) dev_warn(dev, "no modem status irq%i: %i\n", irq, error); } }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren108100.00%1100.00%
Total108100.00%1100.00%

struct phy_mdm6600_map { const char *name; int direction; }; static const struct phy_mdm6600_map phy_mdm6600_ctrl_gpio_map[PHY_MDM6600_NR_CTRL_LINES] = { { "enable", GPIOD_OUT_LOW, }, /* low = phy disabled */ { "power", GPIOD_OUT_LOW, }, /* low = off */ { "reset", GPIOD_OUT_HIGH, }, /* high = reset */ }; /** * phy_mdm6600_init_lines() - initialize mdm6600 GPIO lines * @ddata: device driver data */
static int phy_mdm6600_init_lines(struct phy_mdm6600 *ddata) { struct device *dev = ddata->dev; int i; /* MDM6600 control lines */ for (i = 0; i < ARRAY_SIZE(phy_mdm6600_ctrl_gpio_map); i++) { const struct phy_mdm6600_map *map = &phy_mdm6600_ctrl_gpio_map[i]; struct gpio_desc **gpio = &ddata->ctrl_gpios[i]; *gpio = devm_gpiod_get(dev, map->name, map->direction); if (IS_ERR(*gpio)) { dev_info(dev, "gpio %s error %li\n", map->name, PTR_ERR(*gpio)); return PTR_ERR(*gpio); } } /* MDM6600 USB start-up mode output lines */ ddata->mode_gpios = devm_gpiod_get_array(dev, "motorola,mode", GPIOD_OUT_LOW); if (IS_ERR(ddata->mode_gpios)) return PTR_ERR(ddata->mode_gpios); if (ddata->mode_gpios->ndescs != PHY_MDM6600_NR_MODE_LINES) return -EINVAL; /* MDM6600 status input lines */ ddata->status_gpios = devm_gpiod_get_array(dev, "motorola,status", GPIOD_IN); if (IS_ERR(ddata->status_gpios)) return PTR_ERR(ddata->status_gpios); if (ddata->status_gpios->ndescs != PHY_MDM6600_NR_STATUS_LINES) return -EINVAL; /* MDM6600 cmd output lines */ ddata->cmd_gpios = devm_gpiod_get_array(dev, "motorola,cmd", GPIOD_OUT_LOW); if (IS_ERR(ddata->cmd_gpios)) return PTR_ERR(ddata->cmd_gpios); if (ddata->cmd_gpios->ndescs != PHY_MDM6600_NR_CMD_LINES) return -EINVAL; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren256100.00%1100.00%
Total256100.00%1100.00%

/** * phy_mdm6600_device_power_on() - power on mdm6600 device * @ddata: device driver data * * To get the integrated USB phy in MDM6600 takes some hoops. We must ensure * the shared USB bootmode GPIOs are configured, then request modem start-up, * reset and power-up.. And then we need to recycle the shared USB bootmode * GPIOs as they are also used for Out of Band (OOB) wake for the USB and * TS 27.010 serial mux. */
static int phy_mdm6600_device_power_on(struct phy_mdm6600 *ddata) { struct gpio_desc *mode_gpio0, *mode_gpio1, *reset_gpio, *power_gpio; int error = 0, wakeirq; mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0]; mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; power_gpio = ddata->ctrl_gpios[PHY_MDM6600_POWER]; /* * Shared GPIOs must be low for normal USB mode. After booting * they are used for OOB wake signaling. These can be also used * to configure USB flashing mode later on based on a module * parameter. */ gpiod_set_value_cansleep(mode_gpio0, 0); gpiod_set_value_cansleep(mode_gpio1, 0); /* Request start-up mode */ phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_NO_BYPASS); /* Request a reset first */ gpiod_set_value_cansleep(reset_gpio, 0); msleep(100); /* Toggle power GPIO to request mdm6600 to start */ gpiod_set_value_cansleep(power_gpio, 1); msleep(100); gpiod_set_value_cansleep(power_gpio, 0); /* * Looks like the USB PHY needs between 2.2 to 4 seconds. * If we try to use it before that, we will get L3 errors * from omap-usb-host trying to access the PHY. See also * phy_mdm6600_init() for -EPROBE_DEFER. */ msleep(PHY_MDM6600_PHY_DELAY_MS); ddata->enabled = true; /* Booting up the rest of MDM6600 will take total about 8 seconds */ dev_info(ddata->dev, "Waiting for power up request to complete..\n"); if (wait_for_completion_timeout(&ddata->ack, msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS))) { if (ddata->status > PHY_MDM6600_STATUS_PANIC && ddata->status < PHY_MDM6600_STATUS_SHUTDOWN_ACK) dev_info(ddata->dev, "Powered up OK\n"); } else { ddata->enabled = false; error = -ETIMEDOUT; dev_err(ddata->dev, "Timed out powering up\n"); } /* Reconfigure mode1 GPIO as input for OOB wake */ gpiod_direction_input(mode_gpio1); wakeirq = gpiod_to_irq(mode_gpio1); if (wakeirq <= 0) return wakeirq; error = devm_request_threaded_irq(ddata->dev, wakeirq, NULL, phy_mdm6600_wakeirq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "mdm6600-wake", ddata); if (error) dev_warn(ddata->dev, "no modem wakeirq irq%i: %i\n", wakeirq, error); ddata->running = true; return error; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren286100.00%1100.00%
Total286100.00%1100.00%

/** * phy_mdm6600_device_power_off() - power off mdm6600 device * @ddata: device driver data */
static void phy_mdm6600_device_power_off(struct phy_mdm6600 *ddata) { struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; ddata->enabled = false; phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_BP_SHUTDOWN_REQ); msleep(100); gpiod_set_value_cansleep(reset_gpio, 1); dev_info(ddata->dev, "Waiting for power down request to complete.. "); if (wait_for_completion_timeout(&ddata->ack, msecs_to_jiffies(5000))) { if (ddata->status == PHY_MDM6600_STATUS_PANIC) dev_info(ddata->dev, "Powered down OK\n"); } else { dev_err(ddata->dev, "Timed out powering down\n"); } }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren103100.00%1100.00%
Total103100.00%1100.00%


static void phy_mdm6600_deferred_power_on(struct work_struct *work) { struct phy_mdm6600 *ddata; int error; ddata = container_of(work, struct phy_mdm6600, bootup_work.work); error = phy_mdm6600_device_power_on(ddata); if (error) dev_err(ddata->dev, "Device not functional\n"); }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren53100.00%1100.00%
Total53100.00%1100.00%

static const struct of_device_id phy_mdm6600_id_table[] = { { .compatible = "motorola,mapphone-mdm6600", }, {}, }; MODULE_DEVICE_TABLE(of, phy_mdm6600_id_table);
static int phy_mdm6600_probe(struct platform_device *pdev) { struct phy_mdm6600 *ddata; int error; ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; INIT_DELAYED_WORK(&ddata->bootup_work, phy_mdm6600_deferred_power_on); INIT_DELAYED_WORK(&ddata->status_work, phy_mdm6600_status); init_completion(&ddata->ack); ddata->dev = &pdev->dev; platform_set_drvdata(pdev, ddata); error = phy_mdm6600_init_lines(ddata); if (error) return error; phy_mdm6600_init_irq(ddata); ddata->generic_phy = devm_phy_create(ddata->dev, NULL, &gpio_usb_ops); if (IS_ERR(ddata->generic_phy)) { error = PTR_ERR(ddata->generic_phy); goto cleanup; } phy_set_drvdata(ddata->generic_phy, ddata); ddata->phy_provider = devm_of_phy_provider_register(ddata->dev, of_phy_simple_xlate); if (IS_ERR(ddata->phy_provider)) { error = PTR_ERR(ddata->phy_provider); goto cleanup; } schedule_delayed_work(&ddata->bootup_work, 0); /* * See phy_mdm6600_device_power_on(). We should be able * to remove this eventually when ohci-platform can deal * with -EPROBE_DEFER. */ msleep(PHY_MDM6600_PHY_DELAY_MS + 500); return 0; cleanup: phy_mdm6600_device_power_off(ddata); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren224100.00%1100.00%
Total224100.00%1100.00%


static int phy_mdm6600_remove(struct platform_device *pdev) { struct phy_mdm6600 *ddata = platform_get_drvdata(pdev); struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; if (!ddata->running) wait_for_completion_timeout(&ddata->ack, msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS)); gpiod_set_value_cansleep(reset_gpio, 1); phy_mdm6600_device_power_off(ddata); cancel_delayed_work_sync(&ddata->bootup_work); cancel_delayed_work_sync(&ddata->status_work); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren84100.00%1100.00%
Total84100.00%1100.00%

static struct platform_driver phy_mdm6600_driver = { .probe = phy_mdm6600_probe, .remove = phy_mdm6600_remove, .driver = { .name = "phy-mapphone-mdm6600", .of_match_table = of_match_ptr(phy_mdm6600_id_table), }, }; module_platform_driver(phy_mdm6600_driver); MODULE_ALIAS("platform:gpio_usb"); MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); MODULE_DESCRIPTION("mdm6600 gpio usb phy driver"); MODULE_LICENSE("GPL v2");

Overall Contributors

PersonTokensPropCommitsCommitProp
Tony Lindgren1997100.00%1100.00%
Total1997100.00%1100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.