| /* | 
 |  * Rockchip USB2.0 PHY with Innosilicon IP block driver | 
 |  * | 
 |  * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License as published by | 
 |  * the Free Software Foundation; either version 2 of the License, or | 
 |  * (at your option) any later version. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  */ | 
 |  | 
 | #include <linux/clk.h> | 
 | #include <linux/clk-provider.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/io.h> | 
 | #include <linux/gpio/consumer.h> | 
 | #include <linux/jiffies.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/mutex.h> | 
 | #include <linux/of.h> | 
 | #include <linux/of_address.h> | 
 | #include <linux/of_irq.h> | 
 | #include <linux/of_platform.h> | 
 | #include <linux/phy/phy.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/regmap.h> | 
 | #include <linux/mfd/syscon.h> | 
 |  | 
 | #define BIT_WRITEABLE_SHIFT	16 | 
 | #define SCHEDULE_DELAY	(60 * HZ) | 
 |  | 
 | enum rockchip_usb2phy_port_id { | 
 | 	USB2PHY_PORT_OTG, | 
 | 	USB2PHY_PORT_HOST, | 
 | 	USB2PHY_NUM_PORTS, | 
 | }; | 
 |  | 
 | enum rockchip_usb2phy_host_state { | 
 | 	PHY_STATE_HS_ONLINE	= 0, | 
 | 	PHY_STATE_DISCONNECT	= 1, | 
 | 	PHY_STATE_CONNECT	= 2, | 
 | 	PHY_STATE_FS_LS_ONLINE	= 4, | 
 | }; | 
 |  | 
 | struct usb2phy_reg { | 
 | 	unsigned int	offset; | 
 | 	unsigned int	bitend; | 
 | 	unsigned int	bitstart; | 
 | 	unsigned int	disable; | 
 | 	unsigned int	enable; | 
 | }; | 
 |  | 
 | /** | 
 |  * struct rockchip_usb2phy_port_cfg: usb-phy port configuration. | 
 |  * @phy_sus: phy suspend register. | 
 |  * @ls_det_en: linestate detection enable register. | 
 |  * @ls_det_st: linestate detection state register. | 
 |  * @ls_det_clr: linestate detection clear register. | 
 |  * @utmi_ls: utmi linestate state register. | 
 |  * @utmi_hstdet: utmi host disconnect register. | 
 |  */ | 
 | struct rockchip_usb2phy_port_cfg { | 
 | 	struct usb2phy_reg	phy_sus; | 
 | 	struct usb2phy_reg	ls_det_en; | 
 | 	struct usb2phy_reg	ls_det_st; | 
 | 	struct usb2phy_reg	ls_det_clr; | 
 | 	struct usb2phy_reg	utmi_ls; | 
 | 	struct usb2phy_reg	utmi_hstdet; | 
 | }; | 
 |  | 
 | /** | 
 |  * struct rockchip_usb2phy_cfg: usb-phy configuration. | 
 |  * @reg: the address offset of grf for usb-phy config. | 
 |  * @num_ports: specify how many ports that the phy has. | 
 |  * @clkout_ctl: keep on/turn off output clk of phy. | 
 |  */ | 
 | struct rockchip_usb2phy_cfg { | 
 | 	unsigned int	reg; | 
 | 	unsigned int	num_ports; | 
 | 	struct usb2phy_reg	clkout_ctl; | 
 | 	const struct rockchip_usb2phy_port_cfg	port_cfgs[USB2PHY_NUM_PORTS]; | 
 | }; | 
 |  | 
 | /** | 
 |  * struct rockchip_usb2phy_port: usb-phy port data. | 
 |  * @port_id: flag for otg port or host port. | 
 |  * @suspended: phy suspended flag. | 
 |  * @ls_irq: IRQ number assigned for linestate detection. | 
 |  * @mutex: for register updating in sm_work. | 
 |  * @sm_work: OTG state machine work. | 
 |  * @phy_cfg: port register configuration, assigned by driver data. | 
 |  */ | 
 | struct rockchip_usb2phy_port { | 
 | 	struct phy	*phy; | 
 | 	unsigned int	port_id; | 
 | 	bool		suspended; | 
 | 	int		ls_irq; | 
 | 	struct mutex	mutex; | 
 | 	struct		delayed_work sm_work; | 
 | 	const struct	rockchip_usb2phy_port_cfg *port_cfg; | 
 | }; | 
 |  | 
 | /** | 
 |  * struct rockchip_usb2phy: usb2.0 phy driver data. | 
 |  * @grf: General Register Files regmap. | 
 |  * @clk: clock struct of phy input clk. | 
 |  * @clk480m: clock struct of phy output clk. | 
 |  * @clk_hw: clock struct of phy output clk management. | 
 |  * @phy_cfg: phy register configuration, assigned by driver data. | 
 |  * @ports: phy port instance. | 
 |  */ | 
 | struct rockchip_usb2phy { | 
 | 	struct device	*dev; | 
 | 	struct regmap	*grf; | 
 | 	struct clk	*clk; | 
 | 	struct clk	*clk480m; | 
 | 	struct clk_hw	clk480m_hw; | 
 | 	const struct rockchip_usb2phy_cfg	*phy_cfg; | 
 | 	struct rockchip_usb2phy_port	ports[USB2PHY_NUM_PORTS]; | 
 | }; | 
 |  | 
 | static inline int property_enable(struct rockchip_usb2phy *rphy, | 
 | 				  const struct usb2phy_reg *reg, bool en) | 
 | { | 
 | 	unsigned int val, mask, tmp; | 
 |  | 
 | 	tmp = en ? reg->enable : reg->disable; | 
 | 	mask = GENMASK(reg->bitend, reg->bitstart); | 
 | 	val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT); | 
 |  | 
 | 	return regmap_write(rphy->grf, reg->offset, val); | 
 | } | 
 |  | 
 | static inline bool property_enabled(struct rockchip_usb2phy *rphy, | 
 | 				    const struct usb2phy_reg *reg) | 
 | { | 
 | 	int ret; | 
 | 	unsigned int tmp, orig; | 
 | 	unsigned int mask = GENMASK(reg->bitend, reg->bitstart); | 
 |  | 
 | 	ret = regmap_read(rphy->grf, reg->offset, &orig); | 
 | 	if (ret) | 
 | 		return false; | 
 |  | 
 | 	tmp = (orig & mask) >> reg->bitstart; | 
 | 	return tmp == reg->enable; | 
 | } | 
 |  | 
 | static int rockchip_usb2phy_clk480m_enable(struct clk_hw *hw) | 
 | { | 
 | 	struct rockchip_usb2phy *rphy = | 
 | 		container_of(hw, struct rockchip_usb2phy, clk480m_hw); | 
 | 	int ret; | 
 |  | 
 | 	/* turn on 480m clk output if it is off */ | 
 | 	if (!property_enabled(rphy, &rphy->phy_cfg->clkout_ctl)) { | 
 | 		ret = property_enable(rphy, &rphy->phy_cfg->clkout_ctl, true); | 
 | 		if (ret) | 
 | 			return ret; | 
 |  | 
 | 		/* waitting for the clk become stable */ | 
 | 		mdelay(1); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void rockchip_usb2phy_clk480m_disable(struct clk_hw *hw) | 
 | { | 
 | 	struct rockchip_usb2phy *rphy = | 
 | 		container_of(hw, struct rockchip_usb2phy, clk480m_hw); | 
 |  | 
 | 	/* turn off 480m clk output */ | 
 | 	property_enable(rphy, &rphy->phy_cfg->clkout_ctl, false); | 
 | } | 
 |  | 
 | static int rockchip_usb2phy_clk480m_enabled(struct clk_hw *hw) | 
 | { | 
 | 	struct rockchip_usb2phy *rphy = | 
 | 		container_of(hw, struct rockchip_usb2phy, clk480m_hw); | 
 |  | 
 | 	return property_enabled(rphy, &rphy->phy_cfg->clkout_ctl); | 
 | } | 
 |  | 
 | static unsigned long | 
 | rockchip_usb2phy_clk480m_recalc_rate(struct clk_hw *hw, | 
 | 				     unsigned long parent_rate) | 
 | { | 
 | 	return 480000000; | 
 | } | 
 |  | 
 | static const struct clk_ops rockchip_usb2phy_clkout_ops = { | 
 | 	.enable = rockchip_usb2phy_clk480m_enable, | 
 | 	.disable = rockchip_usb2phy_clk480m_disable, | 
 | 	.is_enabled = rockchip_usb2phy_clk480m_enabled, | 
 | 	.recalc_rate = rockchip_usb2phy_clk480m_recalc_rate, | 
 | }; | 
 |  | 
 | static void rockchip_usb2phy_clk480m_unregister(void *data) | 
 | { | 
 | 	struct rockchip_usb2phy *rphy = data; | 
 |  | 
 | 	of_clk_del_provider(rphy->dev->of_node); | 
 | 	clk_unregister(rphy->clk480m); | 
 | } | 
 |  | 
 | static int | 
 | rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) | 
 | { | 
 | 	struct device_node *node = rphy->dev->of_node; | 
 | 	struct clk_init_data init; | 
 | 	const char *clk_name; | 
 | 	int ret; | 
 |  | 
 | 	init.flags = 0; | 
 | 	init.name = "clk_usbphy_480m"; | 
 | 	init.ops = &rockchip_usb2phy_clkout_ops; | 
 |  | 
 | 	/* optional override of the clockname */ | 
 | 	of_property_read_string(node, "clock-output-names", &init.name); | 
 |  | 
 | 	if (rphy->clk) { | 
 | 		clk_name = __clk_get_name(rphy->clk); | 
 | 		init.parent_names = &clk_name; | 
 | 		init.num_parents = 1; | 
 | 	} else { | 
 | 		init.parent_names = NULL; | 
 | 		init.num_parents = 0; | 
 | 	} | 
 |  | 
 | 	rphy->clk480m_hw.init = &init; | 
 |  | 
 | 	/* register the clock */ | 
 | 	rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw); | 
 | 	if (IS_ERR(rphy->clk480m)) { | 
 | 		ret = PTR_ERR(rphy->clk480m); | 
 | 		goto err_ret; | 
 | 	} | 
 |  | 
 | 	ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m); | 
 | 	if (ret < 0) | 
 | 		goto err_clk_provider; | 
 |  | 
 | 	ret = devm_add_action(rphy->dev, rockchip_usb2phy_clk480m_unregister, | 
 | 			      rphy); | 
 | 	if (ret < 0) | 
 | 		goto err_unreg_action; | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_unreg_action: | 
 | 	of_clk_del_provider(node); | 
 | err_clk_provider: | 
 | 	clk_unregister(rphy->clk480m); | 
 | err_ret: | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int rockchip_usb2phy_init(struct phy *phy) | 
 | { | 
 | 	struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); | 
 | 	struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); | 
 | 	int ret; | 
 |  | 
 | 	if (rport->port_id == USB2PHY_PORT_HOST) { | 
 | 		/* clear linestate and enable linestate detect irq */ | 
 | 		mutex_lock(&rport->mutex); | 
 |  | 
 | 		ret = property_enable(rphy, &rport->port_cfg->ls_det_clr, true); | 
 | 		if (ret) { | 
 | 			mutex_unlock(&rport->mutex); | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		ret = property_enable(rphy, &rport->port_cfg->ls_det_en, true); | 
 | 		if (ret) { | 
 | 			mutex_unlock(&rport->mutex); | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		mutex_unlock(&rport->mutex); | 
 | 		schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int rockchip_usb2phy_power_on(struct phy *phy) | 
 | { | 
 | 	struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); | 
 | 	struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); | 
 | 	int ret; | 
 |  | 
 | 	dev_dbg(&rport->phy->dev, "port power on\n"); | 
 |  | 
 | 	if (!rport->suspended) | 
 | 		return 0; | 
 |  | 
 | 	ret = clk_prepare_enable(rphy->clk480m); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = property_enable(rphy, &rport->port_cfg->phy_sus, false); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	rport->suspended = false; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int rockchip_usb2phy_power_off(struct phy *phy) | 
 | { | 
 | 	struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); | 
 | 	struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); | 
 | 	int ret; | 
 |  | 
 | 	dev_dbg(&rport->phy->dev, "port power off\n"); | 
 |  | 
 | 	if (rport->suspended) | 
 | 		return 0; | 
 |  | 
 | 	ret = property_enable(rphy, &rport->port_cfg->phy_sus, true); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	rport->suspended = true; | 
 | 	clk_disable_unprepare(rphy->clk480m); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int rockchip_usb2phy_exit(struct phy *phy) | 
 | { | 
 | 	struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); | 
 |  | 
 | 	if (rport->port_id == USB2PHY_PORT_HOST) | 
 | 		cancel_delayed_work_sync(&rport->sm_work); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct phy_ops rockchip_usb2phy_ops = { | 
 | 	.init		= rockchip_usb2phy_init, | 
 | 	.exit		= rockchip_usb2phy_exit, | 
 | 	.power_on	= rockchip_usb2phy_power_on, | 
 | 	.power_off	= rockchip_usb2phy_power_off, | 
 | 	.owner		= THIS_MODULE, | 
 | }; | 
 |  | 
 | /* | 
 |  * The function manage host-phy port state and suspend/resume phy port | 
 |  * to save power. | 
 |  * | 
 |  * we rely on utmi_linestate and utmi_hostdisconnect to identify whether | 
 |  * devices is disconnect or not. Besides, we do not need care it is FS/LS | 
 |  * disconnected or HS disconnected, actually, we just only need get the | 
 |  * device is disconnected at last through rearm the delayed work, | 
 |  * to suspend the phy port in _PHY_STATE_DISCONNECT_ case. | 
 |  * | 
 |  * NOTE: It may invoke *phy_powr_off or *phy_power_on which will invoke | 
 |  * some clk related APIs, so do not invoke it from interrupt context directly. | 
 |  */ | 
 | static void rockchip_usb2phy_sm_work(struct work_struct *work) | 
 | { | 
 | 	struct rockchip_usb2phy_port *rport = | 
 | 		container_of(work, struct rockchip_usb2phy_port, sm_work.work); | 
 | 	struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); | 
 | 	unsigned int sh = rport->port_cfg->utmi_hstdet.bitend - | 
 | 			  rport->port_cfg->utmi_hstdet.bitstart + 1; | 
 | 	unsigned int ul, uhd, state; | 
 | 	unsigned int ul_mask, uhd_mask; | 
 | 	int ret; | 
 |  | 
 | 	mutex_lock(&rport->mutex); | 
 |  | 
 | 	ret = regmap_read(rphy->grf, rport->port_cfg->utmi_ls.offset, &ul); | 
 | 	if (ret < 0) | 
 | 		goto next_schedule; | 
 |  | 
 | 	ret = regmap_read(rphy->grf, rport->port_cfg->utmi_hstdet.offset, | 
 | 			  &uhd); | 
 | 	if (ret < 0) | 
 | 		goto next_schedule; | 
 |  | 
 | 	uhd_mask = GENMASK(rport->port_cfg->utmi_hstdet.bitend, | 
 | 			   rport->port_cfg->utmi_hstdet.bitstart); | 
 | 	ul_mask = GENMASK(rport->port_cfg->utmi_ls.bitend, | 
 | 			  rport->port_cfg->utmi_ls.bitstart); | 
 |  | 
 | 	/* stitch on utmi_ls and utmi_hstdet as phy state */ | 
 | 	state = ((uhd & uhd_mask) >> rport->port_cfg->utmi_hstdet.bitstart) | | 
 | 		(((ul & ul_mask) >> rport->port_cfg->utmi_ls.bitstart) << sh); | 
 |  | 
 | 	switch (state) { | 
 | 	case PHY_STATE_HS_ONLINE: | 
 | 		dev_dbg(&rport->phy->dev, "HS online\n"); | 
 | 		break; | 
 | 	case PHY_STATE_FS_LS_ONLINE: | 
 | 		/* | 
 | 		 * For FS/LS device, the online state share with connect state | 
 | 		 * from utmi_ls and utmi_hstdet register, so we distinguish | 
 | 		 * them via suspended flag. | 
 | 		 * | 
 | 		 * Plus, there are two cases, one is D- Line pull-up, and D+ | 
 | 		 * line pull-down, the state is 4; another is D+ line pull-up, | 
 | 		 * and D- line pull-down, the state is 2. | 
 | 		 */ | 
 | 		if (!rport->suspended) { | 
 | 			/* D- line pull-up, D+ line pull-down */ | 
 | 			dev_dbg(&rport->phy->dev, "FS/LS online\n"); | 
 | 			break; | 
 | 		} | 
 | 		/* fall through */ | 
 | 	case PHY_STATE_CONNECT: | 
 | 		if (rport->suspended) { | 
 | 			dev_dbg(&rport->phy->dev, "Connected\n"); | 
 | 			rockchip_usb2phy_power_on(rport->phy); | 
 | 			rport->suspended = false; | 
 | 		} else { | 
 | 			/* D+ line pull-up, D- line pull-down */ | 
 | 			dev_dbg(&rport->phy->dev, "FS/LS online\n"); | 
 | 		} | 
 | 		break; | 
 | 	case PHY_STATE_DISCONNECT: | 
 | 		if (!rport->suspended) { | 
 | 			dev_dbg(&rport->phy->dev, "Disconnected\n"); | 
 | 			rockchip_usb2phy_power_off(rport->phy); | 
 | 			rport->suspended = true; | 
 | 		} | 
 |  | 
 | 		/* | 
 | 		 * activate the linestate detection to get the next device | 
 | 		 * plug-in irq. | 
 | 		 */ | 
 | 		property_enable(rphy, &rport->port_cfg->ls_det_clr, true); | 
 | 		property_enable(rphy, &rport->port_cfg->ls_det_en, true); | 
 |  | 
 | 		/* | 
 | 		 * we don't need to rearm the delayed work when the phy port | 
 | 		 * is suspended. | 
 | 		 */ | 
 | 		mutex_unlock(&rport->mutex); | 
 | 		return; | 
 | 	default: | 
 | 		dev_dbg(&rport->phy->dev, "unknown phy state\n"); | 
 | 		break; | 
 | 	} | 
 |  | 
 | next_schedule: | 
 | 	mutex_unlock(&rport->mutex); | 
 | 	schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); | 
 | } | 
 |  | 
 | static irqreturn_t rockchip_usb2phy_linestate_irq(int irq, void *data) | 
 | { | 
 | 	struct rockchip_usb2phy_port *rport = data; | 
 | 	struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); | 
 |  | 
 | 	if (!property_enabled(rphy, &rport->port_cfg->ls_det_st)) | 
 | 		return IRQ_NONE; | 
 |  | 
 | 	mutex_lock(&rport->mutex); | 
 |  | 
 | 	/* disable linestate detect irq and clear its status */ | 
 | 	property_enable(rphy, &rport->port_cfg->ls_det_en, false); | 
 | 	property_enable(rphy, &rport->port_cfg->ls_det_clr, true); | 
 |  | 
 | 	mutex_unlock(&rport->mutex); | 
 |  | 
 | 	/* | 
 | 	 * In this case for host phy port, a new device is plugged in, | 
 | 	 * meanwhile, if the phy port is suspended, we need rearm the work to | 
 | 	 * resume it and mange its states; otherwise, we do nothing about that. | 
 | 	 */ | 
 | 	if (rport->suspended && rport->port_id == USB2PHY_PORT_HOST) | 
 | 		rockchip_usb2phy_sm_work(&rport->sm_work.work); | 
 |  | 
 | 	return IRQ_HANDLED; | 
 | } | 
 |  | 
 | static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy, | 
 | 					   struct rockchip_usb2phy_port *rport, | 
 | 					   struct device_node *child_np) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	rport->port_id = USB2PHY_PORT_HOST; | 
 | 	rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST]; | 
 | 	rport->suspended = true; | 
 |  | 
 | 	mutex_init(&rport->mutex); | 
 | 	INIT_DELAYED_WORK(&rport->sm_work, rockchip_usb2phy_sm_work); | 
 |  | 
 | 	rport->ls_irq = of_irq_get_byname(child_np, "linestate"); | 
 | 	if (rport->ls_irq < 0) { | 
 | 		dev_err(rphy->dev, "no linestate irq provided\n"); | 
 | 		return rport->ls_irq; | 
 | 	} | 
 |  | 
 | 	ret = devm_request_threaded_irq(rphy->dev, rport->ls_irq, NULL, | 
 | 					rockchip_usb2phy_linestate_irq, | 
 | 					IRQF_ONESHOT, | 
 | 					"rockchip_usb2phy", rport); | 
 | 	if (ret) { | 
 | 		dev_err(rphy->dev, "failed to request irq handle\n"); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int rockchip_usb2phy_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct device *dev = &pdev->dev; | 
 | 	struct device_node *np = dev->of_node; | 
 | 	struct device_node *child_np; | 
 | 	struct phy_provider *provider; | 
 | 	struct rockchip_usb2phy *rphy; | 
 | 	const struct rockchip_usb2phy_cfg *phy_cfgs; | 
 | 	const struct of_device_id *match; | 
 | 	unsigned int reg; | 
 | 	int index, ret; | 
 |  | 
 | 	rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL); | 
 | 	if (!rphy) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	match = of_match_device(dev->driver->of_match_table, dev); | 
 | 	if (!match || !match->data) { | 
 | 		dev_err(dev, "phy configs are not assigned!\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (!dev->parent || !dev->parent->of_node) | 
 | 		return -EINVAL; | 
 |  | 
 | 	rphy->grf = syscon_node_to_regmap(dev->parent->of_node); | 
 | 	if (IS_ERR(rphy->grf)) | 
 | 		return PTR_ERR(rphy->grf); | 
 |  | 
 | 	if (of_property_read_u32(np, "reg", ®)) { | 
 | 		dev_err(dev, "the reg property is not assigned in %s node\n", | 
 | 			np->name); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	rphy->dev = dev; | 
 | 	phy_cfgs = match->data; | 
 | 	platform_set_drvdata(pdev, rphy); | 
 |  | 
 | 	/* find out a proper config which can be matched with dt. */ | 
 | 	index = 0; | 
 | 	while (phy_cfgs[index].reg) { | 
 | 		if (phy_cfgs[index].reg == reg) { | 
 | 			rphy->phy_cfg = &phy_cfgs[index]; | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		++index; | 
 | 	} | 
 |  | 
 | 	if (!rphy->phy_cfg) { | 
 | 		dev_err(dev, "no phy-config can be matched with %s node\n", | 
 | 			np->name); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	rphy->clk = of_clk_get_by_name(np, "phyclk"); | 
 | 	if (!IS_ERR(rphy->clk)) { | 
 | 		clk_prepare_enable(rphy->clk); | 
 | 	} else { | 
 | 		dev_info(&pdev->dev, "no phyclk specified\n"); | 
 | 		rphy->clk = NULL; | 
 | 	} | 
 |  | 
 | 	ret = rockchip_usb2phy_clk480m_register(rphy); | 
 | 	if (ret) { | 
 | 		dev_err(dev, "failed to register 480m output clock\n"); | 
 | 		goto disable_clks; | 
 | 	} | 
 |  | 
 | 	index = 0; | 
 | 	for_each_available_child_of_node(np, child_np) { | 
 | 		struct rockchip_usb2phy_port *rport = &rphy->ports[index]; | 
 | 		struct phy *phy; | 
 |  | 
 | 		/* | 
 | 		 * This driver aim to support both otg-port and host-port, | 
 | 		 * but unfortunately, the otg part is not ready in current, | 
 | 		 * so this comments and below codes are interim, which should | 
 | 		 * be changed after otg-port is supplied soon. | 
 | 		 */ | 
 | 		if (of_node_cmp(child_np->name, "host-port")) | 
 | 			goto next_child; | 
 |  | 
 | 		phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops); | 
 | 		if (IS_ERR(phy)) { | 
 | 			dev_err(dev, "failed to create phy\n"); | 
 | 			ret = PTR_ERR(phy); | 
 | 			goto put_child; | 
 | 		} | 
 |  | 
 | 		rport->phy = phy; | 
 | 		phy_set_drvdata(rport->phy, rport); | 
 |  | 
 | 		ret = rockchip_usb2phy_host_port_init(rphy, rport, child_np); | 
 | 		if (ret) | 
 | 			goto put_child; | 
 |  | 
 | next_child: | 
 | 		/* to prevent out of boundary */ | 
 | 		if (++index >= rphy->phy_cfg->num_ports) | 
 | 			break; | 
 | 	} | 
 |  | 
 | 	provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | 
 | 	return PTR_ERR_OR_ZERO(provider); | 
 |  | 
 | put_child: | 
 | 	of_node_put(child_np); | 
 | disable_clks: | 
 | 	if (rphy->clk) { | 
 | 		clk_disable_unprepare(rphy->clk); | 
 | 		clk_put(rphy->clk); | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = { | 
 | 	{ | 
 | 		.reg = 0x700, | 
 | 		.num_ports	= 2, | 
 | 		.clkout_ctl	= { 0x0724, 15, 15, 1, 0 }, | 
 | 		.port_cfgs	= { | 
 | 			[USB2PHY_PORT_HOST] = { | 
 | 				.phy_sus	= { 0x0728, 15, 0, 0, 0x1d1 }, | 
 | 				.ls_det_en	= { 0x0680, 4, 4, 0, 1 }, | 
 | 				.ls_det_st	= { 0x0690, 4, 4, 0, 1 }, | 
 | 				.ls_det_clr	= { 0x06a0, 4, 4, 0, 1 }, | 
 | 				.utmi_ls	= { 0x049c, 14, 13, 0, 1 }, | 
 | 				.utmi_hstdet	= { 0x049c, 12, 12, 0, 1 } | 
 | 			} | 
 | 		}, | 
 | 	}, | 
 | 	{ /* sentinel */ } | 
 | }; | 
 |  | 
 | static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { | 
 | 	{ | 
 | 		.reg = 0xe450, | 
 | 		.num_ports	= 2, | 
 | 		.clkout_ctl	= { 0xe450, 4, 4, 1, 0 }, | 
 | 		.port_cfgs	= { | 
 | 			[USB2PHY_PORT_HOST] = { | 
 | 				.phy_sus	= { 0xe458, 1, 0, 0x2, 0x1 }, | 
 | 				.ls_det_en	= { 0xe3c0, 6, 6, 0, 1 }, | 
 | 				.ls_det_st	= { 0xe3e0, 6, 6, 0, 1 }, | 
 | 				.ls_det_clr	= { 0xe3d0, 6, 6, 0, 1 }, | 
 | 				.utmi_ls	= { 0xe2ac, 22, 21, 0, 1 }, | 
 | 				.utmi_hstdet	= { 0xe2ac, 23, 23, 0, 1 } | 
 | 			} | 
 | 		}, | 
 | 	}, | 
 | 	{ | 
 | 		.reg = 0xe460, | 
 | 		.num_ports	= 2, | 
 | 		.clkout_ctl	= { 0xe460, 4, 4, 1, 0 }, | 
 | 		.port_cfgs	= { | 
 | 			[USB2PHY_PORT_HOST] = { | 
 | 				.phy_sus	= { 0xe468, 1, 0, 0x2, 0x1 }, | 
 | 				.ls_det_en	= { 0xe3c0, 11, 11, 0, 1 }, | 
 | 				.ls_det_st	= { 0xe3e0, 11, 11, 0, 1 }, | 
 | 				.ls_det_clr	= { 0xe3d0, 11, 11, 0, 1 }, | 
 | 				.utmi_ls	= { 0xe2ac, 26, 25, 0, 1 }, | 
 | 				.utmi_hstdet	= { 0xe2ac, 27, 27, 0, 1 } | 
 | 			} | 
 | 		}, | 
 | 	}, | 
 | 	{ /* sentinel */ } | 
 | }; | 
 |  | 
 | static const struct of_device_id rockchip_usb2phy_dt_match[] = { | 
 | 	{ .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs }, | 
 | 	{ .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, | 
 | 	{} | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match); | 
 |  | 
 | static struct platform_driver rockchip_usb2phy_driver = { | 
 | 	.probe		= rockchip_usb2phy_probe, | 
 | 	.driver		= { | 
 | 		.name	= "rockchip-usb2phy", | 
 | 		.of_match_table = rockchip_usb2phy_dt_match, | 
 | 	}, | 
 | }; | 
 | module_platform_driver(rockchip_usb2phy_driver); | 
 |  | 
 | MODULE_AUTHOR("Frank Wang <frank.wang@rock-chips.com>"); | 
 | MODULE_DESCRIPTION("Rockchip USB2.0 PHY driver"); | 
 | MODULE_LICENSE("GPL v2"); |