blob: d8c99b596357a18165b71ba2a75bbad82231a128 [file] [log] [blame]
/*
* 88pm80x VBus driver for Marvell USB
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/mfd/88pm80x.h>
#include <linux/delay.h>
#include <linux/platform_data/mv_usb.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/mfd/pm802.h>
#define USB_HANDLE_TIME_MSEC (5000)
struct pm80x_usb_info {
struct pm80x_chip *chip;
struct pm80x_subchip *subchip;
struct work_struct meas_id_work;
int vbus_irq;
int id_irq;
int id_level;
int vbus_gpio;
int id_gpadc;
int vbus_detect;
int get_vbus;
bool id_ov_sampling;
int id_ov_samp_count;
int id_ov_samp_sleep;
unsigned int gpadc_meas;
unsigned int gpadc_upp_th;
unsigned int gpadc_low_th;
int vchg_from_exton;
int chg_in;
int id_in;
spinlock_t lock;
};
static struct pm80x_usb_info *usb_info;
static int rm_flag;
static int pm80x_read_usb_val(unsigned int *level)
{
int ret;
unsigned int val;
unsigned long flags;
spin_lock_irqsave(&usb_info->lock, flags);
if (rm_flag) {
*level = VBUS_LOW;
spin_unlock_irqrestore(&usb_info->lock, flags);
pr_info("%s do nothing..\n", __func__);
return 0;
} else {
spin_unlock_irqrestore(&usb_info->lock, flags);
}
ret = regmap_read(usb_info->chip->regmap,
PM800_STATUS_1, &val);
if (ret)
return ret;
if (usb_info->vchg_from_exton)
*level = (val & PM800_EXTON_STS1) ? VBUS_HIGH : VBUS_LOW;
else
*level = (val & PM800_CHG_STS1) ? VBUS_HIGH : VBUS_LOW;
if (usb_info->id_in) {
dev_info(usb_info->chip->dev,
"VBUS requested with USB ID IN\n");
return 0;
}
if (*level == VBUS_HIGH) {
if (!usb_info->chg_in && usb_info->id_gpadc != -1) {
dev_dbg(usb_info->chip->dev,
"USB cable in, and disable OTG interrupts\n");
usb_info->chg_in = 1;
disable_irq(usb_info->id_irq);
}
} else {
if (usb_info->chg_in && usb_info->id_gpadc != -1) {
dev_dbg(usb_info->chip->dev,
"USB cable out, and enable OTG interrupts\n");
usb_info->chg_in = 0;
enable_irq(usb_info->id_irq);
}
}
return 0;
}
#if 0
static int pm80x_read_id_val(unsigned int *level)
{
int ret, data;
unsigned int val;
unsigned int meas1, meas2, upp_th, low_th;
unsigned long flags;
spin_lock_irqsave(&usb_info->lock, flags);
if (rm_flag) {
*level = VBUS_LOW;
spin_unlock_irqrestore(&usb_info->lock, flags);
pr_info("%s do nothing..\n", __func__);
return 0;
} else {
spin_unlock_irqrestore(&usb_info->lock, flags);
}
switch (usb_info->id_gpadc) {
case PM800_GPADC0:
meas1 = PM800_GPADC0_MEAS1;
meas2 = PM800_GPADC0_MEAS2;
low_th = PM800_GPADC0_LOW_TH;
upp_th = PM800_GPADC0_UPP_TH;
break;
case PM800_GPADC1:
meas1 = PM800_GPADC1_MEAS1;
meas2 = PM800_GPADC1_MEAS2;
low_th = PM800_GPADC1_LOW_TH;
upp_th = PM800_GPADC1_UPP_TH;
break;
case PM800_GPADC2:
meas1 = PM800_GPADC2_MEAS1;
meas2 = PM800_GPADC2_MEAS2;
low_th = PM800_GPADC2_LOW_TH;
upp_th = PM800_GPADC2_UPP_TH;
break;
case PM800_GPADC3:
meas1 = PM800_GPADC3_MEAS1;
meas2 = PM800_GPADC3_MEAS2;
low_th = PM800_GPADC3_LOW_TH;
upp_th = PM800_GPADC3_UPP_TH;
break;
case PM800_GPADC4:
meas1 = PM800_GPADC4_MEAS1;
meas2 = PM800_GPADC4_MEAS2;
low_th = PM800_GPADC4_LOW_TH;
upp_th = PM800_GPADC4_UPP_TH;
break;
default:
return -ENODEV;
}
ret = regmap_read(usb_info->subchip->regmap_gpadc, meas1, &val);
data = val << 4;
if (ret)
return ret;
ret = regmap_read(usb_info->subchip->regmap_gpadc, meas2, &val);
data |= val & 0x0F;
if (ret)
return ret;
if (data > 0x10) {
regmap_write(usb_info->subchip->regmap_gpadc, low_th, 0x10);
if (ret)
return ret;
regmap_write(usb_info->subchip->regmap_gpadc, upp_th, 0xff);
if (ret)
return ret;
*level = 1;
} else {
regmap_write(usb_info->subchip->regmap_gpadc, low_th, 0);
if (ret)
return ret;
regmap_write(usb_info->subchip->regmap_gpadc, upp_th, 0x10);
if (ret)
return ret;
*level = 0;
}
return 0;
};
#endif
static int pm80x_ext_read_id_level(unsigned int *level)
{
unsigned long flags;
spin_lock_irqsave(&usb_info->lock, flags);
if (rm_flag) {
*level = 0;
spin_unlock_irqrestore(&usb_info->lock, flags);
pr_info("%s do nothing..\n", __func__);
return 0;
} else {
spin_unlock_irqrestore(&usb_info->lock, flags);
}
if (usb_info->chg_in) {
*level = 1;
dev_info(usb_info->chip->dev, "idpin requested with USB IN\n");
return 0;
}
*level = usb_info->id_level;
if (usb_info->id_level) {
/* USB ID plug out */
if (usb_info->vbus_detect != -1 && usb_info->id_in == 1) {
dev_dbg(usb_info->chip->dev,
"USB ID OUT, and enable VBUS INT\n");
usb_info->id_in = 0;
enable_irq(usb_info->vbus_irq);
}
} else {
/* USB ID plug in */
if (usb_info->vbus_detect != -1 && usb_info->id_in == 0) {
dev_dbg(usb_info->chip->dev,
"USB ID IN, and disable VBUS INT\n");
usb_info->id_in = 1;
disable_irq(usb_info->vbus_irq);
}
}
return 0;
}
static int pm80x_get_id_level(unsigned int *level)
{
int ret, data;
unsigned char buf[2];
ret = regmap_bulk_read(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_meas, buf, 2);
if (ret)
return ret;
data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f);
if (data > 0x100)
*level = 1;
else
*level = 0;
dev_dbg(usb_info->chip->dev,
"usb id voltage = %d mV, level is %s\n",
(((data) & 0xfff) * 175) >> 9, (*level == 1 ? "HIGH" : "LOW"));
return 0;
}
static int pm80x_update_id_level(void)
{
int ret;
ret = pm80x_get_id_level(&usb_info->id_level);
if (ret)
return ret;
if (usb_info->id_level) {
regmap_write(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_low_th, 0x10);
regmap_write(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_upp_th, 0xff);
} else {
regmap_write(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_low_th, 0);
regmap_write(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_upp_th, 0x10);
}
dev_info(usb_info->chip->dev, "idpin is %s\n",
usb_info->id_level ? "HIGH" : "LOW");
return 0;
}
static void pm80x_meas_id_work(struct work_struct *work)
{
int i = 0;
unsigned int level, last_level = 1;
/*
* 1.loop until the line is stable
* 2.in every iteration do the follwing:
* - measure the line voltage
* - check if the voltage is the same as the previous value
* - if not, start the loop again (set loop index to 0)
* - if yes, continue the loop to next iteration
* 3.if we get x (id_meas_count) identical results, loop end
*/
while (i < usb_info->id_ov_samp_count) {
pm80x_get_id_level(&level);
if (i == 0) {
last_level = level;
i++;
} else if (level != last_level) {
i = 0;
} else {
i++;
}
msleep(usb_info->id_ov_samp_sleep);
}
/* set the GPADC thrsholds for next insertion/removal */
if (last_level) {
regmap_write(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_low_th, 0x10);
regmap_write(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_upp_th, 0xff);
} else {
regmap_write(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_low_th, 0);
regmap_write(usb_info->subchip->regmap_gpadc,
usb_info->gpadc_upp_th, 0x10);
}
/* after the line is stable, we can enable the id interrupt */
enable_irq(usb_info->id_irq);
/*
* in case we missed interrupt till we enable it
* we take one more measurment
*/
pm80x_get_id_level(&level);
/*
* if the last measurment is different from the stable value,
* need to start the process again
*/
if (level != last_level) {
dev_dbg(usb_info->chip->dev, "disable irq, resched id work\n");
disable_irq(usb_info->id_irq);
schedule_work(&usb_info->meas_id_work);
return;
}
/* notify to wake up the usb subsystem if ID pin value changed */
if (last_level != usb_info->id_level) {
usb_info->id_level = last_level;
pxa_usb_notify(PXA_USB_DEV_OTG, EVENT_ID, 0);
dev_info(usb_info->chip->dev, "idpin is %s\n",
usb_info->id_level ? "HIGH" : "LOW");
}
}
int pm80x_init_id(void)
{
#if 0
int ret;
unsigned int en;
unsigned long flags;
spin_lock_irqsave(&usb_info->lock, flags);
if (rm_flag) {
spin_unlock_irqrestore(&usb_info->lock, flags);
pr_info("%s do nothing..\n", __func__);
return 0;
} else {
spin_unlock_irqrestore(&usb_info->lock, flags);
}
switch (usb_info->id_gpadc) {
case PM800_GPADC0:
en = PM800_MEAS_GP0_EN;
break;
case PM800_GPADC1:
en = PM800_MEAS_GP1_EN;
break;
case PM800_GPADC2:
en = PM800_MEAS_GP2_EN;
break;
case PM800_GPADC3:
en = PM800_MEAS_GP3_EN;
break;
case PM800_GPADC4:
en = PM800_MEAS_GP4_EN;
break;
default:
return -ENODEV;
}
ret = regmap_update_bits(usb_info->subchip->regmap_gpadc,
PM800_GPADC_MEAS_EN2, en, en);
if (ret)
return ret;
ret = regmap_update_bits(usb_info->subchip->regmap_gpadc,
PM800_GPADC_MISC_CONFIG2, PM800_GPADC_MISC_GPFSM_EN,
PM800_GPADC_MISC_GPFSM_EN);
if (ret)
return ret;
#endif
return 0;
}
static void pm802_usb_id_config(struct pm80x_usb_info *info)
{
unsigned int gpadc_en, bias_en;
/* pm802 based usb id detetction is not supported now */
BUG();
/* set gpadc parameters */
switch (info->id_gpadc) {
case PM802_GPADC0:
info->gpadc_meas = PM802_GPADC0_MEAS1;
info->gpadc_low_th = PM802_GPADC0_LOW_TH;
info->gpadc_upp_th = PM802_GPADC0_HIGH_TH;
gpadc_en = PM802_MEAS_GP0_EN;
bias_en = PM802_GPADC_GP_BIAS_EN0;
/* Set bias to 36uA*/
regmap_update_bits(info->subchip->regmap_gpadc,
PM802_GPADC_BIAS1,
PM802_GPADC_BIAS_MASK0 | PM802_GPADC_BIAS_MASK1,
BIAS_GP_SET(36));
break;
case PM802_GPADC1:
info->gpadc_meas = PM802_GPADC1_MEAS1;
info->gpadc_low_th = PM802_GPADC1_LOW_TH;
info->gpadc_upp_th = PM802_GPADC1_HIGH_TH;
gpadc_en = PM802_MEAS_GP1_EN;
bias_en = PM802_GPADC_GP_BIAS_EN1;
/* Set bias to 36uA*/
regmap_update_bits(info->subchip->regmap_gpadc,
PM802_GPADC_BIAS2,
PM802_GPADC_BIAS_MASK0 | PM802_GPADC_BIAS_MASK1,
(BIAS_GP_SET(36) << 4));
break;
default:
dev_err(info->chip->dev, "no configuration for gpadc %d\n",
info->id_gpadc);
return;
}
/* enable GPADCx BIAS ENABLE AND OUT BIT */
regmap_update_bits(info->subchip->regmap_gpadc,
PM802_GP_BIAS_ENA1,
bias_en, bias_en);
/* enable GPADCx meas function */
regmap_update_bits(info->subchip->regmap_gpadc,
PM802_GPADC_MEAS_EN1, gpadc_en, gpadc_en);
regmap_update_bits(info->subchip->regmap_gpadc,
PM802_GPADC_MISC_CONFIG2, PM802_GPADC_MISC_GPFSM_EN,
PM802_GPADC_MISC_GPFSM_EN);
/* read ID level and set the thresholds for GPADC to prepare for INT */
pm80x_update_id_level();
if (usb_info->id_level == 0)
usb_info->id_in = 1;
else
usb_info->id_in = 0;
}
static void pm80x_usb_id_config(struct pm80x_usb_info *info)
{
unsigned int gpadc_en, bias_en;
/* set gpadc parameters */
switch (info->id_gpadc) {
case PM800_GPADC0:
info->gpadc_meas = PM800_GPADC0_MEAS1;
info->gpadc_low_th = PM800_GPADC0_LOW_TH;
info->gpadc_upp_th = PM800_GPADC0_UPP_TH;
gpadc_en = PM800_MEAS_GP0_EN;
bias_en = PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_OUT0;
/* Set bias to 36uA*/
regmap_update_bits(info->subchip->regmap_gpadc,
PM800_GPADC_BIAS1,
PM800_GPADC_BIAS_MASK0 | PM800_GPADC_BIAS_MASK1,
BIAS_GP_SET(36) | GP_PREBIAS(3));
break;
case PM800_GPADC1:
info->gpadc_meas = PM800_GPADC1_MEAS1;
info->gpadc_low_th = PM800_GPADC1_LOW_TH;
info->gpadc_upp_th = PM800_GPADC1_UPP_TH;
gpadc_en = PM800_MEAS_GP1_EN;
bias_en = PM800_GPADC_GP_BIAS_EN1 | PM800_GPADC_GP_BIAS_OUT1;
/* Set bias to 36uA*/
regmap_update_bits(info->subchip->regmap_gpadc,
PM800_GPADC_BIAS2,
PM800_GPADC_BIAS_MASK0 | PM800_GPADC_BIAS_MASK1,
BIAS_GP_SET(36) | GP_PREBIAS(3));
break;
case PM800_GPADC2:
info->gpadc_meas = PM800_GPADC2_MEAS1;
info->gpadc_low_th = PM800_GPADC2_LOW_TH;
info->gpadc_upp_th = PM800_GPADC2_UPP_TH;
gpadc_en = PM800_MEAS_GP2_EN;
bias_en = PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_OUT2;
/* Set bias to 36uA*/
regmap_update_bits(info->subchip->regmap_gpadc,
PM800_GPADC_BIAS3,
PM800_GPADC_BIAS_MASK0 | PM800_GPADC_BIAS_MASK1,
BIAS_GP_SET(36) | GP_PREBIAS(3));
break;
case PM800_GPADC3:
info->gpadc_meas = PM800_GPADC3_MEAS1;
info->gpadc_low_th = PM800_GPADC3_LOW_TH;
info->gpadc_upp_th = PM800_GPADC3_UPP_TH;
gpadc_en = PM800_MEAS_GP3_EN;
bias_en = PM800_GPADC_GP_BIAS_EN3 | PM800_GPADC_GP_BIAS_OUT3;
/* Set bias to 36uA*/
regmap_update_bits(info->subchip->regmap_gpadc,
PM800_GPADC_BIAS4,
PM800_GPADC_BIAS_MASK0 | PM800_GPADC_BIAS_MASK1,
BIAS_GP_SET(36) | GP_PREBIAS(3));
break;
default:
dev_err(info->chip->dev, "no configuration for gpadc %d\n",
info->id_gpadc);
return;
}
/* enable GPADCx BIAS ENABLE AND OUT BIT */
regmap_update_bits(info->subchip->regmap_gpadc,
PM800_GP_BIAS_ENA1,
bias_en, bias_en);
/* enable GPADCx meas function */
regmap_update_bits(info->subchip->regmap_gpadc,
PM800_GPADC_MEAS_EN2, gpadc_en, gpadc_en);
regmap_update_bits(info->subchip->regmap_gpadc,
PM800_GPADC_MISC_CONFIG2, PM800_GPADC_MISC_GPFSM_EN,
PM800_GPADC_MISC_GPFSM_EN);
/* read ID level and set the thresholds for GPADC to prepare for INT */
pm80x_update_id_level();
if (usb_info->id_level == 0)
usb_info->id_in = 1;
else
usb_info->id_in = 0;
}
static int pm80x_set_vbus(unsigned int vbus)
{
int ret;
unsigned int data = 0, mask, reg = 0;
unsigned long flags;
spin_lock_irqsave(&usb_info->lock, flags);
if (rm_flag) {
spin_unlock_irqrestore(&usb_info->lock, flags);
pr_info("%s do nothing..\n", __func__);
return 0;
} else {
spin_unlock_irqrestore(&usb_info->lock, flags);
}
switch (usb_info->vbus_gpio) {
case PM800_NO_GPIO:
/* OTG5V not supported - Do nothing */
return 0;
case PM800_GPIO0:
/* OTG5V Enable/Disable is connected to GPIO_0 */
mask = PM800_GPIO0_GPIO_MODE(0x01) | PM800_GPIO0_VAL;
reg = PM800_GPIO_0_1_CNTRL;
break;
case PM800_GPIO1:
/* OTG5V Enable/Disable is connected to GPIO_1 */
mask = PM800_GPIO1_GPIO_MODE(0x01) | PM800_GPIO1_VAL;
reg = PM800_GPIO_0_1_CNTRL;
break;
case PM800_GPIO2:
/* OTG5V Enable/Disable is connected to GPIO_2 */
mask = PM800_GPIO2_GPIO_MODE(0x01) | PM800_GPIO2_VAL;
reg = PM800_GPIO_2_3_CNTRL;
break;
case PM800_GPIO3:
/* OTG5V Enable/Disable is connected to GPIO_3 */
mask = PM800_GPIO3_GPIO_MODE(0x01) | PM800_GPIO3_VAL;
reg = PM800_GPIO_2_3_CNTRL;
break;
case PM800_GPIO4:
/* OTG5V Enable/Disable is connected to GPIO_4 */
mask = PM800_GPIO4_GPIO_MODE(0x01) | PM800_GPIO4_VAL;
reg = PM800_GPIO_4_5_CNTRL;
break;
default:
return -ENODEV;
}
if (vbus == VBUS_HIGH)
data = mask;
ret = regmap_update_bits(usb_info->chip->regmap,
reg, mask, data);
if (ret)
return ret;
mdelay(20);
ret = pm80x_read_usb_val(&data);
if (ret)
return ret;
if (ret != vbus)
pr_info("vbus set failed %x\n", vbus);
else
pr_info("vbus set done %x\n", vbus);
return 0;
}
static irqreturn_t vbus_irq(int irq, void *dev)
{
if (usb_info->id_level == 0)
return IRQ_HANDLED;
pm_wakeup_event(usb_info->chip->dev, USB_HANDLE_TIME_MSEC);
pxa_usb_notify(PXA_USB_DEV_OTG, EVENT_VBUS, 0);
dev_info(usb_info->chip->dev, "88pm80x vbus interrupt is served..\n");
return IRQ_HANDLED;
}
static irqreturn_t id_handler(int irq, void *data)
{
struct pm80x_usb_info *info = data;
pm_wakeup_event(info->chip->dev, USB_HANDLE_TIME_MSEC);
if (info->id_ov_sampling) {
/* disable id interrupt, and start measurment process */
disable_irq_nosync(info->id_irq);
schedule_work(&info->meas_id_work);
} else {
/* update id value */
pm80x_update_id_level();
/* notify to wake up usb subsystem if ID pin is pulled down */
pxa_usb_notify(PXA_USB_DEV_OTG, EVENT_ID, 0);
}
dev_info(info->chip->dev, "88pm80x id interrupt is served..\n");
return IRQ_HANDLED;
}
static int pm80x_usb_probe(struct platform_device *pdev)
{
struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
struct pm80x_usb_info *usb;
struct device_node *np = pdev->dev.of_node;
int ret;
usb = kzalloc(sizeof(struct pm80x_usb_info), GFP_KERNEL);
if (!usb)
return -ENOMEM;
usb->chip = chip;
usb->subchip = chip->subchip;
if (!of_device_is_available(pdev->dev.of_node)) {
dev_err(&pdev->dev, "dts disabled!\n");
ret = -EINVAL;
goto out;
}
ret = of_property_read_u32(np, "vbus_gpio", &usb->vbus_gpio);
if (ret)
return ret;
if (usb->vbus_gpio == 0xff)
usb->vbus_gpio = -1;
ret = of_property_read_u32(np, "id-gpadc", &usb->id_gpadc);
if (ret)
return ret;
if (usb->id_gpadc == 0xff)
usb->id_gpadc = -1;
ret = of_property_read_u32(np, "vbus-detect", &usb->vbus_detect);
if (ret)
usb->vbus_detect = 0;
ret = of_property_read_u32(np, "get-vbus", &usb->get_vbus);
if (ret)
usb->get_vbus = 0;
ret = of_property_read_u32(np, "vchg-from-exton",
&usb->vchg_from_exton);
if (ret)
usb->vchg_from_exton = 0;
usb->id_ov_sampling = of_property_read_bool(np,
"id-ov-sampling");
if (usb->id_ov_sampling) {
ret = of_property_read_u32(np, "id-ov-samp-count",
&usb->id_ov_samp_count);
if (ret) {
pr_err("cannot get id measurments count.\n");
return -EINVAL;
}
ret = of_property_read_u32(np, "id-ov-samp-sleep",
&usb->id_ov_samp_sleep);
if (ret) {
pr_err("cannot get id sleep time.\n");
return -EINVAL;
}
}
usb->id_level = 1;
usb->id_in = 0;
usb->chg_in = 0;
spin_lock_init(&usb->lock);
if (usb->vbus_gpio != -1)
pxa_usb_set_extern_call(PXA_USB_DEV_OTG, vbus, set_vbus,
pm80x_set_vbus);
if (usb->get_vbus)
pxa_usb_set_extern_call(PXA_USB_DEV_OTG, vbus, get_vbus,
pm80x_read_usb_val);
if (usb->id_gpadc != -1) {
pxa_usb_set_extern_call(PXA_USB_DEV_OTG, idpin, get_idpin,
pm80x_ext_read_id_level);
pxa_usb_set_extern_call(PXA_USB_DEV_OTG, idpin, init,
pm80x_init_id);
}
INIT_WORK(&usb->meas_id_work, pm80x_meas_id_work);
usb_info = usb;
if (usb->id_gpadc != -1) {
if (usb->chip->type == CHIP_PM801 || usb->chip->type == CHIP_PM800)
pm80x_usb_id_config(usb);
else if (usb->chip->type == CHIP_PM802)
pm802_usb_id_config(usb);
else {
dev_err(&pdev->dev, "unknow PMIC: 0x%x\n", (u32)usb->chip->type);
return -EINVAL;
}
}
usb->vbus_irq = platform_get_irq(pdev, 0);
if (usb->vbus_irq < 0) {
dev_err(&pdev->dev, "failed to get vbus irq\n");
ret = -ENXIO;
goto out;
}
if (usb->id_gpadc != -1) {
usb->id_irq = platform_get_irq(pdev, usb->id_gpadc + 1);
if (usb->id_irq < 0) {
dev_err(&pdev->dev, "failed to get idpin irq\n");
ret = -ENXIO;
goto out;
}
}
if (usb->vbus_detect) {
ret = devm_request_threaded_irq(&pdev->dev, usb->vbus_irq,
NULL, vbus_irq,
IRQF_ONESHOT | IRQF_NO_SUSPEND,
"88pm800-usb-vbus", usb);
if (ret) {
dev_info(&pdev->dev,
"Can not request irq for VBUS, "
"disable clock gating\n");
goto out;
}
}
if (usb->id_gpadc != -1) {
ret = devm_request_threaded_irq(&pdev->dev, usb->id_irq, NULL,
id_handler,
IRQF_ONESHOT | IRQF_NO_SUSPEND,
"88pm800-id-irq", usb);
if (ret) {
dev_info(&pdev->dev,
"cannot request irq for idpin, return\n");
goto out;
}
}
platform_set_drvdata(pdev, usb);
device_init_wakeup(&pdev->dev, 1);
return 0;
out:
kfree(usb);
return ret;
}
static int pm80x_usb_remove(struct platform_device *pdev)
{
struct pm80x_usb_info *usb = platform_get_drvdata(pdev);
unsigned long flags;
if (!usb)
return 0;
if (usb->vbus_detect)
devm_free_irq(&pdev->dev, usb->vbus_irq, usb);
if (usb->id_gpadc != -1)
devm_free_irq(&pdev->dev, usb->id_irq, usb);
platform_set_drvdata(pdev, NULL);
spin_lock_irqsave(&usb->lock, flags);
rm_flag = 1;
spin_unlock_irqrestore(&usb->lock, flags);
kfree(usb);
return 0;
}
static void pm80x_usb_shutdown(struct platform_device *pdev)
{
pm80x_usb_remove(pdev);
return;
}
#ifdef CONFIG_PM
static int pm80x_usb_suspend(struct device *dev)
{
return pm80x_dev_suspend(dev);
}
static int pm80x_usb_resume(struct device *dev)
{
return pm80x_dev_resume(dev);
}
static const struct dev_pm_ops pm80x_usb_pm_ops = {
.suspend = pm80x_usb_suspend,
.resume = pm80x_usb_resume,
};
#endif
static struct platform_driver pm80x_usb_driver = {
.driver = {
.name = "88pm80x-usb",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &pm80x_usb_pm_ops,
#endif
},
.probe = pm80x_usb_probe,
.remove = pm80x_usb_remove,
.shutdown = pm80x_usb_shutdown,
};
static int __init pm80x_usb_init(void)
{
return platform_driver_register(&pm80x_usb_driver);
}
module_init(pm80x_usb_init);
static void __exit pm80x_usb_exit(void)
{
platform_driver_unregister(&pm80x_usb_driver);
}
module_exit(pm80x_usb_exit);
MODULE_DESCRIPTION("VBUS driver for Marvell Semiconductor 88PM80x");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:88pm80x-usb");