Message ID | 1464850090-99371-3-git-send-email-frank.wang@rock-chips.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Thu, Jun 02, 2016 at 02:48:10PM +0800, Frank Wang wrote: > The newer SoCs (rk3366, rk3399) take a different usb-phy IP block > than rk3288 and before, and most of phy-related registers are also > different from the past, so a new phy driver is required necessarily. > > Signed-off-by: Frank Wang <frank.wang@rock-chips.com> > --- > > Changes in v2: > - Changed vbus_host operation from gpio to regulator in *_probe. > - Improved the fault treatment relate to 480m clock register. > - Cleaned up some meaningless codes in *_clk480m_disable. > - made more clear the comment of *_sm_work. > > drivers/phy/Kconfig | 7 + > drivers/phy/Makefile | 1 + > drivers/phy/phy-rockchip-inno-usb2.c | 604 ++++++++++++++++++++++++++++++++++ > 3 files changed, 612 insertions(+) > create mode 100644 drivers/phy/phy-rockchip-inno-usb2.c > > diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig > index b869b98..29ef15c 100644 > --- a/drivers/phy/Kconfig > +++ b/drivers/phy/Kconfig > @@ -347,6 +347,13 @@ config PHY_ROCKCHIP_USB > help > Enable this to support the Rockchip USB 2.0 PHY. > > +config PHY_ROCKCHIP_INNO_USB2 > + tristate "Rockchip INNO USB2PHY Driver" > + depends on ARCH_ROCKCHIP && OF > + select GENERIC_PHY > + help > + Support for Rockchip USB2.0 PHY with Innosilicon IP block. > + > config PHY_ROCKCHIP_EMMC > tristate "Rockchip EMMC PHY Driver" > depends on ARCH_ROCKCHIP && OF > diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile > index 9c3e73c..4963fbc 100644 > --- a/drivers/phy/Makefile > +++ b/drivers/phy/Makefile > @@ -38,6 +38,7 @@ phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o > obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o > obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o > obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o > +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o > obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o > obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o > obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o > diff --git a/drivers/phy/phy-rockchip-inno-usb2.c b/drivers/phy/phy-rockchip-inno-usb2.c > new file mode 100644 > index 0000000..eca46ff > --- /dev/null > +++ b/drivers/phy/phy-rockchip-inno-usb2.c > @@ -0,0 +1,604 @@ > +/* > + * 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. > + * > + * 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_HS_CONNECT = 2, > + PHY_STATE_FS_CONNECT = 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. > + * @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 num_ports; > + struct usb2phy_reg clkout_ctl; > + const struct rockchip_usb2phy_port_cfg *port_cfgs; > +}; > + > +/** > + * 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. > + * @clk480m: clock struct of phy output clk. > + * @clk_hw: clock struct of phy output clk management. > + * @vbus_host: the regulator for vbus_host supply. > + * @phy_cfg: phy register configuration, assigned by driver data. > + * @ports: phy port instance. > + */ > +struct rockchip_usb2phy { > + struct device *dev; > + struct regmap *grf; > + struct clk *clk480m; > + struct clk_hw clk480m_hw; > + struct regulator *vbus_host; > + 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 = 0; > + > + /* 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 ret; > +} > + > +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 struct clk * > +rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) > +{ > + struct device_node *node = rphy->dev->of_node; > + struct clk *clk; > + struct clk_init_data init; > + int ret; > + > + init.name = "clk_usbphy_480m"; > + init.ops = &rockchip_usb2phy_clkout_ops; > + init.flags = CLK_IS_ROOT; > + init.parent_names = NULL; > + init.num_parents = 0; > + rphy->clk480m_hw.init = &init; > + > + /* optional override of the clockname */ > + of_property_read_string(node, "clock-output-names", &init.name); > + > + /* register the clock */ > + clk = clk_register(rphy->dev, &rphy->clk480m_hw); > + if (IS_ERR(clk)) > + return clk; > + > + ret = of_clk_add_provider(node, of_clk_src_simple_get, clk); > + 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 clk; > + > +err_unreg_action: > + of_clk_del_provider(node); > +err_clk_provider: > + clk_unregister(clk); > + return ERR_PTR(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 = 0; > + > + 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 ret; > +} > + > +static int rockchip_usb2phy_resume(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 resume\n"); > + > + 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_suspend(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 suspend\n"); > + > + 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_resume, > + .power_off = rockchip_usb2phy_suspend, > + .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 > + * FS/HS is disconnect or not. Besides, we do not need care it is FS > + * 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_suspend or *phy_resume 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_CONNECT: > + /* > + * For FS device, the online state share with connect state > + * from utmi_ls and utmi_hstdet register, so we distinguish > + * them via suspended flag. > + */ > + if (!rport->suspended) { > + dev_dbg(&rport->phy->dev, "FS online\n"); > + break; > + } > + /* fall through */ > + case PHY_STATE_HS_CONNECT: > + if (rport->suspended) { > + dev_dbg(&rport->phy->dev, "HS/FS connected\n"); > + rockchip_usb2phy_resume(rport->phy); > + rport->suspended = false; > + } > + break; > + case PHY_STATE_DISCONNECT: > + if (!rport->suspended) { > + dev_dbg(&rport->phy->dev, "HS/FS disconnected\n"); > + rockchip_usb2phy_suspend(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 = 0; > + > + rport->port_id = USB2PHY_PORT_HOST; > + 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 ret; > +} > + > +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 of_device_id *match; > + 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 -ENOMEM; > + > + rphy->grf = syscon_node_to_regmap(dev->parent->of_node); > + if (IS_ERR(rphy->grf)) > + return PTR_ERR(rphy->grf); > + > + rphy->dev = dev; > + rphy->phy_cfg = match->data; > + platform_set_drvdata(pdev, rphy); > + > + rphy->clk480m = rockchip_usb2phy_clk480m_register(rphy); > + if (IS_ERR(rphy->clk480m)) > + return PTR_ERR(rphy->clk480m); > + > + rphy->vbus_host = devm_regulator_get_optional(dev, "vbus_host"); > + if (IS_ERR(rphy->vbus_host)) { > + ret = PTR_ERR(rphy->vbus_host); > + if (ret == -EPROBE_DEFER) > + return ret; > + > + dev_info(dev, "vbus_host regulator is not assigned!\n"); > + rphy->vbus_host = NULL; > + } else { > + ret = regulator_enable(rphy->vbus_host); > + if (ret) > + return ret; > + } > + > + index = 0; > + for_each_child_of_node(np, child_np) { > + struct rockchip_usb2phy_port *rport = &rphy->ports[index]; > + struct phy *phy; > + > + 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; > + rport->port_cfg = &rphy->phy_cfg->port_cfgs[index]; > + > + /* initialize otg/host port separately */ > + if (!of_node_cmp(child_np->name, "host-port")) { > + ret = rockchip_usb2phy_host_port_init(rphy, rport, > + child_np); > + if (ret) > + goto put_child; > + } > + > + phy_set_drvdata(rport->phy, rport); > + index++; I am concerned about the use of 'index', and how it maps to port_cfgs[] and ports[]. If I take the example from the devicetree documentations, the host port would get index==1 amd map to rphy->phy_cfg->port_cfgs[1], which is empty in rk3366_phy_cfgs. If someone specifies dummy devicetree sub-nodes, it would be easy to create references to port_cfgs[2] as well as ports[2] or even higher, and the results would be unpredictable. How about something like the following ? if !of_node_cmp(child_np->name, "host-port")) { rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST]; ret = rockchip_usb2phy_host_port_init(...); ... } phy_set_drvdata(rport->phy, rport); 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); > + return ret; > +} > + > +static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs = { > + .num_ports = 1, > + .clkout_ctl = { 0x0724, 15, 15, 1, 0 }, > + .port_cfgs = (struct rockchip_usb2phy_port_cfg[]) { > + { > + .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 } Assuming this is the host port entry, I am wondering if it would be better to explicitly specify the index. struct rockchip_usb2phy_cfg { unsigned int num_ports; struct usb2phy_reg clkout_ctl; struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS]; }; ... static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs = { .num_ports = 1, .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 } } > + }, > +}; > + > +static const struct of_device_id rockchip_usb2phy_dt_match[] = { > + { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_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");
On Thu, Jun 02, 2016 at 02:48:10PM +0800, Frank Wang wrote: > The newer SoCs (rk3366, rk3399) take a different usb-phy IP block > than rk3288 and before, and most of phy-related registers are also > different from the past, so a new phy driver is required necessarily. > > Signed-off-by: Frank Wang <frank.wang@rock-chips.com> > --- > > Changes in v2: > - Changed vbus_host operation from gpio to regulator in *_probe. > - Improved the fault treatment relate to 480m clock register. > - Cleaned up some meaningless codes in *_clk480m_disable. > - made more clear the comment of *_sm_work. > > drivers/phy/Kconfig | 7 + > drivers/phy/Makefile | 1 + > drivers/phy/phy-rockchip-inno-usb2.c | 604 ++++++++++++++++++++++++++++++++++ > 3 files changed, 612 insertions(+) > create mode 100644 drivers/phy/phy-rockchip-inno-usb2.c > > diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig > index b869b98..29ef15c 100644 > --- a/drivers/phy/Kconfig > +++ b/drivers/phy/Kconfig > @@ -347,6 +347,13 @@ config PHY_ROCKCHIP_USB > help > Enable this to support the Rockchip USB 2.0 PHY. > > +config PHY_ROCKCHIP_INNO_USB2 > + tristate "Rockchip INNO USB2PHY Driver" > + depends on ARCH_ROCKCHIP && OF > + select GENERIC_PHY > + help > + Support for Rockchip USB2.0 PHY with Innosilicon IP block. > + > config PHY_ROCKCHIP_EMMC > tristate "Rockchip EMMC PHY Driver" > depends on ARCH_ROCKCHIP && OF > diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile > index 9c3e73c..4963fbc 100644 > --- a/drivers/phy/Makefile > +++ b/drivers/phy/Makefile > @@ -38,6 +38,7 @@ phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o > obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o > obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o > obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o > +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o > obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o > obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o > obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o > diff --git a/drivers/phy/phy-rockchip-inno-usb2.c b/drivers/phy/phy-rockchip-inno-usb2.c > new file mode 100644 > index 0000000..eca46ff > --- /dev/null > +++ b/drivers/phy/phy-rockchip-inno-usb2.c > @@ -0,0 +1,604 @@ > +/* > + * 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. > + * > + * 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_HS_CONNECT = 2, > + PHY_STATE_FS_CONNECT = 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. > + * @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 num_ports; > + struct usb2phy_reg clkout_ctl; > + const struct rockchip_usb2phy_port_cfg *port_cfgs; > +}; > + > +/** > + * 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. > + * @clk480m: clock struct of phy output clk. > + * @clk_hw: clock struct of phy output clk management. > + * @vbus_host: the regulator for vbus_host supply. > + * @phy_cfg: phy register configuration, assigned by driver data. > + * @ports: phy port instance. > + */ > +struct rockchip_usb2phy { > + struct device *dev; > + struct regmap *grf; > + struct clk *clk480m; > + struct clk_hw clk480m_hw; > + struct regulator *vbus_host; > + 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 = 0; > + > + /* 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 ret; > +} > + > +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 struct clk * > +rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) > +{ > + struct device_node *node = rphy->dev->of_node; > + struct clk *clk; > + struct clk_init_data init; > + int ret; > + > + init.name = "clk_usbphy_480m"; > + init.ops = &rockchip_usb2phy_clkout_ops; > + init.flags = CLK_IS_ROOT; One more comment: include/linux/clk-provider.h:#define CLK_IS_ROOT BIT(4) /* Deprecated: Don't use */ ... and the definition has been removed in linux-next. Guenter
Am Freitag, 3. Juni 2016, 12:59:22 schrieb Guenter Roeck: > On Thu, Jun 02, 2016 at 02:48:10PM +0800, Frank Wang wrote: > > The newer SoCs (rk3366, rk3399) take a different usb-phy IP block > > than rk3288 and before, and most of phy-related registers are also > > different from the past, so a new phy driver is required necessarily. > > > > Signed-off-by: Frank Wang <frank.wang@rock-chips.com> > > --- > > > > Changes in v2: > > - Changed vbus_host operation from gpio to regulator in *_probe. > > - Improved the fault treatment relate to 480m clock register. > > - Cleaned up some meaningless codes in *_clk480m_disable. > > - made more clear the comment of *_sm_work. > > > > drivers/phy/Kconfig | 7 + > > drivers/phy/Makefile | 1 + > > drivers/phy/phy-rockchip-inno-usb2.c | 604 > > ++++++++++++++++++++++++++++++++++ 3 files changed, 612 insertions(+) > > create mode 100644 drivers/phy/phy-rockchip-inno-usb2.c > > > > diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig > > index b869b98..29ef15c 100644 > > --- a/drivers/phy/Kconfig > > +++ b/drivers/phy/Kconfig > > @@ -347,6 +347,13 @@ config PHY_ROCKCHIP_USB > > > > help > > > > Enable this to support the Rockchip USB 2.0 PHY. > > > > +config PHY_ROCKCHIP_INNO_USB2 > > + tristate "Rockchip INNO USB2PHY Driver" > > + depends on ARCH_ROCKCHIP && OF > > + select GENERIC_PHY > > + help > > + Support for Rockchip USB2.0 PHY with Innosilicon IP block. > > + > > > > config PHY_ROCKCHIP_EMMC > > > > tristate "Rockchip EMMC PHY Driver" > > depends on ARCH_ROCKCHIP && OF > > > > diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile > > index 9c3e73c..4963fbc 100644 > > --- a/drivers/phy/Makefile > > +++ b/drivers/phy/Makefile > > @@ -38,6 +38,7 @@ phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += > > phy-s5pv210-usb2.o> > > obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o > > obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o > > obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o > > > > +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o > > > > obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o > > obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o > > obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o > > > > diff --git a/drivers/phy/phy-rockchip-inno-usb2.c > > b/drivers/phy/phy-rockchip-inno-usb2.c new file mode 100644 > > index 0000000..eca46ff > > --- /dev/null > > +++ b/drivers/phy/phy-rockchip-inno-usb2.c > > @@ -0,0 +1,604 @@ > > +/* > > + * 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. > > + * > > + * 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_HS_CONNECT = 2, > > + PHY_STATE_FS_CONNECT = 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. > > + * @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 num_ports; > > + struct usb2phy_reg clkout_ctl; > > + const struct rockchip_usb2phy_port_cfg *port_cfgs; > > +}; > > + > > +/** > > + * 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. > > + * @clk480m: clock struct of phy output clk. > > + * @clk_hw: clock struct of phy output clk management. > > + * @vbus_host: the regulator for vbus_host supply. > > + * @phy_cfg: phy register configuration, assigned by driver data. > > + * @ports: phy port instance. > > + */ > > +struct rockchip_usb2phy { > > + struct device *dev; > > + struct regmap *grf; > > + struct clk *clk480m; > > + struct clk_hw clk480m_hw; > > + struct regulator *vbus_host; > > + 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 = 0; > > + > > + /* 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 ret; > > +} > > + > > +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 struct clk * > > +rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) > > +{ > > + struct device_node *node = rphy->dev->of_node; > > + struct clk *clk; > > + struct clk_init_data init; > > + int ret; > > + > > + init.name = "clk_usbphy_480m"; > > + init.ops = &rockchip_usb2phy_clkout_ops; > > + init.flags = CLK_IS_ROOT; > > One more comment: > > include/linux/clk-provider.h:#define CLK_IS_ROOT BIT(4) /* > Deprecated: Don't use */ > > ... and the definition has been removed in linux-next. also, the created clock should be a child of the phy-supply clock. Please take a look at the picophy-variant (phy-rockchip-usb) that already handles that parent assignment. Heiko
Hi Guenter, On 2016/6/4 3:57, Guenter Roeck wrote: > On Thu, Jun 02, 2016 at 02:48:10PM +0800, Frank Wang wrote: >> The newer SoCs (rk3366, rk3399) take a different usb-phy IP block >> than rk3288 and before, and most of phy-related registers are also >> different from the past, so a new phy driver is required necessarily. >> >> Signed-off-by: Frank Wang <frank.wang@rock-chips.com> >> --- >> >> Changes in v2: >> - Changed vbus_host operation from gpio to regulator in *_probe. >> - Improved the fault treatment relate to 480m clock register. >> - Cleaned up some meaningless codes in *_clk480m_disable. >> - made more clear the comment of *_sm_work. >> >> drivers/phy/Kconfig | 7 + >> drivers/phy/Makefile | 1 + >> drivers/phy/phy-rockchip-inno-usb2.c | 604 ++++++++++++++++++++++++++++++++++ >> 3 files changed, 612 insertions(+) >> create mode 100644 drivers/phy/phy-rockchip-inno-usb2.c >> >> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig >> index b869b98..29ef15c 100644 >> --- a/drivers/phy/Kconfig >> +++ b/drivers/phy/Kconfig >> @@ -347,6 +347,13 @@ config PHY_ROCKCHIP_USB >> help >> Enable this to support the Rockchip USB 2.0 PHY. >> >> +config PHY_ROCKCHIP_INNO_USB2 >> + tristate "Rockchip INNO USB2PHY Driver" >> + depends on ARCH_ROCKCHIP && OF >> + select GENERIC_PHY >> + help >> + Support for Rockchip USB2.0 PHY with Innosilicon IP block. >> + >> config PHY_ROCKCHIP_EMMC >> tristate "Rockchip EMMC PHY Driver" >> depends on ARCH_ROCKCHIP && OF >> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile >> index 9c3e73c..4963fbc 100644 >> --- a/drivers/phy/Makefile >> +++ b/drivers/phy/Makefile >> @@ -38,6 +38,7 @@ phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o >> obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o >> obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o >> obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o >> +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o >> obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o >> obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o >> obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o >> diff --git a/drivers/phy/phy-rockchip-inno-usb2.c b/drivers/phy/phy-rockchip-inno-usb2.c >> new file mode 100644 >> index 0000000..eca46ff >> --- /dev/null >> +++ b/drivers/phy/phy-rockchip-inno-usb2.c >> @@ -0,0 +1,604 @@ >> +/* >> + * 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. >> + * >> + * 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_HS_CONNECT = 2, >> + PHY_STATE_FS_CONNECT = 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. >> + * @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 num_ports; >> + struct usb2phy_reg clkout_ctl; >> + const struct rockchip_usb2phy_port_cfg *port_cfgs; >> +}; >> + >> +/** >> + * 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. >> + * @clk480m: clock struct of phy output clk. >> + * @clk_hw: clock struct of phy output clk management. >> + * @vbus_host: the regulator for vbus_host supply. >> + * @phy_cfg: phy register configuration, assigned by driver data. >> + * @ports: phy port instance. >> + */ >> +struct rockchip_usb2phy { >> + struct device *dev; >> + struct regmap *grf; >> + struct clk *clk480m; >> + struct clk_hw clk480m_hw; >> + struct regulator *vbus_host; >> + 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 = 0; >> + >> + /* 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 ret; >> +} >> + >> +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 struct clk * >> +rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) >> +{ >> + struct device_node *node = rphy->dev->of_node; >> + struct clk *clk; >> + struct clk_init_data init; >> + int ret; >> + >> + init.name = "clk_usbphy_480m"; >> + init.ops = &rockchip_usb2phy_clkout_ops; >> + init.flags = CLK_IS_ROOT; >> + init.parent_names = NULL; >> + init.num_parents = 0; >> + rphy->clk480m_hw.init = &init; >> + >> + /* optional override of the clockname */ >> + of_property_read_string(node, "clock-output-names", &init.name); >> + >> + /* register the clock */ >> + clk = clk_register(rphy->dev, &rphy->clk480m_hw); >> + if (IS_ERR(clk)) >> + return clk; >> + >> + ret = of_clk_add_provider(node, of_clk_src_simple_get, clk); >> + 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 clk; >> + >> +err_unreg_action: >> + of_clk_del_provider(node); >> +err_clk_provider: >> + clk_unregister(clk); >> + return ERR_PTR(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 = 0; >> + >> + 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 ret; >> +} >> + >> +static int rockchip_usb2phy_resume(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 resume\n"); >> + >> + 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_suspend(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 suspend\n"); >> + >> + 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_resume, >> + .power_off = rockchip_usb2phy_suspend, >> + .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 >> + * FS/HS is disconnect or not. Besides, we do not need care it is FS >> + * 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_suspend or *phy_resume 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_CONNECT: >> + /* >> + * For FS device, the online state share with connect state >> + * from utmi_ls and utmi_hstdet register, so we distinguish >> + * them via suspended flag. >> + */ >> + if (!rport->suspended) { >> + dev_dbg(&rport->phy->dev, "FS online\n"); >> + break; >> + } >> + /* fall through */ >> + case PHY_STATE_HS_CONNECT: >> + if (rport->suspended) { >> + dev_dbg(&rport->phy->dev, "HS/FS connected\n"); >> + rockchip_usb2phy_resume(rport->phy); >> + rport->suspended = false; >> + } >> + break; >> + case PHY_STATE_DISCONNECT: >> + if (!rport->suspended) { >> + dev_dbg(&rport->phy->dev, "HS/FS disconnected\n"); >> + rockchip_usb2phy_suspend(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 = 0; >> + >> + rport->port_id = USB2PHY_PORT_HOST; >> + 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 ret; >> +} >> + >> +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 of_device_id *match; >> + 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 -ENOMEM; >> + >> + rphy->grf = syscon_node_to_regmap(dev->parent->of_node); >> + if (IS_ERR(rphy->grf)) >> + return PTR_ERR(rphy->grf); >> + >> + rphy->dev = dev; >> + rphy->phy_cfg = match->data; >> + platform_set_drvdata(pdev, rphy); >> + >> + rphy->clk480m = rockchip_usb2phy_clk480m_register(rphy); >> + if (IS_ERR(rphy->clk480m)) >> + return PTR_ERR(rphy->clk480m); >> + >> + rphy->vbus_host = devm_regulator_get_optional(dev, "vbus_host"); >> + if (IS_ERR(rphy->vbus_host)) { >> + ret = PTR_ERR(rphy->vbus_host); >> + if (ret == -EPROBE_DEFER) >> + return ret; >> + >> + dev_info(dev, "vbus_host regulator is not assigned!\n"); >> + rphy->vbus_host = NULL; >> + } else { >> + ret = regulator_enable(rphy->vbus_host); >> + if (ret) >> + return ret; >> + } >> + >> + index = 0; >> + for_each_child_of_node(np, child_np) { >> + struct rockchip_usb2phy_port *rport = &rphy->ports[index]; >> + struct phy *phy; >> + >> + 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; >> + rport->port_cfg = &rphy->phy_cfg->port_cfgs[index]; >> + >> + /* initialize otg/host port separately */ >> + if (!of_node_cmp(child_np->name, "host-port")) { >> + ret = rockchip_usb2phy_host_port_init(rphy, rport, >> + child_np); >> + if (ret) >> + goto put_child; >> + } >> + >> + phy_set_drvdata(rport->phy, rport); >> + index++; > I am concerned about the use of 'index', and how it maps to port_cfgs[] > and ports[]. If I take the example from the devicetree documentations, > the host port would get index==1 amd map to rphy->phy_cfg->port_cfgs[1], > which is empty in rk3366_phy_cfgs. If someone specifies dummy devicetree > sub-nodes, it would be easy to create references to port_cfgs[2] as well > as ports[2] or even higher, and the results would be unpredictable. > > How about something like the following ? > > if !of_node_cmp(child_np->name, "host-port")) { > rport->port_cfg = > &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST]; > ret = rockchip_usb2phy_host_port_init(...); > ... > } > > phy_set_drvdata(rport->phy, rport); > if (++index >= rphy->phy_cfg->num_ports) > break; Well, good comments! I am going to correct and test, if it is practicable, I will add them in patch v3. >> + } >> + >> + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); >> + return PTR_ERR_OR_ZERO(provider); >> + >> +put_child: >> + of_node_put(child_np); >> + return ret; >> +} >> + >> +static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs = { >> + .num_ports = 1, >> + .clkout_ctl = { 0x0724, 15, 15, 1, 0 }, >> + .port_cfgs = (struct rockchip_usb2phy_port_cfg[]) { >> + { >> + .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 } > Assuming this is the host port entry, I am wondering if it would be better > to explicitly specify the index. > > struct rockchip_usb2phy_cfg { > unsigned int num_ports; > struct usb2phy_reg clkout_ctl; > struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS]; > }; > > ... > > static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs = { > .num_ports = 1, > .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 } > } Yeah, as mentioned above, I will try together. >> + }, >> +}; >> + >> +static const struct of_device_id rockchip_usb2phy_dt_match[] = { >> + { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_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"); >
Hi Heiko & Guenter, On 2016/6/4 5:24, Heiko Stübner wrote: > Am Freitag, 3. Juni 2016, 12:59:22 schrieb Guenter Roeck: >> On Thu, Jun 02, 2016 at 02:48:10PM +0800, Frank Wang wrote: >>> The newer SoCs (rk3366, rk3399) take a different usb-phy IP block >>> than rk3288 and before, and most of phy-related registers are also >>> different from the past, so a new phy driver is required necessarily. >>> >>> Signed-off-by: Frank Wang <frank.wang@rock-chips.com> >>> --- >>> >>> Changes in v2: >>> - Changed vbus_host operation from gpio to regulator in *_probe. >>> - Improved the fault treatment relate to 480m clock register. >>> - Cleaned up some meaningless codes in *_clk480m_disable. >>> - made more clear the comment of *_sm_work. >>> >>> [...] >>> >>> +static struct clk * >>> +rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) >>> +{ >>> + struct device_node *node = rphy->dev->of_node; >>> + struct clk *clk; >>> + struct clk_init_data init; >>> + int ret; >>> + >>> + init.name = "clk_usbphy_480m"; >>> + init.ops = &rockchip_usb2phy_clkout_ops; >>> + init.flags = CLK_IS_ROOT; >> One more comment: >> >> include/linux/clk-provider.h:#define CLK_IS_ROOT BIT(4) /* >> Deprecated: Don't use */ >> >> ... and the definition has been removed in linux-next. > also, the created clock should be a child of the phy-supply clock. Please take > a look at the picophy-variant (phy-rockchip-usb) that already handles that > parent assignment. > Okay, I will refer and correct them in the next patch (patch v3) . BR. Frank
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index b869b98..29ef15c 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -347,6 +347,13 @@ config PHY_ROCKCHIP_USB help Enable this to support the Rockchip USB 2.0 PHY. +config PHY_ROCKCHIP_INNO_USB2 + tristate "Rockchip INNO USB2PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Support for Rockchip USB2.0 PHY with Innosilicon IP block. + config PHY_ROCKCHIP_EMMC tristate "Rockchip EMMC PHY Driver" depends on ARCH_ROCKCHIP && OF diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 9c3e73c..4963fbc 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -38,6 +38,7 @@ phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o diff --git a/drivers/phy/phy-rockchip-inno-usb2.c b/drivers/phy/phy-rockchip-inno-usb2.c new file mode 100644 index 0000000..eca46ff --- /dev/null +++ b/drivers/phy/phy-rockchip-inno-usb2.c @@ -0,0 +1,604 @@ +/* + * 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. + * + * 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_HS_CONNECT = 2, + PHY_STATE_FS_CONNECT = 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. + * @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 num_ports; + struct usb2phy_reg clkout_ctl; + const struct rockchip_usb2phy_port_cfg *port_cfgs; +}; + +/** + * 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. + * @clk480m: clock struct of phy output clk. + * @clk_hw: clock struct of phy output clk management. + * @vbus_host: the regulator for vbus_host supply. + * @phy_cfg: phy register configuration, assigned by driver data. + * @ports: phy port instance. + */ +struct rockchip_usb2phy { + struct device *dev; + struct regmap *grf; + struct clk *clk480m; + struct clk_hw clk480m_hw; + struct regulator *vbus_host; + 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 = 0; + + /* 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 ret; +} + +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 struct clk * +rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) +{ + struct device_node *node = rphy->dev->of_node; + struct clk *clk; + struct clk_init_data init; + int ret; + + init.name = "clk_usbphy_480m"; + init.ops = &rockchip_usb2phy_clkout_ops; + init.flags = CLK_IS_ROOT; + init.parent_names = NULL; + init.num_parents = 0; + rphy->clk480m_hw.init = &init; + + /* optional override of the clockname */ + of_property_read_string(node, "clock-output-names", &init.name); + + /* register the clock */ + clk = clk_register(rphy->dev, &rphy->clk480m_hw); + if (IS_ERR(clk)) + return clk; + + ret = of_clk_add_provider(node, of_clk_src_simple_get, clk); + 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 clk; + +err_unreg_action: + of_clk_del_provider(node); +err_clk_provider: + clk_unregister(clk); + return ERR_PTR(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 = 0; + + 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 ret; +} + +static int rockchip_usb2phy_resume(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 resume\n"); + + 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_suspend(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 suspend\n"); + + 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_resume, + .power_off = rockchip_usb2phy_suspend, + .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 + * FS/HS is disconnect or not. Besides, we do not need care it is FS + * 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_suspend or *phy_resume 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_CONNECT: + /* + * For FS device, the online state share with connect state + * from utmi_ls and utmi_hstdet register, so we distinguish + * them via suspended flag. + */ + if (!rport->suspended) { + dev_dbg(&rport->phy->dev, "FS online\n"); + break; + } + /* fall through */ + case PHY_STATE_HS_CONNECT: + if (rport->suspended) { + dev_dbg(&rport->phy->dev, "HS/FS connected\n"); + rockchip_usb2phy_resume(rport->phy); + rport->suspended = false; + } + break; + case PHY_STATE_DISCONNECT: + if (!rport->suspended) { + dev_dbg(&rport->phy->dev, "HS/FS disconnected\n"); + rockchip_usb2phy_suspend(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 = 0; + + rport->port_id = USB2PHY_PORT_HOST; + 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 ret; +} + +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 of_device_id *match; + 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 -ENOMEM; + + rphy->grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(rphy->grf)) + return PTR_ERR(rphy->grf); + + rphy->dev = dev; + rphy->phy_cfg = match->data; + platform_set_drvdata(pdev, rphy); + + rphy->clk480m = rockchip_usb2phy_clk480m_register(rphy); + if (IS_ERR(rphy->clk480m)) + return PTR_ERR(rphy->clk480m); + + rphy->vbus_host = devm_regulator_get_optional(dev, "vbus_host"); + if (IS_ERR(rphy->vbus_host)) { + ret = PTR_ERR(rphy->vbus_host); + if (ret == -EPROBE_DEFER) + return ret; + + dev_info(dev, "vbus_host regulator is not assigned!\n"); + rphy->vbus_host = NULL; + } else { + ret = regulator_enable(rphy->vbus_host); + if (ret) + return ret; + } + + index = 0; + for_each_child_of_node(np, child_np) { + struct rockchip_usb2phy_port *rport = &rphy->ports[index]; + struct phy *phy; + + 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; + rport->port_cfg = &rphy->phy_cfg->port_cfgs[index]; + + /* initialize otg/host port separately */ + if (!of_node_cmp(child_np->name, "host-port")) { + ret = rockchip_usb2phy_host_port_init(rphy, rport, + child_np); + if (ret) + goto put_child; + } + + phy_set_drvdata(rport->phy, rport); + index++; + } + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(provider); + +put_child: + of_node_put(child_np); + return ret; +} + +static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs = { + .num_ports = 1, + .clkout_ctl = { 0x0724, 15, 15, 1, 0 }, + .port_cfgs = (struct rockchip_usb2phy_port_cfg[]) { + { + .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 of_device_id rockchip_usb2phy_dt_match[] = { + { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_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");
The newer SoCs (rk3366, rk3399) take a different usb-phy IP block than rk3288 and before, and most of phy-related registers are also different from the past, so a new phy driver is required necessarily. Signed-off-by: Frank Wang <frank.wang@rock-chips.com> --- Changes in v2: - Changed vbus_host operation from gpio to regulator in *_probe. - Improved the fault treatment relate to 480m clock register. - Cleaned up some meaningless codes in *_clk480m_disable. - made more clear the comment of *_sm_work. drivers/phy/Kconfig | 7 + drivers/phy/Makefile | 1 + drivers/phy/phy-rockchip-inno-usb2.c | 604 ++++++++++++++++++++++++++++++++++ 3 files changed, 612 insertions(+) create mode 100644 drivers/phy/phy-rockchip-inno-usb2.c