blob: 3c2df61539b746f98e25ba1a24d039d1114a42b7 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqreturn.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include <linux/amlogic/usb-v2.h>
#include "amlogic-bc.h"
struct bc_dev *g_bc_dev;
struct usb_aml_regs_v2 g_usb_new_aml_regs_v2;
void amlogic_bc_init(void)
{
struct bc_dev *bc_dev = g_bc_dev;
u32 device_host = 0;
union u2p_r0_v2 u2p_reg0;
union bc_r0 bc_reg0;
union usb_r0_v2 usb_r0;
if (g_bc_dev) {
u2p_reg0.d32 = readl(bc_dev->phy_base);
device_host = u2p_reg0.b.host_device;
if (device_host == 0) {
/*cfg0_reg[31]*/
usb_r0.d32 = readl(bc_dev->phy_base + 0x60);
usb_r0.b.u2d_act = 1;
writel(usb_r0.d32, bc_dev->phy_base + 0x60);
mdelay(20);
bc_reg0.d32 = readl(bc_dev->regs);
bc_reg0.b.bc_en = 1;
writel(bc_reg0.d32, (bc_dev->regs));
} else {
dev_info(bc_dev->dev,
"error, usb phy host mode!\n");
}
}
}
EXPORT_SYMBOL(amlogic_bc_init);
static void amlogic_bc_work(struct work_struct *work)
{
struct bc_dev *bc_dev =
container_of(work, struct bc_dev, work.work);
struct device *dev = bc_dev->dev;
union bc_r1 bc_reg1 = {.d32 = 0};
union usb_r6_v2 r6 = {.d32 = 0};
bc_reg1.d32 = bc_dev->bc_status;
dev_info(dev, "BC STATUS is : ");
switch (bc_reg1.b.port_status) {
case 0:
dev_info(dev, "default\n");
break;
case 1:
dev_info(dev, "SDP\n");
break;
case 2:
dev_info(dev, "DCP\n");
break;
case 3:
dev_info(dev, "CDP\n");
break;
case 4:
dev_info(dev, "ACA_A\n");
break;
case 5:
dev_info(dev, "ACA_B\n");
break;
case 6:
dev_info(dev, "ACA_C\n");
break;
case 7:
dev_info(dev, "ACA_DOCK\n");
break;
case 8:
dev_info(dev, "ACA GND ERROR\n");
break;
case 9:
dev_info(dev, "analog output error\n");
break;
case 10:
dev_info(dev, "VBUS remove\n");
break;
case 11:
dev_info(dev, "VBUS invalid\n");
break;
default:
dev_info(dev, "RESERVED\n");
break;
}
udelay(9);
r6.d32 = readl(g_usb_new_aml_regs_v2.usb_r_v2[6]);
r6.b.usb_vbusdig_irq = 0;
writel(r6.d32, g_usb_new_aml_regs_v2.usb_r_v2[6]);
}
static irqreturn_t amlogic_bc_detect_irq(int irq, void *_dev)
{
struct bc_dev *bc_dev = (struct bc_dev *)_dev;
union bc_r0 bc_reg0 = {.d32 = 0};
bc_dev->bc_status = readl(bc_dev->regs + 0x4);
bc_reg0.d32 = readl(bc_dev->regs);
bc_reg0.b.bc_int_clean = 1;
writel(bc_reg0.d32, (bc_dev->regs));
schedule_delayed_work(&bc_dev->work, msecs_to_jiffies(10));
return IRQ_HANDLED;
}
static void amlogic_vbus_work(struct work_struct *work)
{
struct bc_dev *bc_dev =
container_of(work, struct bc_dev, vbus_work.work);
union bc_r0 bc_reg0 = {.d32 = 0};
amlogic_bc_init();
bc_reg0.d32 = readl(bc_dev->regs);
bc_reg0.b.bc_det_sw = 1;
writel(bc_reg0.d32, (bc_dev->regs));
}
static irqreturn_t amlogic_vbus_detect_irq(int irq, void *dev)
{
struct bc_dev *bc_dev = (struct bc_dev *)dev;
union usb_r6_v2 r6 = {.d32 = 0};
r6.d32 = readl(g_usb_new_aml_regs_v2.usb_r_v2[6]);
r6.b.usb_vbusdig_irq = 0;
writel(r6.d32, g_usb_new_aml_regs_v2.usb_r_v2[6]);
schedule_delayed_work(&bc_dev->vbus_work, msecs_to_jiffies(10));
return IRQ_HANDLED;
}
static int amlogic_bc_probe(struct platform_device *pdev)
{
struct bc_dev *bc_dev;
struct device *dev = &pdev->dev;
struct resource *bc_mem;
struct resource *phy_mem;
void __iomem *bc_base;
void __iomem *phy_base;
int irq;
int retval;
int i;
bc_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
bc_base = devm_ioremap_resource(dev, bc_mem);
if (IS_ERR(bc_base))
return PTR_ERR(bc_base);
bc_dev = devm_kzalloc(&pdev->dev, sizeof(*bc_dev), GFP_KERNEL);
if (!bc_dev)
return -ENOMEM;
phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (phy_mem) {
phy_base = ioremap(phy_mem->start,
resource_size(phy_mem));
if (IS_ERR(phy_base))
return PTR_ERR(phy_base);
} else {
return PTR_ERR(phy_mem);
}
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return -ENODEV;
retval = request_irq(irq, amlogic_bc_detect_irq,
IRQF_SHARED | IRQ_LEVEL,
"amlogic_bc_detect", bc_dev);
if (retval) {
dev_err(&pdev->dev, "request of irq%d failed\n",
irq);
retval = -EBUSY;
return retval;
}
irq = platform_get_irq(pdev, 1);
if (irq < 0)
return -ENODEV;
retval = request_irq(irq, amlogic_vbus_detect_irq,
IRQF_SHARED | IRQ_LEVEL,
"amlogic_vbus_detect", bc_dev);
if (retval) {
dev_err(&pdev->dev, "request of irq%d failed\n",
irq);
retval = -EBUSY;
return retval;
}
bc_dev->dev = dev;
bc_dev->regs = bc_base;
bc_dev->phy_base = phy_base;
for (i = 0; i < 7; i++) {
g_usb_new_aml_regs_v2.usb_r_v2[i] = (void __iomem *)
((unsigned long)phy_base + 0x60 + 4 * i);
}
platform_set_drvdata(pdev, bc_dev);
pm_runtime_enable(bc_dev->dev);
g_bc_dev = bc_dev;
INIT_DELAYED_WORK(&bc_dev->work, amlogic_bc_work);
INIT_DELAYED_WORK(&bc_dev->vbus_work, amlogic_vbus_work);
return 0;
}
static int amlogic_bc_remove(struct platform_device *pdev)
{
return 0;
}
#ifdef CONFIG_PM_RUNTIME
static int amlogic_bc_runtime_suspend(struct device *dev)
{
return 0;
}
static int amlogic_bc_runtime_resume(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops amlogic_bc_pm_ops = {
SET_RUNTIME_PM_OPS(amlogic_bc_runtime_suspend,
amlogic_bc_runtime_resume,
NULL)
};
#define DEV_PM_OPS (&amlogic_bc_pm_ops)
#else
#define DEV_PM_OPS NULL
#endif
#ifdef CONFIG_OF
static const struct of_device_id amlogic_bc_id_table[] = {
{ .compatible = "amlogic, bc" },
{ .compatible = "amlogic,bc" },
{}
};
MODULE_DEVICE_TABLE(of, amlogicbc_id_table);
#endif
static struct platform_driver amlogic_bc_driver = {
.probe = amlogic_bc_probe,
.remove = amlogic_bc_remove,
.driver = {
.name = "amlogic-bc",
.owner = THIS_MODULE,
.pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(amlogic_bc_id_table),
},
};
module_platform_driver(amlogic_bc_driver);
MODULE_ALIAS("platform: amlogic_bc");
MODULE_AUTHOR("Amlogic Inc.");
MODULE_DESCRIPTION("amlogic BC driver");
MODULE_LICENSE("GPL v2");