blob: 864bd65d0e2e164ca84cac222d1d0e184e652578 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/extcon.h>
#include <linux/extcon-provider.h>
#include <linux/delay.h>
#include <linux/sysfs.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/bitops.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/clk.h>
#include <linux/workqueue.h>
#include <linux/power_supply.h>
#include <soc/qcom/scm.h>
#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
#define EUD_TCSR_ENABLE_BIT BIT(0)
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 int check_eud_mode_mgr2(struct eud_chip *chip)
{
u32 val;
val = scm_io_read(chip->eud_mode_mgr2_phys_base);
return val & BIT(0);
}
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 && !check_eud_mode_mgr2(priv)) {
ret = scm_io_write(priv->eud_mode_mgr2_phys_base +
EUD_REG_EUD_EN2, EUD_ENABLE_CMD);
if (ret)
dev_err(&pdev->dev,
"scm_io_write 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 = scm_io_write(priv->eud_mode_mgr2_phys_base +
EUD_REG_EUD_EN2, EUD_DISABLE_CMD);
if (ret)
dev_err(&pdev->dev,
"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;
power_supply_set_property(chip->batt_psy,
POWER_SUPPLY_PROP_INPUT_SUSPEND, &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);
if (reg & EUD_INT_RX) {
dev_dbg(chip->dev, "EUD RX Interrupt is received\n");
eud_uart_rx(chip);
} else if ((reg & EUD_INT_TX) & int_mask_en1) {
dev_dbg(chip->dev, "EUD TX Interrupt is received\n");
eud_uart_tx(chip);
} else if (reg & EUD_INT_VBUS) {
dev_dbg(chip->dev, "EUD VBUS Interrupt is received\n");
chip->int_status = EUD_INT_VBUS;
usb_attach_detach(chip);
} else if (reg & EUD_INT_CHGR) {
dev_dbg(chip->dev, "EUD CHGR Interrupt is received\n");
chip->int_status = EUD_INT_CHGR;
chgr_enable_disable(chip);
} else if (reg & EUD_INT_SAFE_MODE) {
dev_dbg(chip->dev, "EUD SAFE MODE Interrupt is received\n");
pet_eud(chip);
} else {
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;
bool eud_tcsr_check_state;
phys_addr_t eud_tcsr_check;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip) {
ret = -ENOMEM;
return ret;
}
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__);
ret = -ENOMEM;
return ret;
}
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__);
ret = -ENOMEM;
return ret;
}
chip->eud_mode_mgr2_phys_base = res->start;
if (check_eud_mode_mgr2(chip))
enable = 1;
}
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;
/*
* Before enabling EUD, check for TCSR register
* and if present, enable it.
*/
eud_tcsr_check_state = of_property_read_bool(
pdev->dev.of_node, "qcom,eud-tcsr-check-enable");
if (eud_tcsr_check_state) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"eud_tcsr_check_reg");
if (res) {
eud_tcsr_check = res->start;
ret = scm_io_write(eud_tcsr_check,
EUD_TCSR_ENABLE_BIT);
if (ret) {
dev_err(&pdev->dev,
"TCSR scm_io_write failed with rc:%d\n", ret);
}
} else {
dev_err(chip->dev,
"Failed to get resource for tcsr check!\n");
goto error;
}
}
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",
.owner = THIS_MODULE,
.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");