| /* |
| * 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"); |