diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index ffd208bd9923..1913d1b15b0a 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -601,4 +601,15 @@ config MSM_SPCOM is based on shared memory and interrupts. SPCOM provides clients/server API although currently only one client/server is allowed per logical channel. +config QCOM_EUD + tristate "QTI Embedded USB Debugger (EUD)" + depends on ARCH_QCOM + select SERIAL_CORE + help + The EUD (Embedded USB Debugger) is a mini-USB hub implemented + on chip to support the USB-based debug and trace capabilities. + This module enables support for Qualcomm Technologies, Inc. + Embedded USB Debugger (EUD). + If unsure, say N. + endmenu diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index bd69f32afe73..1808ca07f299 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -60,3 +60,4 @@ obj-$(CONFIG_QPNP_PBS) += qpnp-pbs.o obj-$(CONFIG_QCOM_WATCHDOG) += qcom_watchdog.o obj-$(CONFIG_MSM_SPCOM) += spcom.o obj-$(CONFIG_QCOM_FSA4480_I2C) += fsa4480-i2c.o +obj-$(CONFIG_QCOM_EUD) += eud.o diff --git a/drivers/soc/qcom/eud.c b/drivers/soc/qcom/eud.c new file mode 100644 index 000000000000..65a1923e2ae0 --- /dev/null +++ b/drivers/soc/qcom/eud.c @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EUD_ENABLE_CMD 1 +#define EUD_DISABLE_CMD 0 + +#define EUD_REG_COM_TX_ID 0x0000 +#define EUD_REG_COM_TX_LEN 0x0004 +#define EUD_REG_COM_TX_DAT 0x0008 +#define EUD_REG_COM_RX_ID 0x000C +#define EUD_REG_COM_RX_LEN 0x0010 +#define EUD_REG_COM_RX_DAT 0x0014 +#define EUD_REG_EUD_EN2 0x0000 +#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_CHGR_INT_CLR 0x0084 +#define EUD_REG_CSR_EUD_EN 0x1014 +#define EUD_REG_SW_ATTACH_DET 0x1018 + +#define EUD_INT_RX BIT(0) +#define EUD_INT_TX BIT(1) +#define EUD_INT_VBUS BIT(2) +#define EUD_INT_CHGR BIT(3) +#define EUD_INT_SAFE_MODE BIT(4) +#define EUD_INT_ALL (EUD_INT_RX | EUD_INT_TX | \ + EUD_INT_VBUS | EUD_INT_CHGR | \ + EUD_INT_SAFE_MODE) + +#define EUD_NR 1 +#define EUD_CONSOLE NULL +#define UART_ID 0x90 +#define MAX_FIFO_SIZE 14 + +struct eud_chip { + struct device *dev; + int eud_irq; + unsigned int extcon_id; + unsigned int int_status; + bool usb_attach; + bool chgr_enable; + void __iomem *eud_reg_base; + struct extcon_dev *extcon; + struct uart_port port; + struct work_struct eud_work; + struct power_supply *batt_psy; + bool secure_eud_en; + bool need_phy_clk_vote; + phys_addr_t eud_mode_mgr2_phys_base; + struct clk *eud_ahb2phy_clk; +}; + +static const unsigned int eud_extcon_cable[] = { + EXTCON_USB, + EXTCON_CHG_USB_SDP, + EXTCON_NONE, +}; + +/* + * On the kernel command line specify eud.enable=1 to enable EUD. + * EUD is disabled by default. + */ +static int enable; +static bool eud_ready; +static struct platform_device *eud_private; + +static void enable_eud(struct platform_device *pdev) +{ + struct eud_chip *priv = platform_get_drvdata(pdev); + int ret; + + /* write into CSR to enable EUD */ + writel_relaxed(BIT(0), priv->eud_reg_base + EUD_REG_CSR_EUD_EN); + + /* Enable vbus, chgr & safe mode warning interrupts */ + writel_relaxed(EUD_INT_VBUS | EUD_INT_CHGR | EUD_INT_SAFE_MODE, + priv->eud_reg_base + EUD_REG_INT1_EN_MASK); + + /* Enable secure eud if supported */ + if (priv->secure_eud_en) { + ret = qcom_scm_io_writel(priv->eud_mode_mgr2_phys_base + + EUD_REG_EUD_EN2, EUD_ENABLE_CMD); + if (ret) + dev_err(&pdev->dev, + "qcom_scm_io_writel failed with rc:%d\n", ret); + } + + /* Ensure Register Writes Complete */ + wmb(); + + /* + * Set the default cable state to usb connect and charger + * enable + */ + extcon_set_state_sync(priv->extcon, EXTCON_USB, true); + extcon_set_state_sync(priv->extcon, EXTCON_CHG_USB_SDP, true); + + dev_dbg(&pdev->dev, "%s: EUD is Enabled\n", __func__); +} + +static void disable_eud(struct platform_device *pdev) +{ + struct eud_chip *priv = platform_get_drvdata(pdev); + int ret; + + /* write into CSR to disable EUD */ + writel_relaxed(0, priv->eud_reg_base + EUD_REG_CSR_EUD_EN); + + /* Disable secure eud if supported */ + if (priv->secure_eud_en) { + ret = qcom_scm_io_writel(priv->eud_mode_mgr2_phys_base + + EUD_REG_EUD_EN2, EUD_DISABLE_CMD); + if (ret) + dev_err(&pdev->dev, + "qcom_scm_io_write failed with rc:%d\n", ret); + } + + dev_dbg(&pdev->dev, "%s: EUD Disabled!\n", __func__); +} + +static int param_eud_set(const char *val, const struct kernel_param *kp) +{ + int enable = 0; + + if (sscanf(val, "%du", &enable) != 1) + return -EINVAL; + + if (enable != EUD_ENABLE_CMD && enable != EUD_DISABLE_CMD) + return -EINVAL; + + *((uint *)kp->arg) = enable; + if (!eud_ready) + return 0; + + if (enable == EUD_ENABLE_CMD) { + pr_debug("%s: Enbling EUD\n", __func__); + enable_eud(eud_private); + } else if (enable == EUD_DISABLE_CMD) { + pr_debug("%s: Disabling EUD\n", __func__); + disable_eud(eud_private); + } + + return 0; +} + +static const struct kernel_param_ops eud_param_ops = { + .set = param_eud_set, + .get = param_get_int, +}; + +module_param_cb(enable, &eud_param_ops, &enable, 0644); + +static bool is_batt_available(struct eud_chip *chip) +{ + if (!chip->batt_psy) + chip->batt_psy = power_supply_get_by_name("battery"); + + if (!chip->batt_psy) + return false; + + return true; +} + +static void eud_event_notifier(struct work_struct *eud_work) +{ + struct eud_chip *chip = container_of(eud_work, struct eud_chip, + eud_work); + union power_supply_propval pval; + + if (chip->int_status == EUD_INT_VBUS) + extcon_set_state_sync(chip->extcon, chip->extcon_id, + chip->usb_attach); + else if (chip->int_status == EUD_INT_CHGR) { + if (is_batt_available(chip)) { + pval.intval = chip->chgr_enable ? -EINVAL : + chip->chgr_enable; + power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &pval); + } + } +} + +static void usb_attach_detach(struct eud_chip *chip) +{ + u32 reg; + + chip->extcon_id = EXTCON_USB; + /* read ctl_out_1[4] to find USB attach or detach event */ + reg = readl_relaxed(chip->eud_reg_base + EUD_REG_CTL_OUT_1); + if (reg & BIT(4)) + chip->usb_attach = true; + else + chip->usb_attach = false; + + schedule_work(&chip->eud_work); + + /* set and clear vbus_int_clr[0] to clear interrupt */ + writel_relaxed(BIT(0), chip->eud_reg_base + EUD_REG_VBUS_INT_CLR); + /* Ensure Register Writes Complete */ + wmb(); + writel_relaxed(0, chip->eud_reg_base + EUD_REG_VBUS_INT_CLR); +} + +static void chgr_enable_disable(struct eud_chip *chip) +{ + u32 reg; + + chip->extcon_id = EXTCON_CHG_USB_SDP; + /* read ctl_out_1[6] to find charger enable or disable event */ + reg = readl_relaxed(chip->eud_reg_base + EUD_REG_CTL_OUT_1); + if (reg & BIT(6)) + chip->chgr_enable = true; + else + chip->chgr_enable = false; + + schedule_work(&chip->eud_work); + + /* set and clear chgr_int_clr[0] to clear interrupt */ + writel_relaxed(BIT(0), chip->eud_reg_base + EUD_REG_CHGR_INT_CLR); + /* Ensure Register Writes Complete */ + wmb(); + writel_relaxed(0, chip->eud_reg_base + EUD_REG_CHGR_INT_CLR); +} + +static void pet_eud(struct eud_chip *chip) +{ + u32 reg; + + /* read sw_attach_det[0] to find attach/detach event */ + reg = readl_relaxed(chip->eud_reg_base + EUD_REG_SW_ATTACH_DET); + if (reg & BIT(0)) { + /* Detach & Attach pet for EUD */ + writel_relaxed(0, chip->eud_reg_base + EUD_REG_SW_ATTACH_DET); + /* Ensure Register Writes Complete */ + wmb(); + /* Delay to make sure detach pet is done before attach pet */ + udelay(100); + writel_relaxed(BIT(0), chip->eud_reg_base + + EUD_REG_SW_ATTACH_DET); + /* Ensure Register Writes Complete */ + wmb(); + } else { + /* Attach pet for EUD */ + writel_relaxed(BIT(0), chip->eud_reg_base + + EUD_REG_SW_ATTACH_DET); + /* Ensure Register Writes Complete */ + wmb(); + } +} + +static unsigned int eud_tx_empty(struct uart_port *port) +{ + u32 reg; + + /* read status register and cross check for Tx interrupt */ + reg = readl_relaxed(port->membase + EUD_REG_INT_STATUS_1); + if (reg & EUD_INT_TX) + return TIOCSER_TEMT; + else + return 0; +} + +static void eud_stop_tx(struct uart_port *port) +{ + /* Disable Tx interrupt */ + writel_relaxed(~EUD_INT_TX, port->membase + EUD_REG_INT_STATUS_1); + /* Ensure Register Writes Complete */ + wmb(); +} + +static void eud_start_tx(struct uart_port *port) +{ + /* Enable Tx interrupt */ + writel_relaxed(EUD_INT_TX, port->membase + EUD_REG_INT_STATUS_1); + /* Ensure Register Writes Complete */ + wmb(); +} + +static void eud_stop_rx(struct uart_port *port) +{ + /* Disable Rx interrupt */ + writel_relaxed(~EUD_INT_RX, port->membase + EUD_REG_INT_STATUS_1); + /* Ensure Register Writes Complete */ + wmb(); +} + +static int eud_startup(struct uart_port *port) +{ + /* Enable Rx interrupt */ + writel_relaxed(EUD_INT_RX, port->membase + EUD_REG_INT_STATUS_1); + /* Ensure Register Writes Complete */ + wmb(); + return 0; +} + +static void eud_shutdown(struct uart_port *port) +{ + /* Disable both Tx & Rx interrupts */ + writel_relaxed(~EUD_INT_TX | ~EUD_INT_RX, + port->membase + EUD_REG_INT_STATUS_1); + /* Ensure Register Writes Complete */ + wmb(); +} + +static const char *eud_type(struct uart_port *port) +{ + return (port->type == PORT_EUD_UART) ? "EUD UART" : NULL; +} + +static int eud_request_port(struct uart_port *port) +{ + /* Nothing to request */ + return 0; +} + +static void eud_release_port(struct uart_port *port) +{ + /* Nothing to release */ +} + +static void eud_config_port(struct uart_port *port, int flags) +{ + /* set port type, clear Tx and Rx interrupts */ + port->type = PORT_EUD_UART; + writel_relaxed(~EUD_INT_TX | ~EUD_INT_RX, + port->membase + EUD_REG_INT_STATUS_1); + /* Ensure Register Writes Complete */ + wmb(); +} + +static int eud_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if (ser->type != PORT_UNKNOWN && ser->type != PORT_EUD_UART) + return -EINVAL; + return 0; +} + +/* serial functions supported */ +static const struct uart_ops eud_uart_ops = { + .tx_empty = eud_tx_empty, + .stop_tx = eud_stop_tx, + .start_tx = eud_start_tx, + .stop_rx = eud_stop_rx, + .startup = eud_startup, + .shutdown = eud_shutdown, + .type = eud_type, + .release_port = eud_release_port, + .request_port = eud_request_port, + .config_port = eud_config_port, + .verify_port = eud_verify_port, +}; + +static struct uart_driver eud_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "msm-eud", + .dev_name = "ttyEUD", + .nr = EUD_NR, + .cons = EUD_CONSOLE, +}; + +static void eud_uart_rx(struct eud_chip *chip) +{ + struct uart_port *port = &chip->port; + u32 reg; + unsigned int len; + unsigned char ch, flag; + int i; + + reg = readl_relaxed(chip->eud_reg_base + EUD_REG_COM_RX_ID); + if (reg != UART_ID) { + dev_dbg(chip->dev, "Rx interrupt isn't for us\n"); + return; + } + /* Read Rx Len & Data registers */ + spin_lock(&port->lock); + len = readl_relaxed(chip->eud_reg_base + EUD_REG_COM_RX_LEN); + for (i = 0; i < len; i++) { + ch = readl_relaxed(chip->eud_reg_base + EUD_REG_COM_RX_DAT); + flag = TTY_NORMAL; + port->icount.rx++; + + if (uart_handle_sysrq_char(port, ch)) + continue; + uart_insert_char(port, 0, 0, ch, flag); + } + + spin_unlock(&port->lock); + tty_flip_buffer_push(&port->state->port); +} + +static void eud_uart_tx(struct eud_chip *chip) +{ + struct uart_port *port = &chip->port; + struct circ_buf *xmit = &port->state->xmit; + unsigned int len; + u32 reg; + + writel_relaxed(UART_ID, chip->eud_reg_base + EUD_REG_COM_TX_ID); + reg = readl_relaxed(chip->eud_reg_base + EUD_REG_COM_TX_ID); + if (reg != UART_ID) { + dev_dbg(chip->dev, "Tx interrupt isn't for us\n"); + return; + } + /* Write to Tx Len & Data registers */ + spin_lock(&port->lock); + len = uart_circ_chars_pending(xmit); + if (len > 0) { + if (len > port->fifosize) + len = port->fifosize; + while (len--) { + writel_relaxed(xmit->buf[xmit->tail], + port->membase + EUD_REG_COM_TX_DAT); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + } + } + spin_unlock(&port->lock); +} + +static irqreturn_t handle_eud_irq(int irq, void *data) +{ + struct eud_chip *chip = data; + u32 reg; + u32 int_mask_en1 = readl_relaxed(chip->eud_reg_base + + EUD_REG_INT1_EN_MASK); + + /* read status register and find out which interrupt triggered */ + reg = readl_relaxed(chip->eud_reg_base + EUD_REG_INT_STATUS_1); + switch (reg & EUD_INT_ALL) { + case EUD_INT_RX: + dev_dbg(chip->dev, "EUD RX Interrupt is received\n"); + eud_uart_rx(chip); + break; + case EUD_INT_TX: + if (EUD_INT_TX & int_mask_en1) { + dev_dbg(chip->dev, "EUD TX Interrupt is received\n"); + eud_uart_tx(chip); + } + break; + case EUD_INT_VBUS: + dev_dbg(chip->dev, "EUD VBUS Interrupt is received\n"); + chip->int_status = EUD_INT_VBUS; + usb_attach_detach(chip); + break; + case EUD_INT_CHGR: + dev_dbg(chip->dev, "EUD CHGR Interrupt is received\n"); + chip->int_status = EUD_INT_CHGR; + chgr_enable_disable(chip); + break; + case EUD_INT_SAFE_MODE: + dev_dbg(chip->dev, "EUD SAFE MODE Interrupt is received\n"); + pet_eud(chip); + break; + default: + dev_dbg(chip->dev, "Unknown EUD Interrupt is received\n"); + return IRQ_NONE; + } + return IRQ_HANDLED; +} + +static int msm_eud_suspend(struct device *dev) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + + if (chip->need_phy_clk_vote && chip->eud_ahb2phy_clk) + clk_disable_unprepare(chip->eud_ahb2phy_clk); + + return 0; +} + +static int msm_eud_resume(struct device *dev) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + int ret = 0; + + if (chip->need_phy_clk_vote && chip->eud_ahb2phy_clk) { + ret = clk_prepare_enable(chip->eud_ahb2phy_clk); + if (ret) + dev_err(chip->dev, "%s failed to vote ahb2phy clk %d\n", + __func__, ret); + } + + return ret; +} + +static int msm_eud_probe(struct platform_device *pdev) +{ + struct eud_chip *chip; + struct uart_port *port; + struct resource *res; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + platform_set_drvdata(pdev, chip); + + chip->dev = &pdev->dev; + chip->extcon = devm_extcon_dev_allocate(&pdev->dev, eud_extcon_cable); + if (IS_ERR(chip->extcon)) { + dev_err(chip->dev, "%s: failed to allocate extcon device\n", + __func__); + return PTR_ERR(chip->extcon); + } + + ret = devm_extcon_dev_register(&pdev->dev, chip->extcon); + if (ret) { + dev_err(chip->dev, "%s: failed to register extcon device\n", + __func__); + return ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_base"); + if (!res) { + dev_err(chip->dev, "%s: failed to get resource eud_base\n", + __func__); + return -ENOMEM; + } + + chip->eud_reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(chip->eud_reg_base)) + return PTR_ERR(chip->eud_reg_base); + + chip->eud_irq = platform_get_irq_byname(pdev, "eud_irq"); + + chip->secure_eud_en = of_property_read_bool(pdev->dev.of_node, + "qcom,secure-eud-en"); + if (chip->secure_eud_en) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "eud_mode_mgr2"); + if (!res) { + dev_err(chip->dev, + "%s: failed to get resource eud_mode_mgr2\n", + __func__); + return -ENOMEM; + } + + chip->eud_mode_mgr2_phys_base = res->start; + } + + chip->need_phy_clk_vote = of_property_read_bool(pdev->dev.of_node, + "qcom,eud-clock-vote-req"); + if (chip->need_phy_clk_vote) { + chip->eud_ahb2phy_clk = devm_clk_get(&pdev->dev, + "eud_ahb2phy_clk"); + if (IS_ERR(chip->eud_ahb2phy_clk)) { + ret = PTR_ERR(chip->eud_ahb2phy_clk); + return ret; + } + + ret = clk_prepare_enable(chip->eud_ahb2phy_clk); + if (ret) + return ret; + } + + ret = devm_request_irq(&pdev->dev, chip->eud_irq, handle_eud_irq, + IRQF_TRIGGER_HIGH, "eud_irq", chip); + if (ret) { + dev_err(chip->dev, "request failed for eud irq\n"); + goto error; + } + + device_init_wakeup(&pdev->dev, true); + enable_irq_wake(chip->eud_irq); + + INIT_WORK(&chip->eud_work, eud_event_notifier); + + port = &chip->port; + port->line = pdev->id; + port->type = PORT_EUD_UART; + port->dev = chip->dev; + port->fifosize = MAX_FIFO_SIZE; + port->iotype = SERIAL_IO_MEM; + port->flags = UPF_BOOT_AUTOCONF; + port->membase = chip->eud_reg_base; + port->irq = chip->eud_irq; + port->ops = &eud_uart_ops; + + ret = uart_add_one_port(&eud_uart_driver, port); + if (ret) { + dev_err(chip->dev, "failed to add uart port!\n"); + goto error; + } + + eud_private = pdev; + eud_ready = true; + + /* Enable EUD */ + if (enable) + enable_eud(pdev); + + return 0; + +error: + if (chip->need_phy_clk_vote && chip->eud_ahb2phy_clk) + clk_disable_unprepare(chip->eud_ahb2phy_clk); + + return ret; +} + +static int msm_eud_remove(struct platform_device *pdev) +{ + struct eud_chip *chip = platform_get_drvdata(pdev); + struct uart_port *port = &chip->port; + + uart_remove_one_port(&eud_uart_driver, port); + device_init_wakeup(chip->dev, false); + if (chip->need_phy_clk_vote) + clk_disable_unprepare(chip->eud_ahb2phy_clk); + + return 0; +} + +static const struct of_device_id msm_eud_dt_match[] = { + {.compatible = "qcom,msm-eud"}, + {}, +}; +MODULE_DEVICE_TABLE(of, msm_eud_dt_match); + +static const struct dev_pm_ops msm_eud_dev_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(msm_eud_suspend, msm_eud_resume) +}; + +static struct platform_driver msm_eud_driver = { + .probe = msm_eud_probe, + .remove = msm_eud_remove, + .driver = { + .name = "msm-eud", + .pm = &msm_eud_dev_pm_ops, + .of_match_table = msm_eud_dt_match, + }, +}; + +static int __init msm_eud_init(void) +{ + int ret; + + ret = uart_register_driver(&eud_uart_driver); + if (ret) { + pr_err("%s: Failed to register EUD UART driver\n", + __func__); + return ret; + } + + ret = platform_driver_register(&msm_eud_driver); + if (ret) { + pr_err("%s: Failed to register EUD driver\n", + __func__); + uart_unregister_driver(&eud_uart_driver); + return ret; + } + + return 0; +} +module_init(msm_eud_init); + +static void __exit msm_eud_exit(void) +{ + platform_driver_unregister(&msm_eud_driver); + uart_unregister_driver(&eud_uart_driver); +} +module_exit(msm_eud_exit); + +MODULE_DESCRIPTION("QTI EUD driver"); +MODULE_LICENSE("GPL v2");