From 9e3c5ad64f83fcd9452d5e5dc1c639d1ec02ca3a Mon Sep 17 00:00:00 2001 From: Jianing Ren Date: Mon, 20 Jan 2020 17:06:06 +0800 Subject: [PATCH] phy: add usb 2.0 phy driver for Rockchip SoCs This patch implements a usb 2.0 phy driver for Rockchip SoCs with Naneng IP block. Change-Id: I2658ce7c77a4bef60c8ab183a687d81468a512f1 Signed-off-by: Jianing Ren --- .../phy/rockchip/phy-rockchip-naneng-usb2.c | 1613 +++++++++++++++++ 1 file changed, 1613 insertions(+) create mode 100644 drivers/phy/rockchip/phy-rockchip-naneng-usb2.c diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-usb2.c b/drivers/phy/rockchip/phy-rockchip-naneng-usb2.c new file mode 100644 index 000000000000..92e2cb87ba86 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-naneng-usb2.c @@ -0,0 +1,1613 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Rockchip USB2.0 PHY with Naneng IP block driver + * + * Copyright (C) 2020 Fuzhou Rockchip Electronics Co., Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct rockchip_usb2phy; + +#define BIT_WRITEABLE_SHIFT 16 +#define OTG_SCHEDULE_DELAY (1 * HZ) + +enum rockchip_usb2phy_port_id { + USB2PHY_PORT_OTG, + USB2PHY_PORT_HOST, + USB2PHY_NUM_PORTS, +}; + +static const unsigned int rockchip_usb2phy_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_USB_VBUS_EN, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, + EXTCON_NONE, +}; + +struct usb2phy_reg { + unsigned int offset; + unsigned int bitend; + unsigned int bitstart; + unsigned int disable; + unsigned int enable; +}; + +/** + * struct rockchip_chg_det_reg: usb charger detect registers + * @chg_en: charge detector enable signal. + * @chg_rst: charge detector reset signal, active high. + * @chg_valid: charge valid signal. + * @phy_connect: PHY start handshake signal. + */ +struct rockchip_chg_det_reg { + struct usb2phy_reg chg_en; + struct usb2phy_reg chg_rst; + struct usb2phy_reg chg_valid; + struct usb2phy_reg phy_connect; +}; + +/** + * struct rockchip_usb2phy_port_cfg: usb phy port configuration. + * @bypass_otgsuspendm: otg-suspendm bypass control register. + * 0: iddig; 1: grf. + * @bvalidfall_det_en: vbus valid fall detection enable register. + * @bvalidfall_det_st: vbus valid fall detection status register. + * @bvalidfall_det_clr: vbus valid fall detection clear register. + * @bvalidrise_det_en: vbus valid rise detection enable register. + * @bvalidrise_det_st: vbus valid rise detection status register. + * @bvalidrise_det_clr: vbus valid rise detection clear register. + * @disconfall_det_en: host connect detection enable register. + * @disconfall_det_st: host connect detection status register. + * @disconfall_det_clr: host connect detection clear register. + * @disconrise_det_en: host disconnect detection enable register. + * @disconrise_det_st: host disconnect detection status register. + * @disconrise_det_clr: host disconnect detection clear register. + * @idfall_det_en: id fall detection enable register. + * @idfall_det_st: id fall detection state register. + * @idfall_det_clr: id fall detection clear register. + * @idpullup: id pin pullup or pulldown control register. + * @idrise_det_en: id rise detection enable register. + * @idrise_det_st: id rise detection state register. + * @idrise_det_clr: id rise detection clear register. + * @ls_det_en: linestate detection enable register. + * @ls_det_st: linestate detection state register. + * @ls_det_clr: linestate detection clear register. + * @phy_sus: phy suspend register. + * @utmi_bvalid: utmi vbus bvalid status register. + * @utmi_iddig: otg port id pin status register. + * @utmi_hostdet: utmi host disconnect status register. + */ +struct rockchip_usb2phy_port_cfg { + struct usb2phy_reg bypass_otgsuspendm; + struct usb2phy_reg bvalidfall_det_en; + struct usb2phy_reg bvalidfall_det_st; + struct usb2phy_reg bvalidfall_det_clr; + struct usb2phy_reg bvalidrise_det_en; + struct usb2phy_reg bvalidrise_det_st; + struct usb2phy_reg bvalidrise_det_clr; + struct usb2phy_reg disconfall_det_en; + struct usb2phy_reg disconfall_det_st; + struct usb2phy_reg disconfall_det_clr; + struct usb2phy_reg disconrise_det_en; + struct usb2phy_reg disconrise_det_st; + struct usb2phy_reg disconrise_det_clr; + struct usb2phy_reg idfall_det_en; + struct usb2phy_reg idfall_det_st; + struct usb2phy_reg idfall_det_clr; + struct usb2phy_reg idpullup; + struct usb2phy_reg idrise_det_en; + struct usb2phy_reg idrise_det_st; + struct usb2phy_reg idrise_det_clr; + struct usb2phy_reg ls_det_en; + struct usb2phy_reg ls_det_st; + struct usb2phy_reg ls_det_clr; + struct usb2phy_reg phy_sus; + struct usb2phy_reg utmi_bvalid; + struct usb2phy_reg utmi_iddig; + struct usb2phy_reg utmi_hostdet; +}; + +/** + * 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. + * @clks: array of input clocks + * @num_clks: number of input clocks. + * @phy_tuning: phy default parameters tuning. + * @clkout_ctl: keep on/turn off output clk of phy. + * @port_cfgs: ports register configuration, assigned by driver data. + * @chg_det: charger detection registers. + * @last: indicate the last one. + */ +struct rockchip_usb2phy_cfg { + unsigned int reg; + unsigned int num_ports; + const struct clk_bulk_data *clks; + int num_clks; + int (*phy_tuning)(struct rockchip_usb2phy *rphy); + struct usb2phy_reg clkout_ctl; + const struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS]; + const struct rockchip_chg_det_reg chg_det; + bool last; +}; + +/** + * struct rockchip_usb2phy_port: usb phy port data. + * @phy: the struct phy of this port. + * @port_id: flag for otg port or host port. + * @perip_connected: flag for periphyeral connect status. + * @prev_iddig: previous otg port id pin status. + * @suspended: phy suspended flag. + * @vbus_attached: otg device vbus status. + * @vbus_always_on: otg vbus is always powered on. + * @vbus_enabled: vbus regulator status. + * @bvalid_irq: IRQ number assigned for vbus valid rise detection. + * @ls_irq: IRQ number assigned for linestate detection. + * @disconnect_irq: IRQ number assigned for host disconnect detection. + * @id_irq: IRQ number assigned for id fall or rise detection. + * @mutex: for register updating in interrupt thread. + * @otg_sm_work: OTG periphreal connect or disconnect judgement. + * @vbus: vbus regulator supply on few rockchip boards. + * @port_cfg: port register configuration, assigned by driver data. + * @wakelock: wakeup source for otg-port. + * @mode: the dr_mode of the controller. + */ +struct rockchip_usb2phy_port { + struct phy *phy; + unsigned int port_id; + bool perip_connected; + bool prev_iddig; + bool suspended; + bool vbus_attached; + bool vbus_always_on; + bool vbus_enabled; + int bvalid_irq; + int ls_irq; + int disconnect_irq; + int id_irq; + struct mutex mutex; /* protects register of phy */ + struct delayed_work otg_sm_work; + struct regulator *vbus; + const struct rockchip_usb2phy_port_cfg *port_cfg; + struct wake_lock wakelock; + enum usb_dr_mode mode; +}; + +/** + * struct rockchip_usb2phy: usb2.0 phy driver data. + * @dev: pointer to our struct device + * @grf: General Register Files regmap. + * @base: the base address of APB interface. + * @reset: power reset signal for phy. + * @clks: array of input clocks. + * @num_clks: number of input clocks. + * @clk480m: clock struct of phy output clk. + * @clk480m_hw: clock struct of phy output clk management. + * @chg_type: USB charger types. + * @edev_self: represent the source of extcon. + * @edev: extcon device for notification registration + * @phy_cfg: phy register configuration, assigned by driver data. + * @ports: phy port instance. + */ +struct rockchip_usb2phy { + struct device *dev; + struct regmap *grf; + void __iomem *base; + struct reset_control *reset; + struct clk_bulk_data *clks; + int num_clks; + struct clk *clk480m; + struct clk_hw clk480m_hw; + enum power_supply_type chg_type; + bool edev_self; + struct extcon_dev *edev; + const struct rockchip_usb2phy_cfg *phy_cfg; + struct rockchip_usb2phy_port ports[USB2PHY_NUM_PORTS]; +}; + +static inline int property_enable(struct regmap *base, + 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(base, reg->offset, val); +} + +static inline bool property_enabled(struct regmap *base, + const struct usb2phy_reg *reg) +{ + int ret; + unsigned int tmp, orig; + unsigned int mask = GENMASK(reg->bitend, reg->bitstart); + + ret = regmap_read(base, reg->offset, &orig); + if (ret) + return false; + + tmp = (orig & mask) >> reg->bitstart; + return tmp == reg->enable; +} + +static int rockchip_usb2phy_clk480m_prepare(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->grf, &rphy->phy_cfg->clkout_ctl)) { + ret = property_enable(rphy->grf, &rphy->phy_cfg->clkout_ctl, + true); + if (ret) + return ret; + + /* waiting for the clk become stable */ + usleep_range(500, 600); + } + + return 0; +} + +static void rockchip_usb2phy_clk480m_unprepare(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + + /* turn off 480m clk output */ + property_enable(rphy->grf, &rphy->phy_cfg->clkout_ctl, false); +} + +static int rockchip_usb2phy_clk480m_prepared(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + + return property_enabled(rphy->grf, &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 = { + .prepare = rockchip_usb2phy_clk480m_prepare, + .unprepare = rockchip_usb2phy_clk480m_unprepare, + .is_prepared = rockchip_usb2phy_clk480m_prepared, + .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; + struct clk *refclk = of_clk_get_by_name(node, "phyclk"); + 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 (refclk) { + clk_name = __clk_get_name(refclk); + 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; +} + +/* The caller must hold rport->mutex lock */ +static int rockchip_usb2phy_enable_id_irq(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + bool en) +{ + int ret; + + ret = property_enable(rphy->grf, + &rport->port_cfg->idfall_det_clr, true); + if (ret) + goto out; + + ret = property_enable(rphy->grf, &rport->port_cfg->idfall_det_en, en); + if (ret) + goto out; + + ret = property_enable(rphy->grf, + &rport->port_cfg->idrise_det_clr, true); + if (ret) + goto out; + + ret = property_enable(rphy->grf, &rport->port_cfg->idrise_det_en, en); +out: + return ret; +} + +/* The caller must hold rport->mutex lock */ +static int rockchip_usb2phy_enable_vbus_irq(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + bool en) +{ + int ret; + + ret = property_enable(rphy->grf, + &rport->port_cfg->bvalidfall_det_clr, true); + if (ret) + goto out; + + ret = property_enable(rphy->grf, + &rport->port_cfg->bvalidfall_det_en, en); + if (ret) + goto out; + + ret = property_enable(rphy->grf, + &rport->port_cfg->bvalidrise_det_clr, true); + if (ret) + goto out; + + ret = property_enable(rphy->grf, + &rport->port_cfg->bvalidrise_det_en, en); +out: + return ret; +} + +static int rockchip_usb2phy_enable_line_irq(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + bool en) +{ + int ret; + + ret = property_enable(rphy->grf, &rport->port_cfg->ls_det_clr, true); + if (ret) + goto out; + + ret = property_enable(rphy->grf, &rport->port_cfg->ls_det_en, en); +out: + return ret; +} + +static int + rockchip_usb2phy_enable_disconn_irq(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + bool en) +{ + int ret; + + ret = property_enable(rphy->grf, + &rport->port_cfg->disconrise_det_clr, true); + if (ret) + goto out; + + ret = property_enable(rphy->grf, + &rport->port_cfg->disconrise_det_en, en); +out: + return ret; +} + +static int rockchip_usb2phy_extcon_register(struct rockchip_usb2phy *rphy) +{ + int ret; + struct device_node *node = rphy->dev->of_node; + struct extcon_dev *edev; + + if (of_property_read_bool(node, "extcon")) { + edev = extcon_get_edev_by_phandle(rphy->dev, 0); + if (IS_ERR(edev)) { + if (PTR_ERR(edev) != -EPROBE_DEFER) + dev_err(rphy->dev, + "Invalid or missing extcon\n"); + return PTR_ERR(edev); + } + } else { + /* Initialize extcon device */ + edev = devm_extcon_dev_allocate(rphy->dev, + rockchip_usb2phy_extcon_cable); + + if (IS_ERR(edev)) + return -ENOMEM; + + ret = devm_extcon_dev_register(rphy->dev, edev); + if (ret) { + dev_err(rphy->dev, + "failed to register extcon device\n"); + return ret; + } + + rphy->edev_self = true; + } + + rphy->edev = edev; + + return 0; +} + +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; + + mutex_lock(&rport->mutex); + + /* clear disconnect status and enable disconnect detect irq */ + if (rport->disconnect_irq > 0) { + ret = rockchip_usb2phy_enable_disconn_irq(rphy, rport, true); + if (ret) { + dev_err(rphy->dev, "failed to enable disconnect irq\n"); + goto out; + } + } + + /* clear linstate status and enable linestate detect irq */ + if (rport->ls_irq > 0 && + (rport->port_id == USB2PHY_PORT_HOST || + rport->mode == USB_DR_MODE_HOST)) { + ret = rockchip_usb2phy_enable_line_irq(rphy, rport, true); + if (ret) { + dev_err(rphy->dev, "failed to enable linestate irq\n"); + goto out; + } + } + + /* clear bvalid status and enable bvalid detect irq */ + if (rport->bvalid_irq > 0) { + ret = rockchip_usb2phy_enable_vbus_irq(rphy, rport, true); + if (ret) { + dev_err(rphy->dev, + "failed to enable bvalid irq\n"); + goto out; + } + + rport->vbus_attached = + property_enabled(rphy->grf, + &rport->port_cfg->utmi_bvalid); + schedule_delayed_work(&rport->otg_sm_work, OTG_SCHEDULE_DELAY); + } + + /* clear id status and enable id detect irq */ + if (rport->id_irq > 0) { + ret = rockchip_usb2phy_enable_id_irq(rphy, rport, + true); + if (ret) { + dev_err(rphy->dev, + "failed to enable id irq\n"); + goto out; + } + } + +out: + mutex_unlock(&rport->mutex); + return ret; +} + +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"); + + mutex_lock(&rport->mutex); + + if (!rport->suspended) { + ret = 0; + goto unlock; + } + + ret = clk_bulk_enable(rphy->num_clks, rphy->clks); + if (ret) + goto unlock; + + ret = property_enable(rphy->grf, &rport->port_cfg->phy_sus, false); + if (ret) + goto disable_clks; + + /* waiting for the utmi_clk to become stable */ + usleep_range(2500, 3000); + + rport->suspended = false; + + goto unlock; + +disable_clks: + clk_bulk_disable(rphy->num_clks, rphy->clks); +unlock: + mutex_unlock(&rport->mutex); + + return ret; +} + +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"); + + mutex_lock(&rport->mutex); + + if (rport->suspended) { + ret = 0; + goto unlock; + } + + ret = property_enable(rphy->grf, &rport->port_cfg->phy_sus, true); + if (ret) + goto unlock; + + clk_bulk_disable(rphy->num_clks, rphy->clks); + + rport->suspended = true; + +unlock: + mutex_unlock(&rport->mutex); + + return ret; +} + +static int rockchip_usb2phy_exit(struct phy *phy) +{ + return 0; +} + +static int rockchip_set_vbus_power(struct rockchip_usb2phy_port *rport, + bool en) +{ + int ret = 0; + + if (!rport->vbus) + return 0; + + if (en && !rport->vbus_enabled) { + ret = regulator_enable(rport->vbus); + if (ret) + dev_err(&rport->phy->dev, + "Failed to enable VBUS supply\n"); + } else if (!en && rport->vbus_enabled) { + ret = regulator_disable(rport->vbus); + } + + if (ret == 0) + rport->vbus_enabled = en; + + return ret; +} + +static int rockchip_usb2phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + 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_OTG) + return ret; + + switch (mode) { + case PHY_MODE_USB_OTG: + /* fallthrough */ + case PHY_MODE_USB_DEVICE: + /* Disable VBUS supply */ + rockchip_set_vbus_power(rport, false); + extcon_set_state_sync(rphy->edev, EXTCON_USB_VBUS_EN, false); + break; + case PHY_MODE_USB_HOST: + /* Enable VBUS supply */ + ret = rockchip_set_vbus_power(rport, true); + if (ret) { + dev_err(&rport->phy->dev, + "Failed to set host mode\n"); + return ret; + } + + extcon_set_state_sync(rphy->edev, EXTCON_USB_VBUS_EN, true); + break; + default: + dev_info(&rport->phy->dev, "illegal mode\n"); + return ret; + } + + return ret; +} + +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, + .set_mode = rockchip_usb2phy_set_mode, + .owner = THIS_MODULE, +}; + +static const char *chg_to_string(enum power_supply_type chg_type) +{ + switch (chg_type) { + case POWER_SUPPLY_TYPE_USB: + return "USB_SDP_CHARGER"; + case POWER_SUPPLY_TYPE_USB_DCP: + return "USB_DCP_CHARGER"; + case POWER_SUPPLY_TYPE_USB_CDP: + return "USB_CDP_CHARGER"; + default: + return "INVALID_CHARGER"; + } +} + +static void rockchip_chg_detect(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport) +{ + bool chg_valid, phy_connect; + int result; + int cnt; + + mutex_lock(&rport->mutex); + + reset_control_assert(rphy->reset); + + /* CHG_RST is set to 1'b0 to start charge detection */ + property_enable(rphy->grf, &rphy->phy_cfg->chg_det.chg_en, true); + property_enable(rphy->grf, &rphy->phy_cfg->chg_det.chg_rst, false); + + for (cnt = 0; cnt < 12; cnt++) { + msleep(100); + + chg_valid = property_enabled(rphy->grf, + &rphy->phy_cfg->chg_det.chg_valid); + phy_connect = + property_enabled(rphy->grf, + &rphy->phy_cfg->chg_det.phy_connect); + result = (chg_valid << 1) | phy_connect; + + if (result) + break; + } + + switch (result) { + case 1: + rphy->chg_type = POWER_SUPPLY_TYPE_USB; + break; + case 2: + rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP; + break; + case 3: + rphy->chg_type = POWER_SUPPLY_TYPE_USB_CDP; + break; + case 0: + /* fall through */ + default: + rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; + break; + } + + dev_info(&rport->phy->dev, "charger = %s\n", + chg_to_string(rphy->chg_type)); + + usleep_range(1000, 1100); + reset_control_deassert(rphy->reset); + /* disable the chg detection module */ + property_enable(rphy->grf, &rphy->phy_cfg->chg_det.chg_rst, true); + property_enable(rphy->grf, &rphy->phy_cfg->chg_det.chg_en, false); + + mutex_unlock(&rport->mutex); +} + +static irqreturn_t rockchip_usb2phy_disconnect_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->grf, &rport->port_cfg->disconrise_det_st)) + return IRQ_NONE; + + mutex_lock(&rport->mutex); + + /* clear disconnect rise detect irq pending status */ + property_enable(rphy->grf, &rport->port_cfg->disconrise_det_clr, true); + + mutex_unlock(&rport->mutex); + + /* prevent fs/ls device disconnect before enumeration */ + msleep(200); + if (!property_enabled(rphy->grf, &rport->port_cfg->utmi_hostdet)) + return IRQ_HANDLED; + + mutex_lock(&rport->mutex); + + /* enable linestate detect irq to detect next host connect */ + rockchip_usb2phy_enable_line_irq(rphy, rport, true); + + mutex_unlock(&rport->mutex); + + dev_dbg(&rport->phy->dev, "host disconnected\n"); + rockchip_usb2phy_power_off(rport->phy); + + return IRQ_HANDLED; +} + +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->grf, &rport->port_cfg->ls_det_st)) + return IRQ_NONE; + + dev_dbg(&rport->phy->dev, "linestate interrupt\n"); + + mutex_lock(&rport->mutex); + + /* disable linestate detect irq and clear its status */ + rockchip_usb2phy_enable_line_irq(rphy, rport, false); + + mutex_unlock(&rport->mutex); + + if (!rport->suspended) + return IRQ_HANDLED; + + if (rport->port_id != USB2PHY_PORT_HOST && + rport->mode != USB_DR_MODE_HOST) + return IRQ_HANDLED; + + dev_dbg(&rport->phy->dev, "host connected\n"); + rockchip_usb2phy_power_on(rport->phy); + + return IRQ_HANDLED; +} + +static void rockchip_usb2phy_otg_sm_work(struct work_struct *work) +{ + static unsigned int cable; + struct rockchip_usb2phy_port *rport = + container_of(work, struct rockchip_usb2phy_port, + otg_sm_work.work); + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + + if (rport->vbus_attached) { + if (extcon_get_state(rphy->edev, EXTCON_USB_HOST) || + extcon_get_state(rphy->edev, EXTCON_USB_VBUS_EN) || + !property_enabled(rphy->grf, &rport->port_cfg->utmi_iddig)) + return; + + if (rport->perip_connected) + return; + + if (clk_bulk_enable(rphy->num_clks, rphy->clks)) + return; + + /* waiting for the utmi_clk to become stable */ + usleep_range(2500, 3000); + + rockchip_chg_detect(rphy, rport); + + clk_bulk_disable(rphy->num_clks, rphy->clks); + + switch (rphy->chg_type) { + case POWER_SUPPLY_TYPE_USB: + dev_dbg(&rport->phy->dev, "sdp cable is connected\n"); + wake_lock(&rport->wakelock); + cable = EXTCON_CHG_USB_SDP; + rport->perip_connected = true; + break; + case POWER_SUPPLY_TYPE_USB_DCP: + dev_dbg(&rport->phy->dev, "dcp cable is connected\n"); + cable = EXTCON_CHG_USB_DCP; + break; + case POWER_SUPPLY_TYPE_USB_CDP: + dev_dbg(&rport->phy->dev, "cdp cable is connected\n"); + wake_lock(&rport->wakelock); + cable = EXTCON_CHG_USB_CDP; + rport->perip_connected = true; + break; + default: + break; + } + } else { + if (!rport->perip_connected) + return; + + dev_dbg(&rport->phy->dev, "usb peripheral disconnect\n"); + wake_unlock(&rport->wakelock); + rport->perip_connected = false; + rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; + } + + if (extcon_get_state(rphy->edev, cable) != rport->vbus_attached) { + extcon_set_state(rphy->edev, cable, rport->vbus_attached); + extcon_sync(rphy->edev, cable); + } + + if (rphy->edev_self && (extcon_get_state(rphy->edev, EXTCON_USB) != + rport->perip_connected)) { + extcon_set_state(rphy->edev, EXTCON_USB, + rport->perip_connected); + + extcon_sync(rphy->edev, EXTCON_USB); + extcon_sync(rphy->edev, EXTCON_USB_HOST); + } +} + +static irqreturn_t rockchip_usb2phy_bvalid_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->grf, &rport->port_cfg->bvalidfall_det_st) && + !property_enabled(rphy->grf, &rport->port_cfg->bvalidrise_det_st)) + return IRQ_NONE; + + mutex_lock(&rport->mutex); + + /* clear bvalid fall or rise detect irq pending status */ + if (property_enabled(rphy->grf, &rport->port_cfg->bvalidfall_det_st)) { + property_enable(rphy->grf, &rport->port_cfg->bvalidfall_det_clr, + true); + rport->vbus_attached = false; + } else if (property_enabled(rphy->grf, + &rport->port_cfg->bvalidrise_det_st)) { + property_enable(rphy->grf, &rport->port_cfg->bvalidrise_det_clr, + true); + rport->vbus_attached = true; + } + + mutex_unlock(&rport->mutex); + + cancel_delayed_work_sync(&rport->otg_sm_work); + rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); + + return IRQ_HANDLED; +} + +static irqreturn_t rockchip_usb2phy_id_irq(int irq, void *data) +{ + struct rockchip_usb2phy_port *rport = data; + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + bool cable_vbus_state = false; + + if (!property_enabled(rphy->grf, &rport->port_cfg->idfall_det_st) && + !property_enabled(rphy->grf, &rport->port_cfg->idrise_det_st)) + return IRQ_NONE; + + mutex_lock(&rport->mutex); + + /* clear id fall or rise detect irq pending status */ + if (property_enabled(rphy->grf, &rport->port_cfg->idfall_det_st)) { + property_enable(rphy->grf, &rport->port_cfg->idfall_det_clr, + true); + cable_vbus_state = true; + } else if (property_enabled(rphy->grf, + &rport->port_cfg->idrise_det_st)) { + property_enable(rphy->grf, &rport->port_cfg->idrise_det_clr, + true); + cable_vbus_state = false; + } + + dev_dbg(&rport->phy->dev, "id %s interrupt\n", + cable_vbus_state ? "fall" : "rise"); + extcon_set_state(rphy->edev, EXTCON_USB_HOST, cable_vbus_state); + extcon_set_state(rphy->edev, EXTCON_USB_VBUS_EN, cable_vbus_state); + + extcon_sync(rphy->edev, EXTCON_USB_HOST); + extcon_sync(rphy->edev, EXTCON_USB_VBUS_EN); + + rockchip_set_vbus_power(rport, cable_vbus_state); + + mutex_unlock(&rport->mutex); + + return IRQ_HANDLED; +} + +static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + struct device_node *child_np) +{ + int ret = 0; + int iddig; + + mutex_init(&rport->mutex); + + rport->port_id = USB2PHY_PORT_OTG; + rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_OTG]; + rport->vbus_attached = false; + rport->vbus_enabled = false; + rport->prev_iddig = true; + + rport->vbus_always_on = + of_property_read_bool(child_np, "rockchip,vbus-always-on"); + + ret = rockchip_usb2phy_extcon_register(rphy); + if (ret) + return ret; + + /* Get Vbus regulators */ + rport->vbus = devm_regulator_get_optional(&rport->phy->dev, "vbus"); + if (IS_ERR(rport->vbus)) { + if (PTR_ERR(rport->vbus) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + dev_warn(&rport->phy->dev, + "Failed to get VBUS supply regulator\n"); + rport->vbus = NULL; + } + + /* + * The default value of bypass_otgsuspendm is 1 that we must set + * otg_suspendm and LS_PAR_EN by software when switching drd role. + * So we disable the otg_suspend_bypass to let hardware auto-switch + * device mode or host mode. + */ + property_enable(rphy->grf, &rport->port_cfg->bypass_otgsuspendm, + false); + + /* Request linstate interrupt */ + 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 -EINVAL; + } + + 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 linestate irq handle\n"); + return ret; + } + + rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1); + if (rport->mode == USB_DR_MODE_HOST) { + if (rphy->edev_self) { + extcon_set_state(rphy->edev, EXTCON_USB, false); + extcon_set_state(rphy->edev, EXTCON_USB_HOST, true); + } + rockchip_usb2phy_set_mode(rport->phy, PHY_MODE_USB_HOST); + /* + * Here set iddig to 0 by disable idpullup, the otg_suspendm + * will be set to 1 to enable the disconnect detection module, + * and the LS_PAR_EN will be set to 1 to enable low speed device + * enumerate. + */ + property_enable(rphy->grf, &rport->port_cfg->idpullup, false); + + /* Request disconnect interrupt */ + rport->disconnect_irq = of_irq_get_byname(child_np, + "disconnect"); + if (rport->disconnect_irq <= 0) { + dev_err(rphy->dev, "no disconnect irq provided\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(rphy->dev, + rport->disconnect_irq, NULL, + rockchip_usb2phy_disconnect_irq, + IRQF_ONESHOT, + "rockchip_usb2phy", rport); + if (ret) { + dev_err(rphy->dev, + "failed to request disconnect irq handle\n"); + return ret; + } + goto out; + } + + if (rport->vbus_always_on) + goto out; + + /* Request otg bvalid interrupt */ + rport->bvalid_irq = of_irq_get_byname(child_np, "otg-bvalid"); + if (rport->bvalid_irq <= 0) { + dev_err(rphy->dev, "no vbus valid irq provided\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(rphy->dev, rport->bvalid_irq, + NULL, + rockchip_usb2phy_bvalid_irq, + IRQF_ONESHOT, + "rockchip_usb2phy_bvalid", + rport); + if (ret) { + dev_err(rphy->dev, + "failed to request otg-bvalid irq handle\n"); + return ret; + } + + INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work); + + if (!rphy->edev_self) + goto out; + + /* Request otg iddig interrupt only if there is no extcon property */ + rport->id_irq = of_irq_get_byname(child_np, "otg-id"); + if (rport->id_irq <= 0) { + dev_err(rphy->dev, "no otg id irq provided\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(rphy->dev, + rport->id_irq, NULL, + rockchip_usb2phy_id_irq, + IRQF_ONESHOT, + "rockchip_usb2phy_id", + rport); + if (ret) { + dev_err(rphy->dev, + "failed to request otg-id irq handle\n"); + return ret; + } + + iddig = property_enabled(rphy->grf, + &rport->port_cfg->utmi_iddig); + if (!iddig) { + extcon_set_state(rphy->edev, EXTCON_USB, false); + extcon_set_state(rphy->edev, EXTCON_USB_HOST, true); + extcon_set_state(rphy->edev, EXTCON_USB_VBUS_EN, true); + /* Enable VBUS supply */ + ret = rockchip_set_vbus_power(rport, true); + if (ret) + return ret; + } + +out: + /* + * Let us put phy-port into suspend mode here for saving power + * consumption, and usb controller will resume it during probe + * time if needed. + */ + ret = property_enable(rphy->grf, &rport->port_cfg->phy_sus, true); + if (ret) + return ret; + + rport->suspended = true; + + wake_lock_init(&rport->wakelock, WAKE_LOCK_SUSPEND, "rockchip_otg"); + + return ret; +} + +static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + struct device_node *child_np) +{ + int ret = 0; + + mutex_init(&rport->mutex); + + rport->port_id = USB2PHY_PORT_HOST; + rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST]; + + /* Request disconnect interrupt */ + rport->disconnect_irq = of_irq_get_byname(child_np, "disconnect"); + if (rport->disconnect_irq <= 0) { + dev_err(rphy->dev, "no disconnect irq provided\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(rphy->dev, rport->disconnect_irq, NULL, + rockchip_usb2phy_disconnect_irq, + IRQF_ONESHOT, + "rockchip_usb2phy", rport); + if (ret) { + dev_err(rphy->dev, "failed to request disconnect irq handle\n"); + return ret; + } + + /* Request linstate interrupt */ + 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 -EINVAL; + } + + 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 linestate irq handle\n"); + return ret; + } + + /* + * Let us put phy-port into suspend mode here for saving power + * consumption, and usb controller will resume it during probe + * time if needed. + */ + ret = property_enable(rphy->grf, &rport->port_cfg->phy_sus, true); + if (ret) + return ret; + + rport->suspended = true; + + 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 rockchip_usb2phy_cfg *phy_cfgs; + const struct of_device_id *match; + unsigned int reg; + unsigned int index; + struct resource *res; + int ret = 0; + + rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL); + if (!rphy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing memory resource\n"); + return -ENODEV; + } + + rphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(rphy->base)) { + dev_err(rphy->dev, "failed to remap phy regs\n"); + return PTR_ERR(rphy->base); + } + + rphy->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(rphy->grf)) + return PTR_ERR(rphy->grf); + + /* Get PHY power reset */ + rphy->reset = devm_reset_control_get(dev, "u2phy"); + if (IS_ERR(rphy->reset)) + return PTR_ERR(rphy->reset); + + reset_control_assert(rphy->reset); + udelay(1); + reset_control_deassert(rphy->reset); + + 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 (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; + + /* find out a proper config which can be matched with dt. */ + index = 0; + do { + if (phy_cfgs[index].reg == reg) { + rphy->phy_cfg = &phy_cfgs[index]; + break; + } + } while (!phy_cfgs[index++].last); + + if (!rphy->phy_cfg) { + dev_err(dev, "no phy-config can be matched with %s node\n", + np->name); + return -EINVAL; + } + + rphy->num_clks = rphy->phy_cfg->num_clks; + + rphy->clks = devm_kmemdup(dev, rphy->phy_cfg->clks, + rphy->num_clks * sizeof(struct clk_bulk_data), + GFP_KERNEL); + + if (!rphy->clks) + return -ENOMEM; + + ret = devm_clk_bulk_get(dev, rphy->num_clks, rphy->clks); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (ret) + rphy->num_clks = 0; + + ret = clk_bulk_prepare_enable(rphy->num_clks, rphy->clks); + if (ret) + return ret; + + platform_set_drvdata(pdev, rphy); + + if (rphy->phy_cfg->phy_tuning) { + ret = rphy->phy_cfg->phy_tuning(rphy); + if (ret) + 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 aims to support both otg-port and host-port */ + if (of_node_cmp(child_np->name, "host-port") && + of_node_cmp(child_np->name, "otg-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); + + /* 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; + } else { + ret = rockchip_usb2phy_otg_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); + if (IS_ERR_OR_NULL(provider)) + goto put_child; + + ret = rockchip_usb2phy_clk480m_register(rphy); + if (ret) { + dev_err(dev, "failed to register 480m output clock\n"); + goto put_child; + } + + if (of_property_read_bool(np, "wakeup-source")) + device_init_wakeup(rphy->dev, true); + else + device_init_wakeup(rphy->dev, false); + + /* + * Let us disable clks here for saving power consumption, and usb + * controller will resume it during probe time if needed. + */ + clk_bulk_disable(rphy->num_clks, rphy->clks); + return 0; + +put_child: + of_node_put(child_np); +disable_clks: + clk_bulk_disable_unprepare(rphy->num_clks, rphy->clks); + + return ret; +} + +static int rockchip_usb2phy_remove(struct platform_device *pdev) +{ + return 0; +} + +static int rv1126_usb2phy_tuning(struct rockchip_usb2phy *rphy) +{ + int ret = 0; + + /* set iddig interrupt filter time to 10ms */ + ret = regmap_write(rphy->grf, 0x1031c, 0x000f4240); + if (ret) + goto out; + +out: + return ret; +} + +static const struct clk_bulk_data rv1126_clks[] = { + { .id = "phyclk" }, + { .id = "pclk" }, +}; + +#ifdef CONFIG_PM_SLEEP +static int rockchip_usb2phy_pm_suspend(struct device *dev) +{ + int ret = 0; + struct rockchip_usb2phy *rphy = dev_get_drvdata(dev); + struct rockchip_usb2phy_port *rport; + unsigned int index; + bool wakeup_enable = false; + + if (device_may_wakeup(rphy->dev)) + wakeup_enable = true; + + for (index = 0; index < rphy->phy_cfg->num_ports; index++) { + rport = &rphy->ports[index]; + if (!rport->phy) + continue; + + if (rport->port_id == USB2PHY_PORT_OTG && + rport->id_irq > 0) { + mutex_lock(&rport->mutex); + rport->prev_iddig = + property_enabled(rphy->grf, + &rport->port_cfg->utmi_iddig); + ret = rockchip_usb2phy_enable_id_irq(rphy, rport, + false); + mutex_unlock(&rport->mutex); + if (ret) { + dev_err(rphy->dev, + "failed to disable id irq\n"); + return ret; + } + } + + if (rport->port_id == USB2PHY_PORT_OTG && wakeup_enable && + rport->bvalid_irq > 0) + enable_irq_wake(rport->bvalid_irq); + + /* activate the linestate to detect the remove wakeup. */ + mutex_lock(&rport->mutex); + ret = rockchip_usb2phy_enable_line_irq(rphy, rport, true); + mutex_unlock(&rport->mutex); + if (ret) { + dev_err(rphy->dev, "failed to enable linestate irq\n"); + return ret; + } + + if (wakeup_enable && rport->ls_irq > 0) + enable_irq_wake(rport->ls_irq); + } + + return ret; +} + +static int rockchip_usb2phy_pm_resume(struct device *dev) +{ + int ret = 0; + struct rockchip_usb2phy *rphy = dev_get_drvdata(dev); + struct rockchip_usb2phy_port *rport; + unsigned int index; + bool iddig; + bool wakeup_enable = false; + + if (device_may_wakeup(rphy->dev)) + wakeup_enable = true; + + if (rphy->phy_cfg->phy_tuning) + ret = rphy->phy_cfg->phy_tuning(rphy); + + for (index = 0; index < rphy->phy_cfg->num_ports; index++) { + rport = &rphy->ports[index]; + if (!rport->phy) + continue; + + if (rport->port_id == USB2PHY_PORT_OTG && + rport->id_irq > 0) { + mutex_lock(&rport->mutex); + iddig = property_enabled(rphy->grf, + &rport->port_cfg->utmi_iddig); + ret = rockchip_usb2phy_enable_id_irq(rphy, rport, + true); + mutex_unlock(&rport->mutex); + if (ret) { + dev_err(rphy->dev, + "failed to enable id irq\n"); + return ret; + } + + if (iddig != rport->prev_iddig) { + dev_dbg(&rport->phy->dev, + "iddig changed during resume\n"); + rport->prev_iddig = iddig; + extcon_set_state_sync(rphy->edev, + EXTCON_USB_HOST, + !iddig); + extcon_set_state_sync(rphy->edev, + EXTCON_USB_VBUS_EN, + !iddig); + ret = rockchip_set_vbus_power(rport, !iddig); + if (ret) + return ret; + } + } + + if (rport->port_id == USB2PHY_PORT_OTG && wakeup_enable && + rport->bvalid_irq > 0) + disable_irq_wake(rport->bvalid_irq); + + if (wakeup_enable && rport->ls_irq > 0) + disable_irq_wake(rport->ls_irq); + } + + return ret; +} + +static const struct dev_pm_ops rockchip_usb2phy_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rockchip_usb2phy_pm_suspend, + rockchip_usb2phy_pm_resume) +}; + +#define ROCKCHIP_USB2PHY_DEV_PM (&rockchip_usb2phy_dev_pm_ops) +#else +#define ROCKCHIP_USB2PHY_DEV_PM NULL +#endif + +static const struct rockchip_usb2phy_cfg rv1126_phy_cfgs[] = { + { + .reg = 0xff4c0000, + .num_ports = 1, + .phy_tuning = rv1126_usb2phy_tuning, + .num_clks = 2, + .clks = rv1126_clks, + .clkout_ctl = { 0x10230, 14, 14, 0, 1 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .bypass_otgsuspendm = { 0x10234, 12, 12, 0, 1 }, + .bvalidfall_det_en = { 0x10300, 3, 3, 0, 1 }, + .bvalidfall_det_st = { 0x10304, 3, 3, 0, 1 }, + .bvalidfall_det_clr = { 0x10308, 3, 3, 0, 1 }, + .bvalidrise_det_en = { 0x10300, 2, 2, 0, 1 }, + .bvalidrise_det_st = { 0x10304, 2, 2, 0, 1 }, + .bvalidrise_det_clr = { 0x10308, 2, 2, 0, 1 }, + .disconfall_det_en = { 0x10300, 7, 7, 0, 1 }, + .disconfall_det_st = { 0x10304, 7, 7, 0, 1 }, + .disconfall_det_clr = { 0x10308, 7, 7, 0, 1 }, + .disconrise_det_en = { 0x10300, 6, 6, 0, 1 }, + .disconrise_det_st = { 0x10304, 6, 6, 0, 1 }, + .disconrise_det_clr = { 0x10308, 6, 6, 0, 1 }, + .idfall_det_en = { 0x10300, 5, 5, 0, 1 }, + .idfall_det_st = { 0x10304, 5, 5, 0, 1 }, + .idfall_det_clr = { 0x10308, 5, 5, 0, 1 }, + .idpullup = { 0x10230, 11, 11, 0, 1 }, + .idrise_det_en = { 0x10300, 4, 4, 0, 1 }, + .idrise_det_st = { 0x10304, 4, 4, 0, 1 }, + .idrise_det_clr = { 0x10308, 4, 4, 0, 1 }, + .ls_det_en = { 0x10300, 0, 0, 0, 1 }, + .ls_det_st = { 0x10304, 0, 0, 0, 1 }, + .ls_det_clr = { 0x10308, 0, 0, 0, 1 }, + .phy_sus = { 0x10230, 8, 0, 0x052, 0x1d9 }, + .utmi_bvalid = { 0x10248, 9, 9, 0, 1 }, + .utmi_iddig = { 0x10248, 6, 6, 0, 1 }, + .utmi_hostdet = { 0x10248, 7, 7, 0, 1 }, + } + }, + .chg_det = { + .chg_en = { 0x10234, 14, 14, 0, 1 }, + .chg_rst = { 0x10234, 15, 15, 0, 1 }, + .chg_valid = { 0x10248, 12, 12, 0, 1 }, + .phy_connect = { 0x10248, 13, 13, 0, 1 }, + }, + }, + { + .reg = 0xff4c8000, + .num_ports = 1, + .phy_tuning = rv1126_usb2phy_tuning, + .num_clks = 2, + .clks = rv1126_clks, + .clkout_ctl = { 0x10238, 9, 9, 0, 1 }, + .port_cfgs = { + [USB2PHY_PORT_HOST] = { + .disconfall_det_en = { 0x10300, 9, 9, 0, 1 }, + .disconfall_det_st = { 0x10304, 9, 9, 0, 1 }, + .disconfall_det_clr = { 0x10308, 9, 9, 0, 1 }, + .disconrise_det_en = { 0x10300, 8, 8, 0, 1 }, + .disconrise_det_st = { 0x10304, 8, 8, 0, 1 }, + .disconrise_det_clr = { 0x10308, 8, 8, 0, 1 }, + .ls_det_en = { 0x10300, 1, 1, 0, 1 }, + .ls_det_st = { 0x10304, 1, 1, 0, 1 }, + .ls_det_clr = { 0x10308, 1, 1, 0, 1 }, + .phy_sus = { 0x10238, 3, 0, 0x2, 0x9 }, + .utmi_hostdet = { 0x10248, 23, 23, 0, 1 }, + } + }, + .chg_det = { + .chg_en = { 0x10238, 7, 7, 0, 1 }, + .chg_rst = { 0x10238, 8, 8, 0, 1 }, + .chg_valid = { 0x10248, 28, 28, 0, 1 }, + .phy_connect = { 0x10248, 29, 29, 0, 1 }, + }, + .last = true, + }, +}; + +static const struct of_device_id rockchip_usb2phy_dt_match[] = { + { .compatible = "rockchip,rv1126-usb2phy", .data = &rv1126_phy_cfgs }, + {} +}; +MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match); + +static struct platform_driver rockchip_usb2phy_driver = { + .probe = rockchip_usb2phy_probe, + .remove = rockchip_usb2phy_remove, + .driver = { + .name = "rockchip-usb2phy-naneng", + .pm = ROCKCHIP_USB2PHY_DEV_PM, + .of_match_table = rockchip_usb2phy_dt_match, + }, +}; +module_platform_driver(rockchip_usb2phy_driver); + +MODULE_AUTHOR("Jianing Ren "); +MODULE_DESCRIPTION("Rockchip USB2.0 Naneng PHY driver"); +MODULE_LICENSE("GPL v2");