aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/susb/dwc_otg_k3v2.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/susb/dwc_otg_k3v2.c')
-rw-r--r--drivers/usb/susb/dwc_otg_k3v2.c466
1 files changed, 466 insertions, 0 deletions
diff --git a/drivers/usb/susb/dwc_otg_k3v2.c b/drivers/usb/susb/dwc_otg_k3v2.c
new file mode 100644
index 00000000000..a860b65c709
--- /dev/null
+++ b/drivers/usb/susb/dwc_otg_k3v2.c
@@ -0,0 +1,466 @@
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/of_address.h>
+
+#include "lm.h"
+
+
+
+#define D(format, arg...) \
+ do { \
+ printk(KERN_INFO "[USB]"format, ##arg); \
+ } while (0)
+#define E(format, arg...) \
+ do { \
+ printk(KERN_ERR "[USB]"format, ##arg); \
+ } while (0)
+
+
+#define REG_BASE_PCTRL (0xFCA09000)
+#define REG_BASE_SCTRL (0xFC802000)
+
+#define REG_BASE_USB2DVC (0xFD240000)
+#define REG_BASE_USB2DVC_VIRT (0xFE776000)
+#define REG_USB2DVC_IOSIZE PAGE_ALIGN(SZ_256K)
+
+#define IRQ_GIC_START 32
+#define IRQ_USB2DVC (IRQ_GIC_START + 52)
+#define IRQ_USBOTG (IRQ_GIC_START + 52)
+
+// 软复位寄存器1
+#define PER_RST_EN_1_ADDR 0x08C
+#define PER_RST_DIS_1_ADDR 0x090
+#define PER_RST_STATUS_1_ADDR 0x094
+
+#define IP_RST_HSICPHY (1 << 25)
+#define IP_RST_PICOPHY (1 << 24)
+#define IP_RST_NANOPHY (1 << 23)
+
+// 软复位寄存器3
+#define PER_RST_EN_3_ADDR 0x0A4
+#define PER_RST_DIS_3_ADDR 0x0A8
+#define PER_RST_STATUS_3_ADDR 0x0AC
+
+#define IP_PICOPHY_POR (1 << 31)
+#define IP_HSICPHY_POR (1 << 30)
+#define IP_NANOPHY_POR (1 << 29)
+#define IP_RST_USB2DVC_PHY (1 << 28)
+#define IP_RST_USB2DVC (1 << 17)
+
+// 时钟寄存器1
+#define PER_CLK_EN_1_ADDR 0x30
+#define PER_CLK_DIS_1_ADDR 0x34
+#define PER_CLK_EN_STATUS_1_ADDR 0x38
+#define PER_CLK_STATUS_1_ADDR 0x3C
+
+#define GT_CLK_USBHSICPHY480 (1 << 26)
+#define GT_CLK_USBHSICPHY (1 << 25)
+#define GT_CLK_USBPICOPHY (1 << 24)
+#define GT_CLK_USBNANOPHY (1 << 23)
+
+// 时钟寄存器3
+#define PER_CLK_EN_3_ADDR 0x50
+#define PER_CLK_DIS_3_ADDR 0x54
+#define PER_CLK_EN_STATUS_3_ADDR 0x58
+#define PER_CLK_STATUS_3_ADDR 0x5C
+
+#define GT_CLK_USB2HST (1 << 18)
+#define GT_CLK_USB2DVC (1 << 17)
+
+
+// 通用外设控制器16
+#define PERI_CTRL16 0x40
+#define PICOPHY_SIDDQ (1 << 0)
+#define PICOPHY_TXPREEMPHASISTUNE (1 << 31)
+
+// 通用外设控制器17
+#define PERI_CTRL17 0x44
+
+// 通用外设控制器21
+#define PERI_CTRL21 0x01F4
+
+
+enum k3v2_otg_status {
+ K3V2_OTG_OFF = 0,
+ K3V2_OTG_DEVICE,
+ K3V2_OTG_HOST,
+ K3V2_OTG_SUSPEND
+};
+
+
+struct k3v2_otg {
+ enum k3v2_otg_status status;
+ int dvc_clk_on;
+ int pico_clk_on;
+ int is_dvc_regu_on;
+ int is_phy_regu_on;
+ struct clk *dvc_clk;
+ struct clk *pico_phy_clk;
+ struct regulator_bulk_data dvc_regu;
+ struct regulator_bulk_data phy_regu;
+ struct lm_device *k3v2_otg_lm_dev;
+
+
+ void __iomem *pctrl_addr;
+ void __iomem *sysctrl_addr;
+ void __iomem *pmu_addr;
+
+ void __iomem *usb_base_addr;
+ unsigned long usb_resource_length;
+};
+
+static struct k3v2_otg *k3v2_otg_p;
+
+static int enable_dvc_clk(void)
+{
+
+ int ret = 0;
+ if (0 == (k3v2_otg_p->dvc_clk_on)) {
+ ret = clk_prepare_enable(k3v2_otg_p->dvc_clk);
+ if (!ret)
+ k3v2_otg_p->dvc_clk_on = 1;
+ }
+ return ret;
+}
+static void disable_dvc_clk(void)
+{
+ if (k3v2_otg_p->dvc_clk_on) {
+ clk_disable_unprepare(k3v2_otg_p->dvc_clk);
+ k3v2_otg_p->dvc_clk_on = 0;
+ }
+
+}
+static int enable_pico_clk(void)
+{
+ int ret = 0;
+ if (0 == (k3v2_otg_p->pico_clk_on)) {
+ ret = clk_prepare_enable(k3v2_otg_p->pico_phy_clk);
+ if (!ret)
+ k3v2_otg_p->pico_clk_on = 1;
+ }
+ return ret;
+}
+static void disable_pico_clk(void)
+{
+ if (k3v2_otg_p->pico_clk_on) {
+ clk_disable_unprepare(k3v2_otg_p->pico_phy_clk);
+ k3v2_otg_p->pico_clk_on = 0;
+ }
+}
+
+static int enable_dvc_regu(void)
+{
+ int ret = 0;
+
+ if (0 == k3v2_otg_p->is_dvc_regu_on) {
+ if (regulator_bulk_enable(1, &(k3v2_otg_p->dvc_regu)))
+ printk(KERN_ERR "[usbotg]regulator_enable dvc failed!\n");
+ else
+ k3v2_otg_p->is_dvc_regu_on = 1;
+ }
+
+ return ret;
+}
+static int enable_phy_regu(void)
+{
+ int ret = 0;
+
+ if(0 == k3v2_otg_p->is_phy_regu_on) {
+ if (regulator_bulk_enable(1, &(k3v2_otg_p->phy_regu)))
+ printk(KERN_ERR "[usbotg]regulator_enable phy failed!\n");
+ else
+ k3v2_otg_p->is_phy_regu_on = 1;
+ }
+
+ return ret;
+}
+static void disable_dvc_regu(void)
+{
+ if(1 == k3v2_otg_p->is_dvc_regu_on) {
+ regulator_bulk_disable(1, &(k3v2_otg_p->dvc_regu));
+ k3v2_otg_p->is_dvc_regu_on = 0;
+ }
+}
+static void disable_phy_regu(void)
+{
+ if(1 == k3v2_otg_p->is_phy_regu_on) {
+ regulator_bulk_disable(1, &(k3v2_otg_p->phy_regu));
+ k3v2_otg_p->is_phy_regu_on = 0;
+ }
+}
+
+
+static int setup_dvc_and_phy(void)
+{
+ unsigned reg_value;
+ void __iomem *sysctrl_base = k3v2_otg_p->sysctrl_addr;
+ void __iomem *pctrl_base = k3v2_otg_p->pctrl_addr;
+
+ enable_dvc_regu();
+ enable_phy_regu();
+
+ udelay(200);
+
+ /* DVC Core reset */
+ writel(IP_RST_USB2DVC, (sysctrl_base + PER_RST_EN_3_ADDR));
+ /* DVC PHY reset */
+ writel(IP_RST_USB2DVC_PHY, (sysctrl_base + PER_RST_EN_3_ADDR));
+ /* pico phy reset */
+ writel(IP_RST_PICOPHY, (sysctrl_base + PER_RST_EN_1_ADDR));
+ /* pico_phy_por */
+ writel(IP_PICOPHY_POR, (sysctrl_base + PER_RST_EN_3_ADDR));
+
+ /* clk off */
+ disable_pico_clk();
+ disable_dvc_clk();
+
+ /// PCTRL16
+ reg_value = readl((pctrl_base + PERI_CTRL16));
+ reg_value &= (~(PICOPHY_SIDDQ));
+ reg_value |= PICOPHY_TXPREEMPHASISTUNE;
+ /* usb1_phy_otgdisable */
+ reg_value &= ~(0x1 << 9);
+ /* usb1_phy_vbusvldextsel */
+ reg_value &= ~(0x3 << 10);
+ /* bit[19:17]: usb1_phy_compdistune[2:0], disconnect detect threshold tuning */
+ reg_value &= ~(0x7 << 17);
+ reg_value |= (0x6 << 17);
+ writel(reg_value, (pctrl_base + PERI_CTRL16));
+
+ /// PCTRL17
+ reg_value = readl(pctrl_base + PERI_CTRL17);
+ reg_value &= ~0x3F;
+ reg_value |= 0x23;
+ D("phy param: 0x%x\n", reg_value);
+ writel(reg_value, (pctrl_base + PERI_CTRL17));
+
+ /// PCTRL21
+ reg_value = readl((pctrl_base + PERI_CTRL21));
+ reg_value &= ~((0x3 << 1) | (0x3 << 8) | (0x3 << 10));
+ reg_value |= ((0x1 << 1) | (0x3 << 8) | (0x1 << 10));
+ writel(reg_value, (pctrl_base + PERI_CTRL21));
+
+ enable_pico_clk();
+
+ udelay(10);
+
+ // undo phy reset
+ writel(IP_RST_PICOPHY, (sysctrl_base + PER_RST_DIS_1_ADDR));
+ writel(IP_PICOPHY_POR, (sysctrl_base + PER_RST_DIS_3_ADDR));
+
+ udelay(1000);
+
+ enable_dvc_clk();
+
+ // undo dvc reset
+ writel(IP_RST_USB2DVC_PHY, (sysctrl_base + PER_RST_DIS_3_ADDR));
+ udelay(1);
+ writel(IP_RST_USB2DVC, (sysctrl_base + PER_RST_DIS_3_ADDR));
+
+ return 0;
+}
+
+
+static void release_plat_resouce(void)
+{
+
+ disable_phy_regu();
+ disable_dvc_regu();
+ regulator_put(k3v2_otg_p->phy_regu.consumer);
+ regulator_put(k3v2_otg_p->dvc_regu.consumer);
+
+ if (k3v2_otg_p->pico_phy_clk)
+ clk_put(k3v2_otg_p->pico_phy_clk);
+ if (k3v2_otg_p->dvc_clk)
+ clk_put(k3v2_otg_p->dvc_clk);
+}
+
+static void get_plat_resource(struct platform_device *pdev)
+{
+ struct clk *clock;
+ struct regulator *regu;
+ struct device_node *np;
+ int ret;
+
+ // get sysctrl base addr
+ np = of_find_compatible_node(NULL, NULL, "hisilicon,sysctrl");
+ k3v2_otg_p->sysctrl_addr = of_iomap(np, 0);
+ D("sysctrl_addr: %p\n", k3v2_otg_p->sysctrl_addr);
+
+ // get pctrl base addr
+ np = of_find_compatible_node(NULL, NULL, "hisilicon,perictrl");
+ k3v2_otg_p->pctrl_addr= of_iomap(np, 0);
+ D("perictrl_addr: %p\n", k3v2_otg_p->pctrl_addr);
+
+
+ /* get clocks */
+ clock = clk_get(NULL, "clk_usb2dvc");
+ ret = (IS_ERR(clock));
+ if (ret) {
+ E("get dvc_clk failed!\n");
+ return;
+ }
+ k3v2_otg_p->dvc_clk = clock;
+
+ clock = clk_get(NULL, "clk_usbpicophy");
+ ret = (IS_ERR(clock));
+ if (ret) {
+ E("get phy_clk failed!\n");
+ goto error;
+ }
+ k3v2_otg_p->pico_phy_clk = clock;
+
+ /* clock status record */
+ k3v2_otg_p->dvc_clk_on = 0;
+ k3v2_otg_p->pico_clk_on = 0;
+
+ /* get retulator. */
+
+ k3v2_otg_p->dvc_regu.supply = "otgphy";
+ if (regulator_bulk_get(&pdev->dev, 1, &(k3v2_otg_p->dvc_regu))) {
+ E("regulator_get dvc failed!\r\n");
+ goto error;
+ }
+ k3v2_otg_p->is_dvc_regu_on = 0;
+
+
+ k3v2_otg_p->phy_regu.supply = "dvc";
+ if (regulator_bulk_get(&pdev->dev, 1, &(k3v2_otg_p->phy_regu))) {
+ E("regulator_get phy failed!\r\n");
+ goto error;
+ }
+ k3v2_otg_p->is_phy_regu_on = 0;
+
+error:
+ release_plat_resouce();
+}
+
+static void k3v2_otg_setup(void)
+{
+ setup_dvc_and_phy();
+ k3v2_otg_p->status = K3V2_OTG_DEVICE;
+ return;
+}
+
+static int dwc_otg_k3v2_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ int irq = 0;
+ struct device *dev = &pdev->dev;
+ struct lm_device *lm_dev;
+ struct k3v2_otg *k3v2_otg_device;
+ struct resource *res;
+
+D(">>>>[%s]+++\n", __func__);
+ k3v2_otg_device = devm_kzalloc(dev, sizeof(struct k3v2_otg), GFP_KERNEL);
+ if (!k3v2_otg_device) {
+ dev_err(dev, "not enough memory\n");
+ return -ENOMEM;
+ }
+
+ k3v2_otg_p = k3v2_otg_device;
+ platform_set_drvdata(pdev, k3v2_otg_device);
+
+ // get regulator, clock, sysctrl addr, pctrl addr
+ get_plat_resource(pdev);
+
+ k3v2_otg_p->status = K3V2_OTG_OFF;
+
+ // enable regulator, clock, phy...
+ k3v2_otg_setup();
+
+ /* register the dwc otg device */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ E("device: %s resource error\n", dev_name(&pdev->dev));
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (!irq) {
+ E("device: %s irq num error\n", dev_name(&pdev->dev));
+ }
+
+ lm_dev = devm_kzalloc(dev, sizeof(*lm_dev), GFP_KERNEL);
+ if (!lm_dev) {
+ dev_err(dev, "not enough memory\n");
+ return -ENOMEM;
+ }
+
+ k3v2_otg_p->k3v2_otg_lm_dev = lm_dev;
+
+ lm_dev->resource.start = res->start;
+ lm_dev->resource.end = res->end;
+ lm_dev->resource.flags = res->flags;
+ lm_dev->irq = irq;
+ lm_dev->id = -1;
+ lm_dev->dev.init_name = "hisik3-usb-otg";
+
+ ret = lm_device_register(lm_dev);
+ if (ret) {
+ E("register lm device failed\n");
+ return ret;
+ }
+
+D(">>>>[%s]---\n", __func__);
+ return ret;
+}
+
+static int dwc_otg_k3v2_remove(struct platform_device *pdev)
+{
+ release_plat_resouce();
+ return 0;
+}
+
+int dwc_otg_k3v2_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+int dwc_otg_k3v2_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+
+
+#ifdef CONFIG_OF
+static const struct of_device_id dwc_otg_k3v2_mach[] = {
+ { .compatible = "hisilicon,k3v2_otg" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_dwc3_match);
+#else
+#define dwc_otg_k3v2_mach NULL
+#endif
+
+static struct platform_driver dwc_otg_k3v2_driver = {
+ .probe = dwc_otg_k3v2_probe,
+ .remove = dwc_otg_k3v2_remove,
+ .suspend = dwc_otg_k3v2_suspend,
+ .resume = dwc_otg_k3v2_resume,
+ .driver = {
+ .name = "k3v2_dwc_otg",
+ .of_match_table = of_match_ptr(dwc_otg_k3v2_mach),
+ },
+};
+
+module_platform_driver(dwc_otg_k3v2_driver);
+
+MODULE_AUTHOR("l00196665");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("DesignWare OTG2.0 k3v2 Glue Layer");
+