Merge branch 'work.xattr' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[cascardo/linux.git] / drivers / phy / phy-sun4i-usb.c
index 8c7eb33..b9342a2 100644 (file)
@@ -40,6 +40,7 @@
 #include <linux/power_supply.h>
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
+#include <linux/spinlock.h>
 #include <linux/usb/of.h>
 #include <linux/workqueue.h>
 
@@ -50,7 +51,7 @@
 #define REG_PHYCTL_A33                 0x10
 #define REG_PHY_UNK_H3                 0x20
 
-#define REG_PMU_UNK_H3                 0x10
+#define REG_PMU_UNK1                   0x10
 
 #define PHYCTL_DATA                    BIT(7)
 
@@ -98,6 +99,7 @@ enum sun4i_usb_phy_type {
        sun6i_a31_phy,
        sun8i_a33_phy,
        sun8i_h3_phy,
+       sun50i_a64_phy,
 };
 
 struct sun4i_usb_phy_cfg {
@@ -106,13 +108,14 @@ struct sun4i_usb_phy_cfg {
        u32 disc_thresh;
        u8 phyctl_offset;
        bool dedicated_clocks;
+       bool enable_pmu_unk1;
 };
 
 struct sun4i_usb_phy_data {
        void __iomem *base;
        const struct sun4i_usb_phy_cfg *cfg;
        enum usb_dr_mode dr_mode;
-       struct mutex mutex;
+       spinlock_t reg_lock; /* guard access to phyctl reg */
        struct sun4i_usb_phy {
                struct phy *phy;
                void __iomem *pmu;
@@ -122,7 +125,6 @@ struct sun4i_usb_phy_data {
                bool regulator_on;
                int index;
        } phys[MAX_PHYS];
-       int first_phy;
        /* phy0 / otg related variables */
        struct extcon_dev *extcon;
        bool phy0_init;
@@ -131,6 +133,7 @@ struct sun4i_usb_phy_data {
        struct power_supply *vbus_power_supply;
        struct notifier_block vbus_power_nb;
        bool vbus_power_nb_registered;
+       bool force_session_end;
        int id_det_irq;
        int vbus_det_irq;
        int id_det;
@@ -179,12 +182,14 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data,
        struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy);
        u32 temp, usbc_bit = BIT(phy->index * 2);
        void __iomem *phyctl = phy_data->base + phy_data->cfg->phyctl_offset;
+       unsigned long flags;
        int i;
 
-       mutex_lock(&phy_data->mutex);
+       spin_lock_irqsave(&phy_data->reg_lock, flags);
 
-       if (phy_data->cfg->type == sun8i_a33_phy) {
-               /* A33 needs us to set phyctl to 0 explicitly */
+       if (phy_data->cfg->type == sun8i_a33_phy ||
+           phy_data->cfg->type == sun50i_a64_phy) {
+               /* A33 or A64 needs us to set phyctl to 0 explicitly */
                writel(0, phyctl);
        }
 
@@ -218,7 +223,8 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data,
 
                data >>= 1;
        }
-       mutex_unlock(&phy_data->mutex);
+
+       spin_unlock_irqrestore(&phy_data->reg_lock, flags);
 }
 
 static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable)
@@ -258,14 +264,16 @@ static int sun4i_usb_phy_init(struct phy *_phy)
                return ret;
        }
 
+       if (data->cfg->enable_pmu_unk1) {
+               val = readl(phy->pmu + REG_PMU_UNK1);
+               writel(val & ~2, phy->pmu + REG_PMU_UNK1);
+       }
+
        if (data->cfg->type == sun8i_h3_phy) {
                if (phy->index == 0) {
                        val = readl(data->base + REG_PHY_UNK_H3);
                        writel(val & ~1, data->base + REG_PHY_UNK_H3);
                }
-
-               val = readl(phy->pmu + REG_PMU_UNK_H3);
-               writel(val & ~2, phy->pmu + REG_PMU_UNK_H3);
        } else {
                /* Enable USB 45 Ohm resistor calibration */
                if (phy->index == 0)
@@ -320,7 +328,10 @@ static int sun4i_usb_phy0_get_id_det(struct sun4i_usb_phy_data *data)
 {
        switch (data->dr_mode) {
        case USB_DR_MODE_OTG:
-               return gpiod_get_value_cansleep(data->id_det_gpio);
+               if (data->id_det_gpio)
+                       return gpiod_get_value_cansleep(data->id_det_gpio);
+               else
+                       return 1; /* Fallback to peripheral mode */
        case USB_DR_MODE_HOST:
                return 0;
        case USB_DR_MODE_PERIPHERAL:
@@ -382,8 +393,10 @@ static int sun4i_usb_phy_power_on(struct phy *_phy)
 
        /* For phy0 only turn on Vbus if we don't have an ext. Vbus */
        if (phy->index == 0 && sun4i_usb_phy0_have_vbus_det(data) &&
-                               data->vbus_det)
+                               data->vbus_det) {
+               dev_warn(&_phy->dev, "External vbus detected, not enabling our own vbus\n");
                return 0;
+       }
 
        ret = regulator_enable(phy->vbus);
        if (ret)
@@ -419,6 +432,35 @@ static int sun4i_usb_phy_power_off(struct phy *_phy)
        return 0;
 }
 
+static int sun4i_usb_phy_set_mode(struct phy *_phy, enum phy_mode mode)
+{
+       struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
+       struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy);
+
+       if (phy->index != 0)
+               return -EINVAL;
+
+       switch (mode) {
+       case PHY_MODE_USB_HOST:
+               data->dr_mode = USB_DR_MODE_HOST;
+               break;
+       case PHY_MODE_USB_DEVICE:
+               data->dr_mode = USB_DR_MODE_PERIPHERAL;
+               break;
+       case PHY_MODE_USB_OTG:
+               data->dr_mode = USB_DR_MODE_OTG;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       dev_info(&_phy->dev, "Changing dr_mode to %d\n", (int)data->dr_mode);
+       data->force_session_end = true;
+       queue_delayed_work(system_wq, &data->detect, 0);
+
+       return 0;
+}
+
 void sun4i_usb_phy_set_squelch_detect(struct phy *_phy, bool enabled)
 {
        struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
@@ -432,6 +474,7 @@ static const struct phy_ops sun4i_usb_phy_ops = {
        .exit           = sun4i_usb_phy_exit,
        .power_on       = sun4i_usb_phy_power_on,
        .power_off      = sun4i_usb_phy_power_off,
+       .set_mode       = sun4i_usb_phy_set_mode,
        .owner          = THIS_MODULE,
 };
 
@@ -440,7 +483,8 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
        struct sun4i_usb_phy_data *data =
                container_of(work, struct sun4i_usb_phy_data, detect.work);
        struct phy *phy0 = data->phys[0].phy;
-       int id_det, vbus_det, id_notify = 0, vbus_notify = 0;
+       bool force_session_end, id_notify = false, vbus_notify = false;
+       int id_det, vbus_det;
 
        if (phy0 == NULL)
                return;
@@ -455,27 +499,30 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
                return;
        }
 
+       force_session_end = data->force_session_end;
+       data->force_session_end = false;
+
        if (id_det != data->id_det) {
-               /*
-                * When a host cable (id == 0) gets plugged in on systems
-                * without vbus detection report vbus low for long enough for
-                * the musb-ip to end the current device session.
-                */
+               /* id-change, force session end if we've no vbus detection */
                if (data->dr_mode == USB_DR_MODE_OTG &&
-                   !sun4i_usb_phy0_have_vbus_det(data) && id_det == 0) {
+                   !sun4i_usb_phy0_have_vbus_det(data))
+                       force_session_end = true;
+
+               /* When entering host mode (id = 0) force end the session now */
+               if (force_session_end && id_det == 0) {
                        sun4i_usb_phy0_set_vbus_detect(phy0, 0);
                        msleep(200);
                        sun4i_usb_phy0_set_vbus_detect(phy0, 1);
                }
                sun4i_usb_phy0_set_id_detect(phy0, id_det);
                data->id_det = id_det;
-               id_notify = 1;
+               id_notify = true;
        }
 
        if (vbus_det != data->vbus_det) {
                sun4i_usb_phy0_set_vbus_detect(phy0, vbus_det);
                data->vbus_det = vbus_det;
-               vbus_notify = 1;
+               vbus_notify = true;
        }
 
        mutex_unlock(&phy0->mutex);
@@ -483,13 +530,8 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
        if (id_notify) {
                extcon_set_cable_state_(data->extcon, EXTCON_USB_HOST,
                                        !id_det);
-               /*
-                * When a host cable gets unplugged (id == 1) on systems
-                * without vbus detection report vbus low for long enough to
-                * the musb-ip to end the current host session.
-                */
-               if (data->dr_mode == USB_DR_MODE_OTG &&
-                   !sun4i_usb_phy0_have_vbus_det(data) && id_det == 1) {
+               /* When leaving host mode force end the session here */
+               if (force_session_end && id_det == 1) {
                        mutex_lock(&phy0->mutex);
                        sun4i_usb_phy0_set_vbus_detect(phy0, 0);
                        msleep(1000);
@@ -534,8 +576,7 @@ static struct phy *sun4i_usb_phy_xlate(struct device *dev,
 {
        struct sun4i_usb_phy_data *data = dev_get_drvdata(dev);
 
-       if (args->args[0] < data->first_phy ||
-           args->args[0] >= data->cfg->num_phys)
+       if (args->args[0] >= data->cfg->num_phys)
                return ERR_PTR(-ENODEV);
 
        return data->phys[args->args[0]].phy;
@@ -577,7 +618,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
        if (!data)
                return -ENOMEM;
 
-       mutex_init(&data->mutex);
+       spin_lock_init(&data->reg_lock);
        INIT_DELAYED_WORK(&data->detect, sun4i_usb_phy0_id_vbus_det_scan);
        dev_set_drvdata(dev, data);
        data->cfg = of_device_get_match_data(dev);
@@ -610,33 +651,18 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
        }
 
        data->dr_mode = of_usb_get_dr_mode_by_phy(np, 0);
-       switch (data->dr_mode) {
-       case USB_DR_MODE_OTG:
-               /* otg without id_det makes no sense, and is not supported */
-               if (!data->id_det_gpio) {
-                       dev_err(dev, "usb0_id_det missing or invalid\n");
-                       return -ENODEV;
-               }
-               /* fall through */
-       case USB_DR_MODE_HOST:
-       case USB_DR_MODE_PERIPHERAL:
-               data->extcon = devm_extcon_dev_allocate(dev,
-                                                       sun4i_usb_phy0_cable);
-               if (IS_ERR(data->extcon))
-                       return PTR_ERR(data->extcon);
 
-               ret = devm_extcon_dev_register(dev, data->extcon);
-               if (ret) {
-                       dev_err(dev, "failed to register extcon: %d\n", ret);
-                       return ret;
-               }
-               break;
-       default:
-               dev_info(dev, "dr_mode unknown, not registering usb phy0\n");
-               data->first_phy = 1;
+       data->extcon = devm_extcon_dev_allocate(dev, sun4i_usb_phy0_cable);
+       if (IS_ERR(data->extcon))
+               return PTR_ERR(data->extcon);
+
+       ret = devm_extcon_dev_register(dev, data->extcon);
+       if (ret) {
+               dev_err(dev, "failed to register extcon: %d\n", ret);
+               return ret;
        }
 
-       for (i = data->first_phy; i < data->cfg->num_phys; i++) {
+       for (i = 0; i < data->cfg->num_phys; i++) {
                struct sun4i_usb_phy *phy = data->phys + i;
                char name[16];
 
@@ -737,6 +763,7 @@ static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = {
        .disc_thresh = 3,
        .phyctl_offset = REG_PHYCTL_A10,
        .dedicated_clocks = false,
+       .enable_pmu_unk1 = false,
 };
 
 static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = {
@@ -745,6 +772,7 @@ static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = {
        .disc_thresh = 2,
        .phyctl_offset = REG_PHYCTL_A10,
        .dedicated_clocks = false,
+       .enable_pmu_unk1 = false,
 };
 
 static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = {
@@ -753,6 +781,7 @@ static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = {
        .disc_thresh = 3,
        .phyctl_offset = REG_PHYCTL_A10,
        .dedicated_clocks = true,
+       .enable_pmu_unk1 = false,
 };
 
 static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = {
@@ -761,6 +790,7 @@ static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = {
        .disc_thresh = 2,
        .phyctl_offset = REG_PHYCTL_A10,
        .dedicated_clocks = false,
+       .enable_pmu_unk1 = false,
 };
 
 static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = {
@@ -769,6 +799,7 @@ static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = {
        .disc_thresh = 3,
        .phyctl_offset = REG_PHYCTL_A10,
        .dedicated_clocks = true,
+       .enable_pmu_unk1 = false,
 };
 
 static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = {
@@ -777,6 +808,7 @@ static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = {
        .disc_thresh = 3,
        .phyctl_offset = REG_PHYCTL_A33,
        .dedicated_clocks = true,
+       .enable_pmu_unk1 = false,
 };
 
 static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = {
@@ -784,6 +816,16 @@ static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = {
        .type = sun8i_h3_phy,
        .disc_thresh = 3,
        .dedicated_clocks = true,
+       .enable_pmu_unk1 = true,
+};
+
+static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = {
+       .num_phys = 2,
+       .type = sun50i_a64_phy,
+       .disc_thresh = 3,
+       .phyctl_offset = REG_PHYCTL_A33,
+       .dedicated_clocks = true,
+       .enable_pmu_unk1 = true,
 };
 
 static const struct of_device_id sun4i_usb_phy_of_match[] = {
@@ -794,6 +836,8 @@ static const struct of_device_id sun4i_usb_phy_of_match[] = {
        { .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg },
        { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg },
        { .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg },
+       { .compatible = "allwinner,sun50i-a64-usb-phy",
+         .data = &sun50i_a64_cfg},
        { },
 };
 MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match);