diff options
Diffstat (limited to 'drivers/phy/phy-sun4i-usb.c')
-rw-r--r-- | drivers/phy/phy-sun4i-usb.c | 154 |
1 files changed, 99 insertions, 55 deletions
diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c index 8c7eb335622e..b9342a2af7b3 100644 --- a/drivers/phy/phy-sun4i-usb.c +++ b/drivers/phy/phy-sun4i-usb.c @@ -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); |