blob: acdeb9d64798252b22090f7072666a666b5fb298 [file] [log] [blame]
/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/extcon.h>
#include <linux/delay.h>
#include <linux/sysfs.h>
#include <linux/io.h>
#include <linux/bitops.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.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_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;
bool usb_attach;
bool chgr_enable;
void __iomem *eud_reg_base;
struct extcon_dev *extcon;
struct uart_port port;
};
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 struct platform_device *eud_private;
static void enable_eud(struct platform_device *pdev)
{
struct eud_chip *priv = platform_get_drvdata(pdev);
/* write into CSR to enable EUD */
writel_relaxed(BIT(0), priv->eud_reg_base + EUD_REG_CSR_EUD_EN);
/* Ensure Register Writes Complete */
wmb();
dev_dbg(&pdev->dev, "%s: EUD Enabled!\n", __func__);
}
static void disable_eud(struct platform_device *pdev)
{
struct eud_chip *priv = platform_get_drvdata(pdev);
/* write into CSR to disable EUD */
writel_relaxed(0, priv->eud_reg_base + EUD_REG_CSR_EUD_EN);
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;
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);
}
*((uint *)kp->arg) = enable;
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 void usb_attach_detach(struct eud_chip *chip)
{
u32 reg;
/* 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;
extcon_set_cable_state_(chip->extcon, EXTCON_USB, chip->usb_attach);
/* 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;
/* 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;
extcon_set_cable_state_(chip->extcon, EXTCON_CHG_USB_SDP,
chip->chgr_enable);
/* 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_err(chip->dev, "Rx 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_err(chip->dev, "Tx 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;
/* 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!\n");
eud_uart_rx(chip);
} else if (reg & EUD_INT_TX) {
dev_dbg(chip->dev, "EUD TX Interrupt!\n");
eud_uart_tx(chip);
} else if (reg & EUD_INT_VBUS) {
dev_dbg(chip->dev, "EUD VBUS Interrupt!\n");
usb_attach_detach(chip);
} else if (reg & EUD_INT_CHGR) {
dev_dbg(chip->dev, "EUD CHGR Interrupt!\n");
chgr_enable_disable(chip);
} else if (reg & EUD_INT_SAFE_MODE) {
dev_dbg(chip->dev, "EUD SAFE MODE Interrupt!\n");
pet_eud(chip);
} else {
dev_dbg(chip->dev, "Unknown/spurious EUD Interrupt!\n");
return IRQ_NONE;
}
return IRQ_HANDLED;
}
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) {
ret = -ENOMEM;
return ret;
}
platform_set_drvdata(pdev, chip);
chip->extcon = devm_extcon_dev_allocate(&pdev->dev, eud_extcon_cable);
if (IS_ERR(chip->extcon)) {
dev_err(chip->dev, "failed to allocate extcon device\n");
return PTR_ERR(chip->extcon);
}
ret = devm_extcon_dev_register(&pdev->dev, chip->extcon);
if (ret) {
dev_err(chip->dev, "failed to register extcon device\n");
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");
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");
return ret;
}
device_init_wakeup(&pdev->dev, true);
enable_irq_wake(chip->eud_irq);
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");
return ret;
}
/* Enable vbus, chgr & safe mode warning interrupts */
writel_relaxed(EUD_INT_VBUS | EUD_INT_CHGR | EUD_INT_SAFE_MODE,
chip->eud_reg_base + EUD_REG_INT1_EN_MASK);
eud_private = pdev;
/* Enable EUD */
if (enable)
enable_eud(pdev);
return 0;
}
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);
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 struct platform_driver msm_eud_driver = {
.probe = msm_eud_probe,
.remove = msm_eud_remove,
.driver = {
.name = "msm-eud",
.owner = THIS_MODULE,
.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");