firmware/br-ext-chip-ingenic/board/t40/kernel/patches/00000-drivers_usb_phy_phy-i...

435 lines
12 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

diff -drupN a/drivers/usb/phy/phy-ingenic-usb.c b/drivers/usb/phy/phy-ingenic-usb.c
--- a/drivers/usb/phy/phy-ingenic-usb.c 1970-01-01 03:00:00.000000000 +0300
+++ b/drivers/usb/phy/phy-ingenic-usb.c 2022-06-09 05:02:34.000000000 +0300
@@ -0,0 +1,430 @@
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/usb/phy.h>
+#include <linux/platform_device.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/of_gpio.h>
+#include <linux/of_device.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <soc/cpm.h>
+
+enum {
+ OTG_PHY = 0,
+ HOST_PHY
+};
+
+struct ingenic_usb_phy {
+ struct usb_phy phy;
+ struct device *dev;
+ unsigned long ext_rate;
+ struct clk *clk;
+ struct clk *gate_clk;
+ struct regmap *regmap;
+
+ bool is_otg_phy;
+
+ /*otg phy*/
+ enum usb_dr_mode dr_mode;
+ struct gpio_desc *gpiod_drvvbus;
+ struct gpio_desc *gpiod_id;
+ struct gpio_desc *gpiod_vbus;
+#define USB_DETE_VBUS 0x1
+#define USB_DETE_ID 0x2
+ unsigned int usb_dete_state;
+ struct delayed_work work;
+ u32 usbpcr;
+
+ /*uhc phy*/
+ unsigned int has_inited;
+ u32 usbpcr1;
+};
+
+static inline int dwc_usbphy_read(struct usb_phy *x, u32 reg)
+{
+ struct ingenic_usb_phy *jzphy = container_of(x, struct ingenic_usb_phy, phy);
+ u32 val = 0;
+ regmap_read(jzphy->regmap, reg, &val);
+ return val;
+}
+
+static inline int dwc_usbphy_write(struct usb_phy *x, u32 val, u32 reg)
+{
+ struct ingenic_usb_phy *jzphy = container_of(x, struct ingenic_usb_phy, phy);
+
+ return regmap_write(jzphy->regmap, reg, val);
+}
+
+static inline int dwc_usbphy_update_bits(struct usb_phy *x, u32 reg, u32 mask, u32 bits)
+{
+ struct ingenic_usb_phy *jzphy = container_of(x, struct ingenic_usb_phy, phy);
+
+ return regmap_update_bits_check(jzphy->regmap, reg, mask, bits, NULL);
+}
+
+static irqreturn_t usb_dete_irq_handler(int irq, void *data)
+{
+ struct ingenic_usb_phy *jzphy = (struct ingenic_usb_phy *)data;
+
+ schedule_delayed_work(&jzphy->work, msecs_to_jiffies(100));
+ return IRQ_HANDLED;
+}
+
+static void usb_dete_work(struct work_struct *work)
+{
+ struct ingenic_usb_phy *jzphy =
+ container_of(work, struct ingenic_usb_phy, work.work);
+ struct usb_otg *otg = jzphy->phy.otg;
+ int vbus = 0, id_level = 1, usb_state = 0;
+
+ if (jzphy->gpiod_vbus)
+ vbus = gpiod_get_value_cansleep(jzphy->gpiod_vbus);
+
+ if (jzphy->gpiod_id)
+ id_level = gpiod_get_value_cansleep(jzphy->gpiod_id);
+
+ if (vbus && id_level)
+ usb_state |= USB_DETE_VBUS;
+ else
+ usb_state &= ~USB_DETE_VBUS;
+
+ if (id_level)
+ usb_state |= USB_DETE_ID;
+ else
+ usb_state &= ~USB_DETE_ID;
+
+ if (usb_state != jzphy->usb_dete_state) {
+ enum usb_phy_events status;
+ if (!(usb_state & USB_DETE_VBUS)) {
+ usb_gadget_vbus_disconnect(otg->gadget);
+ status = USB_EVENT_NONE;
+ otg->state = OTG_STATE_B_IDLE;
+ jzphy->phy.last_event = status;
+ if (otg->gadget)
+ atomic_notifier_call_chain(&jzphy->phy.notifier, status,
+ otg->gadget);
+ }
+
+ if (!(usb_state & USB_DETE_ID)) {
+ status = USB_EVENT_ID;
+ otg->state = OTG_STATE_A_IDLE;
+ jzphy->phy.last_event = status;
+ if (otg->host)
+ atomic_notifier_call_chain(&jzphy->phy.notifier, status,
+ otg->host);
+ }
+
+ if (usb_state & USB_DETE_VBUS) {
+ status = USB_EVENT_VBUS;
+ otg->state = OTG_STATE_B_PERIPHERAL;
+ jzphy->phy.last_event = status;
+ usb_gadget_vbus_connect(otg->gadget);
+ if (otg->gadget)
+ atomic_notifier_call_chain(&jzphy->phy.notifier, status,
+ otg->gadget);
+ }
+ jzphy->usb_dete_state = usb_state;
+ }
+ return;
+}
+
+static int jzphy_set_suspend(struct usb_phy *x, int suspend);
+static int jzphy_init(struct usb_phy *x)
+{
+ struct ingenic_usb_phy *jzphy = container_of(x, struct ingenic_usb_phy, phy);
+ unsigned long rate, usb_clk_sel = USBPCR1_REFCLKDIV_24M;
+ u32 usbpcr, usbpcr1;
+
+ if (!IS_ERR_OR_NULL(jzphy->clk)) {
+ switch (jzphy->ext_rate/1000000) {
+ case 12:
+ usb_clk_sel = USBPCR1_REFCLKDIV_12M;
+ break;
+ case 48:
+ rate = jzphy->ext_rate;
+ usb_clk_sel = USBPCR1_REFCLKDIV_48M;
+ break;
+ default:
+ case 24:
+ rate = 24000000;
+ usb_clk_sel = USBPCR1_REFCLKDIV_24M;
+ }
+ if (rate != clk_get_rate(jzphy->clk))
+ clk_set_rate(jzphy->clk, rate);
+ clk_prepare_enable(jzphy->clk);
+ }
+
+ if (!IS_ERR_OR_NULL(jzphy->gate_clk))
+ clk_prepare_enable(jzphy->gate_clk);
+
+ jzphy_set_suspend(x, 0);
+
+ /* fil */
+ dwc_usbphy_write(x, 0, CPM_USBVBFIL);
+
+ /* rdt */
+ dwc_usbphy_write(x, USBRDT_USBRDT(0x96) | USBRDT_VBFIL_LD_EN, CPM_USBRDT);
+
+ if (jzphy->is_otg_phy) {
+ usbpcr = USBPCR_COMMONONN | USBPCR_VBUSVLDEXT | USBPCR_VBUSVLDEXTSEL |
+ USBPCR_SQRXTUNE(7) | USBPCR_TXPREEMPHTUNE | USBPCR_TXHSXVTUNE(1) |
+ USBPCR_TXVREFTUNE(7);
+ usbpcr1 = dwc_usbphy_read(x, CPM_USBPCR1);
+ usbpcr1 &= ~(USBPCR1_REFCLKDIV_MSK);
+ usbpcr1 = USBPCR1_REFCLKSEL | USBPCR1_REFCLKDIV(usb_clk_sel) | USBPCR1_WORD_IF_16BIT;
+ if (jzphy->dr_mode == USB_DR_MODE_PERIPHERAL) {
+ pr_info("DWC PHY IN DEVICE ONLY MODE\n");
+ usbpcr1 |= USBPCR1_BVLD_REG;
+ usbpcr |= USBPCR_OTG_DISABLE;
+ } else {
+ pr_info("DWC PHY IN OTG MODE\n");
+ usbpcr |= USBPCR_USB_MODE;
+ }
+ dwc_usbphy_write(x, usbpcr1, CPM_USBPCR1);
+ } else {
+ }
+ /**
+ * Power-On Reset(POR)
+ * Function:This customer-specific signal resets all test registers and state machines
+ * in the USB 2.0 nanoPHY.
+ * The POR signal must be asserted for a minimum of 10 μs.
+ * For POR timing information:
+ *
+ * T0: Power-on reset (POR) is initiated. 0 (reference)
+ * T1: T1 indicates when POR can be set to 1b0. (To provide examples, values for T2 and T3 are also shown
+ * where T1 = T0 + 30 μs.); In general, T1 must be ≥ T0 + 10 μs. T0 + 10 μs ≤ T1
+ * T2: T2 indicates when PHYCLOCK, CLK48MOHCI, and CLK12MOHCI are available at the macro output, based on
+ * the USB 2.0 nanoPHY reference clock source.
+ * Crystal:
+ • When T1 = T0 + 10 μs:
+ T2 < T1 + 805 μs = T0 + 815 μs
+ • When T1 = T0 + 30 μs:
+ T2 < T1 + 805 μs = T0 + 835 μs
+ * see "Reset and Power-Saving Signals" on page 60 an “Powering Up and Powering Down the USB 2.0
+ * nanoPHY” on page 73.
+ */
+ usbpcr |= USBPCR_POR;
+ dwc_usbphy_write(x, usbpcr, CPM_USBPCR);
+ mdelay(1);
+ usbpcr &= ~USBPCR_POR;
+ dwc_usbphy_write(x, usbpcr, CPM_USBPCR);
+ mdelay(1);
+ return 0;
+}
+
+static void jzphy_shutdown(struct usb_phy *x)
+{
+
+ struct ingenic_usb_phy *jzphy = container_of(x, struct ingenic_usb_phy, phy);
+
+ jzphy_set_suspend(x, 1);
+
+ if (jzphy->is_otg_phy)
+ dwc_usbphy_update_bits(x, CPM_USBPCR,
+ USBPCR_OTG_DISABLE|USBPCR_SIDDQ,
+ USBPCR_OTG_DISABLE|USBPCR_SIDDQ);
+ if (!IS_ERR_OR_NULL(jzphy->clk))
+ clk_disable_unprepare(jzphy->clk);
+ if (!IS_ERR_OR_NULL(jzphy->gate_clk))
+ clk_disable_unprepare(jzphy->gate_clk);
+}
+
+static int jzphy_set_vbus(struct usb_phy *x, int on)
+{
+ struct ingenic_usb_phy *jzphy = container_of(x, struct ingenic_usb_phy, phy);
+
+ printk("OTG VBUS %s\n", on ? "ON" : "OFF");
+ gpiod_set_value(jzphy->gpiod_drvvbus, on);
+ return 0;
+}
+
+static int jzphy_set_suspend(struct usb_phy *x, int suspend)
+{
+ struct ingenic_usb_phy *jzphy = container_of(x, struct ingenic_usb_phy, phy);
+ int suspend_bit = jzphy->is_otg_phy ? OPCR_USB_SPENDN : OPCR_USB_SPENDN1;
+
+ pr_debug("usb phy suspend %d suspend bit %x\n", suspend, suspend_bit);
+ dwc_usbphy_update_bits(x, CPM_OPCR,
+ suspend_bit,
+ suspend ? ~suspend_bit : suspend_bit);
+ udelay(suspend ? 5 : 45);
+ return 0;
+}
+
+static int jzphy_set_wakeup(struct usb_phy *x, bool enabled)
+{
+ return 0;
+}
+
+static int jzphy_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ if (!otg)
+ return -ENODEV;
+ otg->host = host;
+ return 0;
+}
+
+static int jzphy_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget)
+{
+ if (!otg)
+ return -ENODEV;
+ otg->gadget = gadget;
+ return 0;
+}
+
+static const struct of_device_id of_matchs[];
+static int usb_phy_ingenic_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct ingenic_usb_phy *jzphy;
+ bool is_otg_phy = false;
+ struct clk *clk;
+ int ret;
+
+ match = of_match_device(of_matchs, &pdev->dev);
+ if (!match)
+ return -ENODEV;
+
+ if ((int)match->data == OTG_PHY)
+ is_otg_phy = true;
+
+ jzphy = (struct ingenic_usb_phy *)
+ devm_kzalloc(&pdev->dev, sizeof(*jzphy), GFP_KERNEL);
+ if (!jzphy)
+ return -ENOMEM;
+
+ jzphy->phy.init = jzphy_init;
+ jzphy->phy.shutdown = jzphy_shutdown;
+ jzphy->phy.dev = &pdev->dev;
+ jzphy->is_otg_phy = is_otg_phy;
+ jzphy->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "ingenic,syscon");
+ if (IS_ERR(jzphy->regmap)) {
+ dev_err(&pdev->dev, "failed to find regmap for usb phy\n");
+ return PTR_ERR(jzphy->regmap);
+ }
+
+ if (is_otg_phy) {
+ if (of_find_property(pdev->dev.of_node, "dr_mode", NULL))
+ jzphy->dr_mode = usb_get_dr_mode(&pdev->dev);
+ else
+ jzphy->dr_mode = USB_DR_MODE_HOST;
+
+ jzphy->gpiod_id = devm_gpiod_get_optional(&pdev->dev,"ingenic,id-dete", GPIOD_IN);
+ jzphy->gpiod_vbus = devm_gpiod_get_optional(&pdev->dev,"ingenic,vbus-dete", GPIOD_ASIS);
+ if (jzphy->gpiod_id || jzphy->gpiod_vbus)
+ INIT_DELAYED_WORK(&jzphy->work, usb_dete_work);
+
+ if (!IS_ERR_OR_NULL(jzphy->gpiod_id)) {
+ ret = devm_request_irq(&pdev->dev,
+ gpiod_to_irq(jzphy->gpiod_id),
+ usb_dete_irq_handler,
+ IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING|IRQF_ONESHOT,
+ "id_dete",
+ (void *)jzphy);
+ if (ret)
+ return ret;
+ } else {
+ jzphy->usb_dete_state |= USB_DETE_ID;
+ jzphy->gpiod_id = NULL;
+ }
+
+ if (!IS_ERR_OR_NULL(jzphy->gpiod_vbus)) {
+ ret = devm_request_irq(&pdev->dev,
+ gpiod_to_irq(jzphy->gpiod_vbus),
+ usb_dete_irq_handler,
+ IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING|IRQF_ONESHOT,
+ "vbus_dete",
+ (void *)jzphy);
+ if (ret)
+ return ret;
+ } else {
+ jzphy->usb_dete_state &= ~USB_DETE_VBUS;
+ jzphy->gpiod_vbus = NULL;
+ }
+
+ jzphy->gpiod_drvvbus = devm_gpiod_get_optional(&pdev->dev,"ingenic,drvvbus", GPIOD_OUT_LOW);
+ if (IS_ERR_OR_NULL(jzphy->gpiod_drvvbus))
+ jzphy->gpiod_drvvbus = NULL;
+ jzphy->phy.set_vbus = jzphy_set_vbus;
+ jzphy->phy.set_suspend = jzphy_set_suspend;
+ jzphy->phy.set_wakeup = jzphy_set_wakeup;
+ jzphy->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*jzphy->phy.otg),
+ GFP_KERNEL);
+ if (!jzphy->phy.otg)
+ return -ENOMEM;
+
+ jzphy->phy.otg->state = OTG_STATE_UNDEFINED;
+ jzphy->phy.otg->usb_phy = &jzphy->phy;
+ jzphy->phy.otg->set_host = jzphy_set_host;
+ jzphy->phy.otg->set_peripheral = jzphy_set_peripheral;
+ }
+
+ jzphy->clk = devm_clk_get(&pdev->dev, "cgu_usb");
+ if (IS_ERR_OR_NULL(jzphy->clk))
+ return -ENODEV;
+
+ jzphy->gate_clk = devm_clk_get(&pdev->dev, "gate_usbphy");
+ if (IS_ERR_OR_NULL(jzphy->gate_clk))
+ return -ENODEV;
+
+ clk = clk_get(NULL, "ext");
+ if (!IS_ERR_OR_NULL(clk)) {
+ jzphy->ext_rate = clk_get_rate(clk);
+ clk_put(clk);
+ }
+
+ ret = usb_add_phy_dev(&jzphy->phy);
+ if (ret) {
+ dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
+ ret);
+ return ret;
+ }
+ platform_set_drvdata(pdev, jzphy);
+
+ return 0;
+}
+
+static int usb_phy_ingenic_remove(struct platform_device *pdev)
+{
+ struct ingenic_usb_phy *jzphy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&jzphy->phy);
+
+ clk_disable_unprepare(jzphy->clk);
+ return 0;
+}
+
+static const struct of_device_id of_matchs[] = {
+ { .compatible = "ingenic,otgphy", .data = (void *)OTG_PHY},
+ { .compatible = "ingenic,usbphy", .data = (void *)HOST_PHY},
+ { }
+};
+MODULE_DEVICE_TABLE(of, ingenic_xceiv_dt_ids);
+
+static struct platform_driver usb_phy_ingenic_driver = {
+ .probe = usb_phy_ingenic_probe,
+ .remove = usb_phy_ingenic_remove,
+ .driver = {
+ .name = "usb_phy",
+ .of_match_table = of_matchs,
+ },
+};
+
+static int __init usb_phy_ingenic_init(void)
+{
+ return platform_driver_register(&usb_phy_ingenic_driver);
+}
+subsys_initcall(usb_phy_ingenic_init);
+
+static void __exit usb_phy_ingenic_exit(void)
+{
+ platform_driver_unregister(&usb_phy_ingenic_driver);
+}
+module_exit(usb_phy_ingenic_exit);