mirror of https://github.com/OpenIPC/firmware.git
435 lines
12 KiB
Diff
435 lines
12 KiB
Diff
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 1’b0. (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);
|