linuxOS_D21X/source/linux-5.10/drivers/usb/host/aic-otg.c
2024-11-29 16:13:46 +08:00

318 lines
7.0 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Artinchip OTG driver
*
* Copyright (C) 2020 ARTINCHIP - All Rights Reserved
*
*
*/
// #define DEBUG
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/usb/ehci_pdriver.h>
#include <linux/pinctrl/consumer.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/debugfs.h>
struct aic_otg_drv_data {
struct platform_device *pdev;
unsigned int gpio_vbus_sw;
unsigned int gpio_id;
int irq_id;
int mode;
bool id_en;
};
#define DRIVER_DESC "Artinchip OTG driver"
#define DRV_NAME "aic-otg"
#define MODE_HOST 0
#define MODE_DEVICE 1
#define MODE_MSK (MODE_HOST | MODE_DEVICE)
#define MODE_FORCE_FLG 0x100
static char *mode_str[] = {
"host",
"device",
};
extern void syscfg_usb_phy0_sw_host(int sw);
static void aic_otg_set_mode(struct aic_otg_drv_data *d, int mode);
static void aic_otg_set_mode_by_id(struct aic_otg_drv_data *d);
static int aic_otg_get_id_val(struct aic_otg_drv_data *d);
static int aic_otg_id_detect_en(struct aic_otg_drv_data *d);
static void aic_otg_id_detect_dis(struct aic_otg_drv_data *d);
static ssize_t otg_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct aic_otg_drv_data *d = dev_get_drvdata(dev);
bool force = d->mode & MODE_FORCE_FLG;
int mode = d->mode & MODE_MSK;
int val = 0;
ssize_t count = 0;
if (force) {
count = snprintf(buf, PAGE_SIZE, "%s\n", mode_str[mode]);
} else {
val = aic_otg_get_id_val(d);
count = snprintf(buf, PAGE_SIZE,
"auto (current mode: %s, id pin: %d)\n",
mode_str[mode], val);
}
return count;
}
static ssize_t otg_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct aic_otg_drv_data *d = dev_get_drvdata(dev);
int ret = 0;
if (!strncmp(buf, "host", 4)) {
aic_otg_id_detect_dis(d);
d->mode = MODE_FORCE_FLG | MODE_HOST;
aic_otg_set_mode(d, MODE_HOST);
} else if (!strncmp(buf, "device", 6)) {
aic_otg_id_detect_dis(d);
d->mode = MODE_FORCE_FLG | MODE_DEVICE;
aic_otg_set_mode(d, MODE_DEVICE);
} else if (!strncmp(buf, "auto", 4)) {
d->mode = 0;
ret = aic_otg_id_detect_en(d);
if (ret)
return ret;
} else {
return -EINVAL;
}
return count;
}
static DEVICE_ATTR(otg_mode, 0644, otg_mode_show, otg_mode_store);
static void aic_otg_set_mode(struct aic_otg_drv_data *d, int mode)
{
if (mode == MODE_DEVICE) {
syscfg_usb_phy0_sw_host(0);
if (gpio_is_valid(d->gpio_vbus_sw))
gpio_set_value(d->gpio_vbus_sw, 0);
pr_info("%s: id pin = high, switch to Device mode.\n",
DRV_NAME);
} else {
syscfg_usb_phy0_sw_host(1);
if (gpio_is_valid(d->gpio_vbus_sw))
gpio_set_value(d->gpio_vbus_sw, 1);
pr_info("%s: id pin = low, switch to Host mode.\n",
DRV_NAME);
}
}
static void aic_otg_set_mode_by_id(struct aic_otg_drv_data *d)
{
int new_mode = 0;
int val = 0;
if (d->mode & MODE_FORCE_FLG)
return;
val = gpio_get_value(d->gpio_id);
/* id pin = high */
if (val)
new_mode = MODE_DEVICE;
/* id pin = low */
else
new_mode = MODE_HOST;
dev_dbg(&d->pdev->dev, "%s: val = %d, new_mode = %d, d->mode = %d.\n",
__func__, val, new_mode, d->mode);
aic_otg_set_mode(d, new_mode);
d->mode = new_mode;
}
static int aic_otg_get_id_val(struct aic_otg_drv_data *d)
{
return gpio_get_value(d->gpio_id);
}
static irqreturn_t aic_otg_irq(int irq, void *data)
{
struct platform_device *pdev = data;
struct aic_otg_drv_data *d = platform_get_drvdata(pdev);
dev_dbg(&pdev->dev, "Interrupt hadnle: %s.\n", __func__);
/* sleep unbonuce */
aic_otg_set_mode_by_id(d);
return IRQ_HANDLED;
}
static int aic_otg_id_detect_en(struct aic_otg_drv_data *d)
{
int ret;
if (!gpio_is_valid(d->gpio_id))
return -EINVAL;
if (d->id_en)
return 0;
d->id_en = true;
gpio_direction_input(d->gpio_id);
gpio_set_debounce(d->gpio_id, 10000);
aic_otg_set_mode_by_id(d);
d->irq_id = gpio_to_irq(d->gpio_id);
dev_dbg(&d->pdev->dev, "%s: d->irq_id = %d.\n",
__func__, d->irq_id);
ret = devm_request_irq(&d->pdev->dev, d->irq_id, aic_otg_irq,
IRQ_TYPE_EDGE_BOTH, DRV_NAME, d->pdev);
return 0;
}
static void aic_otg_id_detect_dis(struct aic_otg_drv_data *d)
{
if (!gpio_is_valid(d->gpio_id))
return;
if (!d->id_en)
return;
d->id_en = false;
disable_irq(d->irq_id);
devm_free_irq(&d->pdev->dev, d->irq_id, d->pdev);
}
static int aic_otg_platform_probe(struct platform_device *pdev)
{
struct aic_otg_drv_data *d;
unsigned int gpio = 0;
int ret = 0;
const char *mod;
bool force = false;
d = kzalloc(sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
platform_set_drvdata(pdev, d);
d->pdev = pdev;
gpio = of_get_named_gpio(pdev->dev.of_node, "vbus-en-gpios", 0);
d->gpio_vbus_sw = gpio;
/* Get force mode define from dts
* Force Mode : field 'otg-mode' need defined as 'host' or 'device'
* Auto Mode : field 'otg-mode' defined ad 'auto' or absent
*/
ret = of_property_read_string(pdev->dev.of_node, "otg-mode", &mod);
if (!ret) {
if (!strncmp(mod, "host", 4)) {
force = true;
d->mode = MODE_FORCE_FLG | MODE_HOST;
aic_otg_set_mode(d, MODE_HOST);
} else if (!strncmp(mod, "device", 6)) {
force = true;
d->mode = MODE_FORCE_FLG | MODE_DEVICE;
aic_otg_set_mode(d, MODE_DEVICE);
} else if (!strncmp(mod, "auto", 4)) {
force = false;
} else {
force = false;
}
}
/* Get id pins gpio
* Auto Mode : must define id pins
* Force Mode : id pins define is not necessary
*/
gpio = of_get_named_gpio(pdev->dev.of_node, "id-gpios", 0);
if (!gpio_is_valid(gpio) && !force) {
ret = -EINVAL;
goto err_free_data;
}
d->gpio_id = gpio;
/* Auto mode enable id pin gpio */
if (!force)
aic_otg_id_detect_en(d);
device_create_file(&pdev->dev, &dev_attr_otg_mode);
return 0;
err_free_data:
if (d)
kfree(d);
return ret;
}
static int aic_otg_platform_remove(struct platform_device *pdev)
{
struct aic_otg_drv_data *d = platform_get_drvdata(pdev);
device_remove_file(&pdev->dev, &dev_attr_otg_mode);
aic_otg_id_detect_dis(d);
if (d)
kfree(d);
return 0;
}
static const struct of_device_id aic_otg_ids[] = {
{ .compatible = "artinchip,aic-otg-v1.0", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, aic_otg_ids);
static struct platform_driver otg_platform_driver = {
.probe = aic_otg_platform_probe,
.remove = aic_otg_platform_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = aic_otg_ids,
}
};
static int __init otg_platform_init(void)
{
if (usb_disabled())
return -ENODEV;
pr_info(DRV_NAME ": " DRIVER_DESC "\n");
return platform_driver_register(&otg_platform_driver);
}
module_init(otg_platform_init);
static void __exit otg_platform_cleanup(void)
{
platform_driver_unregister(&otg_platform_driver);
}
module_exit(otg_platform_cleanup);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");