From: Hai Li Date: Fri, 15 May 2015 17:04:06 +0000 (-0400) Subject: drm/msm/dsi: Separate PHY to another platform device X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=ec31abf6684ebe1134eb3320c96fb92e566eff74;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git drm/msm/dsi: Separate PHY to another platform device There are different types of PHY from one chipset to another, while the DSI host controller is relatively consistent across platforms. Also, the PLL inside PHY is providing the source of DSI byte and pixel clocks, which are used by DSI host controller. Separated devices for clock provider and clock consumer make DSI driver better fit into common clock framework. Signed-off-by: Hai Li Signed-off-by: Rob Clark --- diff --git a/Documentation/devicetree/bindings/drm/msm/dsi.txt b/Documentation/devicetree/bindings/drm/msm/dsi.txt index ff8aebb2f8f8..cd8fe6cf536c 100644 --- a/Documentation/devicetree/bindings/drm/msm/dsi.txt +++ b/Documentation/devicetree/bindings/drm/msm/dsi.txt @@ -1,15 +1,12 @@ Qualcomm Technologies Inc. adreno/snapdragon DSI output +DSI Controller: Required properties: - compatible: * "qcom,mdss-dsi-ctrl" -- reg: Physical base address and length of the registers of controller, PLL, - PHY and PHY regulator +- reg: Physical base address and length of the registers of controller - reg-names: The names of register regions. The following regions are required: * "dsi_ctrl" - * "dsi_pll" - * "dsi_phy" - * "dsi_phy_regulator" - qcom,dsi-host-index: The ID of DSI controller hardware instance. This should be 0 or 1, since we have 2 DSI controllers at most for now. - interrupts: The interrupt signal from the DSI block. @@ -24,10 +21,10 @@ Required properties: * "iface_clk" * "mdp_core_clk" * "pixel_clk" -- #clock-cells: The value should be 1. - vdd-supply: phandle to vdd regulator device node - vddio-supply: phandle to vdd-io regulator device node - vdda-supply: phandle to vdda regulator device node +- qcom,dsi-phy: phandle to DSI PHY device node Optional properties: - panel@0: Node of panel connected to this DSI controller. @@ -42,22 +39,34 @@ Optional properties: - interrupt-parent: phandle to the MDP block if the interrupt signal is routed through MDP block +DSI PHY: +Required properties: +- compatible: Could be the following + * "qcom,dsi-phy-28nm-hpm" + * "qcom,dsi-phy-28nm-lp" +- reg: Physical base address and length of the registers of PLL, PHY and PHY + regulator +- reg-names: The names of register regions. The following regions are required: + * "dsi_pll" + * "dsi_phy" + * "dsi_phy_regulator" +- qcom,dsi-phy-index: The ID of DSI PHY hardware instance. This should + be 0 or 1, since we have 2 DSI PHYs at most for now. +- power-domains: Should be <&mmcc MDSS_GDSC>. +- clocks: device clocks + See Documentation/devicetree/bindings/clocks/clock-bindings.txt for details. +- clock-names: the following clocks are required: + * "iface_clk" +- vddio-supply: phandle to vdd-io regulator device node + Example: mdss_dsi0: qcom,mdss_dsi@fd922800 { compatible = "qcom,mdss-dsi-ctrl"; qcom,dsi-host-index = <0>; interrupt-parent = <&mdss_mdp>; interrupts = <4 0>; - reg-names = - "dsi_ctrl", - "dsi_pll", - "dsi_phy", - "dsi_phy_regulator", - reg = <0xfd922800 0x200>, - <0xfd922a00 0xd4>, - <0xfd922b00 0x2b0>, - <0xfd922d80 0x7b>, - <0xfd828000 0x108>; + reg-names = "dsi_ctrl"; + reg = <0xfd922800 0x200>; power-domains = <&mmcc MDSS_GDSC>; clock-names = "bus_clk", @@ -75,11 +84,12 @@ Example: <&mmcc MDSS_AHB_CLK>, <&mmcc MDSS_MDP_CLK>, <&mmcc MDSS_PCLK0_CLK>; - #clock-cells = <1>; vdda-supply = <&pma8084_l2>; vdd-supply = <&pma8084_l22>; vddio-supply = <&pma8084_l12>; + qcom,dsi-phy = <&mdss_dsi_phy0>; + qcom,dual-panel-mode; qcom,master-panel; qcom,sync-dual-panel; @@ -93,3 +103,18 @@ Example: backlight = <...>; }; }; + + mdss_dsi_phy0: qcom,mdss_dsi_phy@fd922a00 { + compatible = "qcom,dsi-phy-28nm-hpm"; + qcom,dsi-phy-index = <0>; + reg-names = + "dsi_pll", + "dsi_phy", + "dsi_phy_regulator"; + reg = <0xfd922a00 0xd4>, + <0xfd922b00 0x2b0>, + <0xfd922d80 0x7b>; + clock-names = "iface_clk"; + clocks = <&mmcc MDSS_AHB_CLK>; + vddio-supply = <&pma8084_l12>; + }; diff --git a/drivers/gpu/drm/msm/dsi/dsi.c b/drivers/gpu/drm/msm/dsi/dsi.c index beb26dfca276..1f2561e2ff71 100644 --- a/drivers/gpu/drm/msm/dsi/dsi.c +++ b/drivers/gpu/drm/msm/dsi/dsi.c @@ -23,6 +23,34 @@ struct drm_encoder *msm_dsi_get_encoder(struct msm_dsi *msm_dsi) msm_dsi->encoders[MSM_DSI_CMD_ENCODER_ID]; } +static int dsi_get_phy(struct msm_dsi *msm_dsi) +{ + struct platform_device *pdev = msm_dsi->pdev; + struct platform_device *phy_pdev; + struct device_node *phy_node; + + phy_node = of_parse_phandle(pdev->dev.of_node, "qcom,dsi-phy", 0); + if (!phy_node) { + dev_err(&pdev->dev, "cannot find phy device\n"); + return -ENXIO; + } + + phy_pdev = of_find_device_by_node(phy_node); + if (phy_pdev) + msm_dsi->phy = platform_get_drvdata(phy_pdev); + + of_node_put(phy_node); + + if (!phy_pdev || !msm_dsi->phy) { + dev_err(&pdev->dev, "%s: phy driver is not ready\n", __func__); + return -EPROBE_DEFER; + } + + msm_dsi->phy_dev = get_device(&phy_pdev->dev); + + return 0; +} + static void dsi_destroy(struct msm_dsi *msm_dsi) { if (!msm_dsi) @@ -30,9 +58,10 @@ static void dsi_destroy(struct msm_dsi *msm_dsi) msm_dsi_manager_unregister(msm_dsi); - if (msm_dsi->phy) { - msm_dsi_phy_destroy(msm_dsi->phy); + if (msm_dsi->phy_dev) { + put_device(msm_dsi->phy_dev); msm_dsi->phy = NULL; + msm_dsi->phy_dev = NULL; } if (msm_dsi->host) { @@ -49,7 +78,6 @@ static struct msm_dsi *dsi_init(struct platform_device *pdev) int ret; if (!pdev) { - dev_err(&pdev->dev, "no dsi device\n"); ret = -ENXIO; goto fail; } @@ -69,13 +97,10 @@ static struct msm_dsi *dsi_init(struct platform_device *pdev) if (ret) goto fail; - /* Init dsi PHY */ - msm_dsi->phy = msm_dsi_phy_init(pdev, msm_dsi->phy_type, msm_dsi->id); - if (!msm_dsi->phy) { - ret = -ENXIO; - pr_err("%s: phy init failed\n", __func__); + /* GET dsi PHY */ + ret = dsi_get_phy(msm_dsi); + if (ret) goto fail; - } /* Register to dsi manager */ ret = msm_dsi_manager_register(msm_dsi); @@ -156,12 +181,14 @@ static struct platform_driver dsi_driver = { void __init msm_dsi_register(void) { DBG(""); + msm_dsi_phy_driver_register(); platform_driver_register(&dsi_driver); } void __exit msm_dsi_unregister(void) { DBG(""); + msm_dsi_phy_driver_unregister(); platform_driver_unregister(&dsi_driver); } diff --git a/drivers/gpu/drm/msm/dsi/dsi.h b/drivers/gpu/drm/msm/dsi/dsi.h index 8022814f7914..92d697de4858 100644 --- a/drivers/gpu/drm/msm/dsi/dsi.h +++ b/drivers/gpu/drm/msm/dsi/dsi.h @@ -14,6 +14,7 @@ #ifndef __DSI_CONNECTOR_H__ #define __DSI_CONNECTOR_H__ +#include #include #include "drm_crtc.h" @@ -39,12 +40,27 @@ #define DSI_ENCODER_SLAVE DSI_0 enum msm_dsi_phy_type { - MSM_DSI_PHY_UNKNOWN, MSM_DSI_PHY_28NM_HPM, MSM_DSI_PHY_28NM_LP, MSM_DSI_PHY_MAX }; +#define DSI_DEV_REGULATOR_MAX 8 + +/* Regulators for DSI devices */ +struct dsi_reg_entry { + char name[32]; + int min_voltage; + int max_voltage; + int enable_load; + int disable_load; +}; + +struct dsi_reg_config { + int num; + struct dsi_reg_entry regs[DSI_DEV_REGULATOR_MAX]; +}; + struct msm_dsi { struct drm_device *dev; struct platform_device *pdev; @@ -57,7 +73,7 @@ struct msm_dsi { struct drm_panel *panel; unsigned long panel_flags; - enum msm_dsi_phy_type phy_type; + struct device *phy_dev; bool phy_enabled; /* the encoders we are hooked to (outside of dsi block) */ @@ -135,9 +151,8 @@ int msm_dsi_host_init(struct msm_dsi *msm_dsi); /* dsi phy */ struct msm_dsi_phy; -struct msm_dsi_phy *msm_dsi_phy_init(struct platform_device *pdev, - enum msm_dsi_phy_type type, int id); -void msm_dsi_phy_destroy(struct msm_dsi_phy *phy); +void msm_dsi_phy_driver_register(void); +void msm_dsi_phy_driver_unregister(void); int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, const unsigned long bit_rate, const unsigned long esc_rate); int msm_dsi_phy_disable(struct msm_dsi_phy *phy); diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 43ea8a30c121..bc2e405758e3 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -36,35 +36,19 @@ #define DSI_6G_REG_SHIFT 4 -#define DSI_REGULATOR_MAX 8 -struct dsi_reg_entry { - char name[32]; - int min_voltage; - int max_voltage; - int enable_load; - int disable_load; -}; - -struct dsi_reg_config { - int num; - struct dsi_reg_entry regs[DSI_REGULATOR_MAX]; -}; - struct dsi_config { u32 major; u32 minor; u32 io_offset; - enum msm_dsi_phy_type phy_type; struct dsi_reg_config reg_cfg; }; static const struct dsi_config dsi_cfgs[] = { - {MSM_DSI_VER_MAJOR_V2, 0, 0, MSM_DSI_PHY_UNKNOWN}, + {MSM_DSI_VER_MAJOR_V2, 0, 0, {0,} }, { /* 8974 v1 */ .major = MSM_DSI_VER_MAJOR_6G, .minor = MSM_DSI_6G_VER_MINOR_V1_0, .io_offset = DSI_6G_REG_SHIFT, - .phy_type = MSM_DSI_PHY_28NM_HPM, .reg_cfg = { .num = 4, .regs = { @@ -79,7 +63,6 @@ static const struct dsi_config dsi_cfgs[] = { .major = MSM_DSI_VER_MAJOR_6G, .minor = MSM_DSI_6G_VER_MINOR_V1_1, .io_offset = DSI_6G_REG_SHIFT, - .phy_type = MSM_DSI_PHY_28NM_HPM, .reg_cfg = { .num = 4, .regs = { @@ -94,7 +77,6 @@ static const struct dsi_config dsi_cfgs[] = { .major = MSM_DSI_VER_MAJOR_6G, .minor = MSM_DSI_6G_VER_MINOR_V1_1_1, .io_offset = DSI_6G_REG_SHIFT, - .phy_type = MSM_DSI_PHY_28NM_HPM, .reg_cfg = { .num = 4, .regs = { @@ -109,7 +91,6 @@ static const struct dsi_config dsi_cfgs[] = { .major = MSM_DSI_VER_MAJOR_6G, .minor = MSM_DSI_6G_VER_MINOR_V1_2, .io_offset = DSI_6G_REG_SHIFT, - .phy_type = MSM_DSI_PHY_28NM_HPM, .reg_cfg = { .num = 4, .regs = { @@ -124,7 +105,6 @@ static const struct dsi_config dsi_cfgs[] = { .major = MSM_DSI_VER_MAJOR_6G, .minor = MSM_DSI_6G_VER_MINOR_V1_3_1, .io_offset = DSI_6G_REG_SHIFT, - .phy_type = MSM_DSI_PHY_28NM_LP, .reg_cfg = { .num = 4, .regs = { @@ -197,7 +177,7 @@ struct msm_dsi_host { int id; void __iomem *ctrl_base; - struct regulator_bulk_data supplies[DSI_REGULATOR_MAX]; + struct regulator_bulk_data supplies[DSI_DEV_REGULATOR_MAX]; struct clk *mdp_core_clk; struct clk *ahb_clk; struct clk *axi_clk; @@ -1534,7 +1514,6 @@ int msm_dsi_host_init(struct msm_dsi *msm_dsi) msm_dsi->host = &msm_host->base; msm_dsi->id = msm_host->id; - msm_dsi->phy_type = msm_host->cfg->phy_type; DBG("Dsi Host %d initialized", msm_host->id); return 0; diff --git a/drivers/gpu/drm/msm/dsi/dsi_phy.c b/drivers/gpu/drm/msm/dsi/dsi_phy.c index 2b1c8fdb10de..2d3b33ce1cc5 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_phy.c +++ b/drivers/gpu/drm/msm/dsi/dsi_phy.c @@ -11,12 +11,27 @@ * GNU General Public License for more details. */ +#include +#include + #include "dsi.h" #include "dsi.xml.h" #define dsi_phy_read(offset) msm_readl((offset)) #define dsi_phy_write(offset, data) msm_writel((data), (offset)) +struct dsi_phy_ops { + int (*enable)(struct msm_dsi_phy *phy, bool is_dual_panel, + const unsigned long bit_rate, const unsigned long esc_rate); + int (*disable)(struct msm_dsi_phy *phy); +}; + +struct dsi_phy_cfg { + enum msm_dsi_phy_type type; + struct dsi_reg_config reg_cfg; + struct dsi_phy_ops ops; +}; + struct dsi_dphy_timing { u32 clk_pre; u32 clk_post; @@ -40,17 +55,100 @@ struct msm_dsi_phy { int id; struct clk *ahb_clk; + struct regulator_bulk_data supplies[DSI_DEV_REGULATOR_MAX]; struct dsi_dphy_timing timing; - enum msm_dsi_phy_type type; + const struct dsi_phy_cfg *cfg; struct msm_dsi_pll *pll; - - int (*enable)(struct msm_dsi_phy *phy, bool is_dual_panel, - const unsigned long bit_rate, const unsigned long esc_rate); - int (*disable)(struct msm_dsi_phy *phy); }; +static int dsi_phy_regulator_init(struct msm_dsi_phy *phy) +{ + struct regulator_bulk_data *s = phy->supplies; + const struct dsi_reg_entry *regs = phy->cfg->reg_cfg.regs; + struct device *dev = &phy->pdev->dev; + int num = phy->cfg->reg_cfg.num; + int i, ret; + + for (i = 0; i < num; i++) + s[i].supply = regs[i].name; + + ret = devm_regulator_bulk_get(&phy->pdev->dev, num, s); + if (ret < 0) { + dev_err(dev, "%s: failed to init regulator, ret=%d\n", + __func__, ret); + return ret; + } + + for (i = 0; i < num; i++) { + if ((regs[i].min_voltage >= 0) && (regs[i].max_voltage >= 0)) { + ret = regulator_set_voltage(s[i].consumer, + regs[i].min_voltage, regs[i].max_voltage); + if (ret < 0) { + dev_err(dev, + "regulator %d set voltage failed, %d\n", + i, ret); + return ret; + } + } + } + + return 0; +} + +static void dsi_phy_regulator_disable(struct msm_dsi_phy *phy) +{ + struct regulator_bulk_data *s = phy->supplies; + const struct dsi_reg_entry *regs = phy->cfg->reg_cfg.regs; + int num = phy->cfg->reg_cfg.num; + int i; + + DBG(""); + for (i = num - 1; i >= 0; i--) + if (regs[i].disable_load >= 0) + regulator_set_load(s[i].consumer, + regs[i].disable_load); + + regulator_bulk_disable(num, s); +} + +static int dsi_phy_regulator_enable(struct msm_dsi_phy *phy) +{ + struct regulator_bulk_data *s = phy->supplies; + const struct dsi_reg_entry *regs = phy->cfg->reg_cfg.regs; + struct device *dev = &phy->pdev->dev; + int num = phy->cfg->reg_cfg.num; + int ret, i; + + DBG(""); + for (i = 0; i < num; i++) { + if (regs[i].enable_load >= 0) { + ret = regulator_set_load(s[i].consumer, + regs[i].enable_load); + if (ret < 0) { + dev_err(dev, + "regulator %d set op mode failed, %d\n", + i, ret); + goto fail; + } + } + } + + ret = regulator_bulk_enable(num, s); + if (ret < 0) { + dev_err(dev, "regulator enable failed, %d\n", ret); + goto fail; + } + + return 0; + +fail: + for (i--; i >= 0; i--) + regulator_set_load(s[i].consumer, regs[i].disable_load); + return ret; +} + #define S_DIV_ROUND_UP(n, d) \ (((n) >= 0) ? (((n) + (d) - 1) / (d)) : (((n) - (d) + 1) / (d))) @@ -313,51 +411,94 @@ static void dsi_phy_disable_resource(struct msm_dsi_phy *phy) pm_runtime_put_sync(&phy->pdev->dev); } -#define dsi_phy_func_init(name) \ - do { \ - phy->enable = dsi_##name##_phy_enable; \ - phy->disable = dsi_##name##_phy_disable; \ - } while (0) +static const struct dsi_phy_cfg dsi_phy_cfgs[MSM_DSI_PHY_MAX] = { + [MSM_DSI_PHY_28NM_HPM] = { + .type = MSM_DSI_PHY_28NM_HPM, + .reg_cfg = { + .num = 1, + .regs = { + {"vddio", 1800000, 1800000, 100000, 100}, + }, + }, + .ops = { + .enable = dsi_28nm_phy_enable, + .disable = dsi_28nm_phy_disable, + } + }, + [MSM_DSI_PHY_28NM_LP] = { + .type = MSM_DSI_PHY_28NM_LP, + .reg_cfg = { + .num = 1, + .regs = { + {"vddio", 1800000, 1800000, 100000, 100}, + }, + }, + .ops = { + .enable = dsi_28nm_phy_enable, + .disable = dsi_28nm_phy_disable, + } + }, +}; + +static const struct of_device_id dsi_phy_dt_match[] = { + { .compatible = "qcom,dsi-phy-28nm-hpm", + .data = &dsi_phy_cfgs[MSM_DSI_PHY_28NM_HPM],}, + { .compatible = "qcom,dsi-phy-28nm-lp", + .data = &dsi_phy_cfgs[MSM_DSI_PHY_28NM_LP],}, + {} +}; -struct msm_dsi_phy *msm_dsi_phy_init(struct platform_device *pdev, - enum msm_dsi_phy_type type, int id) +static int dsi_phy_driver_probe(struct platform_device *pdev) { struct msm_dsi_phy *phy; + const struct of_device_id *match; int ret; phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); if (!phy) - return NULL; + return -ENOMEM; + + match = of_match_node(dsi_phy_dt_match, pdev->dev.of_node); + if (!match) + return -ENODEV; + + phy->cfg = match->data; + phy->pdev = pdev; + + ret = of_property_read_u32(pdev->dev.of_node, + "qcom,dsi-phy-index", &phy->id); + if (ret) { + dev_err(&pdev->dev, + "%s: PHY index not specified, ret=%d\n", + __func__, ret); + goto fail; + } phy->base = msm_ioremap(pdev, "dsi_phy", "DSI_PHY"); if (IS_ERR(phy->base)) { - pr_err("%s: failed to map phy base\n", __func__); - return NULL; + dev_err(&pdev->dev, "%s: failed to map phy base\n", __func__); + ret = -ENOMEM; + goto fail; } phy->reg_base = msm_ioremap(pdev, "dsi_phy_regulator", "DSI_PHY_REG"); if (IS_ERR(phy->reg_base)) { - pr_err("%s: failed to map phy regulator base\n", __func__); - return NULL; + dev_err(&pdev->dev, + "%s: failed to map phy regulator base\n", __func__); + ret = -ENOMEM; + goto fail; } - switch (type) { - case MSM_DSI_PHY_28NM_HPM: - case MSM_DSI_PHY_28NM_LP: - dsi_phy_func_init(28nm); - break; - default: - pr_err("%s: unsupported type, %d\n", __func__, type); - return NULL; + ret = dsi_phy_regulator_init(phy); + if (ret) { + dev_err(&pdev->dev, "%s: failed to init regulator\n", __func__); + goto fail; } - phy->type = type; - phy->id = id; - phy->pdev = pdev; - phy->ahb_clk = devm_clk_get(&pdev->dev, "iface_clk"); if (IS_ERR(phy->ahb_clk)) { pr_err("%s: Unable to get ahb clk\n", __func__); - return NULL; + ret = PTR_ERR(phy->ahb_clk); + goto fail; } /* PLL init will call into clk_register which requires @@ -365,39 +506,84 @@ struct msm_dsi_phy *msm_dsi_phy_init(struct platform_device *pdev, */ ret = dsi_phy_enable_resource(phy); if (ret) - return NULL; + goto fail; - phy->pll = msm_dsi_pll_init(pdev, type, id); + phy->pll = msm_dsi_pll_init(pdev, phy->cfg->type, phy->id); if (!phy->pll) - pr_info("%s: pll init failed, need separate pll clk driver\n", + dev_info(&pdev->dev, + "%s: pll init failed, need separate pll clk driver\n", __func__); dsi_phy_disable_resource(phy); - return phy; + platform_set_drvdata(pdev, phy); + + return 0; + +fail: + return ret; } -void msm_dsi_phy_destroy(struct msm_dsi_phy *phy) +static int dsi_phy_driver_remove(struct platform_device *pdev) { - if (phy->pll) { + struct msm_dsi_phy *phy = platform_get_drvdata(pdev); + + if (phy && phy->pll) { msm_dsi_pll_destroy(phy->pll); phy->pll = NULL; } + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver dsi_phy_platform_driver = { + .probe = dsi_phy_driver_probe, + .remove = dsi_phy_driver_remove, + .driver = { + .name = "msm_dsi_phy", + .of_match_table = dsi_phy_dt_match, + }, +}; + +void __init msm_dsi_phy_driver_register(void) +{ + platform_driver_register(&dsi_phy_platform_driver); +} + +void __exit msm_dsi_phy_driver_unregister(void) +{ + platform_driver_unregister(&dsi_phy_platform_driver); } int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, const unsigned long bit_rate, const unsigned long esc_rate) { - if (!phy || !phy->enable) + int ret; + + if (!phy || !phy->cfg->ops.enable) return -EINVAL; - return phy->enable(phy, is_dual_panel, bit_rate, esc_rate); + + ret = dsi_phy_regulator_enable(phy); + if (ret) { + dev_err(&phy->pdev->dev, "%s: regulator enable failed, %d\n", + __func__, ret); + return ret; + } + + return phy->cfg->ops.enable(phy, is_dual_panel, bit_rate, esc_rate); } int msm_dsi_phy_disable(struct msm_dsi_phy *phy) { - if (!phy || !phy->disable) + if (!phy || !phy->cfg->ops.disable) return -EINVAL; - return phy->disable(phy); + + phy->cfg->ops.disable(phy); + dsi_phy_regulator_disable(phy); + + return 0; } void msm_dsi_phy_get_clk_pre_post(struct msm_dsi_phy *phy,