| // 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/clk.h> |
| #include <linux/err.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/delay.h> |
| #include <linux/usb/phy.h> |
| #include <linux/amlogic/usb-v2.h> |
| #include <linux/of_gpio.h> |
| #include <linux/workqueue.h> |
| #include <linux/notifier.h> |
| #include <linux/amlogic/usbtype.h> |
| #include "../phy/phy-aml-new-usb-v2.h" |
| |
| struct amlogic_usb_v2 *g_phy_com_v2; |
| |
| BLOCKING_NOTIFIER_HEAD(aml_new_usb_v2_notifier_list); |
| |
| int aml_new_usb_v2_register_notifier(struct notifier_block *nb) |
| { |
| int ret; |
| |
| ret = blocking_notifier_chain_register |
| (&aml_new_usb_v2_notifier_list, nb); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(aml_new_usb_v2_register_notifier); |
| |
| int aml_new_usb_v2_unregister_notifier(struct notifier_block *nb) |
| { |
| int ret; |
| |
| ret = blocking_notifier_chain_unregister |
| (&aml_new_usb_v2_notifier_list, nb); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(aml_new_usb_v2_unregister_notifier); |
| |
| void aml_new_usb3_get_phy(struct amlogic_usb_v2 *phy) |
| { |
| g_phy_com_v2 = phy; |
| } |
| EXPORT_SYMBOL(aml_new_usb3_get_phy); |
| |
| void aml_new_usb_notifier_call(unsigned long is_device_on) |
| { |
| blocking_notifier_call_chain |
| (&aml_new_usb_v2_notifier_list, is_device_on, NULL); |
| } |
| EXPORT_SYMBOL(aml_new_usb_notifier_call); |
| |
| void resume_xhci_port_a(void) |
| { |
| if (!g_phy_com_v2) |
| return; |
| if (g_phy_com_v2->resume_xhci_p_a) |
| g_phy_com_v2->resume_xhci_p_a(); |
| } |
| |
| void force_disable_xhci_port_a(void) |
| { |
| if (!g_phy_com_v2) |
| return; |
| if (g_phy_com_v2->disable_port_a) |
| g_phy_com_v2->disable_port_a(); |
| } |
| |
| void aml_new_usb_v2_init(void) |
| { |
| if (!g_phy_com_v2) |
| return; |
| if (g_phy_com_v2->usb2_phy_init) |
| g_phy_com_v2->usb2_phy_init(); |
| } |
| EXPORT_SYMBOL(aml_new_usb_v2_init); |
| |
| int aml_new_usb_get_mode(void) |
| { |
| if (!g_phy_com_v2) |
| return -1; |
| if (g_phy_com_v2->usb2_get_mode) |
| return g_phy_com_v2->usb2_get_mode(); |
| } |
| EXPORT_SYMBOL(aml_new_usb_get_mode); |
| |
| void cr_bus_addr(unsigned int addr) |
| { |
| union phy3_r4 phy_r4 = {.d32 = 0}; |
| union phy3_r5 phy_r5 = {.d32 = 0}; |
| unsigned long timeout_jiffies; |
| |
| phy_r4.b.phy_cr_data_in = addr; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| |
| phy_r4.b.phy_cr_cap_addr = 0; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| phy_r4.b.phy_cr_cap_addr = 1; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| timeout_jiffies = jiffies + |
| msecs_to_jiffies(1000); |
| do { |
| phy_r5.d32 = readl(g_phy_com_v2->phy3_cfg_r5); |
| } while (phy_r5.b.phy_cr_ack == 0 && |
| time_is_after_jiffies(timeout_jiffies)); |
| |
| phy_r4.b.phy_cr_cap_addr = 0; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| timeout_jiffies = jiffies + |
| msecs_to_jiffies(1000); |
| do { |
| phy_r5.d32 = readl(g_phy_com_v2->phy3_cfg_r5); |
| } while (phy_r5.b.phy_cr_ack == 1 && |
| time_is_after_jiffies(timeout_jiffies)); |
| } |
| EXPORT_SYMBOL(cr_bus_addr); |
| |
| int cr_bus_read(unsigned int addr) |
| { |
| int data; |
| union phy3_r4 phy_r4 = {.d32 = 0}; |
| union phy3_r5 phy_r5 = {.d32 = 0}; |
| unsigned long timeout_jiffies; |
| |
| cr_bus_addr(addr); |
| |
| phy_r4.b.phy_cr_read = 0; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| phy_r4.b.phy_cr_read = 1; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| |
| timeout_jiffies = jiffies + |
| msecs_to_jiffies(1000); |
| do { |
| phy_r5.d32 = readl(g_phy_com_v2->phy3_cfg_r5); |
| } while (phy_r5.b.phy_cr_ack == 0 && |
| time_is_after_jiffies(timeout_jiffies)); |
| |
| data = phy_r5.b.phy_cr_data_out; |
| |
| phy_r4.b.phy_cr_read = 0; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| timeout_jiffies = jiffies + |
| msecs_to_jiffies(1000); |
| do { |
| phy_r5.d32 = readl(g_phy_com_v2->phy3_cfg_r5); |
| } while (phy_r5.b.phy_cr_ack == 1 && |
| time_is_after_jiffies(timeout_jiffies)); |
| |
| return data; |
| } |
| EXPORT_SYMBOL(cr_bus_read); |
| |
| void cr_bus_write(unsigned int addr, unsigned int data) |
| { |
| union phy3_r4 phy_r4 = {.d32 = 0}; |
| union phy3_r5 phy_r5 = {.d32 = 0}; |
| unsigned long timeout_jiffies; |
| |
| cr_bus_addr(addr); |
| |
| phy_r4.b.phy_cr_data_in = data; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| |
| phy_r4.b.phy_cr_cap_data = 0; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| phy_r4.b.phy_cr_cap_data = 1; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| timeout_jiffies = jiffies + |
| msecs_to_jiffies(1000); |
| do { |
| phy_r5.d32 = readl(g_phy_com_v2->phy3_cfg_r5); |
| } while (phy_r5.b.phy_cr_ack == 0 && |
| time_is_after_jiffies(timeout_jiffies)); |
| |
| phy_r4.b.phy_cr_cap_data = 0; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| timeout_jiffies = jiffies + |
| msecs_to_jiffies(1000); |
| do { |
| phy_r5.d32 = readl(g_phy_com_v2->phy3_cfg_r5); |
| } while (phy_r5.b.phy_cr_ack == 1 && |
| time_is_after_jiffies(timeout_jiffies)); |
| |
| phy_r4.b.phy_cr_write = 0; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| phy_r4.b.phy_cr_write = 1; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| timeout_jiffies = jiffies + |
| msecs_to_jiffies(1000); |
| do { |
| phy_r5.d32 = readl(g_phy_com_v2->phy3_cfg_r5); |
| } while (phy_r5.b.phy_cr_ack == 0 && |
| time_is_after_jiffies(timeout_jiffies)); |
| |
| phy_r4.b.phy_cr_write = 0; |
| writel(phy_r4.d32, g_phy_com_v2->phy3_cfg_r4); |
| timeout_jiffies = jiffies + |
| msecs_to_jiffies(1000); |
| do { |
| phy_r5.d32 = readl(g_phy_com_v2->phy3_cfg_r5); |
| } while (phy_r5.b.phy_cr_ack == 1 && |
| time_is_after_jiffies(timeout_jiffies)); |
| } |
| EXPORT_SYMBOL(cr_bus_write); |