Skip to content

Commit 746f316

Browse files
JunlisuzhouPeter Chen
authored andcommitted
usb: chipidea: introduce imx7d USB charger detection
imx7d (and imx8mm, imx8mn) uses Samsung PHY and USB generic PHY driver. The USB generic PHY driver is impossible to have a charger detection for every user, so we implement USB charger detection routine at glue layer. After the detection has finished, it will notify USB PHY charger framework, and the uevents will be triggered. Signed-off-by: Jun Li <jun.li@nxp.com> Signed-off-by: Peter Chen <peter.chen@nxp.com>
1 parent d755cdb commit 746f316

3 files changed

Lines changed: 259 additions & 1 deletion

File tree

drivers/usb/chipidea/ci_hdrc_imx.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned int event)
271271
struct device *dev = ci->dev->parent;
272272
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
273273
int ret = 0;
274+
struct imx_usbmisc_data *mdata = data->usbmisc_data;
274275

275276
switch (event) {
276277
case CI_HDRC_IMX_HSIC_ACTIVE_EVENT:
@@ -284,11 +285,19 @@ static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned int event)
284285
}
285286
break;
286287
case CI_HDRC_IMX_HSIC_SUSPEND_EVENT:
287-
ret = imx_usbmisc_hsic_set_connect(data->usbmisc_data);
288+
ret = imx_usbmisc_hsic_set_connect(mdata);
288289
if (ret)
289290
dev_err(dev,
290291
"hsic_set_connect failed, err=%d\n", ret);
291292
break;
293+
case CI_HDRC_CONTROLLER_VBUS_EVENT:
294+
if (ci->vbus_active)
295+
ret = imx_usbmisc_charger_detection(mdata, true);
296+
else
297+
ret = imx_usbmisc_charger_detection(mdata, false);
298+
if (ci->usb_phy)
299+
schedule_work(&ci->usb_phy->chg_work);
300+
break;
292301
default:
293302
break;
294303
}
@@ -415,6 +424,8 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
415424
}
416425

417426
pdata.usb_phy = data->phy;
427+
if (data->usbmisc_data)
428+
data->usbmisc_data->usb_phy = data->phy;
418429

419430
if ((of_device_is_compatible(np, "fsl,imx53-usb") ||
420431
of_device_is_compatible(np, "fsl,imx51-usb")) && pdata.usb_phy &&

drivers/usb/chipidea/ci_hdrc_imx.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ struct imx_usbmisc_data {
2424
unsigned int hsic:1; /* HSIC controlller */
2525
unsigned int ext_id:1; /* ID from exteranl event */
2626
unsigned int ext_vbus:1; /* Vbus from exteranl event */
27+
struct usb_phy *usb_phy;
2728
};
2829

2930
int imx_usbmisc_init(struct imx_usbmisc_data *data);
3031
int imx_usbmisc_init_post(struct imx_usbmisc_data *data);
3132
int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled);
3233
int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data);
3334
int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on);
35+
int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect);
3436

3537
#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */

drivers/usb/chipidea/usbmisc_imx.c

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <linux/err.h>
99
#include <linux/io.h>
1010
#include <linux/delay.h>
11+
#include <linux/usb/otg.h>
1112

1213
#include "ci_hdrc_imx.h"
1314

@@ -99,6 +100,32 @@
99100
#define MX7D_USB_VBUS_WAKEUP_SOURCE_AVALID MX7D_USB_VBUS_WAKEUP_SOURCE(1)
100101
#define MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID MX7D_USB_VBUS_WAKEUP_SOURCE(2)
101102
#define MX7D_USB_VBUS_WAKEUP_SOURCE_SESS_END MX7D_USB_VBUS_WAKEUP_SOURCE(3)
103+
/* The default DM/DP value is pull-down */
104+
#define MX7D_USBNC_USB_CTRL2_OPMODE(v) (v << 6)
105+
#define MX7D_USBNC_USB_CTRL2_OPMODE_NON_DRIVING MX7D_USBNC_USB_CTRL2_OPMODE(1)
106+
#define MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK (BIT(7) | BIT(6))
107+
#define MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN BIT(8)
108+
#define MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_VAL BIT(12)
109+
#define MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_EN BIT(13)
110+
#define MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_VAL BIT(14)
111+
#define MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_EN BIT(15)
112+
#define MX7D_USBNC_USB_CTRL2_DP_DM_MASK (BIT(12) | BIT(13) | \
113+
BIT(14) | BIT(15))
114+
115+
#define MX7D_USB_OTG_PHY_CFG1 0x30
116+
#define MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL BIT(0)
117+
#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 BIT(1)
118+
#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 BIT(2)
119+
#define MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB BIT(3)
120+
#define MX7D_USB_OTG_PHY_CFG2_DRVVBUS0 BIT(16)
121+
122+
#define MX7D_USB_OTG_PHY_CFG2 0x34
123+
124+
#define MX7D_USB_OTG_PHY_STATUS 0x3c
125+
#define MX7D_USB_OTG_PHY_STATUS_LINE_STATE0 BIT(0)
126+
#define MX7D_USB_OTG_PHY_STATUS_LINE_STATE1 BIT(1)
127+
#define MX7D_USB_OTG_PHY_STATUS_VBUS_VLD BIT(3)
128+
#define MX7D_USB_OTG_PHY_STATUS_CHRGDET BIT(29)
102129

103130
#define MX6_USB_OTG_WAKEUP_BITS (MX6_BM_WAKEUP_ENABLE | MX6_BM_VBUS_WAKEUP | \
104131
MX6_BM_ID_WAKEUP)
@@ -114,6 +141,8 @@ struct usbmisc_ops {
114141
int (*hsic_set_connect)(struct imx_usbmisc_data *data);
115142
/* It's called during suspend/resume */
116143
int (*hsic_set_clk)(struct imx_usbmisc_data *data, bool enabled);
144+
/* usb charger detection */
145+
int (*charger_detection)(struct imx_usbmisc_data *data);
117146
};
118147

119148
struct imx_usbmisc {
@@ -621,6 +650,188 @@ static int usbmisc_imx7d_init(struct imx_usbmisc_data *data)
621650
return 0;
622651
}
623652

653+
static int imx7d_charger_secondary_detection(struct imx_usbmisc_data *data)
654+
{
655+
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
656+
struct usb_phy *usb_phy = data->usb_phy;
657+
int val;
658+
unsigned long flags;
659+
660+
/* VDM_SRC is connected to D- and IDP_SINK is connected to D+ */
661+
spin_lock_irqsave(&usbmisc->lock, flags);
662+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
663+
writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 |
664+
MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 |
665+
MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL,
666+
usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
667+
spin_unlock_irqrestore(&usbmisc->lock, flags);
668+
669+
usleep_range(1000, 2000);
670+
671+
/*
672+
* Per BC 1.2, check voltage of D+:
673+
* DCP: if greater than VDAT_REF;
674+
* CDP: if less than VDAT_REF.
675+
*/
676+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
677+
if (val & MX7D_USB_OTG_PHY_STATUS_CHRGDET) {
678+
dev_dbg(data->dev, "It is a dedicate charging port\n");
679+
usb_phy->chg_type = DCP_TYPE;
680+
} else {
681+
dev_dbg(data->dev, "It is a charging downstream port\n");
682+
usb_phy->chg_type = CDP_TYPE;
683+
}
684+
685+
return 0;
686+
}
687+
688+
static void imx7_disable_charger_detector(struct imx_usbmisc_data *data)
689+
{
690+
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
691+
unsigned long flags;
692+
u32 val;
693+
694+
spin_lock_irqsave(&usbmisc->lock, flags);
695+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
696+
val &= ~(MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB |
697+
MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 |
698+
MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 |
699+
MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL);
700+
writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
701+
702+
/* Set OPMODE to be 2'b00 and disable its override */
703+
val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
704+
val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK;
705+
writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2);
706+
707+
val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
708+
writel(val & ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN,
709+
usbmisc->base + MX7D_USBNC_USB_CTRL2);
710+
spin_unlock_irqrestore(&usbmisc->lock, flags);
711+
}
712+
713+
static int imx7d_charger_data_contact_detect(struct imx_usbmisc_data *data)
714+
{
715+
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
716+
unsigned long flags;
717+
u32 val;
718+
int i, data_pin_contact_count = 0;
719+
720+
/* Enable Data Contact Detect (DCD) per the USB BC 1.2 */
721+
spin_lock_irqsave(&usbmisc->lock, flags);
722+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
723+
writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB,
724+
usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
725+
spin_unlock_irqrestore(&usbmisc->lock, flags);
726+
727+
for (i = 0; i < 100; i = i + 1) {
728+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
729+
if (!(val & MX7D_USB_OTG_PHY_STATUS_LINE_STATE0)) {
730+
if (data_pin_contact_count++ > 5)
731+
/* Data pin makes contact */
732+
break;
733+
usleep_range(5000, 10000);
734+
} else {
735+
data_pin_contact_count = 0;
736+
usleep_range(5000, 6000);
737+
}
738+
}
739+
740+
/* Disable DCD after finished data contact check */
741+
spin_lock_irqsave(&usbmisc->lock, flags);
742+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
743+
writel(val & ~MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB,
744+
usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
745+
spin_unlock_irqrestore(&usbmisc->lock, flags);
746+
747+
if (i == 100) {
748+
dev_err(data->dev,
749+
"VBUS is coming from a dedicated power supply.\n");
750+
return -ENXIO;
751+
}
752+
753+
return 0;
754+
}
755+
756+
static int imx7d_charger_primary_detection(struct imx_usbmisc_data *data)
757+
{
758+
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
759+
struct usb_phy *usb_phy = data->usb_phy;
760+
unsigned long flags;
761+
u32 val;
762+
763+
/* VDP_SRC is connected to D+ and IDM_SINK is connected to D- */
764+
spin_lock_irqsave(&usbmisc->lock, flags);
765+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
766+
val &= ~MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL;
767+
writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 |
768+
MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0,
769+
usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
770+
spin_unlock_irqrestore(&usbmisc->lock, flags);
771+
772+
usleep_range(1000, 2000);
773+
774+
/* Check if D- is less than VDAT_REF to determine an SDP per BC 1.2 */
775+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
776+
if (!(val & MX7D_USB_OTG_PHY_STATUS_CHRGDET)) {
777+
dev_dbg(data->dev, "It is a standard downstream port\n");
778+
usb_phy->chg_type = SDP_TYPE;
779+
}
780+
781+
return 0;
782+
}
783+
784+
/**
785+
* Whole charger detection process:
786+
* 1. OPMODE override to be non-driving
787+
* 2. Data contact check
788+
* 3. Primary detection
789+
* 4. Secondary detection
790+
* 5. Disable charger detection
791+
*/
792+
static int imx7d_charger_detection(struct imx_usbmisc_data *data)
793+
{
794+
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
795+
struct usb_phy *usb_phy = data->usb_phy;
796+
unsigned long flags;
797+
u32 val;
798+
int ret;
799+
800+
/* Check if vbus is valid */
801+
val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
802+
if (!(val & MX7D_USB_OTG_PHY_STATUS_VBUS_VLD)) {
803+
dev_err(data->dev, "vbus is error\n");
804+
return -EINVAL;
805+
}
806+
807+
/*
808+
* Keep OPMODE to be non-driving mode during the whole
809+
* charger detection process.
810+
*/
811+
spin_lock_irqsave(&usbmisc->lock, flags);
812+
val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
813+
val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK;
814+
val |= MX7D_USBNC_USB_CTRL2_OPMODE_NON_DRIVING;
815+
writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2);
816+
817+
val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
818+
writel(val | MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN,
819+
usbmisc->base + MX7D_USBNC_USB_CTRL2);
820+
spin_unlock_irqrestore(&usbmisc->lock, flags);
821+
822+
ret = imx7d_charger_data_contact_detect(data);
823+
if (ret)
824+
return ret;
825+
826+
ret = imx7d_charger_primary_detection(data);
827+
if (!ret && usb_phy->chg_type != SDP_TYPE)
828+
ret = imx7d_charger_secondary_detection(data);
829+
830+
imx7_disable_charger_detector(data);
831+
832+
return ret;
833+
}
834+
624835
static const struct usbmisc_ops imx25_usbmisc_ops = {
625836
.init = usbmisc_imx25_init,
626837
.post = usbmisc_imx25_post,
@@ -659,6 +870,7 @@ static const struct usbmisc_ops imx6sx_usbmisc_ops = {
659870
static const struct usbmisc_ops imx7d_usbmisc_ops = {
660871
.init = usbmisc_imx7d_init,
661872
.set_wakeup = usbmisc_imx7d_set_wakeup,
873+
.charger_detection = imx7d_charger_detection,
662874
};
663875

664876
static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data)
@@ -737,6 +949,39 @@ int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on)
737949
return usbmisc->ops->hsic_set_clk(data, on);
738950
}
739951
EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_clk);
952+
953+
int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect)
954+
{
955+
struct imx_usbmisc *usbmisc;
956+
struct usb_phy *usb_phy;
957+
int ret = 0;
958+
959+
if (!data)
960+
return -EINVAL;
961+
962+
usbmisc = dev_get_drvdata(data->dev);
963+
usb_phy = data->usb_phy;
964+
if (!usbmisc->ops->charger_detection)
965+
return -ENOTSUPP;
966+
967+
if (connect) {
968+
ret = usbmisc->ops->charger_detection(data);
969+
if (ret) {
970+
dev_err(data->dev,
971+
"Error occurs during detection: %d\n",
972+
ret);
973+
usb_phy->chg_state = USB_CHARGER_ABSENT;
974+
} else {
975+
usb_phy->chg_state = USB_CHARGER_PRESENT;
976+
}
977+
} else {
978+
usb_phy->chg_state = USB_CHARGER_ABSENT;
979+
usb_phy->chg_type = UNKNOWN_TYPE;
980+
}
981+
return ret;
982+
}
983+
EXPORT_SYMBOL_GPL(imx_usbmisc_charger_detection);
984+
740985
static const struct of_device_id usbmisc_imx_dt_ids[] = {
741986
{
742987
.compatible = "fsl,imx25-usbmisc",

0 commit comments

Comments
 (0)