From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Patrick Yavitz Date: Fri, 21 Jun 2024 11:54:06 -0400 Subject: add spacemit patch set source: https://gitee.com/bianbu-linux/linux-6.1 Signed-off-by: Patrick Yavitz --- drivers/extcon/Kconfig | 7 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-k1xci.c | 358 ++++++++++ 3 files changed, 366 insertions(+) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 111111111111..222222222222 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -41,6 +41,13 @@ config EXTCON_FSA9480 I2C and enables USB data, stereo and mono audio, video, microphone and UART data to use a common connector port. +config EXTCON_USB_K1XCI + tristate "Spacemit K1-x USB extcon support" + depends on GPIOLIB || COMPILE_TEST + help + say Y here to enable spacemit k1-x usb wakeup irq based USB cable detection extcon support. + Used typically if wakeup irq is used for USB ID pin detection. + config EXTCON_GPIO tristate "GPIO extcon support" depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 111111111111..222222222222 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_EXTCON_PTN5150) += extcon-ptn5150.o obj-$(CONFIG_EXTCON_QCOM_SPMI_MISC) += extcon-qcom-spmi-misc.o obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o +obj-$(CONFIG_EXTCON_USB_K1XCI) += extcon-k1xci.o obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o diff --git a/drivers/extcon/extcon-k1xci.c b/drivers/extcon/extcon-k1xci.c new file mode 100644 index 000000000000..111111111111 --- /dev/null +++ b/drivers/extcon/extcon-k1xci.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * extcon-k1xci.c - Driver for usb vbus/id detect + * + * Copyright (c) 2023, Spacemit Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_GPIO_DEBOUNCE_MS 200 /* ms */ + +/* PMU_SD_ROT_WAKE_CLR */ +#define USB_VBUS_WK_MASK BIT(10) +#define USB_ID_WK_MASK BIT(11) + +#define USB_VBUS_WK_CLR BIT(18) +#define USB_ID_WK_CLR BIT(19) + +#define USB_VBUS_WK_STATUS BIT(26) +#define USB_ID_WK_STATUS BIT(27) + +/* PMUA_USB_PHY_READ */ +#define USB_ID BIT(1) +#define USB_VBUS BIT(2) + +struct mv_usb_extcon_info { + struct device *dev; + struct extcon_dev *edev; + + void __iomem *pmuap_reg; + void __iomem *pin_state_reg; + + int irq; + + unsigned long debounce_jiffies; + struct delayed_work wq_detcable; + + struct freq_qos_request qos_idle; + u32 lpm_qos; + + /* debugfs interface for user-space */ + struct dentry *dbgfs; + char dbgfs_name[32]; + uint32_t dbgfs_qos_mode; +}; + +static const unsigned int usb_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static void mv_enable_wakeup_irqs(struct mv_usb_extcon_info *info) +{ + u32 reg; + reg = readl(info->pmuap_reg); + reg |= (USB_VBUS_WK_MASK | USB_ID_WK_MASK); + writel(reg, info->pmuap_reg); +} + +static void mv_disable_wakeup_irqs(struct mv_usb_extcon_info *info) +{ + u32 reg; + reg = readl(info->pmuap_reg); + reg &= ~(USB_VBUS_WK_MASK | USB_ID_WK_MASK); + writel(reg, info->pmuap_reg); +} + +/* + * "USB" = VBUS and "USB-HOST" = !ID, so we have: + * Both "USB" and "USB-HOST" can't be set as active at the + * same time so if "USB-HOST" is active (i.e. ID is 0) we keep "USB" inactive + * even if VBUS is on. + * + * State | ID | VBUS + * ---------------------------------------- + * [1] USB | H | H + * [2] none | H | L + * [3] USB-HOST | L | H + * [4] USB-HOST | L | L + * + * In case we have only one of these signals: + * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. + * - ID only - we want to distinguish between [1] and [4], so VBUS = ID. +*/ + +static void usb_detect_cable(struct work_struct *work) +{ + int id, vbus; + int id_state, vbus_state; + u32 reg; + u32 state; + + struct mv_usb_extcon_info *info = container_of( + to_delayed_work(work), struct mv_usb_extcon_info, wq_detcable); + + reg = readl(info->pmuap_reg); + id = reg & USB_ID_WK_STATUS; + vbus = reg & USB_VBUS_WK_STATUS; + + pr_info("info->pmuap_reg: 0x%x id: %d vbus: %d \n", reg, id, vbus); + if (id || vbus) { + state = readl(info->pin_state_reg); + id_state = state & USB_ID; + vbus_state = state & USB_VBUS; + + if (!id_state) { + dev_info(info->dev, "USB we as host connected\n"); + extcon_set_state_sync(info->edev, EXTCON_USB_HOST, + true); + } else { + dev_info(info->dev, "USB we as host disconnected\n"); + extcon_set_state_sync(info->edev, EXTCON_USB_HOST, + false); + + if (!vbus_state) { + dev_info(info->dev, + "USB we as peripheral disconnected\n"); + extcon_set_state_sync(info->edev, EXTCON_USB, + false); + } else { + dev_dbg(info->dev, "dbgfs_qos_mode = %d \n", + info->dbgfs_qos_mode); + dev_info(info->dev, + "USB we as peripheral connected\n"); + extcon_set_state_sync(info->edev, EXTCON_USB, + true); + } + } + } + + reg |= (USB_VBUS_WK_CLR | USB_ID_WK_CLR); + writel(reg, info->pmuap_reg); + mv_enable_wakeup_irqs(info); +} + +static irqreturn_t mv_wakeup_interrupt(int irq, void *_info) +{ + struct mv_usb_extcon_info *info = (struct mv_usb_extcon_info *)_info; + + pr_info("extcon_mvci: mv_wakeup_interrupt... \n"); + mv_disable_wakeup_irqs(info); + + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + info->debounce_jiffies); + + return IRQ_HANDLED; +} + +static ssize_t extcon_mvci_dbgfs_read(struct file *filp, char __user *user_buf, + size_t size, loff_t *ppos) +{ + struct mv_usb_extcon_info *info = filp->private_data; + char buf[64]; + int ret, n, copy; + + n = min(sizeof(buf) - 1, size); + + if (info->dbgfs_qos_mode == 1) + copy = sprintf(buf, "enable mvci qos_hold\n"); + else + copy = sprintf(buf, "disable mvci qos_hold\n"); + + copy = min(n, copy); + ret = simple_read_from_buffer(user_buf, size, ppos, buf, copy); + + return ret; +} + +static ssize_t extcon_mvci_dbgfs_write(struct file *filp, + const char __user *user_buf, size_t size, + loff_t *ppos) +{ + struct mv_usb_extcon_info *info = filp->private_data; + char buf[32]; + int buf_size, i = 0; + + buf_size = min(size, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + *(buf + buf_size) = '\0'; + while (*(buf + i) != '\n' && *(buf + i) != '\0') + i++; + *(buf + i) = '\0'; + + i = 0; + while (*(buf + i) == ' ') + i++; + + if (!strncmp(buf + i, "enable", 6)) { + info->dbgfs_qos_mode = 1; + } else if (!strncmp(buf + i, "disable", 7)) { + info->dbgfs_qos_mode = 0; + dev_info(info->dev, "mvci qos release\n"); + } else { + dev_err(info->dev, "only accept: enable, disable\n"); + } + + return size; +} + +static const struct file_operations extcon_mvci_dbgfs_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = extcon_mvci_dbgfs_read, + .write = extcon_mvci_dbgfs_write, +}; + +static int mv_usb_extcon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct resource *res; + struct mv_usb_extcon_info *info; + int ret; + u32 property; +#ifdef CONFIG_PM + //struct freq_constraints *idle_qos; +#endif + + if (!np) + return -EINVAL; + + dev_info(dev, "mv_usb_extcon_probe\n"); + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = dev; + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + dev_err(dev, "missing IRQ resource\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg_pmuap"); + if (!res) { + dev_err(dev, "missing memory base resource\n"); + return -ENODEV; + } + + info->pmuap_reg = + devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!info->pmuap_reg) { + dev_err(dev, "ioremap failed\n"); + return -ENODEV; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pin_state"); + if (!res) { + dev_err(dev, "missing memory base resource\n"); + return -ENODEV; + } + + info->pin_state_reg = + devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!info->pin_state_reg) { + dev_err(dev, "ioremap failed\n"); + return -ENODEV; + } + + info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); + if (IS_ERR(info->edev)) { + dev_err(dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + + ret = devm_extcon_dev_register(dev, info->edev); + if (ret < 0) { + dev_err(dev, "failed to register extcon device\n"); + return ret; + } + + info->dbgfs_qos_mode = 1; + + ret = devm_request_irq(dev, info->irq, mv_wakeup_interrupt, + IRQF_NO_SUSPEND, "mv-wakeup", info); + if (ret) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", info->irq, + ret); + return ret; + } + + if (!of_property_read_s32(pdev->dev.of_node, "lpm-qos", &property)) + info->lpm_qos = property; + else + info->lpm_qos = 15; + + platform_set_drvdata(pdev, info); + device_init_wakeup(dev, 1); + + info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); + + INIT_DELAYED_WORK(&info->wq_detcable, usb_detect_cable); + + mv_enable_wakeup_irqs(info); + + /* Perform initial detection */ + usb_detect_cable(&info->wq_detcable.work); + + info->dbgfs = debugfs_create_file("mvci_extcon_qos", 0644, NULL, info, + &extcon_mvci_dbgfs_ops); + if (!info->dbgfs) { + dev_err(info->dev, "failed to create debugfs\n"); + return -ENOMEM; + } + + return 0; +} + +static int mv_usb_extcon_remove(struct platform_device *pdev) +{ + struct mv_usb_extcon_info *info = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&info->wq_detcable); + device_init_wakeup(&pdev->dev, 0); + + freq_qos_remove_request(&info->qos_idle); + + return 0; +} + +static const struct of_device_id mv_usb_extcon_dt_match[] = { + { + .compatible = "spacemit,vbus-id", + }, + {} +}; + +MODULE_DEVICE_TABLE(of, mv_usb_extcon_dt_match); + +static struct platform_driver usb_extcon_driver = { + .probe = mv_usb_extcon_probe, + .remove = mv_usb_extcon_remove, + .driver = { + .name = "extcon-k1xci-usb", + .of_match_table = mv_usb_extcon_dt_match, + }, +}; + +module_platform_driver(usb_extcon_driver); + +MODULE_LICENSE("GPL v2"); -- Armbian