// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #define EUD_REG_INT1_EN_MASK 0x0024 #define EUD_REG_INT_STATUS_1 0x0044 #define EUD_REG_CTL_OUT_1 0x0074 #define EUD_REG_VBUS_INT_CLR 0x0080 #define EUD_REG_CSR_EUD_EN 0x1014 #define EUD_REG_SW_ATTACH_DET 0x1018 #define EUD_REG_EUD_EN2 0x0000 #define EUD_ENABLE BIT(0) #define EUD_INT_PET_EUD BIT(0) #define EUD_INT_VBUS BIT(2) #define EUD_INT_SAFE_MODE BIT(4) #define EUD_INT_ALL (EUD_INT_VBUS | EUD_INT_SAFE_MODE) struct eud_chip { struct device *dev; struct usb_role_switch *role_sw; void __iomem *base; void __iomem *mode_mgr; unsigned int int_status; int irq; bool enabled; bool usb_attached; }; static int enable_eud(struct eud_chip *priv) { writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN); writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, priv->base + EUD_REG_INT1_EN_MASK); writel(1, priv->mode_mgr + EUD_REG_EUD_EN2); return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE); } static void disable_eud(struct eud_chip *priv) { writel(0, priv->base + EUD_REG_CSR_EUD_EN); writel(0, priv->mode_mgr + EUD_REG_EUD_EN2); } static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct eud_chip *chip = dev_get_drvdata(dev); return sysfs_emit(buf, "%d\n", chip->enabled); } static ssize_t enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct eud_chip *chip = dev_get_drvdata(dev); bool enable; int ret; if (kstrtobool(buf, &enable)) return -EINVAL; if (enable) { ret = enable_eud(chip); if (!ret) chip->enabled = enable; else disable_eud(chip); } else { disable_eud(chip); } return count; } static DEVICE_ATTR_RW(enable); static struct attribute *eud_attrs[] = { &dev_attr_enable.attr, NULL, }; ATTRIBUTE_GROUPS(eud); static void usb_attach_detach(struct eud_chip *chip) { u32 reg; /* read ctl_out_1[4] to find USB attach or detach event */ reg = readl(chip->base + EUD_REG_CTL_OUT_1); chip->usb_attached = reg & EUD_INT_SAFE_MODE; } static void pet_eud(struct eud_chip *chip) { u32 reg; int ret; /* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been * disconnected and we need to detach the pet to check if EUD is in safe * mode before attaching again. */ reg = readl(chip->base + EUD_REG_SW_ATTACH_DET); if (reg & EUD_INT_PET_EUD) { /* Detach & Attach pet for EUD */ writel(0, chip->base + EUD_REG_SW_ATTACH_DET); /* Delay to make sure detach pet is done before attach pet */ ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET, reg, (reg == 0), 1, 100); if (ret) { dev_err(chip->dev, "Detach pet failed\n"); return; } } /* Attach pet for EUD */ writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET); } static irqreturn_t handle_eud_irq(int irq, void *data) { struct eud_chip *chip = data; u32 reg; reg = readl(chip->base + EUD_REG_INT_STATUS_1); switch (reg & EUD_INT_ALL) { case EUD_INT_VBUS: usb_attach_detach(chip); return IRQ_WAKE_THREAD; case EUD_INT_SAFE_MODE: pet_eud(chip); return IRQ_HANDLED; default: return IRQ_NONE; } } static irqreturn_t handle_eud_irq_thread(int irq, void *data) { struct eud_chip *chip = data; int ret; if (chip->usb_attached) ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE); else ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST); if (ret) dev_err(chip->dev, "failed to set role switch\n"); /* set and clear vbus_int_clr[0] to clear interrupt */ writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR); writel(0, chip->base + EUD_REG_VBUS_INT_CLR); return IRQ_HANDLED; } static void eud_role_switch_release(void *data) { struct eud_chip *chip = data; usb_role_switch_put(chip->role_sw); } static int eud_probe(struct platform_device *pdev) { struct eud_chip *chip; int ret; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->dev = &pdev->dev; chip->role_sw = usb_role_switch_get(&pdev->dev); if (IS_ERR(chip->role_sw)) return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw), "failed to get role switch\n"); ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip); if (ret) return dev_err_probe(chip->dev, ret, "failed to add role switch release action\n"); chip->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(chip->base)) return PTR_ERR(chip->base); chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(chip->mode_mgr)) return PTR_ERR(chip->mode_mgr); chip->irq = platform_get_irq(pdev, 0); if (chip->irq < 0) return chip->irq; ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq, handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip); if (ret) return dev_err_probe(chip->dev, ret, "failed to allocate irq\n"); enable_irq_wake(chip->irq); platform_set_drvdata(pdev, chip); return 0; } static void eud_remove(struct platform_device *pdev) { struct eud_chip *chip = platform_get_drvdata(pdev); if (chip->enabled) disable_eud(chip); device_init_wakeup(&pdev->dev, false); disable_irq_wake(chip->irq); } static const struct of_device_id eud_dt_match[] = { { .compatible = "qcom,eud" }, { } }; MODULE_DEVICE_TABLE(of, eud_dt_match); static struct platform_driver eud_driver = { .probe = eud_probe, .remove = eud_remove, .driver = { .name = "qcom_eud", .dev_groups = eud_groups, .of_match_table = eud_dt_match, }, }; module_platform_driver(eud_driver); MODULE_DESCRIPTION("QTI EUD driver"); MODULE_LICENSE("GPL v2");