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);
 |