blob: 806944152ed45c8eec9e7a0e1a968536f6e5a128 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 BayLibre, SAS.
* Author: Fabien Parent <fparent@baylibre.com>
*/
#include <linux/interrupt.h>
#include <linux/mfd/mt6397/core.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/usb/usb_phy_generic.h>
#include <linux/usb/role.h>
#include <linux/usb/typec.h>
#define TYPEC_CTRL 0x06
#define PORT_SUPPORT_ROLE_SINK 0
#define PORT_SUPPORT_ROLE_SOURCE 1
#define PORT_SUPPORT_ROLE_DRP 2
#define PORT_SUPPORT_ROLE_MASK 3
#define TYPEC_CC_SW_CTRL 0x0a
#define STATE_ENTER_DISABLED BIT(0)
#define STATE_ENTER_UNATTACHED_SRC BIT(1)
#define STATE_ENTER_UNATTACHED_SINK BIT(2)
#define TYPEC_INTR_EN_0 0x30
#define TYPEC_INTR_EN_2 0x34
#define TYPEC_INTR_0 0x38
#define INTR_ENTER_ATTACHED_SRC BIT(0)
#define INTR_ENTER_ATTACHED_SINK BIT(1)
#define INTR_ENTER_UNATTACHED_SRC BIT(4)
#define INTR_ENTER_ATTACH_WAIT_SINK BIT(7)
#define TYPEC_INTR_2 0x3c
#define TYPEC_CC_STATUS 0x40
#define TYPEC_PWR_STATUS 0x42
struct mt6392_typec {
struct device *dev;
struct regmap *regmap;
struct usb_role_switch *role_sw;
struct typec_port *port;
struct typec_capability cap;
u32 base_addr;
int irq;
};
static int typec_write(struct mt6392_typec *typec, u32 reg, u16 val)
{
return regmap_write(typec->regmap, typec->base_addr + reg, val);
}
static u16 typec_read(struct mt6392_typec *typec, u32 reg)
{
unsigned int val;
regmap_read(typec->regmap, typec->base_addr + reg, &val);
return val;
}
static irqreturn_t mtk_typec_irq_handler_thread(int irq, void *data)
{
uint16_t intr0;
struct mt6392_typec *typec = data;
intr0 = typec_read(typec, TYPEC_INTR_0);
typec_write(typec, TYPEC_INTR_0, intr0);
if (!intr0)
return IRQ_NONE;
if (intr0 & INTR_ENTER_ATTACHED_SRC) {
usb_role_switch_set_role(typec->role_sw, USB_ROLE_HOST);
typec_set_data_role(typec->port, TYPEC_HOST);
} else if (intr0 & INTR_ENTER_ATTACH_WAIT_SINK) {
usb_role_switch_set_role(typec->role_sw, USB_ROLE_DEVICE);
typec_set_data_role(typec->port, TYPEC_DEVICE);
} else if (intr0 & INTR_ENTER_UNATTACHED_SRC) {
usb_role_switch_set_role(typec->role_sw, USB_ROLE_NONE);
} else {
dev_err(typec->dev, "unexpected IRQ: %d\n", intr0);
return IRQ_NONE;
}
return IRQ_HANDLED;
}
static void mt6392_enable_irq(struct mt6392_typec *typec)
{
regmap_write(typec->regmap, typec->base_addr + TYPEC_INTR_EN_0,
INTR_ENTER_ATTACHED_SRC | INTR_ENTER_UNATTACHED_SRC |
INTR_ENTER_ATTACH_WAIT_SINK);
}
static void mt6392_disable_irq(struct mt6392_typec *typec)
{
regmap_write(typec->regmap, typec->base_addr + TYPEC_INTR_EN_0, 0);
regmap_write(typec->regmap, typec->base_addr + TYPEC_INTR_EN_2, 0);
}
static int mt6392_typec_probe(struct platform_device *pdev)
{
struct resource *res;
struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent);
struct mt6392_typec *typec;
int ret;
typec = devm_kzalloc(&pdev->dev, sizeof(*typec), GFP_KERNEL);
if (!typec)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
typec->base_addr = res->start;
typec->irq = platform_get_irq(pdev, 0);
if (typec->irq < 0)
return typec->irq;
typec->regmap = mt6397_chip->regmap;
typec->dev = &pdev->dev;
platform_set_drvdata(pdev, typec);
typec->role_sw = usb_role_switch_get(&pdev->dev);
if (IS_ERR(typec->role_sw))
return PTR_ERR(typec->role_sw);
typec->cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
typec->cap.type = TYPEC_PORT_DRP;
typec->cap.data = TYPEC_PORT_DRD;
typec->cap.revision = USB_TYPEC_REV_1_1;
typec->port = typec_register_port(typec->dev, &typec->cap);
if (IS_ERR(typec->port)) {
ret = PTR_ERR(typec->port);
goto out_role_switch_put;
}
mt6392_disable_irq(typec);
regmap_update_bits(typec->regmap, typec->base_addr + TYPEC_CTRL,
PORT_SUPPORT_ROLE_MASK, PORT_SUPPORT_ROLE_DRP);
regmap_update_bits(typec->regmap, typec->base_addr + TYPEC_CC_SW_CTRL,
STATE_ENTER_UNATTACHED_SINK, STATE_ENTER_UNATTACHED_SINK);
ret = devm_request_threaded_irq(typec->dev, typec->irq, NULL,
mtk_typec_irq_handler_thread,
IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"mt6392-typec", typec);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ%d: %d\n",
typec->irq, ret);
goto out_typec_unregister_port;
}
mt6392_enable_irq(typec);
return 0;
out_typec_unregister_port:
typec_unregister_port(typec->port);
out_role_switch_put:
usb_role_switch_put(typec->role_sw);
return ret;
}
static int mt6392_typec_remove(struct platform_device *pdev)
{
struct mt6392_typec *typec = platform_get_drvdata(pdev);
disable_irq(typec->irq);
typec_unregister_port(typec->port);
usb_role_switch_put(typec->role_sw);
return 0;
}
static int __maybe_unused mt6392_typec_suspend(struct device *dev)
{
struct mt6392_typec *typec = dev_get_drvdata(dev);
disable_irq(typec->irq);
return 0;
}
static int __maybe_unused mt6392_typec_resume(struct device *dev)
{
struct mt6392_typec *typec = dev_get_drvdata(dev);
enable_irq(typec->irq);
return 0;
}
static SIMPLE_DEV_PM_OPS(mt6392_typec_pm_ops, mt6392_typec_suspend,
mt6392_typec_resume);
static const struct of_device_id mt6392_typec_of_match[] = {
{ .compatible = "mediatek,mt6392-typec" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mt6392_typec_of_match);
static struct platform_driver mt6392_typec_driver = {
.probe = mt6392_typec_probe,
.remove = mt6392_typec_remove,
.driver = {
.name = "mt6392-typec",
.of_match_table = of_match_ptr(mt6392_typec_of_match),
.pm = &mt6392_typec_pm_ops,
},
};
module_platform_driver(mt6392_typec_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Fabien Parent <fparent@baylibre.com>");
MODULE_DESCRIPTION("MediaTek MT6392 Type-C Controller Driver");