Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Phy drivers for Motorola devices
#
config PHY_CPCAP_USB
tristate "CPCAP PMIC USB PHY driver"
depends on USB_SUPPORT && IIO
depends on USB_MUSB_HDRC || USB_MUSB_HDRC=n
select GENERIC_PHY
select USB_PHY
help
Enable this for USB to work on Motorola phones and tablets
such as Droid 4.
config PHY_MAPPHONE_MDM6600
tristate "Motorola Mapphone MDM6600 modem USB PHY driver"
depends on OF && USB_SUPPORT && GPIOLIB
select GENERIC_PHY
help
Enable this for MDM6600 USB modem to work on Motorola phones
and tablets such as Droid 4.
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the phy drivers.
#
obj-$(CONFIG_PHY_CPCAP_USB) += phy-cpcap-usb.o
obj-$(CONFIG_PHY_MAPPHONE_MDM6600) += phy-mapphone-mdm6600.o
@@ -0,0 +1,719 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Motorola CPCAP PMIC USB PHY driver
* Copyright (C) 2017 Tony Lindgren <tony@atomide.com>
*
* Some parts based on earlier Motorola Linux kernel tree code in
* board-mapphone-usb.c and cpcap-usb-det.c:
* Copyright (C) 2007 - 2011 Motorola, Inc.
*/
#include <linux/atomic.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/iio/consumer.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/gpio/consumer.h>
#include <linux/mfd/motorola-cpcap.h>
#include <linux/phy/omap_usb.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/musb.h>
/* CPCAP_REG_USBC1 register bits */
#define CPCAP_BIT_IDPULSE BIT(15)
#define CPCAP_BIT_ID100KPU BIT(14)
#define CPCAP_BIT_IDPUCNTRL BIT(13)
#define CPCAP_BIT_IDPU BIT(12)
#define CPCAP_BIT_IDPD BIT(11)
#define CPCAP_BIT_VBUSCHRGTMR3 BIT(10)
#define CPCAP_BIT_VBUSCHRGTMR2 BIT(9)
#define CPCAP_BIT_VBUSCHRGTMR1 BIT(8)
#define CPCAP_BIT_VBUSCHRGTMR0 BIT(7)
#define CPCAP_BIT_VBUSPU BIT(6)
#define CPCAP_BIT_VBUSPD BIT(5)
#define CPCAP_BIT_DMPD BIT(4)
#define CPCAP_BIT_DPPD BIT(3)
#define CPCAP_BIT_DM1K5PU BIT(2)
#define CPCAP_BIT_DP1K5PU BIT(1)
#define CPCAP_BIT_DP150KPU BIT(0)
/* CPCAP_REG_USBC2 register bits */
#define CPCAP_BIT_ZHSDRV1 BIT(15)
#define CPCAP_BIT_ZHSDRV0 BIT(14)
#define CPCAP_BIT_DPLLCLKREQ BIT(13)
#define CPCAP_BIT_SE0CONN BIT(12)
#define CPCAP_BIT_UARTTXTRI BIT(11)
#define CPCAP_BIT_UARTSWAP BIT(10)
#define CPCAP_BIT_UARTMUX1 BIT(9)
#define CPCAP_BIT_UARTMUX0 BIT(8)
#define CPCAP_BIT_ULPISTPLOW BIT(7)
#define CPCAP_BIT_TXENPOL BIT(6)
#define CPCAP_BIT_USBXCVREN BIT(5)
#define CPCAP_BIT_USBCNTRL BIT(4)
#define CPCAP_BIT_USBSUSPEND BIT(3)
#define CPCAP_BIT_EMUMODE2 BIT(2)
#define CPCAP_BIT_EMUMODE1 BIT(1)
#define CPCAP_BIT_EMUMODE0 BIT(0)
/* CPCAP_REG_USBC3 register bits */
#define CPCAP_BIT_SPARE_898_15 BIT(15)
#define CPCAP_BIT_IHSTX03 BIT(14)
#define CPCAP_BIT_IHSTX02 BIT(13)
#define CPCAP_BIT_IHSTX01 BIT(12)
#define CPCAP_BIT_IHSTX0 BIT(11)
#define CPCAP_BIT_IDPU_SPI BIT(10)
#define CPCAP_BIT_UNUSED_898_9 BIT(9)
#define CPCAP_BIT_VBUSSTBY_EN BIT(8)
#define CPCAP_BIT_VBUSEN_SPI BIT(7)
#define CPCAP_BIT_VBUSPU_SPI BIT(6)
#define CPCAP_BIT_VBUSPD_SPI BIT(5)
#define CPCAP_BIT_DMPD_SPI BIT(4)
#define CPCAP_BIT_DPPD_SPI BIT(3)
#define CPCAP_BIT_SUSPEND_SPI BIT(2)
#define CPCAP_BIT_PU_SPI BIT(1)
#define CPCAP_BIT_ULPI_SPI_SEL BIT(0)
struct cpcap_usb_ints_state {
bool id_ground;
bool id_float;
bool chrg_det;
bool rvrs_chrg;
bool vbusov;
bool chrg_se1b;
bool se0conn;
bool rvrs_mode;
bool chrgcurr1;
bool vbusvld;
bool sessvld;
bool sessend;
bool se1;
bool battdetb;
bool dm;
bool dp;
};
enum cpcap_gpio_mode {
CPCAP_DM_DP,
CPCAP_MDM_RX_TX,
CPCAP_UNKNOWN_DISABLED, /* Seems to disable USB lines */
CPCAP_OTG_DM_DP,
};
struct cpcap_phy_ddata {
struct regmap *reg;
struct device *dev;
struct usb_phy phy;
struct delayed_work detect_work;
struct pinctrl *pins;
struct pinctrl_state *pins_ulpi;
struct pinctrl_state *pins_utmi;
struct pinctrl_state *pins_uart;
struct gpio_desc *gpio[2];
struct iio_channel *vbus;
struct iio_channel *id;
struct regulator *vusb;
atomic_t active;
unsigned int vbus_provider:1;
unsigned int docked:1;
};
static bool cpcap_usb_vbus_valid(struct cpcap_phy_ddata *ddata)
{
int error, value = 0;
error = iio_read_channel_processed(ddata->vbus, &value);
if (error >= 0)
return value > 3900;
dev_err(ddata->dev, "error reading VBUS: %i\n", error);
return false;
}
static int cpcap_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host)
{
otg->host = host;
if (!host)
otg->state = OTG_STATE_UNDEFINED;
return 0;
}
static int cpcap_usb_phy_set_peripheral(struct usb_otg *otg,
struct usb_gadget *gadget)
{
otg->gadget = gadget;
if (!gadget)
otg->state = OTG_STATE_UNDEFINED;
return 0;
}
static const struct phy_ops ops = {
.owner = THIS_MODULE,
};
static int cpcap_phy_get_ints_state(struct cpcap_phy_ddata *ddata,
struct cpcap_usb_ints_state *s)
{
int val, error;
error = regmap_read(ddata->reg, CPCAP_REG_INTS1, &val);
if (error)
return error;
s->id_ground = val & BIT(15);
s->id_float = val & BIT(14);
s->vbusov = val & BIT(11);
error = regmap_read(ddata->reg, CPCAP_REG_INTS2, &val);
if (error)
return error;
s->vbusvld = val & BIT(3);
s->sessvld = val & BIT(2);
s->sessend = val & BIT(1);
s->se1 = val & BIT(0);
error = regmap_read(ddata->reg, CPCAP_REG_INTS4, &val);
if (error)
return error;
s->dm = val & BIT(1);
s->dp = val & BIT(0);
return 0;
}
static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata);
static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata);
static void cpcap_usb_try_musb_mailbox(struct cpcap_phy_ddata *ddata,
enum musb_vbus_id_status status)
{
int error;
error = musb_mailbox(status);
if (!error)
return;
dev_dbg(ddata->dev, "%s: musb_mailbox failed: %i\n",
__func__, error);
}
static void cpcap_usb_detect(struct work_struct *work)
{
struct cpcap_phy_ddata *ddata;
struct cpcap_usb_ints_state s;
bool vbus = false;
int error;
ddata = container_of(work, struct cpcap_phy_ddata, detect_work.work);
error = cpcap_phy_get_ints_state(ddata, &s);
if (error)
return;
vbus = cpcap_usb_vbus_valid(ddata);
/* We need to kick the VBUS as USB A-host */
if (s.id_ground && ddata->vbus_provider) {
dev_dbg(ddata->dev, "still in USB A-host mode, kicking VBUS\n");
cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
CPCAP_BIT_VBUSSTBY_EN |
CPCAP_BIT_VBUSEN_SPI,
CPCAP_BIT_VBUSEN_SPI);
if (error)
goto out_err;
return;
}
if (vbus && s.id_ground && ddata->docked) {
dev_dbg(ddata->dev, "still docked as A-host, signal ID down\n");
cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
return;
}
/* No VBUS needed with docks */
if (vbus && s.id_ground && !ddata->vbus_provider) {
dev_dbg(ddata->dev, "connected to a dock\n");
ddata->docked = true;
error = cpcap_usb_set_usb_mode(ddata);
if (error)
goto out_err;
cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
/*
* Force check state again after musb has reoriented,
* otherwise devices won't enumerate after loading PHY
* driver.
*/
schedule_delayed_work(&ddata->detect_work,
msecs_to_jiffies(1000));
return;
}
if (s.id_ground && !ddata->docked) {
dev_dbg(ddata->dev, "id ground, USB host mode\n");
ddata->vbus_provider = true;
error = cpcap_usb_set_usb_mode(ddata);
if (error)
goto out_err;
cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
CPCAP_BIT_VBUSSTBY_EN |
CPCAP_BIT_VBUSEN_SPI,
CPCAP_BIT_VBUSEN_SPI);
if (error)
goto out_err;
return;
}
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
CPCAP_BIT_VBUSSTBY_EN |
CPCAP_BIT_VBUSEN_SPI, 0);
if (error)
goto out_err;
vbus = cpcap_usb_vbus_valid(ddata);
/* Otherwise assume we're connected to a USB host */
if (vbus) {
dev_dbg(ddata->dev, "connected to USB host\n");
error = cpcap_usb_set_usb_mode(ddata);
if (error)
goto out_err;
cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_VALID);
return;
}
ddata->vbus_provider = false;
ddata->docked = false;
cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_OFF);
/* Default to debug UART mode */
error = cpcap_usb_set_uart_mode(ddata);
if (error)
goto out_err;
dev_dbg(ddata->dev, "set UART mode\n");
return;
out_err:
dev_err(ddata->dev, "error setting cable state: %i\n", error);
}
static irqreturn_t cpcap_phy_irq_thread(int irq, void *data)
{
struct cpcap_phy_ddata *ddata = data;
if (!atomic_read(&ddata->active))
return IRQ_NONE;
schedule_delayed_work(&ddata->detect_work, msecs_to_jiffies(1));
return IRQ_HANDLED;
}
static int cpcap_usb_init_irq(struct platform_device *pdev,
struct cpcap_phy_ddata *ddata,
const char *name)
{
int irq, error;
irq = platform_get_irq_byname(pdev, name);
if (irq < 0)
return -ENODEV;
error = devm_request_threaded_irq(ddata->dev, irq, NULL,
cpcap_phy_irq_thread,
IRQF_SHARED |
IRQF_ONESHOT,
name, ddata);
if (error) {
dev_err(ddata->dev, "could not get irq %s: %i\n",
name, error);
return error;
}
return 0;
}
static const char * const cpcap_phy_irqs[] = {
/* REG_INT_0 */
"id_ground", "id_float",
/* REG_INT1 */
"se0conn", "vbusvld", "sessvld", "sessend", "se1",
/* REG_INT_3 */
"dm", "dp",
};
static int cpcap_usb_init_interrupts(struct platform_device *pdev,
struct cpcap_phy_ddata *ddata)
{
int i, error;
for (i = 0; i < ARRAY_SIZE(cpcap_phy_irqs); i++) {
error = cpcap_usb_init_irq(pdev, ddata, cpcap_phy_irqs[i]);
if (error)
return error;
}
return 0;
}
/*
* Optional pins and modes. At least Motorola mapphone devices
* are using two GPIOs and dynamic pinctrl to multiplex PHY pins
* to UART, ULPI or UTMI mode.
*/
static int cpcap_usb_gpio_set_mode(struct cpcap_phy_ddata *ddata,
enum cpcap_gpio_mode mode)
{
if (!ddata->gpio[0] || !ddata->gpio[1])
return 0;
gpiod_set_value(ddata->gpio[0], mode & 1);
gpiod_set_value(ddata->gpio[1], mode >> 1);
return 0;
}
static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata)
{
int error;
/* Disable lines to prevent glitches from waking up mdm6600 */
error = cpcap_usb_gpio_set_mode(ddata, CPCAP_UNKNOWN_DISABLED);
if (error)
goto out_err;
if (ddata->pins_uart) {
error = pinctrl_select_state(ddata->pins, ddata->pins_uart);
if (error)
goto out_err;
}
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1,
CPCAP_BIT_VBUSPD,
CPCAP_BIT_VBUSPD);
if (error)
goto out_err;
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
0xffff, CPCAP_BIT_UARTMUX0 |
CPCAP_BIT_EMUMODE0);
if (error)
goto out_err;
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, 0x7fff,
CPCAP_BIT_IDPU_SPI);
if (error)
goto out_err;
/* Enable UART mode */
error = cpcap_usb_gpio_set_mode(ddata, CPCAP_DM_DP);
if (error)
goto out_err;
return 0;
out_err:
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
return error;
}
static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata)
{
int error;
/* Disable lines to prevent glitches from waking up mdm6600 */
error = cpcap_usb_gpio_set_mode(ddata, CPCAP_UNKNOWN_DISABLED);
if (error)
return error;
if (ddata->pins_utmi) {
error = pinctrl_select_state(ddata->pins, ddata->pins_utmi);
if (error) {
dev_err(ddata->dev, "could not set usb mode: %i\n",
error);
return error;
}
}
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1,
CPCAP_BIT_VBUSPD, 0);
if (error)
goto out_err;
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
CPCAP_BIT_PU_SPI |
CPCAP_BIT_DMPD_SPI |
CPCAP_BIT_DPPD_SPI |
CPCAP_BIT_SUSPEND_SPI |
CPCAP_BIT_ULPI_SPI_SEL, 0);
if (error)
goto out_err;
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
CPCAP_BIT_USBXCVREN,
CPCAP_BIT_USBXCVREN);
if (error)
goto out_err;
/* Enable USB mode */
error = cpcap_usb_gpio_set_mode(ddata, CPCAP_OTG_DM_DP);
if (error)
goto out_err;
return 0;
out_err:
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
return error;
}
static int cpcap_usb_init_optional_pins(struct cpcap_phy_ddata *ddata)
{
ddata->pins = devm_pinctrl_get(ddata->dev);
if (IS_ERR(ddata->pins)) {
dev_info(ddata->dev, "default pins not configured: %ld\n",
PTR_ERR(ddata->pins));
ddata->pins = NULL;
return 0;
}
ddata->pins_ulpi = pinctrl_lookup_state(ddata->pins, "ulpi");
if (IS_ERR(ddata->pins_ulpi)) {
dev_info(ddata->dev, "ulpi pins not configured\n");
ddata->pins_ulpi = NULL;
}
ddata->pins_utmi = pinctrl_lookup_state(ddata->pins, "utmi");
if (IS_ERR(ddata->pins_utmi)) {
dev_info(ddata->dev, "utmi pins not configured\n");
ddata->pins_utmi = NULL;
}
ddata->pins_uart = pinctrl_lookup_state(ddata->pins, "uart");
if (IS_ERR(ddata->pins_uart)) {
dev_info(ddata->dev, "uart pins not configured\n");
ddata->pins_uart = NULL;
}
if (ddata->pins_uart)
return pinctrl_select_state(ddata->pins, ddata->pins_uart);
return 0;
}
static void cpcap_usb_init_optional_gpios(struct cpcap_phy_ddata *ddata)
{
int i;
for (i = 0; i < 2; i++) {
ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode",
i, GPIOD_OUT_HIGH);
if (IS_ERR(ddata->gpio[i])) {
dev_info(ddata->dev, "no mode change GPIO%i: %li\n",
i, PTR_ERR(ddata->gpio[i]));
ddata->gpio[i] = NULL;
}
}
}
static int cpcap_usb_init_iio(struct cpcap_phy_ddata *ddata)
{
enum iio_chan_type type;
int error;
ddata->vbus = devm_iio_channel_get(ddata->dev, "vbus");
if (IS_ERR(ddata->vbus)) {
error = PTR_ERR(ddata->vbus);
goto out_err;
}
if (!ddata->vbus->indio_dev) {
error = -ENXIO;
goto out_err;
}
error = iio_get_channel_type(ddata->vbus, &type);
if (error < 0)
goto out_err;
if (type != IIO_VOLTAGE) {
error = -EINVAL;
goto out_err;
}
return 0;
out_err:
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
error);
return error;
}
#ifdef CONFIG_OF
static const struct of_device_id cpcap_usb_phy_id_table[] = {
{
.compatible = "motorola,cpcap-usb-phy",
},
{
.compatible = "motorola,mapphone-cpcap-usb-phy",
},
{},
};
MODULE_DEVICE_TABLE(of, cpcap_usb_phy_id_table);
#endif
static int cpcap_usb_phy_probe(struct platform_device *pdev)
{
struct cpcap_phy_ddata *ddata;
struct phy *generic_phy;
struct phy_provider *phy_provider;
struct usb_otg *otg;
int error;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
ddata->reg = dev_get_regmap(pdev->dev.parent, NULL);
if (!ddata->reg)
return -ENODEV;
otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
if (!otg)
return -ENOMEM;
ddata->dev = &pdev->dev;
ddata->phy.dev = ddata->dev;
ddata->phy.label = "cpcap_usb_phy";
ddata->phy.otg = otg;
ddata->phy.type = USB_PHY_TYPE_USB2;
otg->set_host = cpcap_usb_phy_set_host;
otg->set_peripheral = cpcap_usb_phy_set_peripheral;
otg->usb_phy = &ddata->phy;
INIT_DELAYED_WORK(&ddata->detect_work, cpcap_usb_detect);
platform_set_drvdata(pdev, ddata);
ddata->vusb = devm_regulator_get(&pdev->dev, "vusb");
if (IS_ERR(ddata->vusb))
return PTR_ERR(ddata->vusb);
error = regulator_enable(ddata->vusb);
if (error)
return error;
generic_phy = devm_phy_create(ddata->dev, NULL, &ops);
if (IS_ERR(generic_phy)) {
error = PTR_ERR(generic_phy);
goto out_reg_disable;
}
phy_set_drvdata(generic_phy, ddata);
phy_provider = devm_of_phy_provider_register(ddata->dev,
of_phy_simple_xlate);
if (IS_ERR(phy_provider)) {
error = PTR_ERR(phy_provider);
goto out_reg_disable;
}
error = cpcap_usb_init_optional_pins(ddata);
if (error)
goto out_reg_disable;
cpcap_usb_init_optional_gpios(ddata);
error = cpcap_usb_init_iio(ddata);
if (error)
goto out_reg_disable;
error = cpcap_usb_init_interrupts(pdev, ddata);
if (error)
goto out_reg_disable;
usb_add_phy_dev(&ddata->phy);
atomic_set(&ddata->active, 1);
schedule_delayed_work(&ddata->detect_work, msecs_to_jiffies(1));
return 0;
out_reg_disable:
regulator_disable(ddata->vusb);
return error;
}
static void cpcap_usb_phy_remove(struct platform_device *pdev)
{
struct cpcap_phy_ddata *ddata = platform_get_drvdata(pdev);
int error;
atomic_set(&ddata->active, 0);
error = cpcap_usb_set_uart_mode(ddata);
if (error)
dev_err(ddata->dev, "could not set UART mode\n");
cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_OFF);
usb_remove_phy(&ddata->phy);
cancel_delayed_work_sync(&ddata->detect_work);
regulator_disable(ddata->vusb);
}
static struct platform_driver cpcap_usb_phy_driver = {
.probe = cpcap_usb_phy_probe,
.remove = cpcap_usb_phy_remove,
.driver = {
.name = "cpcap-usb-phy",
.of_match_table = of_match_ptr(cpcap_usb_phy_id_table),
},
};
module_platform_driver(cpcap_usb_phy_driver);
MODULE_ALIAS("platform:cpcap_usb");
MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
MODULE_DESCRIPTION("CPCAP usb phy driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,669 @@
// 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>
#include <linux/pinctrl/consumer.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 */
#define PHY_MDM6600_WAKE_KICK_MS 600 /* time on after GPIO toggle */
#define MDM6600_MODEM_IDLE_DELAY_MS 1000 /* modem after USB suspend */
#define MDM6600_MODEM_WAKE_DELAY_MS 200 /* modem response after idle */
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 delayed_work modem_wake_work;
struct completion ack;
bool enabled; /* mdm6600 phy enabled */
bool running; /* mdm6600 boot done */
bool awake; /* mdm6600 respnds on n_gsm */
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;
}
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);
/* Allow aggressive PM for USB, it's only needed for n_gsm port */
if (pm_runtime_enabled(&x->dev))
phy_pm_runtime_put(x);
return 0;
}
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];
int error;
if (!ddata->enabled)
return -ENODEV;
/* Paired with phy_pm_runtime_put() in phy_mdm6600_power_on() */
if (pm_runtime_enabled(&x->dev)) {
error = phy_pm_runtime_get(x);
if (error < 0 && error != -EINPROGRESS)
dev_warn(ddata->dev, "%s: phy_pm_runtime_get: %i\n",
__func__, error);
}
gpiod_set_value_cansleep(enable_gpio, 0);
return 0;
}
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
* @val: value of cmd to be set
*
* Configures the three command request GPIOs to the specified value.
*/
static void phy_mdm6600_cmd(struct phy_mdm6600 *ddata, int val)
{
DECLARE_BITMAP(values, PHY_MDM6600_NR_CMD_LINES);
values[0] = val;
gpiod_multi_set_value_cansleep(ddata->cmd_gpios, values);
}
/**
* phy_mdm6600_status() - read mdm6600 status lines
* @work: work structure
*/
static void phy_mdm6600_status(struct work_struct *work)
{
struct phy_mdm6600 *ddata;
struct device *dev;
DECLARE_BITMAP(values, PHY_MDM6600_NR_STATUS_LINES);
int error;
ddata = container_of(work, struct phy_mdm6600, status_work.work);
dev = ddata->dev;
error = gpiod_get_array_value_cansleep(PHY_MDM6600_NR_STATUS_LINES,
ddata->status_gpios->desc,
ddata->status_gpios->info,
values);
if (error)
return;
ddata->status = values[0] & ((1 << PHY_MDM6600_NR_STATUS_LINES) - 1);
dev_info(dev, "modem status: %i %s\n",
ddata->status,
phy_mdm6600_status_name[ddata->status]);
complete(&ddata->ack);
}
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;
}
/**
* 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;
int error, wakeup;
mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1];
wakeup = gpiod_get_value(mode_gpio1);
if (!wakeup)
return IRQ_NONE;
dev_dbg(ddata->dev, "OOB wake on mode_gpio1: %i\n", wakeup);
error = pm_runtime_get_sync(ddata->dev);
if (error < 0) {
pm_runtime_put_noidle(ddata->dev);
return IRQ_NONE;
}
/* Just wake-up and kick the autosuspend timer */
pm_runtime_mark_last_busy(ddata->dev);
pm_runtime_put_autosuspend(ddata->dev);
return IRQ_HANDLED;
}
/**
* 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);
}
}
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;
}
/**
* 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;
}
/**
* 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];
int error;
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");
}
/*
* Keep reset gpio high with padconf internal pull-up resistor to
* prevent modem from waking up during deeper SoC idle states. The
* gpio bank lines can have glitches if not in the always-on wkup
* domain.
*/
error = pinctrl_pm_select_sleep_state(ddata->dev);
if (error)
dev_warn(ddata->dev, "%s: error with sleep_state: %i\n",
__func__, error);
}
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");
}
/*
* USB suspend puts mdm6600 into low power mode. For any n_gsm using apps,
* we need to keep the modem awake by kicking it's mode0 GPIO. This will
* keep the modem awake for about 1.2 seconds. When no n_gsm apps are using
* the modem, runtime PM auto mode can be enabled so modem can enter low
* power mode.
*/
static void phy_mdm6600_wake_modem(struct phy_mdm6600 *ddata)
{
struct gpio_desc *mode_gpio0;
mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0];
gpiod_set_value_cansleep(mode_gpio0, 1);
usleep_range(5, 15);
gpiod_set_value_cansleep(mode_gpio0, 0);
if (ddata->awake)
usleep_range(5, 15);
else
msleep(MDM6600_MODEM_WAKE_DELAY_MS);
}
static void phy_mdm6600_modem_wake(struct work_struct *work)
{
struct phy_mdm6600 *ddata;
ddata = container_of(work, struct phy_mdm6600, modem_wake_work.work);
phy_mdm6600_wake_modem(ddata);
/*
* The modem does not always stay awake 1.2 seconds after toggling
* the wake GPIO, and sometimes it idles after about some 600 ms
* making writes time out.
*/
schedule_delayed_work(&ddata->modem_wake_work,
msecs_to_jiffies(PHY_MDM6600_WAKE_KICK_MS));
}
static int __maybe_unused phy_mdm6600_runtime_suspend(struct device *dev)
{
struct phy_mdm6600 *ddata = dev_get_drvdata(dev);
cancel_delayed_work_sync(&ddata->modem_wake_work);
ddata->awake = false;
return 0;
}
static int __maybe_unused phy_mdm6600_runtime_resume(struct device *dev)
{
struct phy_mdm6600 *ddata = dev_get_drvdata(dev);
phy_mdm6600_modem_wake(&ddata->modem_wake_work.work);
ddata->awake = true;
return 0;
}
static const struct dev_pm_ops phy_mdm6600_pm_ops = {
SET_RUNTIME_PM_OPS(phy_mdm6600_runtime_suspend,
phy_mdm6600_runtime_resume, NULL)
};
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_DELAYED_WORK(&ddata->modem_wake_work, phy_mdm6600_modem_wake);
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);
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);
/*
* Enable PM runtime only after PHY has been powered up properly.
* It is currently only needed after USB suspends mdm6600 and n_gsm
* needs to access the device. We don't want to do this earlier as
* gpio mode0 pin doubles as mdm6600 wake-up gpio.
*/
pm_runtime_use_autosuspend(ddata->dev);
pm_runtime_set_autosuspend_delay(ddata->dev,
MDM6600_MODEM_IDLE_DELAY_MS);
pm_runtime_enable(ddata->dev);
error = pm_runtime_get_sync(ddata->dev);
if (error < 0) {
dev_warn(ddata->dev, "failed to wake modem: %i\n", error);
pm_runtime_put_noidle(ddata->dev);
goto cleanup;
}
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 idle;
}
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);
idle:
pm_runtime_mark_last_busy(ddata->dev);
pm_runtime_put_autosuspend(ddata->dev);
cleanup:
if (error < 0) {
phy_mdm6600_device_power_off(ddata);
pm_runtime_disable(ddata->dev);
pm_runtime_dont_use_autosuspend(ddata->dev);
}
return error;
}
static void 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];
pm_runtime_get_noresume(ddata->dev);
pm_runtime_dont_use_autosuspend(ddata->dev);
pm_runtime_put_sync(ddata->dev);
pm_runtime_disable(ddata->dev);
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->modem_wake_work);
cancel_delayed_work_sync(&ddata->bootup_work);
cancel_delayed_work_sync(&ddata->status_work);
}
static struct platform_driver phy_mdm6600_driver = {
.probe = phy_mdm6600_probe,
.remove = phy_mdm6600_remove,
.driver = {
.name = "phy-mapphone-mdm6600",
.pm = &phy_mdm6600_pm_ops,
.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");